From 456f2c782658650ad2934b4ab614a971bad1634c Mon Sep 17 00:00:00 2001 From: Johannes Dahlström Date: Fri, 31 Aug 2012 16:09:49 +0300 Subject: Use hashbang (#!) URIs in Navigator; isEmpty() instead of equals("") (#9268, #9441) --- server/src/com/vaadin/navigator/Navigator.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) (limited to 'server') diff --git a/server/src/com/vaadin/navigator/Navigator.java b/server/src/com/vaadin/navigator/Navigator.java index bda422379c..e2e40ea732 100644 --- a/server/src/com/vaadin/navigator/Navigator.java +++ b/server/src/com/vaadin/navigator/Navigator.java @@ -108,12 +108,17 @@ public class Navigator implements Serializable { @Override public String getState() { - return page.getFragment(); + String fragment = page.getFragment(); + if (fragment.startsWith("!")) { + return page.getFragment().substring(1); + } else { + return ""; + } } @Override public void setState(String state) { - page.setFragment(state, false); + page.setFragment("!" + state, false); } @Override @@ -475,7 +480,7 @@ public class Navigator implements Serializable { if (null != viewName && getStateManager() != null) { String navigationState = viewName; - if (!parameters.equals("")) { + if (!parameters.isEmpty()) { navigationState += "/" + parameters; } if (!navigationState.equals(getStateManager().getState())) { -- cgit v1.2.3 From 54190550f969edc47901ba9a32239707231703f1 Mon Sep 17 00:00:00 2001 From: Johannes Dahlström Date: Fri, 31 Aug 2012 16:22:13 +0300 Subject: addListener rename in Navigator (#9285) --- server/src/com/vaadin/navigator/Navigator.java | 21 +-------------------- .../tests/server/navigator/NavigatorTest.java | 6 +++--- 2 files changed, 4 insertions(+), 23 deletions(-) (limited to 'server') diff --git a/server/src/com/vaadin/navigator/Navigator.java b/server/src/com/vaadin/navigator/Navigator.java index e2e40ea732..2f42acb320 100644 --- a/server/src/com/vaadin/navigator/Navigator.java +++ b/server/src/com/vaadin/navigator/Navigator.java @@ -103,7 +103,7 @@ public class Navigator implements Serializable { this.page = page; this.navigator = navigator; - page.addListener(this); + page.addFragmentChangedListener(this); } @Override @@ -666,15 +666,6 @@ public class Navigator implements Serializable { listeners.add(listener); } - /** - * @deprecated Since 7.0, replaced by - * {@link #addViewChangeListener(ViewChangeListener)} - **/ - @Deprecated - public void addListener(ViewChangeListener listener) { - addViewChangeListener(listener); - } - /** * Remove a view change listener. * @@ -684,14 +675,4 @@ public class Navigator implements Serializable { public void removeViewChangeListener(ViewChangeListener listener) { listeners.remove(listener); } - - /** - * @deprecated Since 7.0, replaced by - * {@link #removeViewChangeListener(ViewChangeListener)} - **/ - @Deprecated - public void removeListener(ViewChangeListener listener) { - removeViewChangeListener(listener); - } - } diff --git a/server/tests/src/com/vaadin/tests/server/navigator/NavigatorTest.java b/server/tests/src/com/vaadin/tests/server/navigator/NavigatorTest.java index fa48e4bff6..bcc4c83b1e 100644 --- a/server/tests/src/com/vaadin/tests/server/navigator/NavigatorTest.java +++ b/server/tests/src/com/vaadin/tests/server/navigator/NavigatorTest.java @@ -312,7 +312,7 @@ public class NavigatorTest extends TestCase { // test navigator navigator.addProvider(provider); - navigator.addListener(listener); + navigator.addViewChangeListener(listener); navigator.navigateTo("test1"); navigator.navigateTo("test2"); @@ -385,8 +385,8 @@ public class NavigatorTest extends TestCase { // test navigator navigator.addProvider(provider); - navigator.addListener(listener1); - navigator.addListener(listener2); + navigator.addViewChangeListener(listener1); + navigator.addViewChangeListener(listener2); navigator.navigateTo("test1"); navigator.navigateTo("test1/test"); -- cgit v1.2.3 From 094fabccf991fae1efd507a8a993fce1bc547b89 Mon Sep 17 00:00:00 2001 From: Leif Åstrand Date: Fri, 31 Aug 2012 19:27:09 +0300 Subject: Rename AbstractWebApplicationContext to ApplicationContext (#9402) --- server/src/com/vaadin/Application.java | 2 +- .../server/AbstractCommunicationManager.java | 3 +- .../server/AbstractWebApplicationContext.java | 215 -------------- .../src/com/vaadin/server/ApplicationContext.java | 308 +++++++++++++++++++++ .../com/vaadin/server/DeploymentConfiguration.java | 2 - .../com/vaadin/server/GAEApplicationServlet.java | 1 - .../vaadin/server/HttpServletRequestListener.java | 3 +- .../vaadin/server/PortletApplicationContext2.java | 2 +- .../com/vaadin/server/PortletRequestListener.java | 3 +- server/src/com/vaadin/server/RequestTimer.java | 3 +- .../com/vaadin/server/WebApplicationContext.java | 4 +- .../src/com/vaadin/service/ApplicationContext.java | 125 --------- .../server/TransactionListenersConcurrency.java | 16 +- .../RemoveTransactionListener.java | 4 +- .../tests/components/AbstractTestApplication.java | 12 +- .../vaadin/tests/components/AbstractTestCase.java | 11 +- .../vaadin/tests/components/AbstractTestUI.java | 11 +- 17 files changed, 336 insertions(+), 389 deletions(-) delete mode 100644 server/src/com/vaadin/server/AbstractWebApplicationContext.java create mode 100644 server/src/com/vaadin/server/ApplicationContext.java delete mode 100644 server/src/com/vaadin/service/ApplicationContext.java (limited to 'server') diff --git a/server/src/com/vaadin/Application.java b/server/src/com/vaadin/Application.java index cd34fb7540..2ea7f01eea 100644 --- a/server/src/com/vaadin/Application.java +++ b/server/src/com/vaadin/Application.java @@ -50,6 +50,7 @@ import com.vaadin.data.util.converter.DefaultConverterFactory; import com.vaadin.event.EventRouter; import com.vaadin.server.AbstractApplicationServlet; import com.vaadin.server.AbstractErrorMessage; +import com.vaadin.server.ApplicationContext; import com.vaadin.server.BootstrapFragmentResponse; import com.vaadin.server.BootstrapListener; import com.vaadin.server.BootstrapPageResponse; @@ -67,7 +68,6 @@ import com.vaadin.server.WebApplicationContext; import com.vaadin.server.WrappedRequest; import com.vaadin.server.WrappedRequest.BrowserDetails; import com.vaadin.server.WrappedResponse; -import com.vaadin.service.ApplicationContext; import com.vaadin.shared.ui.ui.UIConstants; import com.vaadin.tools.ReflectTools; import com.vaadin.ui.AbstractComponent; diff --git a/server/src/com/vaadin/server/AbstractCommunicationManager.java b/server/src/com/vaadin/server/AbstractCommunicationManager.java index 72406e629d..526d18fd34 100644 --- a/server/src/com/vaadin/server/AbstractCommunicationManager.java +++ b/server/src/com/vaadin/server/AbstractCommunicationManager.java @@ -1323,8 +1323,7 @@ public abstract class AbstractCommunicationManager implements Serializable { * response. */ private void writePerformanceData(final PrintWriter outWriter) { - AbstractWebApplicationContext ctx = (AbstractWebApplicationContext) application - .getContext(); + ApplicationContext ctx = application.getContext(); outWriter.write(String.format(", \"timings\":[%d, %d]", ctx.getTotalSessionTime(), ctx.getLastRequestTime())); } diff --git a/server/src/com/vaadin/server/AbstractWebApplicationContext.java b/server/src/com/vaadin/server/AbstractWebApplicationContext.java deleted file mode 100644 index cf983f4c80..0000000000 --- a/server/src/com/vaadin/server/AbstractWebApplicationContext.java +++ /dev/null @@ -1,215 +0,0 @@ -/* - * Copyright 2011 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.server; - -import java.io.PrintWriter; -import java.io.Serializable; -import java.io.StringWriter; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.logging.Level; -import java.util.logging.Logger; - -import javax.servlet.http.HttpSessionBindingEvent; -import javax.servlet.http.HttpSessionBindingListener; - -import com.vaadin.Application; -import com.vaadin.service.ApplicationContext; - -/** - * Base class for web application contexts (including portlet contexts) that - * handles the common tasks. - */ -public abstract class AbstractWebApplicationContext implements - ApplicationContext, HttpSessionBindingListener, Serializable { - - protected Collection listeners = Collections - .synchronizedList(new LinkedList()); - - protected final HashSet applications = new HashSet(); - - protected WebBrowser browser = new WebBrowser(); - - protected HashMap applicationToAjaxAppMgrMap = new HashMap(); - - private long totalSessionTime = 0; - - private long lastRequestTime = -1; - - @Override - public void addTransactionListener(TransactionListener listener) { - if (listener != null) { - listeners.add(listener); - } - } - - @Override - public void removeTransactionListener(TransactionListener listener) { - listeners.remove(listener); - } - - /** - * Sends a notification that a transaction is starting. - * - * @param application - * The application associated with the transaction. - * @param request - * the HTTP or portlet request that triggered the transaction. - */ - protected void startTransaction(Application application, Object request) { - ArrayList currentListeners; - synchronized (listeners) { - currentListeners = new ArrayList(listeners); - } - for (TransactionListener listener : currentListeners) { - listener.transactionStart(application, request); - } - } - - /** - * Sends a notification that a transaction has ended. - * - * @param application - * The application associated with the transaction. - * @param request - * the HTTP or portlet request that triggered the transaction. - */ - protected void endTransaction(Application application, Object request) { - LinkedList exceptions = null; - - ArrayList currentListeners; - synchronized (listeners) { - currentListeners = new ArrayList(listeners); - } - - for (TransactionListener listener : currentListeners) { - try { - listener.transactionEnd(application, request); - } catch (final RuntimeException t) { - if (exceptions == null) { - exceptions = new LinkedList(); - } - exceptions.add(t); - } - } - - // If any runtime exceptions occurred, throw a combined exception - if (exceptions != null) { - final StringBuffer msg = new StringBuffer(); - for (Exception e : exceptions) { - if (msg.length() == 0) { - msg.append("\n\n--------------------------\n\n"); - } - msg.append(e.getMessage() + "\n"); - final StringWriter trace = new StringWriter(); - e.printStackTrace(new PrintWriter(trace, true)); - msg.append(trace.toString()); - } - throw new RuntimeException(msg.toString()); - } - } - - /** - * @see javax.servlet.http.HttpSessionBindingListener#valueBound(HttpSessionBindingEvent) - */ - @Override - public void valueBound(HttpSessionBindingEvent arg0) { - // We are not interested in bindings - } - - /** - * @see javax.servlet.http.HttpSessionBindingListener#valueUnbound(HttpSessionBindingEvent) - */ - @Override - public void valueUnbound(HttpSessionBindingEvent event) { - // If we are going to be unbound from the session, the session must be - // closing - try { - while (!applications.isEmpty()) { - final Application app = applications.iterator().next(); - app.close(); - removeApplication(app); - } - } catch (Exception e) { - // This should never happen but is possible with rare - // configurations (e.g. robustness tests). If you have one - // thread doing HTTP socket write and another thread trying to - // remove same application here. Possible if you got e.g. session - // lifetime 1 min but socket write may take longer than 1 min. - // FIXME: Handle exception - getLogger().log(Level.SEVERE, - "Could not remove application, leaking memory.", e); - } - } - - /** - * Get the web browser associated with this application context. - * - * Because application context is related to the http session and server - * maintains one session per browser-instance, each context has exactly one - * web browser associated with it. - * - * @return - */ - public WebBrowser getBrowser() { - return browser; - } - - @Override - public Collection getApplications() { - return Collections.unmodifiableCollection(applications); - } - - protected void removeApplication(Application application) { - applications.remove(application); - applicationToAjaxAppMgrMap.remove(application); - } - - /** - * @return The total time spent servicing requests in this session. - */ - public long getTotalSessionTime() { - return totalSessionTime; - } - - /** - * Sets the time spent servicing the last request in the session and updates - * the total time spent servicing requests in this session. - * - * @param time - * the time spent in the last request. - */ - public void setLastRequestTime(long time) { - lastRequestTime = time; - totalSessionTime += time; - } - - /** - * @return the time spent servicing the last request in this session. - */ - public long getLastRequestTime() { - return lastRequestTime; - } - - private Logger getLogger() { - return Logger.getLogger(AbstractWebApplicationContext.class.getName()); - } - -} \ No newline at end of file diff --git a/server/src/com/vaadin/server/ApplicationContext.java b/server/src/com/vaadin/server/ApplicationContext.java new file mode 100644 index 0000000000..b698ea51c8 --- /dev/null +++ b/server/src/com/vaadin/server/ApplicationContext.java @@ -0,0 +1,308 @@ +/* + * Copyright 2011 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.server; + +import java.io.File; +import java.io.PrintWriter; +import java.io.Serializable; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.servlet.http.HttpSessionBindingEvent; +import javax.servlet.http.HttpSessionBindingListener; + +import com.vaadin.Application; + +/** + * ApplicationContext provides information about the running + * context of the application. Each context is shared by all applications that + * are open for one user. In a web-environment this corresponds to a + * HttpSession. + *

+ * Base class for web application contexts (including portlet contexts) that + * handles the common tasks. + * + * @author Vaadin Ltd. + * @since 3.1 + */ +public abstract class ApplicationContext implements HttpSessionBindingListener, + Serializable { + + /** + * Interface for listening to transaction events. Implement this interface + * to listen to all transactions between the client and the application. + * + */ + public static interface TransactionListener extends Serializable { + + /** + * Invoked at the beginning of every transaction. + * + * The transaction is linked to the context, not the application so if + * you have multiple applications running in the same context you need + * to check that the request is associated with the application you are + * interested in. This can be done looking at the application parameter. + * + * @param application + * the Application object. + * @param transactionData + * the Data identifying the transaction. + */ + public void transactionStart(Application application, + Object transactionData); + + /** + * Invoked at the end of every transaction. + * + * The transaction is linked to the context, not the application so if + * you have multiple applications running in the same context you need + * to check that the request is associated with the application you are + * interested in. This can be done looking at the application parameter. + * + * @param applcation + * the Application object. + * @param transactionData + * the Data identifying the transaction. + */ + public void transactionEnd(Application application, + Object transactionData); + + } + + protected Collection listeners = Collections + .synchronizedList(new LinkedList()); + + protected final HashSet applications = new HashSet(); + + protected WebBrowser browser = new WebBrowser(); + + protected HashMap applicationToAjaxAppMgrMap = new HashMap(); + + private long totalSessionTime = 0; + + private long lastRequestTime = -1; + + /** + * Adds a transaction listener to this context. The transaction listener is + * called before and after each each request related to this session except + * when serving static resources. + * + * The transaction listener must not be null. + * + * @see com.vaadin.service.ApplicationContext#addTransactionListener(com.vaadin.service.ApplicationContext.TransactionListener) + */ + public void addTransactionListener( + ApplicationContext.TransactionListener listener) { + if (listener != null) { + listeners.add(listener); + } + } + + /** + * Removes a transaction listener from this context. + * + * @param listener + * the listener to be removed. + * @see ApplicationContext.TransactionListener + */ + public void removeTransactionListener( + ApplicationContext.TransactionListener listener) { + listeners.remove(listener); + } + + /** + * Sends a notification that a transaction is starting. + * + * @param application + * The application associated with the transaction. + * @param request + * the HTTP or portlet request that triggered the transaction. + */ + protected void startTransaction(Application application, Object request) { + ArrayList currentListeners; + synchronized (listeners) { + currentListeners = new ArrayList( + listeners); + } + for (ApplicationContext.TransactionListener listener : currentListeners) { + listener.transactionStart(application, request); + } + } + + /** + * Sends a notification that a transaction has ended. + * + * @param application + * The application associated with the transaction. + * @param request + * the HTTP or portlet request that triggered the transaction. + */ + protected void endTransaction(Application application, Object request) { + LinkedList exceptions = null; + + ArrayList currentListeners; + synchronized (listeners) { + currentListeners = new ArrayList( + listeners); + } + + for (ApplicationContext.TransactionListener listener : currentListeners) { + try { + listener.transactionEnd(application, request); + } catch (final RuntimeException t) { + if (exceptions == null) { + exceptions = new LinkedList(); + } + exceptions.add(t); + } + } + + // If any runtime exceptions occurred, throw a combined exception + if (exceptions != null) { + final StringBuffer msg = new StringBuffer(); + for (Exception e : exceptions) { + if (msg.length() == 0) { + msg.append("\n\n--------------------------\n\n"); + } + msg.append(e.getMessage() + "\n"); + final StringWriter trace = new StringWriter(); + e.printStackTrace(new PrintWriter(trace, true)); + msg.append(trace.toString()); + } + throw new RuntimeException(msg.toString()); + } + } + + /** + * @see javax.servlet.http.HttpSessionBindingListener#valueBound(HttpSessionBindingEvent) + */ + @Override + public void valueBound(HttpSessionBindingEvent arg0) { + // We are not interested in bindings + } + + /** + * @see javax.servlet.http.HttpSessionBindingListener#valueUnbound(HttpSessionBindingEvent) + */ + @Override + public void valueUnbound(HttpSessionBindingEvent event) { + // If we are going to be unbound from the session, the session must be + // closing + try { + while (!applications.isEmpty()) { + final Application app = applications.iterator().next(); + app.close(); + removeApplication(app); + } + } catch (Exception e) { + // This should never happen but is possible with rare + // configurations (e.g. robustness tests). If you have one + // thread doing HTTP socket write and another thread trying to + // remove same application here. Possible if you got e.g. session + // lifetime 1 min but socket write may take longer than 1 min. + // FIXME: Handle exception + getLogger().log(Level.SEVERE, + "Could not remove application, leaking memory.", e); + } + } + + /** + * Get the web browser associated with this application context. + * + * Because application context is related to the http session and server + * maintains one session per browser-instance, each context has exactly one + * web browser associated with it. + * + * @return + */ + public WebBrowser getBrowser() { + return browser; + } + + /** + * Returns a collection of all the applications in this context. + * + * Each application context contains all active applications for one user. + * + * @return A collection containing all the applications in this context. + */ + public Collection getApplications() { + return Collections.unmodifiableCollection(applications); + } + + protected void removeApplication(Application application) { + applications.remove(application); + applicationToAjaxAppMgrMap.remove(application); + } + + /** + * @return The total time spent servicing requests in this session. + */ + public long getTotalSessionTime() { + return totalSessionTime; + } + + /** + * Sets the time spent servicing the last request in the session and updates + * the total time spent servicing requests in this session. + * + * @param time + * the time spent in the last request. + */ + public void setLastRequestTime(long time) { + lastRequestTime = time; + totalSessionTime += time; + } + + /** + * @return the time spent servicing the last request in this session. + */ + public long getLastRequestTime() { + return lastRequestTime; + } + + private Logger getLogger() { + return Logger.getLogger(ApplicationContext.class.getName()); + } + + /** + * Returns application context base directory. + * + * Typically an application is deployed in a such way that is has an + * application directory. For web applications this directory is the root + * directory of the web applications. In some cases applications might not + * have an application directory (for example web applications running + * inside a war). + * + * @return The application base directory or null if the application has no + * base directory. + */ + public abstract File getBaseDirectory(); + + /** + * Returns the time between requests, in seconds, before this context is + * invalidated. A negative time indicates the context should never timeout. + */ + public abstract int getMaxInactiveInterval(); + +} \ No newline at end of file diff --git a/server/src/com/vaadin/server/DeploymentConfiguration.java b/server/src/com/vaadin/server/DeploymentConfiguration.java index d1cbdfc499..d61d03f4d9 100644 --- a/server/src/com/vaadin/server/DeploymentConfiguration.java +++ b/server/src/com/vaadin/server/DeploymentConfiguration.java @@ -23,8 +23,6 @@ import java.util.Properties; import javax.portlet.PortletContext; import javax.servlet.ServletContext; -import com.vaadin.service.ApplicationContext; - /** * Provide deployment specific settings that are required outside terminal * specific code. diff --git a/server/src/com/vaadin/server/GAEApplicationServlet.java b/server/src/com/vaadin/server/GAEApplicationServlet.java index 16345edead..7e0b52c382 100644 --- a/server/src/com/vaadin/server/GAEApplicationServlet.java +++ b/server/src/com/vaadin/server/GAEApplicationServlet.java @@ -47,7 +47,6 @@ import com.google.appengine.api.memcache.Expiration; import com.google.appengine.api.memcache.MemcacheService; import com.google.appengine.api.memcache.MemcacheServiceFactory; import com.google.apphosting.api.DeadlineExceededException; -import com.vaadin.service.ApplicationContext; /** * ApplicationServlet to be used when deploying to Google App Engine, in diff --git a/server/src/com/vaadin/server/HttpServletRequestListener.java b/server/src/com/vaadin/server/HttpServletRequestListener.java index 1f9f633c17..e871dca05d 100644 --- a/server/src/com/vaadin/server/HttpServletRequestListener.java +++ b/server/src/com/vaadin/server/HttpServletRequestListener.java @@ -23,7 +23,6 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.vaadin.Application; -import com.vaadin.service.ApplicationContext.TransactionListener; /** * {@link Application} that implements this interface gets notified of request @@ -37,7 +36,7 @@ import com.vaadin.service.ApplicationContext.TransactionListener; * *

* Alternatives for implementing similar features are are Servlet {@link Filter} - * s and {@link TransactionListener}s in Vaadin. + * s and {@link ApplicationContext.TransactionListener}s in Vaadin. * * @since 6.2 * @see PortletRequestListener diff --git a/server/src/com/vaadin/server/PortletApplicationContext2.java b/server/src/com/vaadin/server/PortletApplicationContext2.java index aca80f9c17..366c5b160d 100644 --- a/server/src/com/vaadin/server/PortletApplicationContext2.java +++ b/server/src/com/vaadin/server/PortletApplicationContext2.java @@ -56,7 +56,7 @@ import com.vaadin.ui.UI; * @author peholmst */ @SuppressWarnings("serial") -public class PortletApplicationContext2 extends AbstractWebApplicationContext { +public class PortletApplicationContext2 extends ApplicationContext { protected Map> portletListeners = new HashMap>(); diff --git a/server/src/com/vaadin/server/PortletRequestListener.java b/server/src/com/vaadin/server/PortletRequestListener.java index 35f2a946c5..4562ddf7b9 100644 --- a/server/src/com/vaadin/server/PortletRequestListener.java +++ b/server/src/com/vaadin/server/PortletRequestListener.java @@ -22,7 +22,6 @@ import javax.portlet.PortletResponse; import javax.servlet.Filter; import com.vaadin.Application; -import com.vaadin.service.ApplicationContext.TransactionListener; /** * An {@link Application} that implements this interface gets notified of @@ -41,7 +40,7 @@ import com.vaadin.service.ApplicationContext.TransactionListener; * *

* Alternatives for implementing similar features are are Servlet {@link Filter} - * s and {@link TransactionListener}s in Vaadin. + * s and {@link ApplicationContext.TransactionListener}s in Vaadin. * * @since 6.2 * @see HttpServletRequestListener diff --git a/server/src/com/vaadin/server/RequestTimer.java b/server/src/com/vaadin/server/RequestTimer.java index 1dfe24f23b..470677e331 100644 --- a/server/src/com/vaadin/server/RequestTimer.java +++ b/server/src/com/vaadin/server/RequestTimer.java @@ -18,6 +18,7 @@ package com.vaadin.server; import java.io.Serializable; + /** * Times the handling of requests and stores the information as an attribute in * the request. The timing info is later passed on to the client in the UIDL and @@ -43,7 +44,7 @@ public class RequestTimer implements Serializable { * * @param context */ - public void stop(AbstractWebApplicationContext context) { + public void stop(ApplicationContext context) { // Measure and store the total handling time. This data can be // used in TestBench 3 tests. long time = (System.nanoTime() - requestStartTime) / 1000000; diff --git a/server/src/com/vaadin/server/WebApplicationContext.java b/server/src/com/vaadin/server/WebApplicationContext.java index 7059a04682..02e902f79e 100644 --- a/server/src/com/vaadin/server/WebApplicationContext.java +++ b/server/src/com/vaadin/server/WebApplicationContext.java @@ -37,7 +37,7 @@ import com.vaadin.Application; * @since 3.1 */ @SuppressWarnings("serial") -public class WebApplicationContext extends AbstractWebApplicationContext { +public class WebApplicationContext extends ApplicationContext { protected transient HttpSession session; private transient boolean reinitializingSession = false; @@ -119,7 +119,7 @@ public class WebApplicationContext extends AbstractWebApplicationContext { /** * Gets the application context base directory. * - * @see com.vaadin.service.ApplicationContext#getBaseDirectory() + * @see com.vaadin.server.ApplicationContext#getBaseDirectory() */ @Override public File getBaseDirectory() { diff --git a/server/src/com/vaadin/service/ApplicationContext.java b/server/src/com/vaadin/service/ApplicationContext.java deleted file mode 100644 index 591704764f..0000000000 --- a/server/src/com/vaadin/service/ApplicationContext.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright 2011 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.service; - -import java.io.File; -import java.io.Serializable; -import java.util.Collection; - -import com.vaadin.Application; - -/** - * ApplicationContext provides information about the running - * context of the application. Each context is shared by all applications that - * are open for one user. In a web-environment this corresponds to a - * HttpSession. - * - * @author Vaadin Ltd. - * @since 3.1 - */ -public interface ApplicationContext extends Serializable { - - /** - * Returns application context base directory. - * - * Typically an application is deployed in a such way that is has an - * application directory. For web applications this directory is the root - * directory of the web applications. In some cases applications might not - * have an application directory (for example web applications running - * inside a war). - * - * @return The application base directory or null if the application has no - * base directory. - */ - public File getBaseDirectory(); - - /** - * Returns a collection of all the applications in this context. - * - * Each application context contains all active applications for one user. - * - * @return A collection containing all the applications in this context. - */ - public Collection getApplications(); - - /** - * Adds a transaction listener to this context. The transaction listener is - * called before and after each each request related to this session except - * when serving static resources. - * - * The transaction listener must not be null. - * - * @see com.vaadin.service.ApplicationContext#addTransactionListener(com.vaadin.service.ApplicationContext.TransactionListener) - */ - public void addTransactionListener(TransactionListener listener); - - /** - * Removes a transaction listener from this context. - * - * @param listener - * the listener to be removed. - * @see TransactionListener - */ - public void removeTransactionListener(TransactionListener listener); - - /** - * Returns the time between requests, in seconds, before this context is - * invalidated. A negative time indicates the context should never timeout. - */ - public int getMaxInactiveInterval(); - - /** - * Interface for listening to transaction events. Implement this interface - * to listen to all transactions between the client and the application. - * - */ - public interface TransactionListener extends Serializable { - - /** - * Invoked at the beginning of every transaction. - * - * The transaction is linked to the context, not the application so if - * you have multiple applications running in the same context you need - * to check that the request is associated with the application you are - * interested in. This can be done looking at the application parameter. - * - * @param application - * the Application object. - * @param transactionData - * the Data identifying the transaction. - */ - public void transactionStart(Application application, - Object transactionData); - - /** - * Invoked at the end of every transaction. - * - * The transaction is linked to the context, not the application so if - * you have multiple applications running in the same context you need - * to check that the request is associated with the application you are - * interested in. This can be done looking at the application parameter. - * - * @param applcation - * the Application object. - * @param transactionData - * the Data identifying the transaction. - */ - public void transactionEnd(Application application, - Object transactionData); - - } -} diff --git a/server/tests/src/com/vaadin/tests/server/TransactionListenersConcurrency.java b/server/tests/src/com/vaadin/tests/server/TransactionListenersConcurrency.java index 0cacccd08a..302b534d8a 100644 --- a/server/tests/src/com/vaadin/tests/server/TransactionListenersConcurrency.java +++ b/server/tests/src/com/vaadin/tests/server/TransactionListenersConcurrency.java @@ -17,14 +17,13 @@ import javax.servlet.http.HttpSession; import junit.framework.TestCase; +import org.easymock.EasyMock; + import com.vaadin.Application; import com.vaadin.Application.ApplicationStartEvent; -import com.vaadin.server.AbstractWebApplicationContext; +import com.vaadin.server.ApplicationContext; import com.vaadin.server.DeploymentConfiguration; import com.vaadin.server.WebApplicationContext; -import com.vaadin.service.ApplicationContext.TransactionListener; - -import org.easymock.EasyMock; public class TransactionListenersConcurrency extends TestCase { @@ -90,9 +89,9 @@ public class TransactionListenersConcurrency extends TestCase { // Call the transaction listener using reflection as // startTransaction is protected. - Method m = AbstractWebApplicationContext.class - .getDeclaredMethod("startTransaction", - Application.class, Object.class); + Method m = ApplicationContext.class.getDeclaredMethod( + "startTransaction", Application.class, + Object.class); m.setAccessible(true); m.invoke(context, app, null); } catch (Exception e) { @@ -167,7 +166,8 @@ public class TransactionListenersConcurrency extends TestCase { * transactionStart and transactionEnd. * */ - public static class DelayTransactionListener implements TransactionListener { + public static class DelayTransactionListener implements + ApplicationContext.TransactionListener { private int delay; diff --git a/uitest/src/com/vaadin/tests/applicationcontext/RemoveTransactionListener.java b/uitest/src/com/vaadin/tests/applicationcontext/RemoveTransactionListener.java index f1730ed5f5..5927e9c19f 100644 --- a/uitest/src/com/vaadin/tests/applicationcontext/RemoveTransactionListener.java +++ b/uitest/src/com/vaadin/tests/applicationcontext/RemoveTransactionListener.java @@ -1,8 +1,8 @@ package com.vaadin.tests.applicationcontext; import com.vaadin.Application; -import com.vaadin.service.ApplicationContext; -import com.vaadin.service.ApplicationContext.TransactionListener; +import com.vaadin.server.ApplicationContext; +import com.vaadin.server.ApplicationContext.TransactionListener; import com.vaadin.tests.components.TestBase; import com.vaadin.tests.util.Log; diff --git a/uitest/src/com/vaadin/tests/components/AbstractTestApplication.java b/uitest/src/com/vaadin/tests/components/AbstractTestApplication.java index 406158e8e1..db17c67fdd 100644 --- a/uitest/src/com/vaadin/tests/components/AbstractTestApplication.java +++ b/uitest/src/com/vaadin/tests/components/AbstractTestApplication.java @@ -1,9 +1,8 @@ package com.vaadin.tests.components; import com.vaadin.Application; -import com.vaadin.server.AbstractWebApplicationContext; +import com.vaadin.server.ApplicationContext; import com.vaadin.server.WebBrowser; -import com.vaadin.service.ApplicationContext; public abstract class AbstractTestApplication extends Application { protected abstract String getTestDescription(); @@ -12,12 +11,7 @@ public abstract class AbstractTestApplication extends Application { protected WebBrowser getBrowser() { ApplicationContext context = getContext(); - if (context instanceof AbstractWebApplicationContext) { - WebBrowser webBrowser = ((AbstractWebApplicationContext) context) - .getBrowser(); - return webBrowser; - } - - return null; + WebBrowser webBrowser = context.getBrowser(); + return webBrowser; } } diff --git a/uitest/src/com/vaadin/tests/components/AbstractTestCase.java b/uitest/src/com/vaadin/tests/components/AbstractTestCase.java index 4e2ec1d935..f51c74d4a8 100644 --- a/uitest/src/com/vaadin/tests/components/AbstractTestCase.java +++ b/uitest/src/com/vaadin/tests/components/AbstractTestCase.java @@ -1,9 +1,8 @@ package com.vaadin.tests.components; import com.vaadin.Application; -import com.vaadin.server.AbstractWebApplicationContext; +import com.vaadin.server.ApplicationContext; import com.vaadin.server.WebBrowser; -import com.vaadin.service.ApplicationContext; public abstract class AbstractTestCase extends Application.LegacyApplication { @@ -13,12 +12,8 @@ public abstract class AbstractTestCase extends Application.LegacyApplication { protected WebBrowser getBrowser() { ApplicationContext context = getContext(); - if (context instanceof AbstractWebApplicationContext) { - WebBrowser webBrowser = ((AbstractWebApplicationContext) context) - .getBrowser(); - return webBrowser; - } + WebBrowser webBrowser = context.getBrowser(); + return webBrowser; - return null; } } diff --git a/uitest/src/com/vaadin/tests/components/AbstractTestUI.java b/uitest/src/com/vaadin/tests/components/AbstractTestUI.java index ff235c5d9f..21eda56891 100644 --- a/uitest/src/com/vaadin/tests/components/AbstractTestUI.java +++ b/uitest/src/com/vaadin/tests/components/AbstractTestUI.java @@ -1,10 +1,9 @@ package com.vaadin.tests.components; import com.vaadin.Application; -import com.vaadin.server.AbstractWebApplicationContext; +import com.vaadin.server.ApplicationContext; import com.vaadin.server.WebBrowser; import com.vaadin.server.WrappedRequest; -import com.vaadin.service.ApplicationContext; import com.vaadin.shared.ui.label.ContentMode; import com.vaadin.ui.Component; import com.vaadin.ui.Label; @@ -58,12 +57,8 @@ public abstract class AbstractTestUI extends UI { protected WebBrowser getBrowser() { ApplicationContext context = Application.getCurrent().getContext(); - if (context instanceof AbstractWebApplicationContext) { - AbstractWebApplicationContext webContext = (AbstractWebApplicationContext) context; - return webContext.getBrowser(); - } - - return null; + ApplicationContext webContext = context; + return webContext.getBrowser(); } } -- cgit v1.2.3 From 7c3febaf37b8bdfad31c7d407e9150d04e598bb2 Mon Sep 17 00:00:00 2001 From: Leif Åstrand Date: Fri, 31 Aug 2012 19:40:01 +0300 Subject: Rename WebApplicationContext -> ServletApplicationContext (#9402) --- server/src/com/vaadin/Application.java | 4 +- .../vaadin/server/AbstractApplicationServlet.java | 20 +-- server/src/com/vaadin/server/CombinedRequest.java | 4 +- .../com/vaadin/server/CommunicationManager.java | 2 +- .../com/vaadin/server/GAEApplicationServlet.java | 4 +- server/src/com/vaadin/server/Page.java | 3 +- .../vaadin/server/ServletApplicationContext.java | 194 +++++++++++++++++++++ .../com/vaadin/server/WebApplicationContext.java | 194 --------------------- .../vaadin/server/WrappedHttpServletRequest.java | 4 +- .../server/TransactionListenersConcurrency.java | 8 +- .../src/com/vaadin/tests/VerifyBrowserVersion.java | 4 +- .../tests/application/ApplicationCloseTest.java | 4 +- .../tests/applicationcontext/ChangeSessionId.java | 6 +- .../src/com/vaadin/tests/tickets/Ticket1975.java | 4 +- 14 files changed, 226 insertions(+), 229 deletions(-) create mode 100644 server/src/com/vaadin/server/ServletApplicationContext.java delete mode 100644 server/src/com/vaadin/server/WebApplicationContext.java (limited to 'server') diff --git a/server/src/com/vaadin/Application.java b/server/src/com/vaadin/Application.java index 2ea7f01eea..90f354e180 100644 --- a/server/src/com/vaadin/Application.java +++ b/server/src/com/vaadin/Application.java @@ -64,7 +64,7 @@ import com.vaadin.server.RequestHandler; import com.vaadin.server.Terminal; import com.vaadin.server.UIProvider; import com.vaadin.server.VariableOwner; -import com.vaadin.server.WebApplicationContext; +import com.vaadin.server.ServletApplicationContext; import com.vaadin.server.WrappedRequest; import com.vaadin.server.WrappedRequest.BrowserDetails; import com.vaadin.server.WrappedResponse; @@ -867,7 +867,7 @@ public class Application implements Terminal.ErrorListener, Serializable { *

*

* By default, when you are deploying your application to a servlet - * container, the implementation class is {@link WebApplicationContext} - + * container, the implementation class is {@link ServletApplicationContext} - * you can safely cast to this class and use the methods from there. When * you are deploying your application as a portlet, context implementation * is {@link PortletApplicationContext}. diff --git a/server/src/com/vaadin/server/AbstractApplicationServlet.java b/server/src/com/vaadin/server/AbstractApplicationServlet.java index 2f0dad7079..87d36da255 100644 --- a/server/src/com/vaadin/server/AbstractApplicationServlet.java +++ b/server/src/com/vaadin/server/AbstractApplicationServlet.java @@ -275,7 +275,7 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements * Get or create a WebApplicationContext and an ApplicationManager * for the session */ - WebApplicationContext webApplicationContext = getApplicationContext(request + ServletApplicationContext webApplicationContext = getApplicationContext(request .getSession()); CommunicationManager applicationManager = webApplicationContext .getApplicationManager(application, this); @@ -363,7 +363,7 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements // Notifies transaction end try { if (transactionStarted) { - ((WebApplicationContext) application.getContext()) + ((ServletApplicationContext) application.getContext()) .endTransaction(application, request); } @@ -694,7 +694,7 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements throws ServletException, MalformedURLException { Application newApplication = getNewApplication(request); - final WebApplicationContext context = getApplicationContext(request + final ServletApplicationContext context = getApplicationContext(request .getSession()); context.addApplication(newApplication); @@ -856,7 +856,7 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements * @throws MalformedURLException */ private void startApplication(HttpServletRequest request, - Application application, WebApplicationContext webApplicationContext) + Application application, ServletApplicationContext webApplicationContext) throws ServletException, MalformedURLException { if (!application.isRunning()) { @@ -1385,7 +1385,7 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements throw new SessionExpiredException(); } - WebApplicationContext context = getApplicationContext(session); + ServletApplicationContext context = getApplicationContext(session); // Gets application list for the session. final Collection applications = context.getApplications(); @@ -1491,7 +1491,7 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements application.close(); if (session != null) { - WebApplicationContext context = getApplicationContext(session); + ServletApplicationContext context = getApplicationContext(session); context.removeApplication(application); } } @@ -1506,13 +1506,13 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements * the HTTP session. * @return the application context for HttpSession. */ - protected WebApplicationContext getApplicationContext(HttpSession session) { + protected ServletApplicationContext getApplicationContext(HttpSession session) { /* * TODO the ApplicationContext.getApplicationContext() should be removed * and logic moved here. Now overriding context type is possible, but * the whole creation logic should be here. MT 1101 */ - return WebApplicationContext.getApplicationContext(session); + return ServletApplicationContext.getApplicationContext(session); } public class RequestError implements Terminal.ErrorEvent, Serializable { @@ -1535,11 +1535,11 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements * mananger implementation. * * @deprecated Instead of overriding this method, override - * {@link WebApplicationContext} implementation via + * {@link ServletApplicationContext} implementation via * {@link AbstractApplicationServlet#getApplicationContext(HttpSession)} * method and in that customized implementation return your * CommunicationManager in - * {@link WebApplicationContext#getApplicationManager(Application, AbstractApplicationServlet)} + * {@link ServletApplicationContext#getApplicationManager(Application, AbstractApplicationServlet)} * method. * * @param application diff --git a/server/src/com/vaadin/server/CombinedRequest.java b/server/src/com/vaadin/server/CombinedRequest.java index c186fcc87c..0577c0098a 100644 --- a/server/src/com/vaadin/server/CombinedRequest.java +++ b/server/src/com/vaadin/server/CombinedRequest.java @@ -153,8 +153,8 @@ public class CombinedRequest implements WrappedRequest { @Override public WebBrowser getWebBrowser() { - WebApplicationContext context = (WebApplicationContext) Application - .getCurrent().getContext(); + ApplicationContext context = Application.getCurrent() + .getContext(); return context.getBrowser(); } }; diff --git a/server/src/com/vaadin/server/CommunicationManager.java b/server/src/com/vaadin/server/CommunicationManager.java index 3c594eaf02..af28438f57 100644 --- a/server/src/com/vaadin/server/CommunicationManager.java +++ b/server/src/com/vaadin/server/CommunicationManager.java @@ -112,7 +112,7 @@ public class CommunicationManager extends AbstractCommunicationManager { @Override protected InputStream getThemeResourceAsStream(UI uI, String themeName, String resource) { - WebApplicationContext context = (WebApplicationContext) uI + ServletApplicationContext context = (ServletApplicationContext) uI .getApplication().getContext(); ServletContext servletContext = context.getHttpSession() .getServletContext(); diff --git a/server/src/com/vaadin/server/GAEApplicationServlet.java b/server/src/com/vaadin/server/GAEApplicationServlet.java index 7e0b52c382..240984c760 100644 --- a/server/src/com/vaadin/server/GAEApplicationServlet.java +++ b/server/src/com/vaadin/server/GAEApplicationServlet.java @@ -322,7 +322,7 @@ public class GAEApplicationServlet extends ApplicationServlet { ois = new ObjectInputStream(bais); ApplicationContext applicationContext = (ApplicationContext) ois .readObject(); - session.setAttribute(WebApplicationContext.class.getName(), + session.setAttribute(ServletApplicationContext.class.getName(), applicationContext); } catch (IOException e) { getLogger().log( @@ -360,7 +360,7 @@ public class GAEApplicationServlet extends ApplicationServlet { private void cleanSession(HttpServletRequest request) { HttpSession session = request.getSession(false); if (session != null) { - session.removeAttribute(WebApplicationContext.class.getName()); + session.removeAttribute(ServletApplicationContext.class.getName()); } } diff --git a/server/src/com/vaadin/server/Page.java b/server/src/com/vaadin/server/Page.java index b8fdae6cfb..da172ed837 100644 --- a/server/src/com/vaadin/server/Page.java +++ b/server/src/com/vaadin/server/Page.java @@ -391,8 +391,7 @@ public class Page implements Serializable { } public WebBrowser getWebBrowser() { - return ((WebApplicationContext) uI.getApplication().getContext()) - .getBrowser(); + return uI.getApplication().getContext().getBrowser(); } public void setBrowserWindowSize(int width, int height) { diff --git a/server/src/com/vaadin/server/ServletApplicationContext.java b/server/src/com/vaadin/server/ServletApplicationContext.java new file mode 100644 index 0000000000..639ff117d1 --- /dev/null +++ b/server/src/com/vaadin/server/ServletApplicationContext.java @@ -0,0 +1,194 @@ +/* + * Copyright 2011 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.server; + +import java.io.File; +import java.util.Enumeration; +import java.util.HashMap; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpSession; +import javax.servlet.http.HttpSessionBindingEvent; +import javax.servlet.http.HttpSessionBindingListener; + +import com.vaadin.Application; + +/** + * Web application context for Vaadin applications. + * + * This is automatically added as a {@link HttpSessionBindingListener} when + * added to a {@link HttpSession}. + * + * @author Vaadin Ltd. + * @since 3.1 + */ +@SuppressWarnings("serial") +public class ServletApplicationContext extends ApplicationContext { + + protected transient HttpSession session; + private transient boolean reinitializingSession = false; + + /** + * Stores a reference to the currentRequest. Null it not inside a request. + */ + private transient Object currentRequest = null; + + /** + * Creates a new Web Application Context. + * + */ + protected ServletApplicationContext() { + + } + + @Override + protected void startTransaction(Application application, Object request) { + currentRequest = request; + super.startTransaction(application, request); + } + + @Override + protected void endTransaction(Application application, Object request) { + super.endTransaction(application, request); + currentRequest = null; + } + + @Override + public void valueUnbound(HttpSessionBindingEvent event) { + if (!reinitializingSession) { + // Avoid closing the application if we are only reinitializing the + // session. Closing the application would cause the state to be lost + // and a new application to be created, which is not what we want. + super.valueUnbound(event); + } + } + + /** + * Discards the current session and creates a new session with the same + * contents. The purpose of this is to introduce a new session key in order + * to avoid session fixation attacks. + */ + @SuppressWarnings("unchecked") + public void reinitializeSession() { + + HttpSession oldSession = getHttpSession(); + + // Stores all attributes (security key, reference to this context + // instance) so they can be added to the new session + HashMap attrs = new HashMap(); + for (Enumeration e = oldSession.getAttributeNames(); e + .hasMoreElements();) { + String name = e.nextElement(); + attrs.put(name, oldSession.getAttribute(name)); + } + + // Invalidate the current session, set flag to avoid call to + // valueUnbound + reinitializingSession = true; + oldSession.invalidate(); + reinitializingSession = false; + + // Create a new session + HttpSession newSession = ((HttpServletRequest) currentRequest) + .getSession(); + + // Restores all attributes (security key, reference to this context + // instance) + for (String name : attrs.keySet()) { + newSession.setAttribute(name, attrs.get(name)); + } + + // Update the "current session" variable + session = newSession; + } + + /** + * Gets the application context base directory. + * + * @see com.vaadin.server.ApplicationContext#getBaseDirectory() + */ + @Override + public File getBaseDirectory() { + final String realPath = ApplicationServlet.getResourcePath( + session.getServletContext(), "/"); + if (realPath == null) { + return null; + } + return new File(realPath); + } + + /** + * Gets the http-session application is running in. + * + * @return HttpSession this application context resides in. + */ + public HttpSession getHttpSession() { + return session; + } + + /** + * Gets the application context for an HttpSession. + * + * @param session + * the HTTP session. + * @return the application context for HttpSession. + */ + static public ServletApplicationContext getApplicationContext( + HttpSession session) { + ServletApplicationContext cx = (ServletApplicationContext) session + .getAttribute(ServletApplicationContext.class.getName()); + if (cx == null) { + cx = new ServletApplicationContext(); + session.setAttribute(ServletApplicationContext.class.getName(), cx); + } + if (cx.session == null) { + cx.session = session; + } + return cx; + } + + protected void addApplication(Application application) { + applications.add(application); + } + + /** + * Gets communication manager for an application. + * + * If this application has not been running before, a new manager is + * created. + * + * @param application + * @return CommunicationManager + */ + public CommunicationManager getApplicationManager(Application application, + AbstractApplicationServlet servlet) { + CommunicationManager mgr = (CommunicationManager) applicationToAjaxAppMgrMap + .get(application); + + if (mgr == null) { + // Creates new manager + mgr = servlet.createCommunicationManager(application); + applicationToAjaxAppMgrMap.put(application, mgr); + } + return mgr; + } + + @Override + public int getMaxInactiveInterval() { + return getHttpSession().getMaxInactiveInterval(); + } +} diff --git a/server/src/com/vaadin/server/WebApplicationContext.java b/server/src/com/vaadin/server/WebApplicationContext.java deleted file mode 100644 index 02e902f79e..0000000000 --- a/server/src/com/vaadin/server/WebApplicationContext.java +++ /dev/null @@ -1,194 +0,0 @@ -/* - * Copyright 2011 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.server; - -import java.io.File; -import java.util.Enumeration; -import java.util.HashMap; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpSession; -import javax.servlet.http.HttpSessionBindingEvent; -import javax.servlet.http.HttpSessionBindingListener; - -import com.vaadin.Application; - -/** - * Web application context for Vaadin applications. - * - * This is automatically added as a {@link HttpSessionBindingListener} when - * added to a {@link HttpSession}. - * - * @author Vaadin Ltd. - * @since 3.1 - */ -@SuppressWarnings("serial") -public class WebApplicationContext extends ApplicationContext { - - protected transient HttpSession session; - private transient boolean reinitializingSession = false; - - /** - * Stores a reference to the currentRequest. Null it not inside a request. - */ - private transient Object currentRequest = null; - - /** - * Creates a new Web Application Context. - * - */ - protected WebApplicationContext() { - - } - - @Override - protected void startTransaction(Application application, Object request) { - currentRequest = request; - super.startTransaction(application, request); - } - - @Override - protected void endTransaction(Application application, Object request) { - super.endTransaction(application, request); - currentRequest = null; - } - - @Override - public void valueUnbound(HttpSessionBindingEvent event) { - if (!reinitializingSession) { - // Avoid closing the application if we are only reinitializing the - // session. Closing the application would cause the state to be lost - // and a new application to be created, which is not what we want. - super.valueUnbound(event); - } - } - - /** - * Discards the current session and creates a new session with the same - * contents. The purpose of this is to introduce a new session key in order - * to avoid session fixation attacks. - */ - @SuppressWarnings("unchecked") - public void reinitializeSession() { - - HttpSession oldSession = getHttpSession(); - - // Stores all attributes (security key, reference to this context - // instance) so they can be added to the new session - HashMap attrs = new HashMap(); - for (Enumeration e = oldSession.getAttributeNames(); e - .hasMoreElements();) { - String name = e.nextElement(); - attrs.put(name, oldSession.getAttribute(name)); - } - - // Invalidate the current session, set flag to avoid call to - // valueUnbound - reinitializingSession = true; - oldSession.invalidate(); - reinitializingSession = false; - - // Create a new session - HttpSession newSession = ((HttpServletRequest) currentRequest) - .getSession(); - - // Restores all attributes (security key, reference to this context - // instance) - for (String name : attrs.keySet()) { - newSession.setAttribute(name, attrs.get(name)); - } - - // Update the "current session" variable - session = newSession; - } - - /** - * Gets the application context base directory. - * - * @see com.vaadin.server.ApplicationContext#getBaseDirectory() - */ - @Override - public File getBaseDirectory() { - final String realPath = ApplicationServlet.getResourcePath( - session.getServletContext(), "/"); - if (realPath == null) { - return null; - } - return new File(realPath); - } - - /** - * Gets the http-session application is running in. - * - * @return HttpSession this application context resides in. - */ - public HttpSession getHttpSession() { - return session; - } - - /** - * Gets the application context for an HttpSession. - * - * @param session - * the HTTP session. - * @return the application context for HttpSession. - */ - static public WebApplicationContext getApplicationContext( - HttpSession session) { - WebApplicationContext cx = (WebApplicationContext) session - .getAttribute(WebApplicationContext.class.getName()); - if (cx == null) { - cx = new WebApplicationContext(); - session.setAttribute(WebApplicationContext.class.getName(), cx); - } - if (cx.session == null) { - cx.session = session; - } - return cx; - } - - protected void addApplication(Application application) { - applications.add(application); - } - - /** - * Gets communication manager for an application. - * - * If this application has not been running before, a new manager is - * created. - * - * @param application - * @return CommunicationManager - */ - public CommunicationManager getApplicationManager(Application application, - AbstractApplicationServlet servlet) { - CommunicationManager mgr = (CommunicationManager) applicationToAjaxAppMgrMap - .get(application); - - if (mgr == null) { - // Creates new manager - mgr = servlet.createCommunicationManager(application); - applicationToAjaxAppMgrMap.put(application, mgr); - } - return mgr; - } - - @Override - public int getMaxInactiveInterval() { - return getHttpSession().getMaxInactiveInterval(); - } -} diff --git a/server/src/com/vaadin/server/WrappedHttpServletRequest.java b/server/src/com/vaadin/server/WrappedHttpServletRequest.java index cb8a9e8c5f..b069235843 100644 --- a/server/src/com/vaadin/server/WrappedHttpServletRequest.java +++ b/server/src/com/vaadin/server/WrappedHttpServletRequest.java @@ -99,8 +99,8 @@ public class WrappedHttpServletRequest extends HttpServletRequestWrapper @Override public WebBrowser getWebBrowser() { - WebApplicationContext context = (WebApplicationContext) Application - .getCurrent().getContext(); + ApplicationContext context = Application.getCurrent() + .getContext(); return context.getBrowser(); } }; diff --git a/server/tests/src/com/vaadin/tests/server/TransactionListenersConcurrency.java b/server/tests/src/com/vaadin/tests/server/TransactionListenersConcurrency.java index 302b534d8a..05ffcd1e36 100644 --- a/server/tests/src/com/vaadin/tests/server/TransactionListenersConcurrency.java +++ b/server/tests/src/com/vaadin/tests/server/TransactionListenersConcurrency.java @@ -23,7 +23,7 @@ import com.vaadin.Application; import com.vaadin.Application.ApplicationStartEvent; import com.vaadin.server.ApplicationContext; import com.vaadin.server.DeploymentConfiguration; -import com.vaadin.server.WebApplicationContext; +import com.vaadin.server.ServletApplicationContext; public class TransactionListenersConcurrency extends TestCase { @@ -39,7 +39,7 @@ public class TransactionListenersConcurrency extends TestCase { final List exceptions = new ArrayList(); HttpSession session = createSession(); - final WebApplicationContext context = WebApplicationContext + final ServletApplicationContext context = ServletApplicationContext .getApplicationContext(session); List threads = new ArrayList(); @@ -151,10 +151,10 @@ public class TransactionListenersConcurrency extends TestCase { private static HttpSession createSession() { HttpSession session = createMock(HttpSession.class); EasyMock.expect( - session.getAttribute(WebApplicationContext.class.getName())) + session.getAttribute(ServletApplicationContext.class.getName())) .andReturn(null).anyTimes(); session.setAttribute( - EasyMock.eq(WebApplicationContext.class.getName()), + EasyMock.eq(ServletApplicationContext.class.getName()), EasyMock.anyObject()); EasyMock.replay(session); diff --git a/uitest/src/com/vaadin/tests/VerifyBrowserVersion.java b/uitest/src/com/vaadin/tests/VerifyBrowserVersion.java index 8a07168ef7..f4cf236a24 100644 --- a/uitest/src/com/vaadin/tests/VerifyBrowserVersion.java +++ b/uitest/src/com/vaadin/tests/VerifyBrowserVersion.java @@ -1,6 +1,6 @@ package com.vaadin.tests; -import com.vaadin.server.WebApplicationContext; +import com.vaadin.server.ApplicationContext; import com.vaadin.server.WebBrowser; import com.vaadin.tests.components.TestBase; import com.vaadin.ui.Label; @@ -9,7 +9,7 @@ public class VerifyBrowserVersion extends TestBase { @Override protected void setup() { - WebApplicationContext context = (WebApplicationContext) getContext(); + ApplicationContext context = getContext(); WebBrowser browser = context.getBrowser(); addComponent(new Label(browser.getBrowserApplication())); addComponent(new Label("Touch device? " diff --git a/uitest/src/com/vaadin/tests/application/ApplicationCloseTest.java b/uitest/src/com/vaadin/tests/application/ApplicationCloseTest.java index 233da9722e..1f5f0dc691 100644 --- a/uitest/src/com/vaadin/tests/application/ApplicationCloseTest.java +++ b/uitest/src/com/vaadin/tests/application/ApplicationCloseTest.java @@ -1,7 +1,6 @@ package com.vaadin.tests.application; import com.vaadin.Application; -import com.vaadin.server.WebApplicationContext; import com.vaadin.shared.ui.label.ContentMode; import com.vaadin.tests.components.TestBase; import com.vaadin.ui.Button; @@ -16,8 +15,7 @@ public class ApplicationCloseTest extends TestBase { protected void setup() { Label applications = new Label("Applications in session:
", ContentMode.XHTML); - for (Application a : ((WebApplicationContext) getContext()) - .getApplications()) { + for (Application a : getContext().getApplications()) { applications.setValue(applications.getValue() + "App: " + a + "
"); } diff --git a/uitest/src/com/vaadin/tests/applicationcontext/ChangeSessionId.java b/uitest/src/com/vaadin/tests/applicationcontext/ChangeSessionId.java index 9146cf5dea..96ebe1345f 100644 --- a/uitest/src/com/vaadin/tests/applicationcontext/ChangeSessionId.java +++ b/uitest/src/com/vaadin/tests/applicationcontext/ChangeSessionId.java @@ -1,6 +1,6 @@ package com.vaadin.tests.applicationcontext; -import com.vaadin.server.WebApplicationContext; +import com.vaadin.server.ServletApplicationContext; import com.vaadin.tests.components.AbstractTestCase; import com.vaadin.tests.util.Log; import com.vaadin.ui.Button; @@ -32,7 +32,7 @@ public class ChangeSessionId extends AbstractTestCase { loginButton.addListener(new ClickListener() { @Override public void buttonClick(ClickEvent event) { - WebApplicationContext context = ((WebApplicationContext) getContext()); + ServletApplicationContext context = ((ServletApplicationContext) getContext()); String oldSessionId = context.getHttpSession().getId(); context.reinitializeSession(); @@ -55,7 +55,7 @@ public class ChangeSessionId extends AbstractTestCase { } protected String getSessionId() { - return ((WebApplicationContext) getContext()).getHttpSession().getId(); + return ((ServletApplicationContext) getContext()).getHttpSession().getId(); } @Override diff --git a/uitest/src/com/vaadin/tests/tickets/Ticket1975.java b/uitest/src/com/vaadin/tests/tickets/Ticket1975.java index 9c6dd8c272..e85fe294f2 100644 --- a/uitest/src/com/vaadin/tests/tickets/Ticket1975.java +++ b/uitest/src/com/vaadin/tests/tickets/Ticket1975.java @@ -5,7 +5,7 @@ import java.io.File; import java.io.FileInputStream; import com.vaadin.Application; -import com.vaadin.server.WebApplicationContext; +import com.vaadin.server.ServletApplicationContext; import com.vaadin.ui.Button; import com.vaadin.ui.Button.ClickEvent; import com.vaadin.ui.Button.ClickListener; @@ -33,7 +33,7 @@ public class Ticket1975 extends Application.LegacyApplication { try { cl1 = new CustomLayout(new ByteArrayInputStream(s.getBytes())); layout.addComponent(cl1); - WebApplicationContext wc = ((WebApplicationContext) getContext()); + ServletApplicationContext wc = ((ServletApplicationContext) getContext()); layout.addComponent(new Button("Disable/Enable", new ClickListener() { -- cgit v1.2.3 From 55afccf800425f66437fdffe3212b6b92fbeb1e5 Mon Sep 17 00:00:00 2001 From: Leif Åstrand Date: Mon, 3 Sep 2012 09:57:35 +0300 Subject: Remove Application.getVersion() (#9402) --- .../src/com/vaadin/client/ApplicationConfiguration.java | 16 ---------------- client/src/com/vaadin/client/ApplicationConnection.java | 1 - server/src/com/vaadin/Application.java | 17 +++-------------- server/src/com/vaadin/server/BootstrapHandler.java | 1 - 4 files changed, 3 insertions(+), 32 deletions(-) (limited to 'server') diff --git a/client/src/com/vaadin/client/ApplicationConfiguration.java b/client/src/com/vaadin/client/ApplicationConfiguration.java index e364facb43..0bb9bf1989 100644 --- a/client/src/com/vaadin/client/ApplicationConfiguration.java +++ b/client/src/com/vaadin/client/ApplicationConfiguration.java @@ -153,18 +153,6 @@ public class ApplicationConfiguration implements EntryPoint { return this.getConfig("versionInfo").vaadinVersion; }-*/; - /** - * Gets the version of the application running on the server. - * - * @return a string with the application version - * - * @see com.vaadin.Application#getVersion() - */ - private native String getApplicationVersion() - /*-{ - return this.getConfig("versionInfo").applicationVersion; - }-*/; - private native String getUIDL() /*-{ return this.getConfig("uidl"); @@ -393,10 +381,6 @@ public class ApplicationConfiguration implements EntryPoint { return getJsoConfiguration(id).getVaadinVersion(); } - public String getApplicationVersion() { - return getJsoConfiguration(id).getApplicationVersion(); - } - public Class getConnectorClassByEncodedTag( int tag) { Class type = classes.get(tag); diff --git a/client/src/com/vaadin/client/ApplicationConnection.java b/client/src/com/vaadin/client/ApplicationConnection.java index 88510e7bf6..6a1474fa45 100644 --- a/client/src/com/vaadin/client/ApplicationConnection.java +++ b/client/src/com/vaadin/client/ApplicationConnection.java @@ -264,7 +264,6 @@ public class ApplicationConnection { VConsole.log("Vaadin application servlet version: " + cnf.getServletVersion()); - VConsole.log("Application version: " + cnf.getApplicationVersion()); if (!cnf.getServletVersion().equals(Version.getFullVersion())) { VConsole.error("Warning: your widget set seems to be built with a different " diff --git a/server/src/com/vaadin/Application.java b/server/src/com/vaadin/Application.java index 90f354e180..f8ff812c94 100644 --- a/server/src/com/vaadin/Application.java +++ b/server/src/com/vaadin/Application.java @@ -61,10 +61,10 @@ import com.vaadin.server.CombinedRequest; import com.vaadin.server.DeploymentConfiguration; import com.vaadin.server.GlobalResourceHandler; import com.vaadin.server.RequestHandler; +import com.vaadin.server.ServletApplicationContext; import com.vaadin.server.Terminal; import com.vaadin.server.UIProvider; import com.vaadin.server.VariableOwner; -import com.vaadin.server.ServletApplicationContext; import com.vaadin.server.WrappedRequest; import com.vaadin.server.WrappedRequest.BrowserDetails; import com.vaadin.server.WrappedResponse; @@ -867,8 +867,8 @@ public class Application implements Terminal.ErrorListener, Serializable { *

*

* By default, when you are deploying your application to a servlet - * container, the implementation class is {@link ServletApplicationContext} - - * you can safely cast to this class and use the methods from there. When + * container, the implementation class is {@link ServletApplicationContext} + * - you can safely cast to this class and use the methods from there. When * you are deploying your application as a portlet, context implementation * is {@link PortletApplicationContext}. *

@@ -879,17 +879,6 @@ public class Application implements Terminal.ErrorListener, Serializable { return context; } - /** - * Override this method to return correct version number of your - * Application. Version information is delivered for example to Testing - * Tools test results. By default this returns a string "NONVERSIONED". - * - * @return version string - */ - public String getVersion() { - return "NONVERSIONED"; - } - /** * Gets the application error handler. * diff --git a/server/src/com/vaadin/server/BootstrapHandler.java b/server/src/com/vaadin/server/BootstrapHandler.java index 60ac40916c..df46bb12ee 100644 --- a/server/src/com/vaadin/server/BootstrapHandler.java +++ b/server/src/com/vaadin/server/BootstrapHandler.java @@ -401,7 +401,6 @@ public abstract class BootstrapHandler implements RequestHandler { JSONObject versionInfo = new JSONObject(); versionInfo.put("vaadinVersion", Version.getFullVersion()); - versionInfo.put("applicationVersion", application.getVersion()); appConfig.put("versionInfo", versionInfo); appConfig.put("widgetset", context.getWidgetsetName()); -- cgit v1.2.3 From 3dd5a4824cda3dd7842c937a618feebc2b1a0f27 Mon Sep 17 00:00:00 2001 From: Leif Åstrand Date: Mon, 3 Sep 2012 10:38:11 +0300 Subject: Move SystemMessages to DeploymentConfiguration (#9402) --- server/src/com/vaadin/Application.java | 25 -- .../vaadin/server/AbstractApplicationPortlet.java | 33 +- .../vaadin/server/AbstractApplicationServlet.java | 55 +--- .../server/AbstractCommunicationManager.java | 54 +--- .../src/com/vaadin/server/ApplicationServlet.java | 6 +- server/src/com/vaadin/server/BootstrapHandler.java | 8 +- .../vaadin/server/CustomizedSystemMessages.java | 339 +++++++++++++++++++++ .../com/vaadin/server/DeploymentConfiguration.java | 7 + .../com/vaadin/server/ServletPortletHelper.java | 4 + server/src/com/vaadin/server/SystemMessages.java | 310 +++++++++++++++++++ .../vaadin/launcher/ApplicationRunnerServlet.java | 14 - .../src/com/vaadin/tests/tickets/Ticket1673.java | 7 +- .../src/com/vaadin/tests/tickets/Ticket2106.java | 6 +- 13 files changed, 701 insertions(+), 167 deletions(-) create mode 100644 server/src/com/vaadin/server/CustomizedSystemMessages.java create mode 100644 server/src/com/vaadin/server/SystemMessages.java (limited to 'server') diff --git a/server/src/com/vaadin/Application.java b/server/src/com/vaadin/Application.java index f8ff812c94..8a043154d7 100644 --- a/server/src/com/vaadin/Application.java +++ b/server/src/com/vaadin/Application.java @@ -462,12 +462,6 @@ public class Application implements Terminal.ErrorListener, Serializable { */ private String logoutURL = null; - /** - * The default SystemMessages (read-only). Change by overriding - * getSystemMessages() and returning CustomizedSystemMessages - */ - private static final SystemMessages DEFAULT_SYSTEM_MESSAGES = new SystemMessages(); - /** * Application wide error handler which is used by default if an error is * left unhandled. @@ -791,25 +785,6 @@ public class Application implements Terminal.ErrorListener, Serializable { this.logoutURL = logoutURL; } - /** - * Gets the SystemMessages for this application. SystemMessages are used to - * notify the user of various critical situations that can occur, such as - * session expiration, client/server out of sync, and internal server error. - * - * You can customize the messages by "overriding" this method and returning - * {@link CustomizedSystemMessages}. To "override" this method, re-implement - * this method in your application (the class that extends - * {@link Application}). Even though overriding static methods is not - * possible in Java, Vaadin selects to call the static method from the - * subclass instead of the original {@link #getSystemMessages()} if such a - * method exists. - * - * @return the SystemMessages for this application - */ - public static SystemMessages getSystemMessages() { - return DEFAULT_SYSTEM_MESSAGES; - } - /** *

* Invoked by the terminal on any exception that occurs in application and diff --git a/server/src/com/vaadin/server/AbstractApplicationPortlet.java b/server/src/com/vaadin/server/AbstractApplicationPortlet.java index e8151462aa..7fc69b05f7 100644 --- a/server/src/com/vaadin/server/AbstractApplicationPortlet.java +++ b/server/src/com/vaadin/server/AbstractApplicationPortlet.java @@ -22,7 +22,6 @@ import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.io.Serializable; -import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.MalformedURLException; import java.security.GeneralSecurityException; @@ -55,7 +54,6 @@ import com.liferay.portal.kernel.util.PortalClassInvoker; import com.liferay.portal.kernel.util.PropsUtil; import com.vaadin.Application; import com.vaadin.Application.ApplicationStartEvent; -import com.vaadin.Application.SystemMessages; import com.vaadin.server.AbstractCommunicationManager.Callback; import com.vaadin.ui.UI; @@ -321,6 +319,11 @@ public abstract class AbstractApplicationPortlet extends GenericPortlet public String getMimeType(String resourceName) { return getPortletContext().getMimeType(resourceName); } + + @Override + public SystemMessages getSystemMessages() { + return AbstractApplicationPortlet.this.getSystemMessages(); + } }; addonContext = new AddonContext(deploymentConfiguration); @@ -912,29 +915,7 @@ public abstract class AbstractApplicationPortlet extends GenericPortlet * @return */ protected SystemMessages getSystemMessages() { - try { - Class appCls = getApplicationClass(); - Method m = appCls.getMethod("getSystemMessages", (Class[]) null); - return (Application.SystemMessages) m.invoke(null, (Object[]) null); - } catch (ClassNotFoundException e) { - // This should never happen - throw new SystemMessageException(e); - } catch (SecurityException e) { - throw new SystemMessageException( - "Application.getSystemMessage() should be static public", e); - } catch (NoSuchMethodException e) { - // This is completely ok and should be silently ignored - } catch (IllegalArgumentException e) { - // This should never happen - throw new SystemMessageException(e); - } catch (IllegalAccessException e) { - throw new SystemMessageException( - "Application.getSystemMessage() should be static public", e); - } catch (InvocationTargetException e) { - // This should never happen - throw new SystemMessageException(e); - } - return Application.getSystemMessages(); + return ServletPortletHelper.DEFAULT_SYSTEM_MESSAGES; } private void handleServiceException(WrappedPortletRequest request, @@ -945,7 +926,7 @@ public abstract class AbstractApplicationPortlet extends GenericPortlet // if this was an UIDL request, response UIDL back to client if (getRequestType(request) == RequestType.UIDL) { - Application.SystemMessages ci = getSystemMessages(); + SystemMessages ci = getSystemMessages(); criticalNotification(request, response, ci.getInternalErrorCaption(), ci.getInternalErrorMessage(), null, ci.getInternalErrorURL()); diff --git a/server/src/com/vaadin/server/AbstractApplicationServlet.java b/server/src/com/vaadin/server/AbstractApplicationServlet.java index 87d36da255..8b3103b794 100644 --- a/server/src/com/vaadin/server/AbstractApplicationServlet.java +++ b/server/src/com/vaadin/server/AbstractApplicationServlet.java @@ -22,8 +22,6 @@ import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.io.Serializable; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; import java.net.MalformedURLException; import java.net.URL; import java.net.URLConnection; @@ -48,7 +46,6 @@ import javax.servlet.http.HttpSession; import com.vaadin.Application; import com.vaadin.Application.ApplicationStartEvent; -import com.vaadin.Application.SystemMessages; import com.vaadin.server.AbstractCommunicationManager.Callback; import com.vaadin.shared.ApplicationConstants; import com.vaadin.ui.UI; @@ -165,6 +162,11 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements public String getMimeType(String resourceName) { return getServletContext().getMimeType(resourceName); } + + @Override + public SystemMessages getSystemMessages() { + return AbstractApplicationServlet.this.getSystemMessages(); + } }; addonContext = new AddonContext(deploymentConfiguration); @@ -706,7 +708,7 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements Throwable e) throws IOException, ServletException { // if this was an UIDL request, response UIDL back to client if (getRequestType(request) == RequestType.UIDL) { - Application.SystemMessages ci = getSystemMessages(); + SystemMessages ci = getSystemMessages(); criticalNotification(request, response, ci.getInternalErrorCaption(), ci.getInternalErrorMessage(), null, ci.getInternalErrorURL()); @@ -769,7 +771,7 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements } try { - Application.SystemMessages ci = getSystemMessages(); + SystemMessages ci = getSystemMessages(); if (getRequestType(request) != RequestType.UIDL) { // 'plain' http req - e.g. browser reload; // just go ahead redirect the browser @@ -811,7 +813,7 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements } try { - Application.SystemMessages ci = getSystemMessages(); + SystemMessages ci = getSystemMessages(); if (getRequestType(request) != RequestType.UIDL) { // 'plain' http req - e.g. browser reload; // just go ahead redirect the browser @@ -1177,51 +1179,14 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements } /** - * Get system messages from the current application class + * Get system messages * * @return */ protected SystemMessages getSystemMessages() { - Class appCls = null; - try { - appCls = getApplicationClass(); - } catch (ClassNotFoundException e) { - // Previous comment claimed that this should never happen - throw new SystemMessageException(e); - } - return getSystemMessages(appCls); + return ServletPortletHelper.DEFAULT_SYSTEM_MESSAGES; } - public static SystemMessages getSystemMessages( - Class appCls) { - try { - if (appCls != null) { - Method m = appCls - .getMethod("getSystemMessages", (Class[]) null); - return (Application.SystemMessages) m.invoke(null, - (Object[]) null); - } - } catch (SecurityException e) { - throw new SystemMessageException( - "Application.getSystemMessage() should be static public", e); - } catch (NoSuchMethodException e) { - // This is completely ok and should be silently ignored - } catch (IllegalArgumentException e) { - // This should never happen - throw new SystemMessageException(e); - } catch (IllegalAccessException e) { - throw new SystemMessageException( - "Application.getSystemMessage() should be static public", e); - } catch (InvocationTargetException e) { - // This should never happen - throw new SystemMessageException(e); - } - return Application.getSystemMessages(); - } - - protected abstract Class getApplicationClass() - throws ClassNotFoundException; - /** * Return the URL from where static files, e.g. the widgetset and the theme, * are served. In a standard configuration the VAADIN folder inside the diff --git a/server/src/com/vaadin/server/AbstractCommunicationManager.java b/server/src/com/vaadin/server/AbstractCommunicationManager.java index 526d18fd34..740ecf843b 100644 --- a/server/src/com/vaadin/server/AbstractCommunicationManager.java +++ b/server/src/com/vaadin/server/AbstractCommunicationManager.java @@ -27,8 +27,6 @@ import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.io.Serializable; import java.io.StringWriter; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; import java.lang.reflect.Type; import java.net.URI; import java.net.URISyntaxException; @@ -59,7 +57,6 @@ import java.util.logging.Logger; import javax.servlet.http.HttpServletResponse; import com.vaadin.Application; -import com.vaadin.Application.SystemMessages; import com.vaadin.annotations.JavaScript; import com.vaadin.annotations.StyleSheet; import com.vaadin.external.json.JSONArray; @@ -574,28 +571,15 @@ public abstract class AbstractCommunicationManager implements Serializable { if (!handleVariables(request, response, callback, application, uI)) { // var inconsistency; the client is probably out-of-sync - SystemMessages ci = null; - try { - Method m = application.getClass().getMethod( - "getSystemMessages", (Class[]) null); - ci = (Application.SystemMessages) m.invoke(null, - (Object[]) null); - } catch (Exception e2) { - // FIXME: Handle exception - // Not critical, but something is still wrong; print - // stacktrace - getLogger().log(Level.WARNING, - "getSystemMessages() failed - continuing", e2); - } - if (ci != null) { - String msg = ci.getOutOfSyncMessage(); - String cap = ci.getOutOfSyncCaption(); - if (msg != null || cap != null) { - callback.criticalNotification(request, response, cap, - msg, null, ci.getOutOfSyncURL()); - // will reload page after this - return; - } + SystemMessages ci = response.getDeploymentConfiguration() + .getSystemMessages(); + String msg = ci.getOutOfSyncMessage(); + String cap = ci.getOutOfSyncCaption(); + if (msg != null || cap != null) { + callback.criticalNotification(request, response, cap, msg, + null, ci.getOutOfSyncURL()); + // will reload page after this + return; } // No message to show, let's just repaint all. repaintAll = true; @@ -1028,24 +1012,8 @@ public abstract class AbstractCommunicationManager implements Serializable { } } - SystemMessages ci = null; - try { - Method m = application.getClass().getMethod("getSystemMessages", - (Class[]) null); - ci = (Application.SystemMessages) m.invoke(null, (Object[]) null); - } catch (NoSuchMethodException e) { - getLogger().log(Level.WARNING, - "getSystemMessages() failed - continuing", e); - } catch (IllegalArgumentException e) { - getLogger().log(Level.WARNING, - "getSystemMessages() failed - continuing", e); - } catch (IllegalAccessException e) { - getLogger().log(Level.WARNING, - "getSystemMessages() failed - continuing", e); - } catch (InvocationTargetException e) { - getLogger().log(Level.WARNING, - "getSystemMessages() failed - continuing", e); - } + SystemMessages ci = request.getDeploymentConfiguration() + .getSystemMessages(); // meta instruction for client to enable auto-forward to // sessionExpiredURL after timer expires. diff --git a/server/src/com/vaadin/server/ApplicationServlet.java b/server/src/com/vaadin/server/ApplicationServlet.java index af0cebcf86..905b48d62f 100644 --- a/server/src/com/vaadin/server/ApplicationServlet.java +++ b/server/src/com/vaadin/server/ApplicationServlet.java @@ -76,14 +76,10 @@ public class ApplicationServlet extends AbstractApplicationServlet { throw new ServletException("getNewApplication failed", e); } catch (final InstantiationException e) { throw new ServletException("getNewApplication failed", e); - } catch (ClassNotFoundException e) { - throw new ServletException("getNewApplication failed", e); } } - @Override - protected Class getApplicationClass() - throws ClassNotFoundException { + protected Class getApplicationClass() { return applicationClass; } } diff --git a/server/src/com/vaadin/server/BootstrapHandler.java b/server/src/com/vaadin/server/BootstrapHandler.java index df46bb12ee..98ed1071de 100644 --- a/server/src/com/vaadin/server/BootstrapHandler.java +++ b/server/src/com/vaadin/server/BootstrapHandler.java @@ -420,10 +420,12 @@ public abstract class BootstrapHandler implements RequestHandler { WrappedRequest request = context.getRequest(); Application application = context.getApplication(); + DeploymentConfiguration deploymentConfiguration = request + .getDeploymentConfiguration(); // Get system messages - Application.SystemMessages systemMessages = AbstractApplicationServlet - .getSystemMessages(application.getClass()); + SystemMessages systemMessages = deploymentConfiguration + .getSystemMessages(); if (systemMessages != null) { // Write the CommunicationError -message to client JSONObject comErrMsg = new JSONObject(); @@ -445,8 +447,6 @@ public abstract class BootstrapHandler implements RequestHandler { defaults.put("authErrMsg", authErrMsg); } - DeploymentConfiguration deploymentConfiguration = request - .getDeploymentConfiguration(); String staticFileLocation = deploymentConfiguration .getStaticFileLocation(request); String widgetsetBase = staticFileLocation + "/" diff --git a/server/src/com/vaadin/server/CustomizedSystemMessages.java b/server/src/com/vaadin/server/CustomizedSystemMessages.java new file mode 100644 index 0000000000..8e3d7bf8e0 --- /dev/null +++ b/server/src/com/vaadin/server/CustomizedSystemMessages.java @@ -0,0 +1,339 @@ +/* + * Copyright 2011 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.server; + +import java.io.Serializable; + +/** + * Contains the system messages used to notify the user about various + * critical situations that can occur. + *

+ * Vaadin gets the SystemMessages from your application by calling a static + * getSystemMessages() method. By default the + * Application.getSystemMessages() is used. You can customize this by + * defining a static MyApplication.getSystemMessages() and returning + * CustomizedSystemMessages. Note that getSystemMessages() is static - + * changing the system messages will by default change the message for all + * users of the application. + *

+ *

+ * The default behavior is to show a notification, and restart the + * application the the user clicks the message.
+ * Instead of restarting the application, you can set a specific URL that + * the user is taken to.
+ * Setting both caption and message to null will restart the application (or + * go to the specified URL) without displaying a notification. + * set*NotificationEnabled(false) will achieve the same thing. + *

+ *

+ * The situations are: + *

  • Session expired: the user session has expired, usually due to + * inactivity.
  • + *
  • Communication error: the client failed to contact the server, or the + * server returned and invalid response.
  • + *
  • Internal error: unhandled critical server error (e.g out of memory, + * database crash) + *
  • Out of sync: the client is not in sync with the server. E.g the user + * opens two windows showing the same application, but the application does + * not support this and uses the same Window instance. When the user makes + * changes in one of the windows - the other window is no longer in sync, + * and (for instance) pressing a button that is no longer present in the UI + * will cause a out-of-sync -situation. + *

    + */ + +public class CustomizedSystemMessages extends SystemMessages + implements Serializable { + + /** + * Sets the URL to go to when the session has expired. + * + * @param sessionExpiredURL + * the URL to go to, or null to reload current + */ + public void setSessionExpiredURL(String sessionExpiredURL) { + this.sessionExpiredURL = sessionExpiredURL; + } + + /** + * Enables or disables the notification. If disabled, the set URL (or + * current) is loaded directly when next transaction between server and + * client happens. + * + * @param sessionExpiredNotificationEnabled + * true = enabled, false = disabled + */ + public void setSessionExpiredNotificationEnabled( + boolean sessionExpiredNotificationEnabled) { + this.sessionExpiredNotificationEnabled = sessionExpiredNotificationEnabled; + } + + /** + * Sets the caption of the notification. Set to null for no caption. If + * both caption and message are null, client automatically forwards to + * sessionExpiredUrl after timeout timer expires. Timer uses value read + * from HTTPSession.getMaxInactiveInterval() + * + * @param sessionExpiredCaption + * the caption + */ + public void setSessionExpiredCaption(String sessionExpiredCaption) { + this.sessionExpiredCaption = sessionExpiredCaption; + } + + /** + * Sets the message of the notification. Set to null for no message. If + * both caption and message are null, client automatically forwards to + * sessionExpiredUrl after timeout timer expires. Timer uses value read + * from HTTPSession.getMaxInactiveInterval() + * + * @param sessionExpiredMessage + * the message + */ + public void setSessionExpiredMessage(String sessionExpiredMessage) { + this.sessionExpiredMessage = sessionExpiredMessage; + } + + /** + * Sets the URL to go to when there is a authentication error. + * + * @param authenticationErrorURL + * the URL to go to, or null to reload current + */ + public void setAuthenticationErrorURL(String authenticationErrorURL) { + this.authenticationErrorURL = authenticationErrorURL; + } + + /** + * Enables or disables the notification. If disabled, the set URL (or + * current) is loaded directly. + * + * @param authenticationErrorNotificationEnabled + * true = enabled, false = disabled + */ + public void setAuthenticationErrorNotificationEnabled( + boolean authenticationErrorNotificationEnabled) { + this.authenticationErrorNotificationEnabled = authenticationErrorNotificationEnabled; + } + + /** + * Sets the caption of the notification. Set to null for no caption. If + * both caption and message is null, the notification is disabled; + * + * @param authenticationErrorCaption + * the caption + */ + public void setAuthenticationErrorCaption( + String authenticationErrorCaption) { + this.authenticationErrorCaption = authenticationErrorCaption; + } + + /** + * Sets the message of the notification. Set to null for no message. If + * both caption and message is null, the notification is disabled; + * + * @param authenticationErrorMessage + * the message + */ + public void setAuthenticationErrorMessage( + String authenticationErrorMessage) { + this.authenticationErrorMessage = authenticationErrorMessage; + } + + /** + * Sets the URL to go to when there is a communication error. + * + * @param communicationErrorURL + * the URL to go to, or null to reload current + */ + public void setCommunicationErrorURL(String communicationErrorURL) { + this.communicationErrorURL = communicationErrorURL; + } + + /** + * Enables or disables the notification. If disabled, the set URL (or + * current) is loaded directly. + * + * @param communicationErrorNotificationEnabled + * true = enabled, false = disabled + */ + public void setCommunicationErrorNotificationEnabled( + boolean communicationErrorNotificationEnabled) { + this.communicationErrorNotificationEnabled = communicationErrorNotificationEnabled; + } + + /** + * Sets the caption of the notification. Set to null for no caption. If + * both caption and message is null, the notification is disabled; + * + * @param communicationErrorCaption + * the caption + */ + public void setCommunicationErrorCaption( + String communicationErrorCaption) { + this.communicationErrorCaption = communicationErrorCaption; + } + + /** + * Sets the message of the notification. Set to null for no message. If + * both caption and message is null, the notification is disabled; + * + * @param communicationErrorMessage + * the message + */ + public void setCommunicationErrorMessage( + String communicationErrorMessage) { + this.communicationErrorMessage = communicationErrorMessage; + } + + /** + * Sets the URL to go to when an internal error occurs. + * + * @param internalErrorURL + * the URL to go to, or null to reload current + */ + public void setInternalErrorURL(String internalErrorURL) { + this.internalErrorURL = internalErrorURL; + } + + /** + * Enables or disables the notification. If disabled, the set URL (or + * current) is loaded directly. + * + * @param internalErrorNotificationEnabled + * true = enabled, false = disabled + */ + public void setInternalErrorNotificationEnabled( + boolean internalErrorNotificationEnabled) { + this.internalErrorNotificationEnabled = internalErrorNotificationEnabled; + } + + /** + * Sets the caption of the notification. Set to null for no caption. If + * both caption and message is null, the notification is disabled; + * + * @param internalErrorCaption + * the caption + */ + public void setInternalErrorCaption(String internalErrorCaption) { + this.internalErrorCaption = internalErrorCaption; + } + + /** + * Sets the message of the notification. Set to null for no message. If + * both caption and message is null, the notification is disabled; + * + * @param internalErrorMessage + * the message + */ + public void setInternalErrorMessage(String internalErrorMessage) { + this.internalErrorMessage = internalErrorMessage; + } + + /** + * Sets the URL to go to when the client is out-of-sync. + * + * @param outOfSyncURL + * the URL to go to, or null to reload current + */ + public void setOutOfSyncURL(String outOfSyncURL) { + this.outOfSyncURL = outOfSyncURL; + } + + /** + * Enables or disables the notification. If disabled, the set URL (or + * current) is loaded directly. + * + * @param outOfSyncNotificationEnabled + * true = enabled, false = disabled + */ + public void setOutOfSyncNotificationEnabled( + boolean outOfSyncNotificationEnabled) { + this.outOfSyncNotificationEnabled = outOfSyncNotificationEnabled; + } + + /** + * Sets the caption of the notification. Set to null for no caption. If + * both caption and message is null, the notification is disabled; + * + * @param outOfSyncCaption + * the caption + */ + public void setOutOfSyncCaption(String outOfSyncCaption) { + this.outOfSyncCaption = outOfSyncCaption; + } + + /** + * Sets the message of the notification. Set to null for no message. If + * both caption and message is null, the notification is disabled; + * + * @param outOfSyncMessage + * the message + */ + public void setOutOfSyncMessage(String outOfSyncMessage) { + this.outOfSyncMessage = outOfSyncMessage; + } + + /** + * Sets the URL to redirect to when the browser has cookies disabled. + * + * @param cookiesDisabledURL + * the URL to redirect to, or null to reload the current URL + */ + public void setCookiesDisabledURL(String cookiesDisabledURL) { + this.cookiesDisabledURL = cookiesDisabledURL; + } + + /** + * Enables or disables the notification for "cookies disabled" messages. + * If disabled, the URL returned by {@link #getCookiesDisabledURL()} is + * loaded directly. + * + * @param cookiesDisabledNotificationEnabled + * true to enable "cookies disabled" messages, false + * otherwise + */ + public void setCookiesDisabledNotificationEnabled( + boolean cookiesDisabledNotificationEnabled) { + this.cookiesDisabledNotificationEnabled = cookiesDisabledNotificationEnabled; + } + + /** + * Sets the caption of the "cookies disabled" notification. Set to null + * for no caption. If both caption and message is null, the notification + * is disabled. + * + * @param cookiesDisabledCaption + * the caption for the "cookies disabled" notification + */ + public void setCookiesDisabledCaption(String cookiesDisabledCaption) { + this.cookiesDisabledCaption = cookiesDisabledCaption; + } + + /** + * Sets the message of the "cookies disabled" notification. Set to null + * for no message. If both caption and message is null, the notification + * is disabled. + * + * @param cookiesDisabledMessage + * the message for the "cookies disabled" notification + */ + public void setCookiesDisabledMessage(String cookiesDisabledMessage) { + this.cookiesDisabledMessage = cookiesDisabledMessage; + } + +} \ No newline at end of file diff --git a/server/src/com/vaadin/server/DeploymentConfiguration.java b/server/src/com/vaadin/server/DeploymentConfiguration.java index d61d03f4d9..acfba405e6 100644 --- a/server/src/com/vaadin/server/DeploymentConfiguration.java +++ b/server/src/com/vaadin/server/DeploymentConfiguration.java @@ -181,4 +181,11 @@ public interface DeploymentConfiguration extends Serializable { * indefinitely. */ public boolean isIdleUICleanupEnabled(); + + /** + * Gets the system messages object + * + * @return the system messages object + */ + public SystemMessages getSystemMessages(); } diff --git a/server/src/com/vaadin/server/ServletPortletHelper.java b/server/src/com/vaadin/server/ServletPortletHelper.java index cce98ab925..f9ca55b50e 100644 --- a/server/src/com/vaadin/server/ServletPortletHelper.java +++ b/server/src/com/vaadin/server/ServletPortletHelper.java @@ -24,6 +24,10 @@ import com.vaadin.ui.UI; class ServletPortletHelper implements Serializable { public static final String UPLOAD_URL_PREFIX = "APP/UPLOAD/"; + /** + * The default SystemMessages (read-only). + */ + static final SystemMessages DEFAULT_SYSTEM_MESSAGES = new SystemMessages(); public static class ApplicationClassException extends Exception { diff --git a/server/src/com/vaadin/server/SystemMessages.java b/server/src/com/vaadin/server/SystemMessages.java new file mode 100644 index 0000000000..17aed0003e --- /dev/null +++ b/server/src/com/vaadin/server/SystemMessages.java @@ -0,0 +1,310 @@ +/* + * Copyright 2011 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.server; + +import java.io.Serializable; + +import com.vaadin.Application; + + +/** + * Contains the system messages used to notify the user about various + * critical situations that can occur. + *

    + * Customize by overriding the static + * {@link Application#getSystemMessages()} and returning + * {@link CustomizedSystemMessages}. + *

    + *

    + * The defaults defined in this class are: + *

      + *
    • sessionExpiredURL = null
    • + *
    • sessionExpiredNotificationEnabled = true
    • + *
    • sessionExpiredCaption = ""
    • + *
    • sessionExpiredMessage = + * "Take note of any unsaved data, and click here to continue."
    • + *
    • communicationErrorURL = null
    • + *
    • communicationErrorNotificationEnabled = true
    • + *
    • communicationErrorCaption = "Communication problem"
    • + *
    • communicationErrorMessage = + * "Take note of any unsaved data, and click here to continue."
    • + *
    • internalErrorURL = null
    • + *
    • internalErrorNotificationEnabled = true
    • + *
    • internalErrorCaption = "Internal error"
    • + *
    • internalErrorMessage = "Please notify the administrator.
      + * Take note of any unsaved data, and click here to continue."
    • + *
    • outOfSyncURL = null
    • + *
    • outOfSyncNotificationEnabled = true
    • + *
    • outOfSyncCaption = "Out of sync"
    • + *
    • outOfSyncMessage = "Something has caused us to be out of sync + * with the server.
      + * Take note of any unsaved data, and click here to re-sync."
    • + *
    • cookiesDisabledURL = null
    • + *
    • cookiesDisabledNotificationEnabled = true
    • + *
    • cookiesDisabledCaption = "Cookies disabled"
    • + *
    • cookiesDisabledMessage = "This application requires cookies to + * function.
      + * Please enable cookies in your browser and click here to try again. + *
    • + *
    + *

    + * + */ +public class SystemMessages implements Serializable { + protected String sessionExpiredURL = null; + protected boolean sessionExpiredNotificationEnabled = true; + protected String sessionExpiredCaption = "Session Expired"; + protected String sessionExpiredMessage = "Take note of any unsaved data, and click here to continue."; + + protected String communicationErrorURL = null; + protected boolean communicationErrorNotificationEnabled = true; + protected String communicationErrorCaption = "Communication problem"; + protected String communicationErrorMessage = "Take note of any unsaved data, and click here to continue."; + + protected String authenticationErrorURL = null; + protected boolean authenticationErrorNotificationEnabled = true; + protected String authenticationErrorCaption = "Authentication problem"; + protected String authenticationErrorMessage = "Take note of any unsaved data, and click here to continue."; + + protected String internalErrorURL = null; + protected boolean internalErrorNotificationEnabled = true; + protected String internalErrorCaption = "Internal error"; + protected String internalErrorMessage = "Please notify the administrator.
    Take note of any unsaved data, and click here to continue."; + + protected String outOfSyncURL = null; + protected boolean outOfSyncNotificationEnabled = true; + protected String outOfSyncCaption = "Out of sync"; + protected String outOfSyncMessage = "Something has caused us to be out of sync with the server.
    Take note of any unsaved data, and click here to re-sync."; + + protected String cookiesDisabledURL = null; + protected boolean cookiesDisabledNotificationEnabled = true; + protected String cookiesDisabledCaption = "Cookies disabled"; + protected String cookiesDisabledMessage = "This application requires cookies to function.
    Please enable cookies in your browser and click here to try again."; + + /** + * Use {@link CustomizedSystemMessages} to customize + */ + SystemMessages() { + + } + + /** + * @return null to indicate that the application will be restarted after + * session expired message has been shown. + */ + public String getSessionExpiredURL() { + return sessionExpiredURL; + } + + /** + * @return true to show session expiration message. + */ + public boolean isSessionExpiredNotificationEnabled() { + return sessionExpiredNotificationEnabled; + } + + /** + * @return "" to show no caption. + */ + public String getSessionExpiredCaption() { + return (sessionExpiredNotificationEnabled ? sessionExpiredCaption + : null); + } + + /** + * @return + * "Take note of any unsaved data, and click here to continue." + */ + public String getSessionExpiredMessage() { + return (sessionExpiredNotificationEnabled ? sessionExpiredMessage + : null); + } + + /** + * @return null to reload the application after communication error + * message. + */ + public String getCommunicationErrorURL() { + return communicationErrorURL; + } + + /** + * @return true to show the communication error message. + */ + public boolean isCommunicationErrorNotificationEnabled() { + return communicationErrorNotificationEnabled; + } + + /** + * @return "Communication problem" + */ + public String getCommunicationErrorCaption() { + return (communicationErrorNotificationEnabled ? communicationErrorCaption + : null); + } + + /** + * @return + * "Take note of any unsaved data, and click here to continue." + */ + public String getCommunicationErrorMessage() { + return (communicationErrorNotificationEnabled ? communicationErrorMessage + : null); + } + + /** + * @return null to reload the application after authentication error + * message. + */ + public String getAuthenticationErrorURL() { + return authenticationErrorURL; + } + + /** + * @return true to show the authentication error message. + */ + public boolean isAuthenticationErrorNotificationEnabled() { + return authenticationErrorNotificationEnabled; + } + + /** + * @return "Authentication problem" + */ + public String getAuthenticationErrorCaption() { + return (authenticationErrorNotificationEnabled ? authenticationErrorCaption + : null); + } + + /** + * @return + * "Take note of any unsaved data, and click here to continue." + */ + public String getAuthenticationErrorMessage() { + return (authenticationErrorNotificationEnabled ? authenticationErrorMessage + : null); + } + + /** + * @return null to reload the current URL after internal error message + * has been shown. + */ + public String getInternalErrorURL() { + return internalErrorURL; + } + + /** + * @return true to enable showing of internal error message. + */ + public boolean isInternalErrorNotificationEnabled() { + return internalErrorNotificationEnabled; + } + + /** + * @return "Internal error" + */ + public String getInternalErrorCaption() { + return (internalErrorNotificationEnabled ? internalErrorCaption + : null); + } + + /** + * @return "Please notify the administrator.
    + * Take note of any unsaved data, and click here to + * continue." + */ + public String getInternalErrorMessage() { + return (internalErrorNotificationEnabled ? internalErrorMessage + : null); + } + + /** + * @return null to reload the application after out of sync message. + */ + public String getOutOfSyncURL() { + return outOfSyncURL; + } + + /** + * @return true to enable showing out of sync message + */ + public boolean isOutOfSyncNotificationEnabled() { + return outOfSyncNotificationEnabled; + } + + /** + * @return "Out of sync" + */ + public String getOutOfSyncCaption() { + return (outOfSyncNotificationEnabled ? outOfSyncCaption : null); + } + + /** + * @return "Something has caused us to be out of sync with the server.
    + * Take note of any unsaved data, and click here to + * re-sync." + */ + public String getOutOfSyncMessage() { + return (outOfSyncNotificationEnabled ? outOfSyncMessage : null); + } + + /** + * Returns the URL the user should be redirected to after dismissing the + * "you have to enable your cookies" message. Typically null. + * + * @return A URL the user should be redirected to after dismissing the + * message or null to reload the current URL. + */ + public String getCookiesDisabledURL() { + return cookiesDisabledURL; + } + + /** + * Determines if "cookies disabled" messages should be shown to the end + * user or not. If the notification is disabled the user will be + * immediately redirected to the URL returned by + * {@link #getCookiesDisabledURL()}. + * + * @return true to show "cookies disabled" messages to the end user, + * false to redirect to the given URL directly + */ + public boolean isCookiesDisabledNotificationEnabled() { + return cookiesDisabledNotificationEnabled; + } + + /** + * Returns the caption of the message shown to the user when cookies are + * disabled in the browser. + * + * @return The caption of the "cookies disabled" message + */ + public String getCookiesDisabledCaption() { + return (cookiesDisabledNotificationEnabled ? cookiesDisabledCaption + : null); + } + + /** + * Returns the message shown to the user when cookies are disabled in + * the browser. + * + * @return The "cookies disabled" message + */ + public String getCookiesDisabledMessage() { + return (cookiesDisabledNotificationEnabled ? cookiesDisabledMessage + : null); + } + +} \ No newline at end of file diff --git a/uitest/src/com/vaadin/launcher/ApplicationRunnerServlet.java b/uitest/src/com/vaadin/launcher/ApplicationRunnerServlet.java index bceecaf35a..e2fe5df4c7 100644 --- a/uitest/src/com/vaadin/launcher/ApplicationRunnerServlet.java +++ b/uitest/src/com/vaadin/launcher/ApplicationRunnerServlet.java @@ -209,20 +209,6 @@ public class ApplicationRunnerServlet extends AbstractApplicationServlet { return uris; } - @Override - protected Class getApplicationClass() - throws ClassNotFoundException { - Class classToRun = getClassToRun(); - if (UI.class.isAssignableFrom(classToRun)) { - return Application.class; - } else if (Application.class.isAssignableFrom(classToRun)) { - return classToRun.asSubclass(Application.class); - } else { - throw new ClassCastException(classToRun.getCanonicalName() - + " is not an Application nor a UI"); - } - } - private Class getClassToRun() throws ClassNotFoundException { // TODO use getClassLoader() ? diff --git a/uitest/src/com/vaadin/tests/tickets/Ticket1673.java b/uitest/src/com/vaadin/tests/tickets/Ticket1673.java index 99f213541a..bf95001464 100644 --- a/uitest/src/com/vaadin/tests/tickets/Ticket1673.java +++ b/uitest/src/com/vaadin/tests/tickets/Ticket1673.java @@ -1,6 +1,7 @@ package com.vaadin.tests.tickets; -import com.vaadin.Application; +import com.vaadin.server.CustomizedSystemMessages; +import com.vaadin.server.SystemMessages; import com.vaadin.ui.Button; import com.vaadin.ui.Button.ClickEvent; import com.vaadin.ui.UI.LegacyWindow; @@ -22,8 +23,8 @@ public class Ticket1673 extends com.vaadin.Application.LegacyApplication { } - public static Application.SystemMessages getSystemMessages() { - Application.CustomizedSystemMessages msgs = new Application.CustomizedSystemMessages(); + public static SystemMessages getSystemMessages() { + CustomizedSystemMessages msgs = new CustomizedSystemMessages(); msgs.setSessionExpiredURL("http://www.vaadin.com/"); msgs.setSessionExpiredCaption("Foo"); diff --git a/uitest/src/com/vaadin/tests/tickets/Ticket2106.java b/uitest/src/com/vaadin/tests/tickets/Ticket2106.java index 9d6e198f03..a57a20cdc3 100644 --- a/uitest/src/com/vaadin/tests/tickets/Ticket2106.java +++ b/uitest/src/com/vaadin/tests/tickets/Ticket2106.java @@ -3,6 +3,8 @@ package com.vaadin.tests.tickets; import java.util.Date; import com.vaadin.Application; +import com.vaadin.server.CustomizedSystemMessages; +import com.vaadin.server.SystemMessages; import com.vaadin.ui.Button; import com.vaadin.ui.Button.ClickEvent; import com.vaadin.ui.Label; @@ -10,7 +12,7 @@ import com.vaadin.ui.UI.LegacyWindow; public class Ticket2106 extends Application.LegacyApplication { - private static CustomizedSystemMessages msgs = new Application.CustomizedSystemMessages(); + private static CustomizedSystemMessages msgs = new CustomizedSystemMessages(); static { // We will forward the user to www.vaadin.com when the session expires msgs.setSessionExpiredURL("http://www.vaadin.com"); @@ -18,7 +20,7 @@ public class Ticket2106 extends Application.LegacyApplication { msgs.setSessionExpiredCaption(null); } - public static Application.SystemMessages getSystemMessages() { + public static SystemMessages getSystemMessages() { return msgs; } -- cgit v1.2.3 From 6f276f4451cb185919084181b54fb84532a1d984 Mon Sep 17 00:00:00 2001 From: Leif Åstrand Date: Mon, 3 Sep 2012 13:57:20 +0300 Subject: Combine ApplicationServlet and AAS and rename to VaadinServlet (#9460) --- WebContent/WEB-INF/web.xml | 4 +- .../vaadin/client/ApplicationConfiguration.java | 2 +- server/src/com/vaadin/Application.java | 4 +- server/src/com/vaadin/data/Validator.java | 5 +- .../vaadin/server/AbstractApplicationServlet.java | 1557 ------------------- .../server/AbstractCommunicationManager.java | 4 +- .../com/vaadin/server/AbstractErrorMessage.java | 7 +- .../src/com/vaadin/server/ApplicationServlet.java | 85 -- server/src/com/vaadin/server/BootstrapHandler.java | 10 +- .../com/vaadin/server/CommunicationManager.java | 8 +- .../com/vaadin/server/GAEApplicationServlet.java | 428 ------ server/src/com/vaadin/server/GAEVaadinServlet.java | 428 ++++++ .../vaadin/server/ServletApplicationContext.java | 4 +- server/src/com/vaadin/server/SystemError.java | 3 +- server/src/com/vaadin/server/VaadinServlet.java | 1564 ++++++++++++++++++++ server/src/com/vaadin/server/WebBrowser.java | 10 +- server/src/com/vaadin/ui/UI.java | 4 +- ...tractApplicationServletStaticFilesLocation.java | 17 +- .../vaadin/launcher/ApplicationRunnerServlet.java | 4 +- 19 files changed, 2030 insertions(+), 2118 deletions(-) delete mode 100644 server/src/com/vaadin/server/AbstractApplicationServlet.java delete mode 100644 server/src/com/vaadin/server/ApplicationServlet.java delete mode 100644 server/src/com/vaadin/server/GAEApplicationServlet.java create mode 100644 server/src/com/vaadin/server/GAEVaadinServlet.java create mode 100644 server/src/com/vaadin/server/VaadinServlet.java (limited to 'server') diff --git a/WebContent/WEB-INF/web.xml b/WebContent/WEB-INF/web.xml index 58a125c10f..70c875b10c 100644 --- a/WebContent/WEB-INF/web.xml +++ b/WebContent/WEB-INF/web.xml @@ -25,10 +25,10 @@ com.vaadin.launcher.ApplicationRunnerServlet - + IntegrationTest - com.vaadin.server.ApplicationServlet + com.vaadin.server.VaadinServlet application com.vaadin.tests.integration.IntegrationTestApplication diff --git a/client/src/com/vaadin/client/ApplicationConfiguration.java b/client/src/com/vaadin/client/ApplicationConfiguration.java index 0bb9bf1989..f620a39a70 100644 --- a/client/src/com/vaadin/client/ApplicationConfiguration.java +++ b/client/src/com/vaadin/client/ApplicationConfiguration.java @@ -146,7 +146,7 @@ public class ApplicationConfiguration implements EntryPoint { * * @return a string with the version * - * @see com.vaadin.server.AbstractApplicationServlet#VERSION + * @see com.vaadin.server.VaadinServlet#VERSION */ private native String getVaadinVersion() /*-{ diff --git a/server/src/com/vaadin/Application.java b/server/src/com/vaadin/Application.java index 8a043154d7..2af3c6a59f 100644 --- a/server/src/com/vaadin/Application.java +++ b/server/src/com/vaadin/Application.java @@ -48,7 +48,6 @@ import com.vaadin.data.util.converter.Converter; import com.vaadin.data.util.converter.ConverterFactory; import com.vaadin.data.util.converter.DefaultConverterFactory; import com.vaadin.event.EventRouter; -import com.vaadin.server.AbstractApplicationServlet; import com.vaadin.server.AbstractErrorMessage; import com.vaadin.server.ApplicationContext; import com.vaadin.server.BootstrapFragmentResponse; @@ -64,6 +63,7 @@ import com.vaadin.server.RequestHandler; import com.vaadin.server.ServletApplicationContext; import com.vaadin.server.Terminal; import com.vaadin.server.UIProvider; +import com.vaadin.server.VaadinServlet; import com.vaadin.server.VariableOwner; import com.vaadin.server.WrappedRequest; import com.vaadin.server.WrappedRequest.BrowserDetails; @@ -1728,7 +1728,7 @@ public class Application implements Terminal.ErrorListener, Serializable { * Handles a request by passing it to each registered {@link RequestHandler} * in turn until one produces a response. This method is used for requests * that have not been handled by any specific functionality in the terminal - * implementation (e.g. {@link AbstractApplicationServlet}). + * implementation (e.g. {@link VaadinServlet}). *

    * The request handlers are invoked in the revere order in which they were * added to the application until a response has been produced. This means diff --git a/server/src/com/vaadin/data/Validator.java b/server/src/com/vaadin/data/Validator.java index 1755a44920..421d88f574 100644 --- a/server/src/com/vaadin/data/Validator.java +++ b/server/src/com/vaadin/data/Validator.java @@ -18,7 +18,7 @@ package com.vaadin.data; import java.io.Serializable; -import com.vaadin.server.AbstractApplicationServlet; +import com.vaadin.server.VaadinServlet; /** * Interface that implements a method for validating if an {@link Object} is @@ -144,8 +144,7 @@ public interface Validator extends Serializable { * Note that this API may change in future versions. */ public String getHtmlMessage() { - return AbstractApplicationServlet - .safeEscapeForHtml(getLocalizedMessage()); + return VaadinServlet.safeEscapeForHtml(getLocalizedMessage()); } /** diff --git a/server/src/com/vaadin/server/AbstractApplicationServlet.java b/server/src/com/vaadin/server/AbstractApplicationServlet.java deleted file mode 100644 index 8b3103b794..0000000000 --- a/server/src/com/vaadin/server/AbstractApplicationServlet.java +++ /dev/null @@ -1,1557 +0,0 @@ -/* - * Copyright 2011 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.server; - -import java.io.BufferedWriter; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.io.PrintWriter; -import java.io.Serializable; -import java.net.MalformedURLException; -import java.net.URL; -import java.net.URLConnection; -import java.security.GeneralSecurityException; -import java.util.Arrays; -import java.util.Collection; -import java.util.Enumeration; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Locale; -import java.util.Properties; -import java.util.logging.Level; -import java.util.logging.Logger; - -import javax.servlet.ServletContext; -import javax.servlet.ServletException; -import javax.servlet.ServletOutputStream; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpSession; - -import com.vaadin.Application; -import com.vaadin.Application.ApplicationStartEvent; -import com.vaadin.server.AbstractCommunicationManager.Callback; -import com.vaadin.shared.ApplicationConstants; -import com.vaadin.ui.UI; - -/** - * Abstract implementation of the ApplicationServlet which handles all - * communication between the client and the server. - * - * It is possible to extend this class to provide own functionality but in most - * cases this is unnecessary. - * - * - * @author Vaadin Ltd. - * @since 6.0 - */ - -@SuppressWarnings("serial") -public abstract class AbstractApplicationServlet extends HttpServlet implements - Constants { - - private static class AbstractApplicationServletWrapper implements Callback { - - private final AbstractApplicationServlet servlet; - - public AbstractApplicationServletWrapper( - AbstractApplicationServlet servlet) { - this.servlet = servlet; - } - - @Override - public void criticalNotification(WrappedRequest request, - WrappedResponse response, String cap, String msg, - String details, String outOfSyncURL) throws IOException { - servlet.criticalNotification( - WrappedHttpServletRequest.cast(request), - ((WrappedHttpServletResponse) response), cap, msg, details, - outOfSyncURL); - } - } - - // TODO Move some (all?) of the constants to a separate interface (shared - // with portlet) - - private final String resourcePath = null; - - private DeploymentConfiguration deploymentConfiguration; - - private AddonContext addonContext; - - /** - * Called by the servlet container to indicate to a servlet that the servlet - * is being placed into service. - * - * @param servletConfig - * the object containing the servlet's configuration and - * initialization parameters - * @throws javax.servlet.ServletException - * if an exception has occurred that interferes with the - * servlet's normal operation. - */ - @Override - public void init(javax.servlet.ServletConfig servletConfig) - throws javax.servlet.ServletException { - super.init(servletConfig); - Properties applicationProperties = new Properties(); - - // Read default parameters from server.xml - final ServletContext context = servletConfig.getServletContext(); - for (final Enumeration e = context.getInitParameterNames(); e - .hasMoreElements();) { - final String name = e.nextElement(); - applicationProperties.setProperty(name, - context.getInitParameter(name)); - } - - // Override with application config from web.xml - for (final Enumeration e = servletConfig - .getInitParameterNames(); e.hasMoreElements();) { - final String name = e.nextElement(); - applicationProperties.setProperty(name, - servletConfig.getInitParameter(name)); - } - - deploymentConfiguration = new AbstractDeploymentConfiguration( - getClass(), applicationProperties) { - - @Override - public String getStaticFileLocation(WrappedRequest request) { - HttpServletRequest servletRequest = WrappedHttpServletRequest - .cast(request); - return AbstractApplicationServlet.this - .getStaticFilesLocation(servletRequest); - } - - @Override - public String getConfiguredWidgetset(WrappedRequest request) { - return getApplicationOrSystemProperty( - AbstractApplicationServlet.PARAMETER_WIDGETSET, - AbstractApplicationServlet.DEFAULT_WIDGETSET); - } - - @Override - public String getConfiguredTheme(WrappedRequest request) { - // Use the default - return AbstractApplicationServlet.getDefaultTheme(); - } - - @Override - public boolean isStandalone(WrappedRequest request) { - return true; - } - - @Override - public String getMimeType(String resourceName) { - return getServletContext().getMimeType(resourceName); - } - - @Override - public SystemMessages getSystemMessages() { - return AbstractApplicationServlet.this.getSystemMessages(); - } - }; - - addonContext = new AddonContext(deploymentConfiguration); - addonContext.init(); - } - - @Override - public void destroy() { - super.destroy(); - - addonContext.destroy(); - } - - /** - * Returns true if the servlet is running in production mode. Production - * mode disables all debug facilities. - * - * @return true if in production mode, false if in debug mode - */ - public boolean isProductionMode() { - return getDeploymentConfiguration().isProductionMode(); - } - - /** - * Returns the number of seconds the browser should cache a file. Default is - * 1 hour (3600 s). - * - * @return The number of seconds files are cached in the browser - */ - public int getResourceCacheTime() { - return getDeploymentConfiguration().getResourceCacheTime(); - } - - /** - * Receives standard HTTP requests from the public service method and - * dispatches them. - * - * @param request - * the object that contains the request the client made of the - * servlet. - * @param response - * the object that contains the response the servlet returns to - * the client. - * @throws ServletException - * if an input or output error occurs while the servlet is - * handling the TRACE request. - * @throws IOException - * if the request for the TRACE cannot be handled. - */ - - @Override - protected void service(HttpServletRequest request, - HttpServletResponse response) throws ServletException, IOException { - service(createWrappedRequest(request), createWrappedResponse(response)); - } - - private void service(WrappedHttpServletRequest request, - WrappedHttpServletResponse response) throws ServletException, - IOException { - RequestTimer requestTimer = new RequestTimer(); - requestTimer.start(); - - AbstractApplicationServletWrapper servletWrapper = new AbstractApplicationServletWrapper( - this); - - RequestType requestType = getRequestType(request); - if (!ensureCookiesEnabled(requestType, request, response)) { - return; - } - - if (requestType == RequestType.STATIC_FILE) { - serveStaticResources(request, response); - return; - } - - Application application = null; - boolean transactionStarted = false; - boolean requestStarted = false; - boolean applicationRunning = false; - - try { - // If a duplicate "close application" URL is received for an - // application that is not open, redirect to the application's main - // page. - // This is needed as e.g. Spring Security remembers the last - // URL from the application, which is the logout URL, and repeats - // it. - // We can tell apart a real onunload request from a repeated one - // based on the real one having content (at least the UIDL security - // key). - if (requestType == RequestType.UIDL - && request.getParameterMap().containsKey( - ApplicationConstants.PARAM_UNLOADBURST) - && request.getContentLength() < 1 - && getExistingApplication(request, false) == null) { - redirectToApplication(request, response); - return; - } - - // Find out which application this request is related to - application = findApplicationInstance(request, requestType); - if (application == null) { - return; - } - Application.setCurrent(application); - - /* - * Get or create a WebApplicationContext and an ApplicationManager - * for the session - */ - ServletApplicationContext webApplicationContext = getApplicationContext(request - .getSession()); - CommunicationManager applicationManager = webApplicationContext - .getApplicationManager(application, this); - - if (requestType == RequestType.CONNECTOR_RESOURCE) { - applicationManager.serveConnectorResource(request, response); - return; - } else if (requestType == RequestType.HEARTBEAT) { - applicationManager.handleHeartbeatRequest(request, response, - application); - return; - } - - /* Update browser information from the request */ - webApplicationContext.getBrowser().updateRequestDetails(request); - - /* - * Call application requestStart before Application.init() is called - * (bypasses the limitation in TransactionListener) - */ - if (application instanceof HttpServletRequestListener) { - ((HttpServletRequestListener) application).onRequestStart( - request, response); - requestStarted = true; - } - - // Start the application if it's newly created - startApplication(request, application, webApplicationContext); - applicationRunning = true; - - /* - * Transaction starts. Call transaction listeners. Transaction end - * is called in the finally block below. - */ - webApplicationContext.startTransaction(application, request); - transactionStarted = true; - - /* Handle the request */ - if (requestType == RequestType.FILE_UPLOAD) { - // UI is resolved in communication manager - applicationManager.handleFileUpload(application, request, - response); - return; - } else if (requestType == RequestType.UIDL) { - UI uI = application.getUIForRequest(request); - if (uI == null) { - throw new ServletException(ERROR_NO_UI_FOUND); - } - // Handles AJAX UIDL requests - applicationManager.handleUidlRequest(request, response, - servletWrapper, uI); - return; - } else if (requestType == RequestType.BROWSER_DETAILS) { - // Browser details - not related to a specific UI - applicationManager.handleBrowserDetailsRequest(request, - response, application); - return; - } - - // Removes application if it has stopped (maybe by thread or - // transactionlistener) - if (!application.isRunning()) { - endApplication(request, response, application); - return; - } - - if (applicationManager.handleApplicationRequest(request, response)) { - return; - } - // TODO Should return 404 error here and not do anything more - - } catch (final SessionExpiredException e) { - // Session has expired, notify user - handleServiceSessionExpired(request, response); - } catch (final GeneralSecurityException e) { - handleServiceSecurityException(request, response); - } catch (final Throwable e) { - handleServiceException(request, response, application, e); - } finally { - - if (applicationRunning) { - application.closeInactiveUIs(); - } - - // Notifies transaction end - try { - if (transactionStarted) { - ((ServletApplicationContext) application.getContext()) - .endTransaction(application, request); - - } - - } finally { - try { - if (requestStarted) { - ((HttpServletRequestListener) application) - .onRequestEnd(request, response); - } - } finally { - UI.setCurrent(null); - Application.setCurrent(null); - - HttpSession session = request.getSession(false); - if (session != null) { - requestTimer.stop(getApplicationContext(session)); - } - } - } - - } - } - - private WrappedHttpServletResponse createWrappedResponse( - HttpServletResponse response) { - WrappedHttpServletResponse wrappedResponse = new WrappedHttpServletResponse( - response, getDeploymentConfiguration()); - return wrappedResponse; - } - - /** - * Create a wrapped request for a http servlet request. This method can be - * overridden if the wrapped request should have special properties. - * - * @param request - * the original http servlet request - * @return a wrapped request for the original request - */ - protected WrappedHttpServletRequest createWrappedRequest( - HttpServletRequest request) { - return new WrappedHttpServletRequest(request, - getDeploymentConfiguration()); - } - - /** - * Gets a the deployment configuration for this servlet. - * - * @return the deployment configuration - */ - protected DeploymentConfiguration getDeploymentConfiguration() { - return deploymentConfiguration; - } - - /** - * Check that cookie support is enabled in the browser. Only checks UIDL - * requests. - * - * @param requestType - * Type of the request as returned by - * {@link #getRequestType(HttpServletRequest)} - * @param request - * The request from the browser - * @param response - * The response to which an error can be written - * @return false if cookies are disabled, true otherwise - * @throws IOException - */ - private boolean ensureCookiesEnabled(RequestType requestType, - WrappedHttpServletRequest request, - WrappedHttpServletResponse response) throws IOException { - if (requestType == RequestType.UIDL && !isRepaintAll(request)) { - // In all other but the first UIDL request a cookie should be - // returned by the browser. - // This can be removed if cookieless mode (#3228) is supported - if (request.getRequestedSessionId() == null) { - // User has cookies disabled - criticalNotification(request, response, getSystemMessages() - .getCookiesDisabledCaption(), getSystemMessages() - .getCookiesDisabledMessage(), null, getSystemMessages() - .getCookiesDisabledURL()); - return false; - } - } - return true; - } - - /** - * Send a notification to client's application. Used to notify client of - * critical errors, session expiration and more. Server has no knowledge of - * what application client refers to. - * - * @param request - * the HTTP request instance. - * @param response - * the HTTP response to write to. - * @param caption - * the notification caption - * @param message - * to notification body - * @param details - * a detail message to show in addition to the message. Currently - * shown directly below the message but could be hidden behind a - * details drop down in the future. Mainly used to give - * additional information not necessarily useful to the end user. - * @param url - * url to load when the message is dismissed. Null will reload - * the current page. - * @throws IOException - * if the writing failed due to input/output error. - */ - protected void criticalNotification(WrappedHttpServletRequest request, - HttpServletResponse response, String caption, String message, - String details, String url) throws IOException { - - if (ServletPortletHelper.isUIDLRequest(request)) { - - if (caption != null) { - caption = "\"" + JsonPaintTarget.escapeJSON(caption) + "\""; - } - if (details != null) { - if (message == null) { - message = details; - } else { - message += "

    " + details; - } - } - - if (message != null) { - message = "\"" + JsonPaintTarget.escapeJSON(message) + "\""; - } - if (url != null) { - url = "\"" + JsonPaintTarget.escapeJSON(url) + "\""; - } - - String output = "for(;;);[{\"changes\":[], \"meta\" : {" - + "\"appError\": {" + "\"caption\":" + caption + "," - + "\"message\" : " + message + "," + "\"url\" : " + url - + "}}, \"resources\": {}, \"locales\":[]}]"; - writeResponse(response, "application/json; charset=UTF-8", output); - } else { - // Create an HTML reponse with the error - String output = ""; - - if (url != null) { - output += ""; - } - if (caption != null) { - output += "" + caption + "
    "; - } - if (message != null) { - output += message; - output += "

    "; - } - - if (details != null) { - output += details; - output += "

    "; - } - if (url != null) { - output += "
    "; - } - writeResponse(response, "text/html; charset=UTF-8", output); - - } - - } - - /** - * Writes the response in {@code output} using the contentType given in - * {@code contentType} to the provided {@link HttpServletResponse} - * - * @param response - * @param contentType - * @param output - * Output to write (UTF-8 encoded) - * @throws IOException - */ - private void writeResponse(HttpServletResponse response, - String contentType, String output) throws IOException { - response.setContentType(contentType); - final ServletOutputStream out = response.getOutputStream(); - // Set the response type - final PrintWriter outWriter = new PrintWriter(new BufferedWriter( - new OutputStreamWriter(out, "UTF-8"))); - outWriter.print(output); - outWriter.flush(); - outWriter.close(); - out.flush(); - - } - - /** - * Returns the application instance to be used for the request. If an - * existing instance is not found a new one is created or null is returned - * to indicate that the application is not available. - * - * @param request - * @param requestType - * @return - * @throws MalformedURLException - * @throws IllegalAccessException - * @throws InstantiationException - * @throws ServletException - * @throws SessionExpiredException - */ - private Application findApplicationInstance(HttpServletRequest request, - RequestType requestType) throws MalformedURLException, - ServletException, SessionExpiredException { - - boolean requestCanCreateApplication = requestCanCreateApplication( - request, requestType); - - /* Find an existing application for this request. */ - Application application = getExistingApplication(request, - requestCanCreateApplication); - - if (application != null) { - /* - * There is an existing application. We can use this as long as the - * user not specifically requested to close or restart it. - */ - - final boolean restartApplication = (request - .getParameter(URL_PARAMETER_RESTART_APPLICATION) != null); - final boolean closeApplication = (request - .getParameter(URL_PARAMETER_CLOSE_APPLICATION) != null); - - if (restartApplication) { - closeApplication(application, request.getSession(false)); - return createApplication(request); - } else if (closeApplication) { - closeApplication(application, request.getSession(false)); - return null; - } else { - return application; - } - } - - // No existing application was found - - if (requestCanCreateApplication) { - /* - * If the request is such that it should create a new application if - * one as not found, we do that. - */ - return createApplication(request); - } else { - /* - * The application was not found and a new one should not be - * created. Assume the session has expired. - */ - throw new SessionExpiredException(); - } - - } - - /** - * Check if the request should create an application if an existing - * application is not found. - * - * @param request - * @param requestType - * @return true if an application should be created, false otherwise - */ - boolean requestCanCreateApplication(HttpServletRequest request, - RequestType requestType) { - if (requestType == RequestType.UIDL && isRepaintAll(request)) { - /* - * UIDL request contains valid repaintAll=1 event, the user probably - * wants to initiate a new application through a custom index.html - * without using the bootstrap page. - */ - return true; - - } else if (requestType == RequestType.OTHER) { - /* - * I.e URIs that are not application resources or static (theme) - * files. - */ - return true; - - } - - return false; - } - - /** - * Gets resource path using different implementations. Required to - * supporting different servlet container implementations (application - * servers). - * - * @param servletContext - * @param path - * the resource path. - * @return the resource path. - */ - protected static String getResourcePath(ServletContext servletContext, - String path) { - String resultPath = null; - resultPath = servletContext.getRealPath(path); - if (resultPath != null) { - return resultPath; - } else { - try { - final URL url = servletContext.getResource(path); - resultPath = url.getFile(); - } catch (final Exception e) { - // FIXME: Handle exception - getLogger().log(Level.INFO, - "Could not find resource path " + path, e); - } - } - return resultPath; - } - - /** - * Creates a new application and registers it into WebApplicationContext - * (aka session). This is not meant to be overridden. Override - * getNewApplication to create the application instance in a custom way. - * - * @param request - * @return - * @throws ServletException - * @throws MalformedURLException - */ - private Application createApplication(HttpServletRequest request) - throws ServletException, MalformedURLException { - Application newApplication = getNewApplication(request); - - final ServletApplicationContext context = getApplicationContext(request - .getSession()); - context.addApplication(newApplication); - - return newApplication; - } - - private void handleServiceException(WrappedHttpServletRequest request, - WrappedHttpServletResponse response, Application application, - Throwable e) throws IOException, ServletException { - // if this was an UIDL request, response UIDL back to client - if (getRequestType(request) == RequestType.UIDL) { - SystemMessages ci = getSystemMessages(); - criticalNotification(request, response, - ci.getInternalErrorCaption(), ci.getInternalErrorMessage(), - null, ci.getInternalErrorURL()); - if (application != null) { - application.getErrorHandler() - .terminalError(new RequestError(e)); - } else { - throw new ServletException(e); - } - } else { - // Re-throw other exceptions - throw new ServletException(e); - } - - } - - /** - * A helper method to strip away characters that might somehow be used for - * XSS attacs. Leaves at least alphanumeric characters intact. Also removes - * eg. ( and ), so values should be safe in javascript too. - * - * @param themeName - * @return - */ - protected static String stripSpecialChars(String themeName) { - StringBuilder sb = new StringBuilder(); - char[] charArray = themeName.toCharArray(); - for (int i = 0; i < charArray.length; i++) { - char c = charArray[i]; - if (!CHAR_BLACKLIST.contains(c)) { - sb.append(c); - } - } - return sb.toString(); - } - - private static final Collection CHAR_BLACKLIST = new HashSet( - Arrays.asList(new Character[] { '&', '"', '\'', '<', '>', '(', ')', - ';' })); - - /** - * Returns the default theme. Must never return null. - * - * @return - */ - public static String getDefaultTheme() { - return DEFAULT_THEME_NAME; - } - - void handleServiceSessionExpired(WrappedHttpServletRequest request, - WrappedHttpServletResponse response) throws IOException, - ServletException { - - if (isOnUnloadRequest(request)) { - /* - * Request was an unload request (e.g. window close event) and the - * client expects no response if it fails. - */ - return; - } - - try { - SystemMessages ci = getSystemMessages(); - if (getRequestType(request) != RequestType.UIDL) { - // 'plain' http req - e.g. browser reload; - // just go ahead redirect the browser - response.sendRedirect(ci.getSessionExpiredURL()); - } else { - /* - * Invalidate session (weird to have session if we're saying - * that it's expired, and worse: portal integration will fail - * since the session is not created by the portal. - * - * Session must be invalidated before criticalNotification as it - * commits the response. - */ - request.getSession().invalidate(); - - // send uidl redirect - criticalNotification(request, response, - ci.getSessionExpiredCaption(), - ci.getSessionExpiredMessage(), null, - ci.getSessionExpiredURL()); - - } - } catch (SystemMessageException ee) { - throw new ServletException(ee); - } - - } - - private void handleServiceSecurityException( - WrappedHttpServletRequest request, - WrappedHttpServletResponse response) throws IOException, - ServletException { - if (isOnUnloadRequest(request)) { - /* - * Request was an unload request (e.g. window close event) and the - * client expects no response if it fails. - */ - return; - } - - try { - SystemMessages ci = getSystemMessages(); - if (getRequestType(request) != RequestType.UIDL) { - // 'plain' http req - e.g. browser reload; - // just go ahead redirect the browser - response.sendRedirect(ci.getCommunicationErrorURL()); - } else { - // send uidl redirect - criticalNotification(request, response, - ci.getCommunicationErrorCaption(), - ci.getCommunicationErrorMessage(), - INVALID_SECURITY_KEY_MSG, ci.getCommunicationErrorURL()); - /* - * Invalidate session. Portal integration will fail otherwise - * since the session is not created by the portal. - */ - request.getSession().invalidate(); - } - } catch (SystemMessageException ee) { - throw new ServletException(ee); - } - - log("Invalid security key received from " + request.getRemoteHost()); - } - - /** - * Creates a new application for the given request. - * - * @param request - * the HTTP request. - * @return A new Application instance. - * @throws ServletException - */ - protected abstract Application getNewApplication(HttpServletRequest request) - throws ServletException; - - /** - * Starts the application if it is not already running. - * - * @param request - * @param application - * @param webApplicationContext - * @throws ServletException - * @throws MalformedURLException - */ - private void startApplication(HttpServletRequest request, - Application application, ServletApplicationContext webApplicationContext) - throws ServletException, MalformedURLException { - - if (!application.isRunning()) { - // Create application - final URL applicationUrl = getApplicationUrl(request); - - // Initial locale comes from the request - Locale locale = request.getLocale(); - application.setLocale(locale); - application.start(new ApplicationStartEvent(applicationUrl, - getDeploymentConfiguration(), webApplicationContext)); - addonContext.fireApplicationStarted(application); - } - } - - /** - * Check if this is a request for a static resource and, if it is, serve the - * resource to the client. - * - * @param request - * @param response - * @return true if a file was served and the request has been handled, false - * otherwise. - * @throws IOException - * @throws ServletException - */ - private boolean serveStaticResources(HttpServletRequest request, - HttpServletResponse response) throws IOException, ServletException { - - // FIXME What does 10 refer to? - String pathInfo = request.getPathInfo(); - if (pathInfo == null || pathInfo.length() <= 10) { - return false; - } - - if ((request.getContextPath() != null) - && (request.getRequestURI().startsWith("/VAADIN/"))) { - serveStaticResourcesInVAADIN(request.getRequestURI(), request, - response); - return true; - } else if (request.getRequestURI().startsWith( - request.getContextPath() + "/VAADIN/")) { - serveStaticResourcesInVAADIN( - request.getRequestURI().substring( - request.getContextPath().length()), request, - response); - return true; - } - - return false; - } - - /** - * Serve resources from VAADIN directory. - * - * @param filename - * The filename to serve. Should always start with /VAADIN/. - * @param request - * @param response - * @throws IOException - * @throws ServletException - */ - private void serveStaticResourcesInVAADIN(String filename, - HttpServletRequest request, HttpServletResponse response) - throws IOException, ServletException { - - final ServletContext sc = getServletContext(); - URL resourceUrl = sc.getResource(filename); - if (resourceUrl == null) { - // try if requested file is found from classloader - - // strip leading "/" otherwise stream from JAR wont work - filename = filename.substring(1); - resourceUrl = getDeploymentConfiguration().getClassLoader() - .getResource(filename); - - if (resourceUrl == null) { - // cannot serve requested file - getLogger() - .info("Requested resource [" - + filename - + "] not found from filesystem or through class loader." - + " Add widgetset and/or theme JAR to your classpath or add files to WebContent/VAADIN folder."); - response.setStatus(HttpServletResponse.SC_NOT_FOUND); - return; - } - - // security check: do not permit navigation out of the VAADIN - // directory - if (!isAllowedVAADINResourceUrl(request, resourceUrl)) { - getLogger() - .info("Requested resource [" - + filename - + "] not accessible in the VAADIN directory or access to it is forbidden."); - response.setStatus(HttpServletResponse.SC_FORBIDDEN); - return; - } - } - - // Find the modification timestamp - long lastModifiedTime = 0; - URLConnection connection = null; - try { - connection = resourceUrl.openConnection(); - lastModifiedTime = connection.getLastModified(); - // Remove milliseconds to avoid comparison problems (milliseconds - // are not returned by the browser in the "If-Modified-Since" - // header). - lastModifiedTime = lastModifiedTime - lastModifiedTime % 1000; - - if (browserHasNewestVersion(request, lastModifiedTime)) { - response.setStatus(HttpServletResponse.SC_NOT_MODIFIED); - return; - } - } catch (Exception e) { - // Failed to find out last modified timestamp. Continue without it. - getLogger() - .log(Level.FINEST, - "Failed to find out last modified timestamp. Continuing without it.", - e); - } finally { - if (connection instanceof URLConnection) { - try { - // Explicitly close the input stream to prevent it - // from remaining hanging - // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4257700 - InputStream is = connection.getInputStream(); - if (is != null) { - is.close(); - } - } catch (IOException e) { - getLogger().log(Level.INFO, - "Error closing URLConnection input stream", e); - } - } - } - - // Set type mime type if we can determine it based on the filename - final String mimetype = sc.getMimeType(filename); - if (mimetype != null) { - response.setContentType(mimetype); - } - - // Provide modification timestamp to the browser if it is known. - if (lastModifiedTime > 0) { - response.setDateHeader("Last-Modified", lastModifiedTime); - /* - * The browser is allowed to cache for 1 hour without checking if - * the file has changed. This forces browsers to fetch a new version - * when the Vaadin version is updated. This will cause more requests - * to the servlet than without this but for high volume sites the - * static files should never be served through the servlet. The - * cache timeout can be configured by setting the resourceCacheTime - * parameter in web.xml - */ - response.setHeader("Cache-Control", - "max-age= " + String.valueOf(getResourceCacheTime())); - } - - // Write the resource to the client. - final OutputStream os = response.getOutputStream(); - final byte buffer[] = new byte[DEFAULT_BUFFER_SIZE]; - int bytes; - InputStream is = resourceUrl.openStream(); - while ((bytes = is.read(buffer)) >= 0) { - os.write(buffer, 0, bytes); - } - is.close(); - } - - /** - * Check whether a URL obtained from a classloader refers to a valid static - * resource in the directory VAADIN. - * - * Warning: Overriding of this method is not recommended, but is possible to - * support non-default classloaders or servers that may produce URLs - * different from the normal ones. The method prototype may change in the - * future. Care should be taken not to expose class files or other resources - * outside the VAADIN directory if the method is overridden. - * - * @param request - * @param resourceUrl - * @return - * - * @since 6.6.7 - */ - protected boolean isAllowedVAADINResourceUrl(HttpServletRequest request, - URL resourceUrl) { - if ("jar".equals(resourceUrl.getProtocol())) { - // This branch is used for accessing resources directly from the - // Vaadin JAR in development environments and in similar cases. - - // Inside a JAR, a ".." would mean a real directory named ".." so - // using it in paths should just result in the file not being found. - // However, performing a check in case some servers or class loaders - // try to normalize the path by collapsing ".." before the class - // loader sees it. - - if (!resourceUrl.getPath().contains("!/VAADIN/")) { - getLogger().info( - "Blocked attempt to access a JAR entry not starting with /VAADIN/: " - + resourceUrl); - return false; - } - getLogger().fine( - "Accepted access to a JAR entry using a class loader: " - + resourceUrl); - return true; - } else { - // Some servers such as GlassFish extract files from JARs (file:) - // and e.g. JBoss 5+ use protocols vsf: and vfsfile: . - - // Check that the URL is in a VAADIN directory and does not contain - // "/../" - if (!resourceUrl.getPath().contains("/VAADIN/") - || resourceUrl.getPath().contains("/../")) { - getLogger().info( - "Blocked attempt to access file: " + resourceUrl); - return false; - } - getLogger().fine( - "Accepted access to a file using a class loader: " - + resourceUrl); - return true; - } - } - - /** - * Checks if the browser has an up to date cached version of requested - * resource. Currently the check is performed using the "If-Modified-Since" - * header. Could be expanded if needed. - * - * @param request - * The HttpServletRequest from the browser. - * @param resourceLastModifiedTimestamp - * The timestamp when the resource was last modified. 0 if the - * last modification time is unknown. - * @return true if the If-Modified-Since header tells the cached version in - * the browser is up to date, false otherwise - */ - private boolean browserHasNewestVersion(HttpServletRequest request, - long resourceLastModifiedTimestamp) { - if (resourceLastModifiedTimestamp < 1) { - // We do not know when it was modified so the browser cannot have an - // up-to-date version - return false; - } - /* - * The browser can request the resource conditionally using an - * If-Modified-Since header. Check this against the last modification - * time. - */ - try { - // If-Modified-Since represents the timestamp of the version cached - // in the browser - long headerIfModifiedSince = request - .getDateHeader("If-Modified-Since"); - - if (headerIfModifiedSince >= resourceLastModifiedTimestamp) { - // Browser has this an up-to-date version of the resource - return true; - } - } catch (Exception e) { - // Failed to parse header. Fail silently - the browser does not have - // an up-to-date version in its cache. - } - return false; - } - - protected enum RequestType { - FILE_UPLOAD, BROWSER_DETAILS, UIDL, OTHER, STATIC_FILE, APPLICATION_RESOURCE, CONNECTOR_RESOURCE, HEARTBEAT; - } - - protected RequestType getRequestType(WrappedHttpServletRequest request) { - if (ServletPortletHelper.isFileUploadRequest(request)) { - return RequestType.FILE_UPLOAD; - } else if (ServletPortletHelper.isConnectorResourceRequest(request)) { - return RequestType.CONNECTOR_RESOURCE; - } else if (isBrowserDetailsRequest(request)) { - return RequestType.BROWSER_DETAILS; - } else if (ServletPortletHelper.isUIDLRequest(request)) { - return RequestType.UIDL; - } else if (isStaticResourceRequest(request)) { - return RequestType.STATIC_FILE; - } else if (ServletPortletHelper.isApplicationResourceRequest(request)) { - return RequestType.APPLICATION_RESOURCE; - } else if (ServletPortletHelper.isHeartbeatRequest(request)) { - return RequestType.HEARTBEAT; - } - return RequestType.OTHER; - - } - - private static boolean isBrowserDetailsRequest(HttpServletRequest request) { - return "POST".equals(request.getMethod()) - && request.getParameter("browserDetails") != null; - } - - private boolean isStaticResourceRequest(HttpServletRequest request) { - String pathInfo = request.getPathInfo(); - if (pathInfo == null || pathInfo.length() <= 10) { - return false; - } - - if ((request.getContextPath() != null) - && (request.getRequestURI().startsWith("/VAADIN/"))) { - return true; - } else if (request.getRequestURI().startsWith( - request.getContextPath() + "/VAADIN/")) { - return true; - } - - return false; - } - - private boolean isOnUnloadRequest(HttpServletRequest request) { - return request.getParameter(ApplicationConstants.PARAM_UNLOADBURST) != null; - } - - /** - * Get system messages - * - * @return - */ - protected SystemMessages getSystemMessages() { - return ServletPortletHelper.DEFAULT_SYSTEM_MESSAGES; - } - - /** - * Return the URL from where static files, e.g. the widgetset and the theme, - * are served. In a standard configuration the VAADIN folder inside the - * returned folder is what is used for widgetsets and themes. - * - * The returned folder is usually the same as the context path and - * independent of the application. - * - * @param request - * @return The location of static resources (should contain the VAADIN - * directory). Never ends with a slash (/). - */ - protected String getStaticFilesLocation(HttpServletRequest request) { - - return getWebApplicationsStaticFileLocation(request); - } - - /** - * The default method to fetch static files location (URL). This method does - * not check for request attribute {@value #REQUEST_VAADIN_STATIC_FILE_PATH} - * - * @param request - * @return - */ - private String getWebApplicationsStaticFileLocation( - HttpServletRequest request) { - String staticFileLocation; - // if property is defined in configurations, use that - staticFileLocation = getDeploymentConfiguration() - .getApplicationOrSystemProperty(PARAMETER_VAADIN_RESOURCES, - null); - if (staticFileLocation != null) { - return staticFileLocation; - } - - // the last (but most common) option is to generate default location - // from request - - // if context is specified add it to widgetsetUrl - String ctxPath = request.getContextPath(); - - // FIXME: ctxPath.length() == 0 condition is probably unnecessary and - // might even be wrong. - - if (ctxPath.length() == 0 - && request.getAttribute("javax.servlet.include.context_path") != null) { - // include request (e.g portlet), get context path from - // attribute - ctxPath = (String) request - .getAttribute("javax.servlet.include.context_path"); - } - - // Remove heading and trailing slashes from the context path - ctxPath = removeHeadingOrTrailing(ctxPath, "/"); - - if (ctxPath.equals("")) { - return ""; - } else { - return "/" + ctxPath; - } - } - - /** - * Remove any heading or trailing "what" from the "string". - * - * @param string - * @param what - * @return - */ - private static String removeHeadingOrTrailing(String string, String what) { - while (string.startsWith(what)) { - string = string.substring(1); - } - - while (string.endsWith(what)) { - string = string.substring(0, string.length() - 1); - } - - return string; - } - - /** - * Write a redirect response to the main page of the application. - * - * @param request - * @param response - * @throws IOException - * if sending the redirect fails due to an input/output error or - * a bad application URL - */ - private void redirectToApplication(HttpServletRequest request, - HttpServletResponse response) throws IOException { - String applicationUrl = getApplicationUrl(request).toExternalForm(); - response.sendRedirect(response.encodeRedirectURL(applicationUrl)); - } - - /** - * Gets the current application URL from request. - * - * @param request - * the HTTP request. - * @throws MalformedURLException - * if the application is denied access to the persistent data - * store represented by the given URL. - */ - protected URL getApplicationUrl(HttpServletRequest request) - throws MalformedURLException { - final URL reqURL = new URL( - (request.isSecure() ? "https://" : "http://") - + request.getServerName() - + ((request.isSecure() && request.getServerPort() == 443) - || (!request.isSecure() && request - .getServerPort() == 80) ? "" : ":" - + request.getServerPort()) - + request.getRequestURI()); - String servletPath = ""; - if (request.getAttribute("javax.servlet.include.servlet_path") != null) { - // this is an include request - servletPath = request.getAttribute( - "javax.servlet.include.context_path").toString() - + request - .getAttribute("javax.servlet.include.servlet_path"); - - } else { - servletPath = request.getContextPath() + request.getServletPath(); - } - - if (servletPath.length() == 0 - || servletPath.charAt(servletPath.length() - 1) != '/') { - servletPath = servletPath + "/"; - } - URL u = new URL(reqURL, servletPath); - return u; - } - - /** - * Gets the existing application for given request. Looks for application - * instance for given request based on the requested URL. - * - * @param request - * the HTTP request. - * @param allowSessionCreation - * true if a session should be created if no session exists, - * false if no session should be created - * @return Application instance, or null if the URL does not map to valid - * application. - * @throws MalformedURLException - * if the application is denied access to the persistent data - * store represented by the given URL. - * @throws IllegalAccessException - * @throws InstantiationException - * @throws SessionExpiredException - */ - protected Application getExistingApplication(HttpServletRequest request, - boolean allowSessionCreation) throws MalformedURLException, - SessionExpiredException { - - // Ensures that the session is still valid - final HttpSession session = request.getSession(allowSessionCreation); - if (session == null) { - throw new SessionExpiredException(); - } - - ServletApplicationContext context = getApplicationContext(session); - - // Gets application list for the session. - final Collection applications = context.getApplications(); - - // Search for the application (using the application URI) from the list - for (final Iterator i = applications.iterator(); i - .hasNext();) { - final Application sessionApplication = i.next(); - final String sessionApplicationPath = sessionApplication.getURL() - .getPath(); - String requestApplicationPath = getApplicationUrl(request) - .getPath(); - - if (requestApplicationPath.equals(sessionApplicationPath)) { - // Found a running application - if (sessionApplication.isRunning()) { - return sessionApplication; - } - // Application has stopped, so remove it before creating a new - // application - getApplicationContext(session).removeApplication( - sessionApplication); - break; - } - } - - // Existing application not found - return null; - } - - /** - * Ends the application. - * - * @param request - * the HTTP request. - * @param response - * the HTTP response to write to. - * @param application - * the application to end. - * @throws IOException - * if the writing failed due to input/output error. - */ - private void endApplication(HttpServletRequest request, - HttpServletResponse response, Application application) - throws IOException { - - String logoutUrl = application.getLogoutURL(); - if (logoutUrl == null) { - logoutUrl = application.getURL().toString(); - } - - final HttpSession session = request.getSession(); - if (session != null) { - getApplicationContext(session).removeApplication(application); - } - - response.sendRedirect(response.encodeRedirectURL(logoutUrl)); - } - - /** - * Returns the path info; note that this _can_ be different than - * request.getPathInfo(). Examples where this might be useful: - *

      - *
    • An application runner servlet that runs different Vaadin applications - * based on an identifier.
    • - *
    • Providing a REST interface in the context root, while serving a - * Vaadin UI on a sub-URI using only one servlet (e.g. REST on - * http://example.com/foo, UI on http://example.com/foo/vaadin)
    • - * - * @param request - * @return - */ - protected String getRequestPathInfo(HttpServletRequest request) { - return request.getPathInfo(); - } - - /** - * Gets relative location of a theme resource. - * - * @param theme - * the Theme name. - * @param resource - * the Theme resource. - * @return External URI specifying the resource - */ - public String getResourceLocation(String theme, ThemeResource resource) { - - if (resourcePath == null) { - return resource.getResourceId(); - } - return resourcePath + theme + "/" + resource.getResourceId(); - } - - private boolean isRepaintAll(HttpServletRequest request) { - return (request.getParameter(URL_PARAMETER_REPAINT_ALL) != null) - && (request.getParameter(URL_PARAMETER_REPAINT_ALL).equals("1")); - } - - private void closeApplication(Application application, HttpSession session) { - if (application == null) { - return; - } - - application.close(); - if (session != null) { - ServletApplicationContext context = getApplicationContext(session); - context.removeApplication(application); - } - } - - /** - * - * Gets the application context from an HttpSession. If no context is - * currently stored in a session a new context is created and stored in the - * session. - * - * @param session - * the HTTP session. - * @return the application context for HttpSession. - */ - protected ServletApplicationContext getApplicationContext(HttpSession session) { - /* - * TODO the ApplicationContext.getApplicationContext() should be removed - * and logic moved here. Now overriding context type is possible, but - * the whole creation logic should be here. MT 1101 - */ - return ServletApplicationContext.getApplicationContext(session); - } - - public class RequestError implements Terminal.ErrorEvent, Serializable { - - private final Throwable throwable; - - public RequestError(Throwable throwable) { - this.throwable = throwable; - } - - @Override - public Throwable getThrowable() { - return throwable; - } - - } - - /** - * Override this method if you need to use a specialized communicaiton - * mananger implementation. - * - * @deprecated Instead of overriding this method, override - * {@link ServletApplicationContext} implementation via - * {@link AbstractApplicationServlet#getApplicationContext(HttpSession)} - * method and in that customized implementation return your - * CommunicationManager in - * {@link ServletApplicationContext#getApplicationManager(Application, AbstractApplicationServlet)} - * method. - * - * @param application - * @return - */ - @Deprecated - public CommunicationManager createCommunicationManager( - Application application) { - return new CommunicationManager(application); - } - - /** - * Escapes characters to html entities. An exception is made for some - * "safe characters" to keep the text somewhat readable. - * - * @param unsafe - * @return a safe string to be added inside an html tag - */ - public static final String safeEscapeForHtml(String unsafe) { - if (null == unsafe) { - return null; - } - StringBuilder safe = new StringBuilder(); - char[] charArray = unsafe.toCharArray(); - for (int i = 0; i < charArray.length; i++) { - char c = charArray[i]; - if (isSafe(c)) { - safe.append(c); - } else { - safe.append("&#"); - safe.append((int) c); - safe.append(";"); - } - } - - return safe.toString(); - } - - private static boolean isSafe(char c) { - return // - c > 47 && c < 58 || // alphanum - c > 64 && c < 91 || // A-Z - c > 96 && c < 123 // a-z - ; - } - - private static final Logger getLogger() { - return Logger.getLogger(AbstractApplicationServlet.class.getName()); - } -} diff --git a/server/src/com/vaadin/server/AbstractCommunicationManager.java b/server/src/com/vaadin/server/AbstractCommunicationManager.java index 740ecf843b..2e42f51249 100644 --- a/server/src/com/vaadin/server/AbstractCommunicationManager.java +++ b/server/src/com/vaadin/server/AbstractCommunicationManager.java @@ -1484,7 +1484,7 @@ public abstract class AbstractCommunicationManager implements Serializable { themeName = requestThemeName; } if (themeName == null) { - themeName = AbstractApplicationServlet.getDefaultTheme(); + themeName = VaadinServlet.getDefaultTheme(); } return themeName; } @@ -1502,7 +1502,7 @@ public abstract class AbstractCommunicationManager implements Serializable { public boolean isXSRFEnabled(Application application) { return !"true" .equals(application - .getProperty(AbstractApplicationServlet.SERVLET_PARAMETER_DISABLE_XSRF_PROTECTION)); + .getProperty(VaadinServlet.SERVLET_PARAMETER_DISABLE_XSRF_PROTECTION)); } /** diff --git a/server/src/com/vaadin/server/AbstractErrorMessage.java b/server/src/com/vaadin/server/AbstractErrorMessage.java index 0928ea4bd4..4e30dff06f 100644 --- a/server/src/com/vaadin/server/AbstractErrorMessage.java +++ b/server/src/com/vaadin/server/AbstractErrorMessage.java @@ -109,12 +109,11 @@ public abstract class AbstractErrorMessage implements ErrorMessage { String result = null; switch (getMode()) { case TEXT: - result = AbstractApplicationServlet.safeEscapeForHtml(getMessage()); + result = VaadinServlet.safeEscapeForHtml(getMessage()); break; case PREFORMATTED: - result = "
      "
      -                    + AbstractApplicationServlet
      -                            .safeEscapeForHtml(getMessage()) + "
      "; + result = "
      " + VaadinServlet.safeEscapeForHtml(getMessage())
      +                    + "
      "; break; case XHTML: result = getMessage(); diff --git a/server/src/com/vaadin/server/ApplicationServlet.java b/server/src/com/vaadin/server/ApplicationServlet.java deleted file mode 100644 index 905b48d62f..0000000000 --- a/server/src/com/vaadin/server/ApplicationServlet.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright 2011 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.server; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; - -import com.vaadin.Application; -import com.vaadin.server.ServletPortletHelper.ApplicationClassException; - -/** - * This servlet connects a Vaadin Application to Web. - * - * @author Vaadin Ltd. - * @since 5.0 - */ - -@SuppressWarnings("serial") -public class ApplicationServlet extends AbstractApplicationServlet { - - // Private fields - private Class applicationClass; - - /** - * Called by the servlet container to indicate to a servlet that the servlet - * is being placed into service. - * - * @param servletConfig - * the object containing the servlet's configuration and - * initialization parameters - * @throws javax.servlet.ServletException - * if an exception has occurred that interferes with the - * servlet's normal operation. - */ - @Override - public void init(javax.servlet.ServletConfig servletConfig) - throws javax.servlet.ServletException { - super.init(servletConfig); - - // Loads the application class using the classloader defined in the - // deployment configuration - - try { - applicationClass = ServletPortletHelper - .getApplicationClass(getDeploymentConfiguration()); - } catch (ApplicationClassException e) { - throw new ServletException(e); - } - } - - @Override - protected Application getNewApplication(HttpServletRequest request) - throws ServletException { - - // Creates a new application instance - try { - final Application application = getApplicationClass().newInstance(); - application.addUIProvider(new DefaultUIProvider()); - - return application; - } catch (final IllegalAccessException e) { - throw new ServletException("getNewApplication failed", e); - } catch (final InstantiationException e) { - throw new ServletException("getNewApplication failed", e); - } - } - - protected Class getApplicationClass() { - return applicationClass; - } -} diff --git a/server/src/com/vaadin/server/BootstrapHandler.java b/server/src/com/vaadin/server/BootstrapHandler.java index 98ed1071de..a1438312b6 100644 --- a/server/src/com/vaadin/server/BootstrapHandler.java +++ b/server/src/com/vaadin/server/BootstrapHandler.java @@ -277,7 +277,7 @@ public abstract class BootstrapHandler implements RequestHandler { .getConfiguredWidgetset(request); } - widgetset = AbstractApplicationServlet.stripSpecialChars(widgetset); + widgetset = VaadinServlet.stripSpecialChars(widgetset); return widgetset; } @@ -450,7 +450,7 @@ public abstract class BootstrapHandler implements RequestHandler { String staticFileLocation = deploymentConfiguration .getStaticFileLocation(request); String widgetsetBase = staticFileLocation + "/" - + AbstractApplicationServlet.WIDGETSET_DIRECTORY_PATH; + + VaadinServlet.WIDGETSET_DIRECTORY_PATH; defaults.put("widgetsetBase", widgetsetBase); if (!application.isProductionMode()) { @@ -486,8 +486,8 @@ public abstract class BootstrapHandler implements RequestHandler { WrappedRequest request = context.getRequest(); final String staticFilePath = request.getDeploymentConfiguration() .getStaticFileLocation(request); - return staticFilePath + "/" - + AbstractApplicationServlet.THEME_DIRECTORY_PATH + themeName; + return staticFilePath + "/" + VaadinServlet.THEME_DIRECTORY_PATH + + themeName; } /** @@ -517,7 +517,7 @@ public abstract class BootstrapHandler implements RequestHandler { // XSS preventation, theme names shouldn't contain special chars anyway. // The servlet denies them via url parameter. - themeName = AbstractApplicationServlet.stripSpecialChars(themeName); + themeName = VaadinServlet.stripSpecialChars(themeName); return themeName; } diff --git a/server/src/com/vaadin/server/CommunicationManager.java b/server/src/com/vaadin/server/CommunicationManager.java index af28438f57..cc92023919 100644 --- a/server/src/com/vaadin/server/CommunicationManager.java +++ b/server/src/com/vaadin/server/CommunicationManager.java @@ -45,7 +45,7 @@ public class CommunicationManager extends AbstractCommunicationManager { */ @Deprecated public CommunicationManager(Application application, - AbstractApplicationServlet applicationServlet) { + VaadinServlet applicationServlet) { super(application); } @@ -100,7 +100,7 @@ public class CommunicationManager extends AbstractCommunicationManager { @Override public String getThemeName(BootstrapContext context) { String themeName = context.getRequest().getParameter( - AbstractApplicationServlet.URL_PARAMETER_THEME); + VaadinServlet.URL_PARAMETER_THEME); if (themeName == null) { themeName = super.getThemeName(context); } @@ -117,7 +117,7 @@ public class CommunicationManager extends AbstractCommunicationManager { ServletContext servletContext = context.getHttpSession() .getServletContext(); return servletContext.getResourceAsStream("/" - + AbstractApplicationServlet.THEME_DIRECTORY_PATH + themeName - + "/" + resource); + + VaadinServlet.THEME_DIRECTORY_PATH + themeName + "/" + + resource); } } diff --git a/server/src/com/vaadin/server/GAEApplicationServlet.java b/server/src/com/vaadin/server/GAEApplicationServlet.java deleted file mode 100644 index 240984c760..0000000000 --- a/server/src/com/vaadin/server/GAEApplicationServlet.java +++ /dev/null @@ -1,428 +0,0 @@ -/* - * Copyright 2011 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.server; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.NotSerializableException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import java.util.logging.Level; -import java.util.logging.Logger; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpSession; - -import com.google.appengine.api.datastore.Blob; -import com.google.appengine.api.datastore.DatastoreService; -import com.google.appengine.api.datastore.DatastoreServiceFactory; -import com.google.appengine.api.datastore.Entity; -import com.google.appengine.api.datastore.EntityNotFoundException; -import com.google.appengine.api.datastore.FetchOptions.Builder; -import com.google.appengine.api.datastore.Key; -import com.google.appengine.api.datastore.KeyFactory; -import com.google.appengine.api.datastore.PreparedQuery; -import com.google.appengine.api.datastore.Query; -import com.google.appengine.api.datastore.Query.FilterOperator; -import com.google.appengine.api.memcache.Expiration; -import com.google.appengine.api.memcache.MemcacheService; -import com.google.appengine.api.memcache.MemcacheServiceFactory; -import com.google.apphosting.api.DeadlineExceededException; - -/** - * ApplicationServlet to be used when deploying to Google App Engine, in - * web.xml: - * - *
      - *      <servlet>
      - *              <servlet-name>HelloWorld</servlet-name>
      - *              <servlet-class>com.vaadin.server.GAEApplicationServlet</servlet-class>
      - *              <init-param>
      - *                      <param-name>application</param-name>
      - *                      <param-value>com.vaadin.demo.HelloWorld</param-value>
      - *              </init-param>
      - *      </servlet>
      - * 
      - * - * Session support must be enabled in appengine-web.xml: - * - *
      - *      <sessions-enabled>true</sessions-enabled>
      - * 
      - * - * Appengine datastore cleanup can be invoked by calling one of the applications - * with an additional path "/CLEAN". This can be set up as a cron-job in - * cron.xml (see appengine documentation for more information): - * - *
      - * <cronentries>
      - *   <cron>
      - *     <url>/HelloWorld/CLEAN</url>
      - *     <description>Clean up sessions</description>
      - *     <schedule>every 2 hours</schedule>
      - *   </cron>
      - * </cronentries>
      - * 
      - * - * It is recommended (but not mandatory) to extract themes and widgetsets and - * have App Engine server these statically. Extract VAADIN folder (and it's - * contents) 'next to' the WEB-INF folder, and add the following to - * appengine-web.xml: - * - *
      - *      <static-files>
      - *           <include path="/VAADIN/**" />
      - *      </static-files>
      - * 
      - * - * Additional limitations: - *
        - *
      • Do not change application state when serving an ApplicationResource. - *
      • Avoid changing application state in transaction handlers, unless you're - * confident you fully understand the synchronization issues in App Engine. - *
      • The application remains locked while uploading - no progressbar is - * possible. - *
      - */ -public class GAEApplicationServlet extends ApplicationServlet { - - // memcache mutex is MUTEX_BASE + sessio id - private static final String MUTEX_BASE = "_vmutex"; - - // used identify ApplicationContext in memcache and datastore - private static final String AC_BASE = "_vac"; - - // UIDL requests will attempt to gain access for this long before telling - // the client to retry - private static final int MAX_UIDL_WAIT_MILLISECONDS = 5000; - - // Tell client to retry after this delay. - // Note: currently interpreting Retry-After as ms, not sec - private static final int RETRY_AFTER_MILLISECONDS = 100; - - // Properties used in the datastore - private static final String PROPERTY_EXPIRES = "expires"; - private static final String PROPERTY_DATA = "data"; - - // path used for cleanup - private static final String CLEANUP_PATH = "/CLEAN"; - // max entities to clean at once - private static final int CLEANUP_LIMIT = 200; - // appengine session kind - private static final String APPENGINE_SESSION_KIND = "_ah_SESSION"; - // appengine session expires-parameter - private static final String PROPERTY_APPENGINE_EXPIRES = "_expires"; - - protected void sendDeadlineExceededNotification( - WrappedHttpServletRequest request, - WrappedHttpServletResponse response) throws IOException { - criticalNotification( - request, - response, - "Deadline Exceeded", - "I'm sorry, but the operation took too long to complete. We'll try reloading to see where we're at, please take note of any unsaved data...", - "", null); - } - - protected void sendNotSerializableNotification( - WrappedHttpServletRequest request, - WrappedHttpServletResponse response) throws IOException { - criticalNotification( - request, - response, - "NotSerializableException", - "I'm sorry, but there seems to be a serious problem, please contact the administrator. And please take note of any unsaved data...", - "", getApplicationUrl(request).toString() - + "?restartApplication"); - } - - protected void sendCriticalErrorNotification( - WrappedHttpServletRequest request, - WrappedHttpServletResponse response) throws IOException { - criticalNotification( - request, - response, - "Critical error", - "I'm sorry, but there seems to be a serious problem, please contact the administrator. And please take note of any unsaved data...", - "", getApplicationUrl(request).toString() - + "?restartApplication"); - } - - @Override - protected void service(HttpServletRequest unwrappedRequest, - HttpServletResponse unwrappedResponse) throws ServletException, - IOException { - WrappedHttpServletRequest request = new WrappedHttpServletRequest( - unwrappedRequest, getDeploymentConfiguration()); - WrappedHttpServletResponse response = new WrappedHttpServletResponse( - unwrappedResponse, getDeploymentConfiguration()); - - if (isCleanupRequest(request)) { - cleanDatastore(); - return; - } - - RequestType requestType = getRequestType(request); - - if (requestType == RequestType.STATIC_FILE) { - // no locking needed, let superclass handle - super.service(request, response); - cleanSession(request); - return; - } - - if (requestType == RequestType.APPLICATION_RESOURCE) { - // no locking needed, let superclass handle - getApplicationContext(request, - MemcacheServiceFactory.getMemcacheService()); - super.service(request, response); - cleanSession(request); - return; - } - - final HttpSession session = request - .getSession(requestCanCreateApplication(request, requestType)); - if (session == null) { - handleServiceSessionExpired(request, response); - cleanSession(request); - return; - } - - boolean locked = false; - MemcacheService memcache = null; - String mutex = MUTEX_BASE + session.getId(); - memcache = MemcacheServiceFactory.getMemcacheService(); - try { - // try to get lock - long started = new Date().getTime(); - // non-UIDL requests will try indefinitely - while (requestType != RequestType.UIDL - || new Date().getTime() - started < MAX_UIDL_WAIT_MILLISECONDS) { - locked = memcache.put(mutex, 1, Expiration.byDeltaSeconds(40), - MemcacheService.SetPolicy.ADD_ONLY_IF_NOT_PRESENT); - if (locked) { - break; - } - try { - Thread.sleep(RETRY_AFTER_MILLISECONDS); - } catch (InterruptedException e) { - getLogger().finer( - "Thread.sleep() interrupted while waiting for lock. Trying again. " - + e); - } - } - - if (!locked) { - // Not locked; only UIDL can get trough here unlocked: tell - // client to retry - response.setStatus(HttpServletResponse.SC_SERVICE_UNAVAILABLE); - // Note: currently interpreting Retry-After as ms, not sec - response.setHeader("Retry-After", "" + RETRY_AFTER_MILLISECONDS); - return; - } - - // de-serialize or create application context, store in session - ApplicationContext ctx = getApplicationContext(request, memcache); - - super.service(request, response); - - // serialize - started = new Date().getTime(); - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - ObjectOutputStream oos = new ObjectOutputStream(baos); - oos.writeObject(ctx); - oos.flush(); - byte[] bytes = baos.toByteArray(); - - started = new Date().getTime(); - - String id = AC_BASE + session.getId(); - Date expire = new Date(started - + (session.getMaxInactiveInterval() * 1000)); - Expiration expires = Expiration.onDate(expire); - - memcache.put(id, bytes, expires); - - DatastoreService ds = DatastoreServiceFactory.getDatastoreService(); - Entity entity = new Entity(AC_BASE, id); - entity.setProperty(PROPERTY_EXPIRES, expire.getTime()); - entity.setProperty(PROPERTY_DATA, new Blob(bytes)); - ds.put(entity); - - } catch (DeadlineExceededException e) { - getLogger().warning("DeadlineExceeded for " + session.getId()); - sendDeadlineExceededNotification(request, response); - } catch (NotSerializableException e) { - getLogger().log(Level.SEVERE, "Not serializable!", e); - - // TODO this notification is usually not shown - should we redirect - // in some other way - can we? - sendNotSerializableNotification(request, response); - } catch (Exception e) { - getLogger().log(Level.WARNING, - "An exception occurred while servicing request.", e); - - sendCriticalErrorNotification(request, response); - } finally { - // "Next, please!" - if (locked) { - memcache.delete(mutex); - } - cleanSession(request); - } - } - - protected ApplicationContext getApplicationContext( - HttpServletRequest request, MemcacheService memcache) { - HttpSession session = request.getSession(); - String id = AC_BASE + session.getId(); - byte[] serializedAC = (byte[]) memcache.get(id); - if (serializedAC == null) { - DatastoreService ds = DatastoreServiceFactory.getDatastoreService(); - Key key = KeyFactory.createKey(AC_BASE, id); - Entity entity = null; - try { - entity = ds.get(key); - } catch (EntityNotFoundException e) { - // Ok, we were a bit optimistic; we'll create a new one later - } - if (entity != null) { - Blob blob = (Blob) entity.getProperty(PROPERTY_DATA); - serializedAC = blob.getBytes(); - // bring it to memcache - memcache.put(AC_BASE + session.getId(), serializedAC, - Expiration.byDeltaSeconds(session - .getMaxInactiveInterval()), - MemcacheService.SetPolicy.ADD_ONLY_IF_NOT_PRESENT); - } - } - if (serializedAC != null) { - ByteArrayInputStream bais = new ByteArrayInputStream(serializedAC); - ObjectInputStream ois; - try { - ois = new ObjectInputStream(bais); - ApplicationContext applicationContext = (ApplicationContext) ois - .readObject(); - session.setAttribute(ServletApplicationContext.class.getName(), - applicationContext); - } catch (IOException e) { - getLogger().log( - Level.WARNING, - "Could not de-serialize ApplicationContext for " - + session.getId() - + " A new one will be created. ", e); - } catch (ClassNotFoundException e) { - getLogger().log( - Level.WARNING, - "Could not de-serialize ApplicationContext for " - + session.getId() - + " A new one will be created. ", e); - } - } - // will create new context if the above did not - return getApplicationContext(session); - - } - - private boolean isCleanupRequest(HttpServletRequest request) { - String path = getRequestPathInfo(request); - if (path != null && path.equals(CLEANUP_PATH)) { - return true; - } - return false; - } - - /** - * Removes the ApplicationContext from the session in order to minimize the - * data serialized to datastore and memcache. - * - * @param request - */ - private void cleanSession(HttpServletRequest request) { - HttpSession session = request.getSession(false); - if (session != null) { - session.removeAttribute(ServletApplicationContext.class.getName()); - } - } - - /** - * This will look at the timestamp and delete expired persisted Vaadin and - * appengine sessions from the datastore. - * - * TODO Possible improvements include: 1. Use transactions (requires entity - * groups - overkill?) 2. Delete one-at-a-time, catch possible exception, - * continue w/ next. - */ - private void cleanDatastore() { - long expire = new Date().getTime(); - try { - DatastoreService ds = DatastoreServiceFactory.getDatastoreService(); - // Vaadin stuff first - { - Query q = new Query(AC_BASE); - q.setKeysOnly(); - - q.addFilter(PROPERTY_EXPIRES, - FilterOperator.LESS_THAN_OR_EQUAL, expire); - PreparedQuery pq = ds.prepare(q); - List entities = pq.asList(Builder - .withLimit(CLEANUP_LIMIT)); - if (entities != null) { - getLogger().info( - "Vaadin cleanup deleting " + entities.size() - + " expired Vaadin sessions."); - List keys = new ArrayList(); - for (Entity e : entities) { - keys.add(e.getKey()); - } - ds.delete(keys); - } - } - // Also cleanup GAE sessions - { - Query q = new Query(APPENGINE_SESSION_KIND); - q.setKeysOnly(); - q.addFilter(PROPERTY_APPENGINE_EXPIRES, - FilterOperator.LESS_THAN_OR_EQUAL, expire); - PreparedQuery pq = ds.prepare(q); - List entities = pq.asList(Builder - .withLimit(CLEANUP_LIMIT)); - if (entities != null) { - getLogger().info( - "Vaadin cleanup deleting " + entities.size() - + " expired appengine sessions."); - List keys = new ArrayList(); - for (Entity e : entities) { - keys.add(e.getKey()); - } - ds.delete(keys); - } - } - } catch (Exception e) { - getLogger().log(Level.WARNING, "Exception while cleaning.", e); - } - } - - private static final Logger getLogger() { - return Logger.getLogger(GAEApplicationServlet.class.getName()); - } -} diff --git a/server/src/com/vaadin/server/GAEVaadinServlet.java b/server/src/com/vaadin/server/GAEVaadinServlet.java new file mode 100644 index 0000000000..642737f73b --- /dev/null +++ b/server/src/com/vaadin/server/GAEVaadinServlet.java @@ -0,0 +1,428 @@ +/* + * Copyright 2011 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.server; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.NotSerializableException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +import com.google.appengine.api.datastore.Blob; +import com.google.appengine.api.datastore.DatastoreService; +import com.google.appengine.api.datastore.DatastoreServiceFactory; +import com.google.appengine.api.datastore.Entity; +import com.google.appengine.api.datastore.EntityNotFoundException; +import com.google.appengine.api.datastore.FetchOptions.Builder; +import com.google.appengine.api.datastore.Key; +import com.google.appengine.api.datastore.KeyFactory; +import com.google.appengine.api.datastore.PreparedQuery; +import com.google.appengine.api.datastore.Query; +import com.google.appengine.api.datastore.Query.FilterOperator; +import com.google.appengine.api.memcache.Expiration; +import com.google.appengine.api.memcache.MemcacheService; +import com.google.appengine.api.memcache.MemcacheServiceFactory; +import com.google.apphosting.api.DeadlineExceededException; + +/** + * ApplicationServlet to be used when deploying to Google App Engine, in + * web.xml: + * + *
      + *      <servlet>
      + *              <servlet-name>HelloWorld</servlet-name>
      + *              <servlet-class>com.vaadin.server.GAEApplicationServlet</servlet-class>
      + *              <init-param>
      + *                      <param-name>application</param-name>
      + *                      <param-value>com.vaadin.demo.HelloWorld</param-value>
      + *              </init-param>
      + *      </servlet>
      + * 
      + * + * Session support must be enabled in appengine-web.xml: + * + *
      + *      <sessions-enabled>true</sessions-enabled>
      + * 
      + * + * Appengine datastore cleanup can be invoked by calling one of the applications + * with an additional path "/CLEAN". This can be set up as a cron-job in + * cron.xml (see appengine documentation for more information): + * + *
      + * <cronentries>
      + *   <cron>
      + *     <url>/HelloWorld/CLEAN</url>
      + *     <description>Clean up sessions</description>
      + *     <schedule>every 2 hours</schedule>
      + *   </cron>
      + * </cronentries>
      + * 
      + * + * It is recommended (but not mandatory) to extract themes and widgetsets and + * have App Engine server these statically. Extract VAADIN folder (and it's + * contents) 'next to' the WEB-INF folder, and add the following to + * appengine-web.xml: + * + *
      + *      <static-files>
      + *           <include path="/VAADIN/**" />
      + *      </static-files>
      + * 
      + * + * Additional limitations: + *
        + *
      • Do not change application state when serving an ApplicationResource. + *
      • Avoid changing application state in transaction handlers, unless you're + * confident you fully understand the synchronization issues in App Engine. + *
      • The application remains locked while uploading - no progressbar is + * possible. + *
      + */ +public class GAEVaadinServlet extends VaadinServlet { + + // memcache mutex is MUTEX_BASE + sessio id + private static final String MUTEX_BASE = "_vmutex"; + + // used identify ApplicationContext in memcache and datastore + private static final String AC_BASE = "_vac"; + + // UIDL requests will attempt to gain access for this long before telling + // the client to retry + private static final int MAX_UIDL_WAIT_MILLISECONDS = 5000; + + // Tell client to retry after this delay. + // Note: currently interpreting Retry-After as ms, not sec + private static final int RETRY_AFTER_MILLISECONDS = 100; + + // Properties used in the datastore + private static final String PROPERTY_EXPIRES = "expires"; + private static final String PROPERTY_DATA = "data"; + + // path used for cleanup + private static final String CLEANUP_PATH = "/CLEAN"; + // max entities to clean at once + private static final int CLEANUP_LIMIT = 200; + // appengine session kind + private static final String APPENGINE_SESSION_KIND = "_ah_SESSION"; + // appengine session expires-parameter + private static final String PROPERTY_APPENGINE_EXPIRES = "_expires"; + + protected void sendDeadlineExceededNotification( + WrappedHttpServletRequest request, + WrappedHttpServletResponse response) throws IOException { + criticalNotification( + request, + response, + "Deadline Exceeded", + "I'm sorry, but the operation took too long to complete. We'll try reloading to see where we're at, please take note of any unsaved data...", + "", null); + } + + protected void sendNotSerializableNotification( + WrappedHttpServletRequest request, + WrappedHttpServletResponse response) throws IOException { + criticalNotification( + request, + response, + "NotSerializableException", + "I'm sorry, but there seems to be a serious problem, please contact the administrator. And please take note of any unsaved data...", + "", getApplicationUrl(request).toString() + + "?restartApplication"); + } + + protected void sendCriticalErrorNotification( + WrappedHttpServletRequest request, + WrappedHttpServletResponse response) throws IOException { + criticalNotification( + request, + response, + "Critical error", + "I'm sorry, but there seems to be a serious problem, please contact the administrator. And please take note of any unsaved data...", + "", getApplicationUrl(request).toString() + + "?restartApplication"); + } + + @Override + protected void service(HttpServletRequest unwrappedRequest, + HttpServletResponse unwrappedResponse) throws ServletException, + IOException { + WrappedHttpServletRequest request = new WrappedHttpServletRequest( + unwrappedRequest, getDeploymentConfiguration()); + WrappedHttpServletResponse response = new WrappedHttpServletResponse( + unwrappedResponse, getDeploymentConfiguration()); + + if (isCleanupRequest(request)) { + cleanDatastore(); + return; + } + + RequestType requestType = getRequestType(request); + + if (requestType == RequestType.STATIC_FILE) { + // no locking needed, let superclass handle + super.service(request, response); + cleanSession(request); + return; + } + + if (requestType == RequestType.APPLICATION_RESOURCE) { + // no locking needed, let superclass handle + getApplicationContext(request, + MemcacheServiceFactory.getMemcacheService()); + super.service(request, response); + cleanSession(request); + return; + } + + final HttpSession session = request + .getSession(requestCanCreateApplication(request, requestType)); + if (session == null) { + handleServiceSessionExpired(request, response); + cleanSession(request); + return; + } + + boolean locked = false; + MemcacheService memcache = null; + String mutex = MUTEX_BASE + session.getId(); + memcache = MemcacheServiceFactory.getMemcacheService(); + try { + // try to get lock + long started = new Date().getTime(); + // non-UIDL requests will try indefinitely + while (requestType != RequestType.UIDL + || new Date().getTime() - started < MAX_UIDL_WAIT_MILLISECONDS) { + locked = memcache.put(mutex, 1, Expiration.byDeltaSeconds(40), + MemcacheService.SetPolicy.ADD_ONLY_IF_NOT_PRESENT); + if (locked) { + break; + } + try { + Thread.sleep(RETRY_AFTER_MILLISECONDS); + } catch (InterruptedException e) { + getLogger().finer( + "Thread.sleep() interrupted while waiting for lock. Trying again. " + + e); + } + } + + if (!locked) { + // Not locked; only UIDL can get trough here unlocked: tell + // client to retry + response.setStatus(HttpServletResponse.SC_SERVICE_UNAVAILABLE); + // Note: currently interpreting Retry-After as ms, not sec + response.setHeader("Retry-After", "" + RETRY_AFTER_MILLISECONDS); + return; + } + + // de-serialize or create application context, store in session + ApplicationContext ctx = getApplicationContext(request, memcache); + + super.service(request, response); + + // serialize + started = new Date().getTime(); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(baos); + oos.writeObject(ctx); + oos.flush(); + byte[] bytes = baos.toByteArray(); + + started = new Date().getTime(); + + String id = AC_BASE + session.getId(); + Date expire = new Date(started + + (session.getMaxInactiveInterval() * 1000)); + Expiration expires = Expiration.onDate(expire); + + memcache.put(id, bytes, expires); + + DatastoreService ds = DatastoreServiceFactory.getDatastoreService(); + Entity entity = new Entity(AC_BASE, id); + entity.setProperty(PROPERTY_EXPIRES, expire.getTime()); + entity.setProperty(PROPERTY_DATA, new Blob(bytes)); + ds.put(entity); + + } catch (DeadlineExceededException e) { + getLogger().warning("DeadlineExceeded for " + session.getId()); + sendDeadlineExceededNotification(request, response); + } catch (NotSerializableException e) { + getLogger().log(Level.SEVERE, "Not serializable!", e); + + // TODO this notification is usually not shown - should we redirect + // in some other way - can we? + sendNotSerializableNotification(request, response); + } catch (Exception e) { + getLogger().log(Level.WARNING, + "An exception occurred while servicing request.", e); + + sendCriticalErrorNotification(request, response); + } finally { + // "Next, please!" + if (locked) { + memcache.delete(mutex); + } + cleanSession(request); + } + } + + protected ApplicationContext getApplicationContext( + HttpServletRequest request, MemcacheService memcache) { + HttpSession session = request.getSession(); + String id = AC_BASE + session.getId(); + byte[] serializedAC = (byte[]) memcache.get(id); + if (serializedAC == null) { + DatastoreService ds = DatastoreServiceFactory.getDatastoreService(); + Key key = KeyFactory.createKey(AC_BASE, id); + Entity entity = null; + try { + entity = ds.get(key); + } catch (EntityNotFoundException e) { + // Ok, we were a bit optimistic; we'll create a new one later + } + if (entity != null) { + Blob blob = (Blob) entity.getProperty(PROPERTY_DATA); + serializedAC = blob.getBytes(); + // bring it to memcache + memcache.put(AC_BASE + session.getId(), serializedAC, + Expiration.byDeltaSeconds(session + .getMaxInactiveInterval()), + MemcacheService.SetPolicy.ADD_ONLY_IF_NOT_PRESENT); + } + } + if (serializedAC != null) { + ByteArrayInputStream bais = new ByteArrayInputStream(serializedAC); + ObjectInputStream ois; + try { + ois = new ObjectInputStream(bais); + ApplicationContext applicationContext = (ApplicationContext) ois + .readObject(); + session.setAttribute(ServletApplicationContext.class.getName(), + applicationContext); + } catch (IOException e) { + getLogger().log( + Level.WARNING, + "Could not de-serialize ApplicationContext for " + + session.getId() + + " A new one will be created. ", e); + } catch (ClassNotFoundException e) { + getLogger().log( + Level.WARNING, + "Could not de-serialize ApplicationContext for " + + session.getId() + + " A new one will be created. ", e); + } + } + // will create new context if the above did not + return getApplicationContext(session); + + } + + private boolean isCleanupRequest(HttpServletRequest request) { + String path = getRequestPathInfo(request); + if (path != null && path.equals(CLEANUP_PATH)) { + return true; + } + return false; + } + + /** + * Removes the ApplicationContext from the session in order to minimize the + * data serialized to datastore and memcache. + * + * @param request + */ + private void cleanSession(HttpServletRequest request) { + HttpSession session = request.getSession(false); + if (session != null) { + session.removeAttribute(ServletApplicationContext.class.getName()); + } + } + + /** + * This will look at the timestamp and delete expired persisted Vaadin and + * appengine sessions from the datastore. + * + * TODO Possible improvements include: 1. Use transactions (requires entity + * groups - overkill?) 2. Delete one-at-a-time, catch possible exception, + * continue w/ next. + */ + private void cleanDatastore() { + long expire = new Date().getTime(); + try { + DatastoreService ds = DatastoreServiceFactory.getDatastoreService(); + // Vaadin stuff first + { + Query q = new Query(AC_BASE); + q.setKeysOnly(); + + q.addFilter(PROPERTY_EXPIRES, + FilterOperator.LESS_THAN_OR_EQUAL, expire); + PreparedQuery pq = ds.prepare(q); + List entities = pq.asList(Builder + .withLimit(CLEANUP_LIMIT)); + if (entities != null) { + getLogger().info( + "Vaadin cleanup deleting " + entities.size() + + " expired Vaadin sessions."); + List keys = new ArrayList(); + for (Entity e : entities) { + keys.add(e.getKey()); + } + ds.delete(keys); + } + } + // Also cleanup GAE sessions + { + Query q = new Query(APPENGINE_SESSION_KIND); + q.setKeysOnly(); + q.addFilter(PROPERTY_APPENGINE_EXPIRES, + FilterOperator.LESS_THAN_OR_EQUAL, expire); + PreparedQuery pq = ds.prepare(q); + List entities = pq.asList(Builder + .withLimit(CLEANUP_LIMIT)); + if (entities != null) { + getLogger().info( + "Vaadin cleanup deleting " + entities.size() + + " expired appengine sessions."); + List keys = new ArrayList(); + for (Entity e : entities) { + keys.add(e.getKey()); + } + ds.delete(keys); + } + } + } catch (Exception e) { + getLogger().log(Level.WARNING, "Exception while cleaning.", e); + } + } + + private static final Logger getLogger() { + return Logger.getLogger(GAEVaadinServlet.class.getName()); + } +} diff --git a/server/src/com/vaadin/server/ServletApplicationContext.java b/server/src/com/vaadin/server/ServletApplicationContext.java index 639ff117d1..a38c523254 100644 --- a/server/src/com/vaadin/server/ServletApplicationContext.java +++ b/server/src/com/vaadin/server/ServletApplicationContext.java @@ -123,7 +123,7 @@ public class ServletApplicationContext extends ApplicationContext { */ @Override public File getBaseDirectory() { - final String realPath = ApplicationServlet.getResourcePath( + final String realPath = VaadinServlet.getResourcePath( session.getServletContext(), "/"); if (realPath == null) { return null; @@ -175,7 +175,7 @@ public class ServletApplicationContext extends ApplicationContext { * @return CommunicationManager */ public CommunicationManager getApplicationManager(Application application, - AbstractApplicationServlet servlet) { + VaadinServlet servlet) { CommunicationManager mgr = (CommunicationManager) applicationToAjaxAppMgrMap .get(application); diff --git a/server/src/com/vaadin/server/SystemError.java b/server/src/com/vaadin/server/SystemError.java index 5d1426e87c..aa9ffcaf52 100644 --- a/server/src/com/vaadin/server/SystemError.java +++ b/server/src/com/vaadin/server/SystemError.java @@ -80,8 +80,7 @@ public class SystemError extends AbstractErrorMessage { StringBuilder sb = new StringBuilder(); if (getMessage() != null) { sb.append("

      "); - sb.append(AbstractApplicationServlet - .safeEscapeForHtml(getMessage())); + sb.append(VaadinServlet.safeEscapeForHtml(getMessage())); sb.append("

      "); } return sb.toString(); diff --git a/server/src/com/vaadin/server/VaadinServlet.java b/server/src/com/vaadin/server/VaadinServlet.java new file mode 100644 index 0000000000..0766d46e93 --- /dev/null +++ b/server/src/com/vaadin/server/VaadinServlet.java @@ -0,0 +1,1564 @@ +/* + * Copyright 2011 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.server; + +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.io.Serializable; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLConnection; +import java.security.GeneralSecurityException; +import java.util.Arrays; +import java.util.Collection; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Locale; +import java.util.Properties; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +import com.vaadin.Application; +import com.vaadin.Application.ApplicationStartEvent; +import com.vaadin.server.AbstractCommunicationManager.Callback; +import com.vaadin.server.ServletPortletHelper.ApplicationClassException; +import com.vaadin.shared.ApplicationConstants; +import com.vaadin.ui.UI; + +@SuppressWarnings("serial") +public class VaadinServlet extends HttpServlet implements Constants { + + private static class AbstractApplicationServletWrapper implements Callback { + + private final VaadinServlet servlet; + + public AbstractApplicationServletWrapper(VaadinServlet servlet) { + this.servlet = servlet; + } + + @Override + public void criticalNotification(WrappedRequest request, + WrappedResponse response, String cap, String msg, + String details, String outOfSyncURL) throws IOException { + servlet.criticalNotification( + WrappedHttpServletRequest.cast(request), + ((WrappedHttpServletResponse) response), cap, msg, details, + outOfSyncURL); + } + } + + // TODO Move some (all?) of the constants to a separate interface (shared + // with portlet) + + private final String resourcePath = null; + + private DeploymentConfiguration deploymentConfiguration; + + private AddonContext addonContext; + + /** + * Called by the servlet container to indicate to a servlet that the servlet + * is being placed into service. + * + * @param servletConfig + * the object containing the servlet's configuration and + * initialization parameters + * @throws javax.servlet.ServletException + * if an exception has occurred that interferes with the + * servlet's normal operation. + */ + @Override + public void init(javax.servlet.ServletConfig servletConfig) + throws javax.servlet.ServletException { + super.init(servletConfig); + Properties applicationProperties = new Properties(); + + // Read default parameters from server.xml + final ServletContext context = servletConfig.getServletContext(); + for (final Enumeration e = context.getInitParameterNames(); e + .hasMoreElements();) { + final String name = e.nextElement(); + applicationProperties.setProperty(name, + context.getInitParameter(name)); + } + + // Override with application config from web.xml + for (final Enumeration e = servletConfig + .getInitParameterNames(); e.hasMoreElements();) { + final String name = e.nextElement(); + applicationProperties.setProperty(name, + servletConfig.getInitParameter(name)); + } + + deploymentConfiguration = new AbstractDeploymentConfiguration( + getClass(), applicationProperties) { + + @Override + public String getStaticFileLocation(WrappedRequest request) { + HttpServletRequest servletRequest = WrappedHttpServletRequest + .cast(request); + return VaadinServlet.this + .getStaticFilesLocation(servletRequest); + } + + @Override + public String getConfiguredWidgetset(WrappedRequest request) { + return getApplicationOrSystemProperty( + VaadinServlet.PARAMETER_WIDGETSET, + VaadinServlet.DEFAULT_WIDGETSET); + } + + @Override + public String getConfiguredTheme(WrappedRequest request) { + // Use the default + return VaadinServlet.getDefaultTheme(); + } + + @Override + public boolean isStandalone(WrappedRequest request) { + return true; + } + + @Override + public String getMimeType(String resourceName) { + return getServletContext().getMimeType(resourceName); + } + + @Override + public SystemMessages getSystemMessages() { + return VaadinServlet.this.getSystemMessages(); + } + }; + + addonContext = new AddonContext(deploymentConfiguration); + addonContext.init(); + } + + @Override + public void destroy() { + super.destroy(); + + addonContext.destroy(); + } + + /** + * Returns true if the servlet is running in production mode. Production + * mode disables all debug facilities. + * + * @return true if in production mode, false if in debug mode + */ + public boolean isProductionMode() { + return getDeploymentConfiguration().isProductionMode(); + } + + /** + * Returns the number of seconds the browser should cache a file. Default is + * 1 hour (3600 s). + * + * @return The number of seconds files are cached in the browser + */ + public int getResourceCacheTime() { + return getDeploymentConfiguration().getResourceCacheTime(); + } + + /** + * Receives standard HTTP requests from the public service method and + * dispatches them. + * + * @param request + * the object that contains the request the client made of the + * servlet. + * @param response + * the object that contains the response the servlet returns to + * the client. + * @throws ServletException + * if an input or output error occurs while the servlet is + * handling the TRACE request. + * @throws IOException + * if the request for the TRACE cannot be handled. + */ + + @Override + protected void service(HttpServletRequest request, + HttpServletResponse response) throws ServletException, IOException { + service(createWrappedRequest(request), createWrappedResponse(response)); + } + + private void service(WrappedHttpServletRequest request, + WrappedHttpServletResponse response) throws ServletException, + IOException { + RequestTimer requestTimer = new RequestTimer(); + requestTimer.start(); + + AbstractApplicationServletWrapper servletWrapper = new AbstractApplicationServletWrapper( + this); + + RequestType requestType = getRequestType(request); + if (!ensureCookiesEnabled(requestType, request, response)) { + return; + } + + if (requestType == RequestType.STATIC_FILE) { + serveStaticResources(request, response); + return; + } + + Application application = null; + boolean transactionStarted = false; + boolean requestStarted = false; + boolean applicationRunning = false; + + try { + // If a duplicate "close application" URL is received for an + // application that is not open, redirect to the application's main + // page. + // This is needed as e.g. Spring Security remembers the last + // URL from the application, which is the logout URL, and repeats + // it. + // We can tell apart a real onunload request from a repeated one + // based on the real one having content (at least the UIDL security + // key). + if (requestType == RequestType.UIDL + && request.getParameterMap().containsKey( + ApplicationConstants.PARAM_UNLOADBURST) + && request.getContentLength() < 1 + && getExistingApplication(request, false) == null) { + redirectToApplication(request, response); + return; + } + + // Find out which application this request is related to + application = findApplicationInstance(request, requestType); + if (application == null) { + return; + } + Application.setCurrent(application); + + /* + * Get or create a WebApplicationContext and an ApplicationManager + * for the session + */ + ServletApplicationContext webApplicationContext = getApplicationContext(request + .getSession()); + CommunicationManager applicationManager = webApplicationContext + .getApplicationManager(application, this); + + if (requestType == RequestType.CONNECTOR_RESOURCE) { + applicationManager.serveConnectorResource(request, response); + return; + } else if (requestType == RequestType.HEARTBEAT) { + applicationManager.handleHeartbeatRequest(request, response, + application); + return; + } + + /* Update browser information from the request */ + webApplicationContext.getBrowser().updateRequestDetails(request); + + /* + * Call application requestStart before Application.init() is called + * (bypasses the limitation in TransactionListener) + */ + if (application instanceof HttpServletRequestListener) { + ((HttpServletRequestListener) application).onRequestStart( + request, response); + requestStarted = true; + } + + // Start the application if it's newly created + startApplication(request, application, webApplicationContext); + applicationRunning = true; + + /* + * Transaction starts. Call transaction listeners. Transaction end + * is called in the finally block below. + */ + webApplicationContext.startTransaction(application, request); + transactionStarted = true; + + /* Handle the request */ + if (requestType == RequestType.FILE_UPLOAD) { + // UI is resolved in communication manager + applicationManager.handleFileUpload(application, request, + response); + return; + } else if (requestType == RequestType.UIDL) { + UI uI = application.getUIForRequest(request); + if (uI == null) { + throw new ServletException(ERROR_NO_UI_FOUND); + } + // Handles AJAX UIDL requests + applicationManager.handleUidlRequest(request, response, + servletWrapper, uI); + return; + } else if (requestType == RequestType.BROWSER_DETAILS) { + // Browser details - not related to a specific UI + applicationManager.handleBrowserDetailsRequest(request, + response, application); + return; + } + + // Removes application if it has stopped (maybe by thread or + // transactionlistener) + if (!application.isRunning()) { + endApplication(request, response, application); + return; + } + + if (applicationManager.handleApplicationRequest(request, response)) { + return; + } + // TODO Should return 404 error here and not do anything more + + } catch (final SessionExpiredException e) { + // Session has expired, notify user + handleServiceSessionExpired(request, response); + } catch (final GeneralSecurityException e) { + handleServiceSecurityException(request, response); + } catch (final Throwable e) { + handleServiceException(request, response, application, e); + } finally { + + if (applicationRunning) { + application.closeInactiveUIs(); + } + + // Notifies transaction end + try { + if (transactionStarted) { + ((ServletApplicationContext) application.getContext()) + .endTransaction(application, request); + + } + + } finally { + try { + if (requestStarted) { + ((HttpServletRequestListener) application) + .onRequestEnd(request, response); + } + } finally { + UI.setCurrent(null); + Application.setCurrent(null); + + HttpSession session = request.getSession(false); + if (session != null) { + requestTimer.stop(getApplicationContext(session)); + } + } + } + + } + } + + private WrappedHttpServletResponse createWrappedResponse( + HttpServletResponse response) { + WrappedHttpServletResponse wrappedResponse = new WrappedHttpServletResponse( + response, getDeploymentConfiguration()); + return wrappedResponse; + } + + /** + * Create a wrapped request for a http servlet request. This method can be + * overridden if the wrapped request should have special properties. + * + * @param request + * the original http servlet request + * @return a wrapped request for the original request + */ + protected WrappedHttpServletRequest createWrappedRequest( + HttpServletRequest request) { + return new WrappedHttpServletRequest(request, + getDeploymentConfiguration()); + } + + /** + * Gets a the deployment configuration for this servlet. + * + * @return the deployment configuration + */ + protected DeploymentConfiguration getDeploymentConfiguration() { + return deploymentConfiguration; + } + + /** + * Check that cookie support is enabled in the browser. Only checks UIDL + * requests. + * + * @param requestType + * Type of the request as returned by + * {@link #getRequestType(HttpServletRequest)} + * @param request + * The request from the browser + * @param response + * The response to which an error can be written + * @return false if cookies are disabled, true otherwise + * @throws IOException + */ + private boolean ensureCookiesEnabled(RequestType requestType, + WrappedHttpServletRequest request, + WrappedHttpServletResponse response) throws IOException { + if (requestType == RequestType.UIDL && !isRepaintAll(request)) { + // In all other but the first UIDL request a cookie should be + // returned by the browser. + // This can be removed if cookieless mode (#3228) is supported + if (request.getRequestedSessionId() == null) { + // User has cookies disabled + criticalNotification(request, response, getSystemMessages() + .getCookiesDisabledCaption(), getSystemMessages() + .getCookiesDisabledMessage(), null, getSystemMessages() + .getCookiesDisabledURL()); + return false; + } + } + return true; + } + + /** + * Send a notification to client's application. Used to notify client of + * critical errors, session expiration and more. Server has no knowledge of + * what application client refers to. + * + * @param request + * the HTTP request instance. + * @param response + * the HTTP response to write to. + * @param caption + * the notification caption + * @param message + * to notification body + * @param details + * a detail message to show in addition to the message. Currently + * shown directly below the message but could be hidden behind a + * details drop down in the future. Mainly used to give + * additional information not necessarily useful to the end user. + * @param url + * url to load when the message is dismissed. Null will reload + * the current page. + * @throws IOException + * if the writing failed due to input/output error. + */ + protected void criticalNotification(WrappedHttpServletRequest request, + HttpServletResponse response, String caption, String message, + String details, String url) throws IOException { + + if (ServletPortletHelper.isUIDLRequest(request)) { + + if (caption != null) { + caption = "\"" + JsonPaintTarget.escapeJSON(caption) + "\""; + } + if (details != null) { + if (message == null) { + message = details; + } else { + message += "

      " + details; + } + } + + if (message != null) { + message = "\"" + JsonPaintTarget.escapeJSON(message) + "\""; + } + if (url != null) { + url = "\"" + JsonPaintTarget.escapeJSON(url) + "\""; + } + + String output = "for(;;);[{\"changes\":[], \"meta\" : {" + + "\"appError\": {" + "\"caption\":" + caption + "," + + "\"message\" : " + message + "," + "\"url\" : " + url + + "}}, \"resources\": {}, \"locales\":[]}]"; + writeResponse(response, "application/json; charset=UTF-8", output); + } else { + // Create an HTML reponse with the error + String output = ""; + + if (url != null) { + output += ""; + } + if (caption != null) { + output += "" + caption + "
      "; + } + if (message != null) { + output += message; + output += "

      "; + } + + if (details != null) { + output += details; + output += "

      "; + } + if (url != null) { + output += "
      "; + } + writeResponse(response, "text/html; charset=UTF-8", output); + + } + + } + + /** + * Writes the response in {@code output} using the contentType given in + * {@code contentType} to the provided {@link HttpServletResponse} + * + * @param response + * @param contentType + * @param output + * Output to write (UTF-8 encoded) + * @throws IOException + */ + private void writeResponse(HttpServletResponse response, + String contentType, String output) throws IOException { + response.setContentType(contentType); + final ServletOutputStream out = response.getOutputStream(); + // Set the response type + final PrintWriter outWriter = new PrintWriter(new BufferedWriter( + new OutputStreamWriter(out, "UTF-8"))); + outWriter.print(output); + outWriter.flush(); + outWriter.close(); + out.flush(); + + } + + /** + * Returns the application instance to be used for the request. If an + * existing instance is not found a new one is created or null is returned + * to indicate that the application is not available. + * + * @param request + * @param requestType + * @return + * @throws MalformedURLException + * @throws IllegalAccessException + * @throws InstantiationException + * @throws ServletException + * @throws SessionExpiredException + */ + private Application findApplicationInstance(HttpServletRequest request, + RequestType requestType) throws MalformedURLException, + ServletException, SessionExpiredException { + + boolean requestCanCreateApplication = requestCanCreateApplication( + request, requestType); + + /* Find an existing application for this request. */ + Application application = getExistingApplication(request, + requestCanCreateApplication); + + if (application != null) { + /* + * There is an existing application. We can use this as long as the + * user not specifically requested to close or restart it. + */ + + final boolean restartApplication = (request + .getParameter(URL_PARAMETER_RESTART_APPLICATION) != null); + final boolean closeApplication = (request + .getParameter(URL_PARAMETER_CLOSE_APPLICATION) != null); + + if (restartApplication) { + closeApplication(application, request.getSession(false)); + return createApplication(request); + } else if (closeApplication) { + closeApplication(application, request.getSession(false)); + return null; + } else { + return application; + } + } + + // No existing application was found + + if (requestCanCreateApplication) { + /* + * If the request is such that it should create a new application if + * one as not found, we do that. + */ + return createApplication(request); + } else { + /* + * The application was not found and a new one should not be + * created. Assume the session has expired. + */ + throw new SessionExpiredException(); + } + + } + + /** + * Check if the request should create an application if an existing + * application is not found. + * + * @param request + * @param requestType + * @return true if an application should be created, false otherwise + */ + boolean requestCanCreateApplication(HttpServletRequest request, + RequestType requestType) { + if (requestType == RequestType.UIDL && isRepaintAll(request)) { + /* + * UIDL request contains valid repaintAll=1 event, the user probably + * wants to initiate a new application through a custom index.html + * without using the bootstrap page. + */ + return true; + + } else if (requestType == RequestType.OTHER) { + /* + * I.e URIs that are not application resources or static (theme) + * files. + */ + return true; + + } + + return false; + } + + /** + * Gets resource path using different implementations. Required to + * supporting different servlet container implementations (application + * servers). + * + * @param servletContext + * @param path + * the resource path. + * @return the resource path. + */ + protected static String getResourcePath(ServletContext servletContext, + String path) { + String resultPath = null; + resultPath = servletContext.getRealPath(path); + if (resultPath != null) { + return resultPath; + } else { + try { + final URL url = servletContext.getResource(path); + resultPath = url.getFile(); + } catch (final Exception e) { + // FIXME: Handle exception + getLogger().log(Level.INFO, + "Could not find resource path " + path, e); + } + } + return resultPath; + } + + /** + * Creates a new application and registers it into WebApplicationContext + * (aka session). This is not meant to be overridden. Override + * getNewApplication to create the application instance in a custom way. + * + * @param request + * @return + * @throws ServletException + * @throws MalformedURLException + */ + private Application createApplication(HttpServletRequest request) + throws ServletException, MalformedURLException { + Application newApplication = getNewApplication(request); + + final ServletApplicationContext context = getApplicationContext(request + .getSession()); + context.addApplication(newApplication); + + return newApplication; + } + + private void handleServiceException(WrappedHttpServletRequest request, + WrappedHttpServletResponse response, Application application, + Throwable e) throws IOException, ServletException { + // if this was an UIDL request, response UIDL back to client + if (getRequestType(request) == RequestType.UIDL) { + SystemMessages ci = getSystemMessages(); + criticalNotification(request, response, + ci.getInternalErrorCaption(), ci.getInternalErrorMessage(), + null, ci.getInternalErrorURL()); + if (application != null) { + application.getErrorHandler() + .terminalError(new RequestError(e)); + } else { + throw new ServletException(e); + } + } else { + // Re-throw other exceptions + throw new ServletException(e); + } + + } + + /** + * A helper method to strip away characters that might somehow be used for + * XSS attacs. Leaves at least alphanumeric characters intact. Also removes + * eg. ( and ), so values should be safe in javascript too. + * + * @param themeName + * @return + */ + protected static String stripSpecialChars(String themeName) { + StringBuilder sb = new StringBuilder(); + char[] charArray = themeName.toCharArray(); + for (int i = 0; i < charArray.length; i++) { + char c = charArray[i]; + if (!CHAR_BLACKLIST.contains(c)) { + sb.append(c); + } + } + return sb.toString(); + } + + private static final Collection CHAR_BLACKLIST = new HashSet( + Arrays.asList(new Character[] { '&', '"', '\'', '<', '>', '(', ')', + ';' })); + + /** + * Returns the default theme. Must never return null. + * + * @return + */ + public static String getDefaultTheme() { + return DEFAULT_THEME_NAME; + } + + void handleServiceSessionExpired(WrappedHttpServletRequest request, + WrappedHttpServletResponse response) throws IOException, + ServletException { + + if (isOnUnloadRequest(request)) { + /* + * Request was an unload request (e.g. window close event) and the + * client expects no response if it fails. + */ + return; + } + + try { + SystemMessages ci = getSystemMessages(); + if (getRequestType(request) != RequestType.UIDL) { + // 'plain' http req - e.g. browser reload; + // just go ahead redirect the browser + response.sendRedirect(ci.getSessionExpiredURL()); + } else { + /* + * Invalidate session (weird to have session if we're saying + * that it's expired, and worse: portal integration will fail + * since the session is not created by the portal. + * + * Session must be invalidated before criticalNotification as it + * commits the response. + */ + request.getSession().invalidate(); + + // send uidl redirect + criticalNotification(request, response, + ci.getSessionExpiredCaption(), + ci.getSessionExpiredMessage(), null, + ci.getSessionExpiredURL()); + + } + } catch (SystemMessageException ee) { + throw new ServletException(ee); + } + + } + + private void handleServiceSecurityException( + WrappedHttpServletRequest request, + WrappedHttpServletResponse response) throws IOException, + ServletException { + if (isOnUnloadRequest(request)) { + /* + * Request was an unload request (e.g. window close event) and the + * client expects no response if it fails. + */ + return; + } + + try { + SystemMessages ci = getSystemMessages(); + if (getRequestType(request) != RequestType.UIDL) { + // 'plain' http req - e.g. browser reload; + // just go ahead redirect the browser + response.sendRedirect(ci.getCommunicationErrorURL()); + } else { + // send uidl redirect + criticalNotification(request, response, + ci.getCommunicationErrorCaption(), + ci.getCommunicationErrorMessage(), + INVALID_SECURITY_KEY_MSG, ci.getCommunicationErrorURL()); + /* + * Invalidate session. Portal integration will fail otherwise + * since the session is not created by the portal. + */ + request.getSession().invalidate(); + } + } catch (SystemMessageException ee) { + throw new ServletException(ee); + } + + log("Invalid security key received from " + request.getRemoteHost()); + } + + /** + * Creates a new application for the given request. + * + * @param request + * the HTTP request. + * @return A new Application instance. + * @throws ServletException + */ + protected Application getNewApplication(HttpServletRequest request) + throws ServletException { + + // Creates a new application instance + try { + Class applicationClass = ServletPortletHelper + .getApplicationClass(getDeploymentConfiguration()); + + final Application application = applicationClass.newInstance(); + application.addUIProvider(new DefaultUIProvider()); + + return application; + } catch (final IllegalAccessException e) { + throw new ServletException("getNewApplication failed", e); + } catch (final InstantiationException e) { + throw new ServletException("getNewApplication failed", e); + } catch (ApplicationClassException e) { + throw new ServletException("getNewApplication failed", e); + } + } + + /** + * Starts the application if it is not already running. + * + * @param request + * @param application + * @param webApplicationContext + * @throws ServletException + * @throws MalformedURLException + */ + private void startApplication(HttpServletRequest request, + Application application, + ServletApplicationContext webApplicationContext) + throws ServletException, MalformedURLException { + + if (!application.isRunning()) { + // Create application + final URL applicationUrl = getApplicationUrl(request); + + // Initial locale comes from the request + Locale locale = request.getLocale(); + application.setLocale(locale); + application.start(new ApplicationStartEvent(applicationUrl, + getDeploymentConfiguration(), webApplicationContext)); + addonContext.fireApplicationStarted(application); + } + } + + /** + * Check if this is a request for a static resource and, if it is, serve the + * resource to the client. + * + * @param request + * @param response + * @return true if a file was served and the request has been handled, false + * otherwise. + * @throws IOException + * @throws ServletException + */ + private boolean serveStaticResources(HttpServletRequest request, + HttpServletResponse response) throws IOException, ServletException { + + // FIXME What does 10 refer to? + String pathInfo = request.getPathInfo(); + if (pathInfo == null || pathInfo.length() <= 10) { + return false; + } + + if ((request.getContextPath() != null) + && (request.getRequestURI().startsWith("/VAADIN/"))) { + serveStaticResourcesInVAADIN(request.getRequestURI(), request, + response); + return true; + } else if (request.getRequestURI().startsWith( + request.getContextPath() + "/VAADIN/")) { + serveStaticResourcesInVAADIN( + request.getRequestURI().substring( + request.getContextPath().length()), request, + response); + return true; + } + + return false; + } + + /** + * Serve resources from VAADIN directory. + * + * @param filename + * The filename to serve. Should always start with /VAADIN/. + * @param request + * @param response + * @throws IOException + * @throws ServletException + */ + private void serveStaticResourcesInVAADIN(String filename, + HttpServletRequest request, HttpServletResponse response) + throws IOException, ServletException { + + final ServletContext sc = getServletContext(); + URL resourceUrl = sc.getResource(filename); + if (resourceUrl == null) { + // try if requested file is found from classloader + + // strip leading "/" otherwise stream from JAR wont work + filename = filename.substring(1); + resourceUrl = getDeploymentConfiguration().getClassLoader() + .getResource(filename); + + if (resourceUrl == null) { + // cannot serve requested file + getLogger() + .info("Requested resource [" + + filename + + "] not found from filesystem or through class loader." + + " Add widgetset and/or theme JAR to your classpath or add files to WebContent/VAADIN folder."); + response.setStatus(HttpServletResponse.SC_NOT_FOUND); + return; + } + + // security check: do not permit navigation out of the VAADIN + // directory + if (!isAllowedVAADINResourceUrl(request, resourceUrl)) { + getLogger() + .info("Requested resource [" + + filename + + "] not accessible in the VAADIN directory or access to it is forbidden."); + response.setStatus(HttpServletResponse.SC_FORBIDDEN); + return; + } + } + + // Find the modification timestamp + long lastModifiedTime = 0; + URLConnection connection = null; + try { + connection = resourceUrl.openConnection(); + lastModifiedTime = connection.getLastModified(); + // Remove milliseconds to avoid comparison problems (milliseconds + // are not returned by the browser in the "If-Modified-Since" + // header). + lastModifiedTime = lastModifiedTime - lastModifiedTime % 1000; + + if (browserHasNewestVersion(request, lastModifiedTime)) { + response.setStatus(HttpServletResponse.SC_NOT_MODIFIED); + return; + } + } catch (Exception e) { + // Failed to find out last modified timestamp. Continue without it. + getLogger() + .log(Level.FINEST, + "Failed to find out last modified timestamp. Continuing without it.", + e); + } finally { + if (connection instanceof URLConnection) { + try { + // Explicitly close the input stream to prevent it + // from remaining hanging + // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4257700 + InputStream is = connection.getInputStream(); + if (is != null) { + is.close(); + } + } catch (IOException e) { + getLogger().log(Level.INFO, + "Error closing URLConnection input stream", e); + } + } + } + + // Set type mime type if we can determine it based on the filename + final String mimetype = sc.getMimeType(filename); + if (mimetype != null) { + response.setContentType(mimetype); + } + + // Provide modification timestamp to the browser if it is known. + if (lastModifiedTime > 0) { + response.setDateHeader("Last-Modified", lastModifiedTime); + /* + * The browser is allowed to cache for 1 hour without checking if + * the file has changed. This forces browsers to fetch a new version + * when the Vaadin version is updated. This will cause more requests + * to the servlet than without this but for high volume sites the + * static files should never be served through the servlet. The + * cache timeout can be configured by setting the resourceCacheTime + * parameter in web.xml + */ + response.setHeader("Cache-Control", + "max-age= " + String.valueOf(getResourceCacheTime())); + } + + // Write the resource to the client. + final OutputStream os = response.getOutputStream(); + final byte buffer[] = new byte[DEFAULT_BUFFER_SIZE]; + int bytes; + InputStream is = resourceUrl.openStream(); + while ((bytes = is.read(buffer)) >= 0) { + os.write(buffer, 0, bytes); + } + is.close(); + } + + /** + * Check whether a URL obtained from a classloader refers to a valid static + * resource in the directory VAADIN. + * + * Warning: Overriding of this method is not recommended, but is possible to + * support non-default classloaders or servers that may produce URLs + * different from the normal ones. The method prototype may change in the + * future. Care should be taken not to expose class files or other resources + * outside the VAADIN directory if the method is overridden. + * + * @param request + * @param resourceUrl + * @return + * + * @since 6.6.7 + */ + protected boolean isAllowedVAADINResourceUrl(HttpServletRequest request, + URL resourceUrl) { + if ("jar".equals(resourceUrl.getProtocol())) { + // This branch is used for accessing resources directly from the + // Vaadin JAR in development environments and in similar cases. + + // Inside a JAR, a ".." would mean a real directory named ".." so + // using it in paths should just result in the file not being found. + // However, performing a check in case some servers or class loaders + // try to normalize the path by collapsing ".." before the class + // loader sees it. + + if (!resourceUrl.getPath().contains("!/VAADIN/")) { + getLogger().info( + "Blocked attempt to access a JAR entry not starting with /VAADIN/: " + + resourceUrl); + return false; + } + getLogger().fine( + "Accepted access to a JAR entry using a class loader: " + + resourceUrl); + return true; + } else { + // Some servers such as GlassFish extract files from JARs (file:) + // and e.g. JBoss 5+ use protocols vsf: and vfsfile: . + + // Check that the URL is in a VAADIN directory and does not contain + // "/../" + if (!resourceUrl.getPath().contains("/VAADIN/") + || resourceUrl.getPath().contains("/../")) { + getLogger().info( + "Blocked attempt to access file: " + resourceUrl); + return false; + } + getLogger().fine( + "Accepted access to a file using a class loader: " + + resourceUrl); + return true; + } + } + + /** + * Checks if the browser has an up to date cached version of requested + * resource. Currently the check is performed using the "If-Modified-Since" + * header. Could be expanded if needed. + * + * @param request + * The HttpServletRequest from the browser. + * @param resourceLastModifiedTimestamp + * The timestamp when the resource was last modified. 0 if the + * last modification time is unknown. + * @return true if the If-Modified-Since header tells the cached version in + * the browser is up to date, false otherwise + */ + private boolean browserHasNewestVersion(HttpServletRequest request, + long resourceLastModifiedTimestamp) { + if (resourceLastModifiedTimestamp < 1) { + // We do not know when it was modified so the browser cannot have an + // up-to-date version + return false; + } + /* + * The browser can request the resource conditionally using an + * If-Modified-Since header. Check this against the last modification + * time. + */ + try { + // If-Modified-Since represents the timestamp of the version cached + // in the browser + long headerIfModifiedSince = request + .getDateHeader("If-Modified-Since"); + + if (headerIfModifiedSince >= resourceLastModifiedTimestamp) { + // Browser has this an up-to-date version of the resource + return true; + } + } catch (Exception e) { + // Failed to parse header. Fail silently - the browser does not have + // an up-to-date version in its cache. + } + return false; + } + + protected enum RequestType { + FILE_UPLOAD, BROWSER_DETAILS, UIDL, OTHER, STATIC_FILE, APPLICATION_RESOURCE, CONNECTOR_RESOURCE, HEARTBEAT; + } + + protected RequestType getRequestType(WrappedHttpServletRequest request) { + if (ServletPortletHelper.isFileUploadRequest(request)) { + return RequestType.FILE_UPLOAD; + } else if (ServletPortletHelper.isConnectorResourceRequest(request)) { + return RequestType.CONNECTOR_RESOURCE; + } else if (isBrowserDetailsRequest(request)) { + return RequestType.BROWSER_DETAILS; + } else if (ServletPortletHelper.isUIDLRequest(request)) { + return RequestType.UIDL; + } else if (isStaticResourceRequest(request)) { + return RequestType.STATIC_FILE; + } else if (ServletPortletHelper.isApplicationResourceRequest(request)) { + return RequestType.APPLICATION_RESOURCE; + } else if (ServletPortletHelper.isHeartbeatRequest(request)) { + return RequestType.HEARTBEAT; + } + return RequestType.OTHER; + + } + + private static boolean isBrowserDetailsRequest(HttpServletRequest request) { + return "POST".equals(request.getMethod()) + && request.getParameter("browserDetails") != null; + } + + private boolean isStaticResourceRequest(HttpServletRequest request) { + String pathInfo = request.getPathInfo(); + if (pathInfo == null || pathInfo.length() <= 10) { + return false; + } + + if ((request.getContextPath() != null) + && (request.getRequestURI().startsWith("/VAADIN/"))) { + return true; + } else if (request.getRequestURI().startsWith( + request.getContextPath() + "/VAADIN/")) { + return true; + } + + return false; + } + + private boolean isOnUnloadRequest(HttpServletRequest request) { + return request.getParameter(ApplicationConstants.PARAM_UNLOADBURST) != null; + } + + /** + * Get system messages + * + * @return + */ + protected SystemMessages getSystemMessages() { + return ServletPortletHelper.DEFAULT_SYSTEM_MESSAGES; + } + + /** + * Return the URL from where static files, e.g. the widgetset and the theme, + * are served. In a standard configuration the VAADIN folder inside the + * returned folder is what is used for widgetsets and themes. + * + * The returned folder is usually the same as the context path and + * independent of the application. + * + * @param request + * @return The location of static resources (should contain the VAADIN + * directory). Never ends with a slash (/). + */ + protected String getStaticFilesLocation(HttpServletRequest request) { + + return getWebApplicationsStaticFileLocation(request); + } + + /** + * The default method to fetch static files location (URL). This method does + * not check for request attribute {@value #REQUEST_VAADIN_STATIC_FILE_PATH} + * + * @param request + * @return + */ + private String getWebApplicationsStaticFileLocation( + HttpServletRequest request) { + String staticFileLocation; + // if property is defined in configurations, use that + staticFileLocation = getDeploymentConfiguration() + .getApplicationOrSystemProperty(PARAMETER_VAADIN_RESOURCES, + null); + if (staticFileLocation != null) { + return staticFileLocation; + } + + // the last (but most common) option is to generate default location + // from request + + // if context is specified add it to widgetsetUrl + String ctxPath = request.getContextPath(); + + // FIXME: ctxPath.length() == 0 condition is probably unnecessary and + // might even be wrong. + + if (ctxPath.length() == 0 + && request.getAttribute("javax.servlet.include.context_path") != null) { + // include request (e.g portlet), get context path from + // attribute + ctxPath = (String) request + .getAttribute("javax.servlet.include.context_path"); + } + + // Remove heading and trailing slashes from the context path + ctxPath = removeHeadingOrTrailing(ctxPath, "/"); + + if (ctxPath.equals("")) { + return ""; + } else { + return "/" + ctxPath; + } + } + + /** + * Remove any heading or trailing "what" from the "string". + * + * @param string + * @param what + * @return + */ + private static String removeHeadingOrTrailing(String string, String what) { + while (string.startsWith(what)) { + string = string.substring(1); + } + + while (string.endsWith(what)) { + string = string.substring(0, string.length() - 1); + } + + return string; + } + + /** + * Write a redirect response to the main page of the application. + * + * @param request + * @param response + * @throws IOException + * if sending the redirect fails due to an input/output error or + * a bad application URL + */ + private void redirectToApplication(HttpServletRequest request, + HttpServletResponse response) throws IOException { + String applicationUrl = getApplicationUrl(request).toExternalForm(); + response.sendRedirect(response.encodeRedirectURL(applicationUrl)); + } + + /** + * Gets the current application URL from request. + * + * @param request + * the HTTP request. + * @throws MalformedURLException + * if the application is denied access to the persistent data + * store represented by the given URL. + */ + protected URL getApplicationUrl(HttpServletRequest request) + throws MalformedURLException { + final URL reqURL = new URL( + (request.isSecure() ? "https://" : "http://") + + request.getServerName() + + ((request.isSecure() && request.getServerPort() == 443) + || (!request.isSecure() && request + .getServerPort() == 80) ? "" : ":" + + request.getServerPort()) + + request.getRequestURI()); + String servletPath = ""; + if (request.getAttribute("javax.servlet.include.servlet_path") != null) { + // this is an include request + servletPath = request.getAttribute( + "javax.servlet.include.context_path").toString() + + request + .getAttribute("javax.servlet.include.servlet_path"); + + } else { + servletPath = request.getContextPath() + request.getServletPath(); + } + + if (servletPath.length() == 0 + || servletPath.charAt(servletPath.length() - 1) != '/') { + servletPath = servletPath + "/"; + } + URL u = new URL(reqURL, servletPath); + return u; + } + + /** + * Gets the existing application for given request. Looks for application + * instance for given request based on the requested URL. + * + * @param request + * the HTTP request. + * @param allowSessionCreation + * true if a session should be created if no session exists, + * false if no session should be created + * @return Application instance, or null if the URL does not map to valid + * application. + * @throws MalformedURLException + * if the application is denied access to the persistent data + * store represented by the given URL. + * @throws IllegalAccessException + * @throws InstantiationException + * @throws SessionExpiredException + */ + protected Application getExistingApplication(HttpServletRequest request, + boolean allowSessionCreation) throws MalformedURLException, + SessionExpiredException { + + // Ensures that the session is still valid + final HttpSession session = request.getSession(allowSessionCreation); + if (session == null) { + throw new SessionExpiredException(); + } + + ServletApplicationContext context = getApplicationContext(session); + + // Gets application list for the session. + final Collection applications = context.getApplications(); + + // Search for the application (using the application URI) from the list + for (final Iterator i = applications.iterator(); i + .hasNext();) { + final Application sessionApplication = i.next(); + final String sessionApplicationPath = sessionApplication.getURL() + .getPath(); + String requestApplicationPath = getApplicationUrl(request) + .getPath(); + + if (requestApplicationPath.equals(sessionApplicationPath)) { + // Found a running application + if (sessionApplication.isRunning()) { + return sessionApplication; + } + // Application has stopped, so remove it before creating a new + // application + getApplicationContext(session).removeApplication( + sessionApplication); + break; + } + } + + // Existing application not found + return null; + } + + /** + * Ends the application. + * + * @param request + * the HTTP request. + * @param response + * the HTTP response to write to. + * @param application + * the application to end. + * @throws IOException + * if the writing failed due to input/output error. + */ + private void endApplication(HttpServletRequest request, + HttpServletResponse response, Application application) + throws IOException { + + String logoutUrl = application.getLogoutURL(); + if (logoutUrl == null) { + logoutUrl = application.getURL().toString(); + } + + final HttpSession session = request.getSession(); + if (session != null) { + getApplicationContext(session).removeApplication(application); + } + + response.sendRedirect(response.encodeRedirectURL(logoutUrl)); + } + + /** + * Returns the path info; note that this _can_ be different than + * request.getPathInfo(). Examples where this might be useful: + *
        + *
      • An application runner servlet that runs different Vaadin applications + * based on an identifier.
      • + *
      • Providing a REST interface in the context root, while serving a + * Vaadin UI on a sub-URI using only one servlet (e.g. REST on + * http://example.com/foo, UI on http://example.com/foo/vaadin)
      • + * + * @param request + * @return + */ + protected String getRequestPathInfo(HttpServletRequest request) { + return request.getPathInfo(); + } + + /** + * Gets relative location of a theme resource. + * + * @param theme + * the Theme name. + * @param resource + * the Theme resource. + * @return External URI specifying the resource + */ + public String getResourceLocation(String theme, ThemeResource resource) { + + if (resourcePath == null) { + return resource.getResourceId(); + } + return resourcePath + theme + "/" + resource.getResourceId(); + } + + private boolean isRepaintAll(HttpServletRequest request) { + return (request.getParameter(URL_PARAMETER_REPAINT_ALL) != null) + && (request.getParameter(URL_PARAMETER_REPAINT_ALL).equals("1")); + } + + private void closeApplication(Application application, HttpSession session) { + if (application == null) { + return; + } + + application.close(); + if (session != null) { + ServletApplicationContext context = getApplicationContext(session); + context.removeApplication(application); + } + } + + /** + * + * Gets the application context from an HttpSession. If no context is + * currently stored in a session a new context is created and stored in the + * session. + * + * @param session + * the HTTP session. + * @return the application context for HttpSession. + */ + protected ServletApplicationContext getApplicationContext( + HttpSession session) { + /* + * TODO the ApplicationContext.getApplicationContext() should be removed + * and logic moved here. Now overriding context type is possible, but + * the whole creation logic should be here. MT 1101 + */ + return ServletApplicationContext.getApplicationContext(session); + } + + public class RequestError implements Terminal.ErrorEvent, Serializable { + + private final Throwable throwable; + + public RequestError(Throwable throwable) { + this.throwable = throwable; + } + + @Override + public Throwable getThrowable() { + return throwable; + } + + } + + /** + * Override this method if you need to use a specialized communicaiton + * mananger implementation. + * + * @deprecated Instead of overriding this method, override + * {@link ServletApplicationContext} implementation via + * {@link VaadinServlet#getApplicationContext(HttpSession)} + * method and in that customized implementation return your + * CommunicationManager in + * {@link ServletApplicationContext#getApplicationManager(Application, VaadinServlet)} + * method. + * + * @param application + * @return + */ + @Deprecated + public CommunicationManager createCommunicationManager( + Application application) { + return new CommunicationManager(application); + } + + /** + * Escapes characters to html entities. An exception is made for some + * "safe characters" to keep the text somewhat readable. + * + * @param unsafe + * @return a safe string to be added inside an html tag + */ + public static final String safeEscapeForHtml(String unsafe) { + if (null == unsafe) { + return null; + } + StringBuilder safe = new StringBuilder(); + char[] charArray = unsafe.toCharArray(); + for (int i = 0; i < charArray.length; i++) { + char c = charArray[i]; + if (isSafe(c)) { + safe.append(c); + } else { + safe.append("&#"); + safe.append((int) c); + safe.append(";"); + } + } + + return safe.toString(); + } + + private static boolean isSafe(char c) { + return // + c > 47 && c < 58 || // alphanum + c > 64 && c < 91 || // A-Z + c > 96 && c < 123 // a-z + ; + } + + private static final Logger getLogger() { + return Logger.getLogger(VaadinServlet.class.getName()); + } +} diff --git a/server/src/com/vaadin/server/WebBrowser.java b/server/src/com/vaadin/server/WebBrowser.java index 92090da14a..96cab99e40 100644 --- a/server/src/com/vaadin/server/WebBrowser.java +++ b/server/src/com/vaadin/server/WebBrowser.java @@ -335,9 +335,8 @@ public class WebBrowser { } /** - * For internal use by AbstractApplicationServlet/AbstractApplicationPortlet - * only. Updates all properties in the class according to the given - * information. + * For internal use by VaadinServlet/VaadinPortlet only. Updates all + * properties in the class according to the given information. * * @param sw * Screen width @@ -406,9 +405,8 @@ public class WebBrowser { } /** - * For internal use by AbstractApplicationServlet/AbstractApplicationPortlet - * only. Updates all properties in the class according to the given - * information. + * For internal use by VaadinServlet/VaadinPortlet only. Updates all + * properties in the class according to the given information. * * @param request * the wrapped request to read the information from diff --git a/server/src/com/vaadin/ui/UI.java b/server/src/com/vaadin/ui/UI.java index d1ccaacde3..0f914d3947 100644 --- a/server/src/com/vaadin/ui/UI.java +++ b/server/src/com/vaadin/ui/UI.java @@ -33,7 +33,6 @@ import com.vaadin.event.Action.Handler; import com.vaadin.event.ActionManager; import com.vaadin.event.MouseEvents.ClickEvent; import com.vaadin.event.MouseEvents.ClickListener; -import com.vaadin.server.AbstractApplicationServlet; import com.vaadin.server.LegacyComponent; import com.vaadin.server.Page; import com.vaadin.server.Page.BrowserWindowResizeEvent; @@ -41,6 +40,7 @@ import com.vaadin.server.Page.BrowserWindowResizeListener; import com.vaadin.server.PaintException; import com.vaadin.server.PaintTarget; import com.vaadin.server.Resource; +import com.vaadin.server.VaadinServlet; import com.vaadin.server.WrappedRequest; import com.vaadin.server.WrappedRequest.BrowserDetails; import com.vaadin.shared.EventId; @@ -63,7 +63,7 @@ import com.vaadin.tools.ReflectTools; *

        *

        * When a new UI instance is needed, typically because the user opens a URL in a - * browser window which points to {@link AbstractApplicationServlet}, + * browser window which points to {@link VaadinServlet}, * {@link Application#getUIForRequest(WrappedRequest)} is invoked to get a UI. * That method does by default create a UI according to the * {@value Application#UI_PARAMETER} parameter from web.xml. diff --git a/server/tests/src/com/vaadin/server/TestAbstractApplicationServletStaticFilesLocation.java b/server/tests/src/com/vaadin/server/TestAbstractApplicationServletStaticFilesLocation.java index df16e98bba..3ae41610fa 100644 --- a/server/tests/src/com/vaadin/server/TestAbstractApplicationServletStaticFilesLocation.java +++ b/server/tests/src/com/vaadin/server/TestAbstractApplicationServletStaticFilesLocation.java @@ -15,14 +15,11 @@ import javax.servlet.ServletConfig; import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; -import com.vaadin.server.AbstractApplicationServlet; -import com.vaadin.server.ApplicationServlet; - import junit.framework.TestCase; public class TestAbstractApplicationServletStaticFilesLocation extends TestCase { - ApplicationServlet servlet; + VaadinServlet servlet; private Method getStaticFilesLocationMethod; @@ -30,18 +27,16 @@ public class TestAbstractApplicationServletStaticFilesLocation extends TestCase protected void setUp() throws Exception { super.setUp(); - servlet = new ApplicationServlet(); + servlet = new VaadinServlet(); // Workaround to avoid calling init and creating servlet config - Field f = AbstractApplicationServlet.class - .getDeclaredField("applicationProperties"); + Field f = VaadinServlet.class.getDeclaredField("applicationProperties"); f.setAccessible(true); f.set(servlet, new Properties()); - getStaticFilesLocationMethod = AbstractApplicationServlet.class - .getDeclaredMethod( - "getStaticFilesLocation", - new Class[] { javax.servlet.http.HttpServletRequest.class }); + getStaticFilesLocationMethod = VaadinServlet.class.getDeclaredMethod( + "getStaticFilesLocation", + new Class[] { javax.servlet.http.HttpServletRequest.class }); getStaticFilesLocationMethod.setAccessible(true); } diff --git a/uitest/src/com/vaadin/launcher/ApplicationRunnerServlet.java b/uitest/src/com/vaadin/launcher/ApplicationRunnerServlet.java index e2fe5df4c7..bbe6e061fb 100644 --- a/uitest/src/com/vaadin/launcher/ApplicationRunnerServlet.java +++ b/uitest/src/com/vaadin/launcher/ApplicationRunnerServlet.java @@ -30,7 +30,7 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.vaadin.Application; -import com.vaadin.server.AbstractApplicationServlet; +import com.vaadin.server.VaadinServlet; import com.vaadin.server.AbstractUIProvider; import com.vaadin.server.WrappedHttpServletRequest; import com.vaadin.server.WrappedRequest; @@ -38,7 +38,7 @@ import com.vaadin.tests.components.TestBase; import com.vaadin.ui.UI; @SuppressWarnings("serial") -public class ApplicationRunnerServlet extends AbstractApplicationServlet { +public class ApplicationRunnerServlet extends VaadinServlet { /** * The name of the application class currently used. Only valid within one -- cgit v1.2.3 From ae2c302f71554f49b3f57b649cec7450c652cf09 Mon Sep 17 00:00:00 2001 From: Leif Åstrand Date: Mon, 3 Sep 2012 14:03:46 +0300 Subject: Combine ApplicationPortlet and AAP and rename to VaadinPortlet (#9460) --- WebContent/WEB-INF/portlet.xml | 4 +- .../vaadin/server/AbstractApplicationPortlet.java | 1041 ------------------- .../src/com/vaadin/server/ApplicationPortlet2.java | 50 - .../vaadin/server/PortletApplicationContext2.java | 2 +- .../vaadin/server/PortletCommunicationManager.java | 17 +- server/src/com/vaadin/server/VaadinPortlet.java | 1045 ++++++++++++++++++++ .../com/vaadin/server/WrappedPortletRequest.java | 2 +- .../tests/server/TestClassesSerializable.java | 3 +- 8 files changed, 1057 insertions(+), 1107 deletions(-) delete mode 100644 server/src/com/vaadin/server/AbstractApplicationPortlet.java delete mode 100644 server/src/com/vaadin/server/ApplicationPortlet2.java create mode 100644 server/src/com/vaadin/server/VaadinPortlet.java (limited to 'server') diff --git a/WebContent/WEB-INF/portlet.xml b/WebContent/WEB-INF/portlet.xml index f695551cf2..5faafc5c36 100644 --- a/WebContent/WEB-INF/portlet.xml +++ b/WebContent/WEB-INF/portlet.xml @@ -6,7 +6,7 @@ JSR286TestPortlet Vaadin Portlet 2.0 Test - com.vaadin.server.ApplicationPortlet2 + com.vaadin.server.VaadinPortlet application com.vaadin.tests.integration.JSR286PortletApplication @@ -44,7 +44,7 @@ Vaadin Liferay Theme Portlet Vaadin Liferay Theme - com.vaadin.server.ApplicationPortlet2 + com.vaadin.server.VaadinPortlet application com.vaadin.tests.integration.LiferayThemeDemo diff --git a/server/src/com/vaadin/server/AbstractApplicationPortlet.java b/server/src/com/vaadin/server/AbstractApplicationPortlet.java deleted file mode 100644 index 7fc69b05f7..0000000000 --- a/server/src/com/vaadin/server/AbstractApplicationPortlet.java +++ /dev/null @@ -1,1041 +0,0 @@ -/* - * Copyright 2011 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.server; - -import java.io.BufferedWriter; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.io.PrintWriter; -import java.io.Serializable; -import java.lang.reflect.Method; -import java.net.MalformedURLException; -import java.security.GeneralSecurityException; -import java.util.Enumeration; -import java.util.Locale; -import java.util.Map; -import java.util.Properties; -import java.util.logging.Logger; - -import javax.portlet.ActionRequest; -import javax.portlet.ActionResponse; -import javax.portlet.EventRequest; -import javax.portlet.EventResponse; -import javax.portlet.GenericPortlet; -import javax.portlet.PortletConfig; -import javax.portlet.PortletContext; -import javax.portlet.PortletException; -import javax.portlet.PortletRequest; -import javax.portlet.PortletResponse; -import javax.portlet.PortletSession; -import javax.portlet.RenderRequest; -import javax.portlet.RenderResponse; -import javax.portlet.ResourceRequest; -import javax.portlet.ResourceResponse; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletRequestWrapper; -import javax.servlet.http.HttpServletResponse; - -import com.liferay.portal.kernel.util.PortalClassInvoker; -import com.liferay.portal.kernel.util.PropsUtil; -import com.vaadin.Application; -import com.vaadin.Application.ApplicationStartEvent; -import com.vaadin.server.AbstractCommunicationManager.Callback; -import com.vaadin.ui.UI; - -/** - * Portlet 2.0 base class. This replaces the servlet in servlet/portlet 1.0 - * deployments and handles various portlet requests from the browser. - * - * TODO Document me! - * - * @author peholmst - */ -public abstract class AbstractApplicationPortlet extends GenericPortlet - implements Constants { - - public static final String RESOURCE_URL_ID = "APP"; - - public static class WrappedHttpAndPortletRequest extends - WrappedPortletRequest { - - public WrappedHttpAndPortletRequest(PortletRequest request, - HttpServletRequest originalRequest, - DeploymentConfiguration deploymentConfiguration) { - super(request, deploymentConfiguration); - this.originalRequest = originalRequest; - } - - private final HttpServletRequest originalRequest; - - @Override - public String getParameter(String name) { - String parameter = super.getParameter(name); - if (parameter == null) { - parameter = originalRequest.getParameter(name); - } - return parameter; - } - - @Override - public String getRemoteAddr() { - return originalRequest.getRemoteAddr(); - } - - @Override - public String getHeader(String name) { - String header = super.getHeader(name); - if (header == null) { - header = originalRequest.getHeader(name); - } - return header; - } - - @Override - public Map getParameterMap() { - Map parameterMap = super.getParameterMap(); - if (parameterMap == null) { - parameterMap = originalRequest.getParameterMap(); - } - return parameterMap; - } - } - - public static class WrappedGateinRequest extends - WrappedHttpAndPortletRequest { - public WrappedGateinRequest(PortletRequest request, - DeploymentConfiguration deploymentConfiguration) { - super(request, getOriginalRequest(request), deploymentConfiguration); - } - - private static final HttpServletRequest getOriginalRequest( - PortletRequest request) { - try { - Method getRealReq = request.getClass().getMethod( - "getRealRequest"); - HttpServletRequestWrapper origRequest = (HttpServletRequestWrapper) getRealReq - .invoke(request); - return origRequest; - } catch (Exception e) { - throw new IllegalStateException("GateIn request not detected", - e); - } - } - } - - public static class WrappedLiferayRequest extends - WrappedHttpAndPortletRequest { - - public WrappedLiferayRequest(PortletRequest request, - DeploymentConfiguration deploymentConfiguration) { - super(request, getOriginalRequest(request), deploymentConfiguration); - } - - @Override - public String getPortalProperty(String name) { - return PropsUtil.get(name); - } - - private static HttpServletRequest getOriginalRequest( - PortletRequest request) { - try { - // httpRequest = PortalUtil.getHttpServletRequest(request); - HttpServletRequest httpRequest = (HttpServletRequest) PortalClassInvoker - .invoke("com.liferay.portal.util.PortalUtil", - "getHttpServletRequest", request); - - // httpRequest = - // PortalUtil.getOriginalServletRequest(httpRequest); - httpRequest = (HttpServletRequest) PortalClassInvoker.invoke( - "com.liferay.portal.util.PortalUtil", - "getOriginalServletRequest", httpRequest); - return httpRequest; - } catch (Exception e) { - throw new IllegalStateException("Liferay request not detected", - e); - } - } - - } - - public static class AbstractApplicationPortletWrapper implements Callback { - - private final AbstractApplicationPortlet portlet; - - public AbstractApplicationPortletWrapper( - AbstractApplicationPortlet portlet) { - this.portlet = portlet; - } - - @Override - public void criticalNotification(WrappedRequest request, - WrappedResponse response, String cap, String msg, - String details, String outOfSyncURL) throws IOException { - portlet.criticalNotification(WrappedPortletRequest.cast(request), - (WrappedPortletResponse) response, cap, msg, details, - outOfSyncURL); - } - } - - /** - * This portlet parameter is used to add styles to the main element. E.g - * "height:500px" generates a style="height:500px" to the main element. - */ - public static final String PORTLET_PARAMETER_STYLE = "style"; - - /** - * This portal parameter is used to define the name of the Vaadin theme that - * is used for all Vaadin applications in the portal. - */ - public static final String PORTAL_PARAMETER_VAADIN_THEME = "vaadin.theme"; - - public static final String WRITE_AJAX_PAGE_SCRIPT_WIDGETSET_SHOULD_WRITE = "writeAjaxPageScriptWidgetsetShouldWrite"; - - // TODO some parts could be shared with AbstractApplicationServlet - - // TODO Can we close the application when the portlet is removed? Do we know - // when the portlet is removed? - - private DeploymentConfiguration deploymentConfiguration; - private AddonContext addonContext; - - @Override - public void init(PortletConfig config) throws PortletException { - super.init(config); - Properties applicationProperties = new Properties(); - - // Read default parameters from the context - final PortletContext context = config.getPortletContext(); - for (final Enumeration e = context.getInitParameterNames(); e - .hasMoreElements();) { - final String name = e.nextElement(); - applicationProperties.setProperty(name, - context.getInitParameter(name)); - } - - // Override with application settings from portlet.xml - for (final Enumeration e = config.getInitParameterNames(); e - .hasMoreElements();) { - final String name = e.nextElement(); - applicationProperties.setProperty(name, - config.getInitParameter(name)); - } - - deploymentConfiguration = new AbstractDeploymentConfiguration( - getClass(), applicationProperties) { - @Override - public String getConfiguredWidgetset(WrappedRequest request) { - - String widgetset = getApplicationOrSystemProperty( - PARAMETER_WIDGETSET, null); - - if (widgetset == null) { - // If no widgetset defined for the application, check the - // portal property - widgetset = WrappedPortletRequest.cast(request) - .getPortalProperty( - PORTAL_PARAMETER_VAADIN_WIDGETSET); - } - - if (widgetset == null) { - // If no widgetset defined for the portal, use the default - widgetset = DEFAULT_WIDGETSET; - } - - return widgetset; - } - - @Override - public String getConfiguredTheme(WrappedRequest request) { - - // is the default theme defined by the portal? - String themeName = WrappedPortletRequest.cast(request) - .getPortalProperty( - Constants.PORTAL_PARAMETER_VAADIN_THEME); - - if (themeName == null) { - // no, using the default theme defined by Vaadin - themeName = DEFAULT_THEME_NAME; - } - - return themeName; - } - - @Override - public boolean isStandalone(WrappedRequest request) { - return false; - } - - /* - * (non-Javadoc) - * - * @see - * com.vaadin.terminal.DeploymentConfiguration#getStaticFileLocation - * (com.vaadin.terminal.WrappedRequest) - * - * Return the URL from where static files, e.g. the widgetset and - * the theme, are served. In a standard configuration the VAADIN - * folder inside the returned folder is what is used for widgetsets - * and themes. - * - * @return The location of static resources (inside which there - * should be a VAADIN directory). Does not end with a slash (/). - */ - - @Override - public String getStaticFileLocation(WrappedRequest request) { - String staticFileLocation = WrappedPortletRequest - .cast(request) - .getPortalProperty( - Constants.PORTAL_PARAMETER_VAADIN_RESOURCE_PATH); - if (staticFileLocation != null) { - // remove trailing slash if any - while (staticFileLocation.endsWith(".")) { - staticFileLocation = staticFileLocation.substring(0, - staticFileLocation.length() - 1); - } - return staticFileLocation; - } else { - // default for Liferay - return "/html"; - } - } - - @Override - public String getMimeType(String resourceName) { - return getPortletContext().getMimeType(resourceName); - } - - @Override - public SystemMessages getSystemMessages() { - return AbstractApplicationPortlet.this.getSystemMessages(); - } - }; - - addonContext = new AddonContext(deploymentConfiguration); - addonContext.init(); - } - - @Override - public void destroy() { - super.destroy(); - - addonContext.destroy(); - } - - protected enum RequestType { - FILE_UPLOAD, UIDL, RENDER, STATIC_FILE, APPLICATION_RESOURCE, DUMMY, EVENT, ACTION, UNKNOWN, BROWSER_DETAILS, CONNECTOR_RESOURCE, HEARTBEAT; - } - - protected RequestType getRequestType(WrappedPortletRequest wrappedRequest) { - PortletRequest request = wrappedRequest.getPortletRequest(); - if (request instanceof RenderRequest) { - return RequestType.RENDER; - } else if (request instanceof ResourceRequest) { - ResourceRequest resourceRequest = (ResourceRequest) request; - if (ServletPortletHelper.isUIDLRequest(wrappedRequest)) { - return RequestType.UIDL; - } else if (isBrowserDetailsRequest(resourceRequest)) { - return RequestType.BROWSER_DETAILS; - } else if (ServletPortletHelper.isFileUploadRequest(wrappedRequest)) { - return RequestType.FILE_UPLOAD; - } else if (ServletPortletHelper - .isConnectorResourceRequest(wrappedRequest)) { - return RequestType.CONNECTOR_RESOURCE; - } else if (ServletPortletHelper - .isApplicationResourceRequest(wrappedRequest)) { - return RequestType.APPLICATION_RESOURCE; - } else if (ServletPortletHelper.isHeartbeatRequest(wrappedRequest)) { - return RequestType.HEARTBEAT; - } else if (isDummyRequest(resourceRequest)) { - return RequestType.DUMMY; - } else { - return RequestType.STATIC_FILE; - } - } else if (request instanceof ActionRequest) { - return RequestType.ACTION; - } else if (request instanceof EventRequest) { - return RequestType.EVENT; - } - return RequestType.UNKNOWN; - } - - private boolean isBrowserDetailsRequest(ResourceRequest request) { - return request.getResourceID() != null - && request.getResourceID().equals("browserDetails"); - } - - private boolean isDummyRequest(ResourceRequest request) { - return request.getResourceID() != null - && request.getResourceID().equals("DUMMY"); - } - - /** - * Returns true if the portlet is running in production mode. Production - * mode disables all debug facilities. - * - * @return true if in production mode, false if in debug mode - */ - public boolean isProductionMode() { - return deploymentConfiguration.isProductionMode(); - } - - protected void handleRequest(PortletRequest request, - PortletResponse response) throws PortletException, IOException { - RequestTimer requestTimer = new RequestTimer(); - requestTimer.start(); - - AbstractApplicationPortletWrapper portletWrapper = new AbstractApplicationPortletWrapper( - this); - - WrappedPortletRequest wrappedRequest = createWrappedRequest(request); - - WrappedPortletResponse wrappedResponse = new WrappedPortletResponse( - response, getDeploymentConfiguration()); - - RequestType requestType = getRequestType(wrappedRequest); - - if (requestType == RequestType.UNKNOWN) { - handleUnknownRequest(request, response); - } else if (requestType == RequestType.DUMMY) { - /* - * This dummy page is used by action responses to redirect to, in - * order to prevent the boot strap code from being rendered into - * strange places such as iframes. - */ - ((ResourceResponse) response).setContentType("text/html"); - final OutputStream out = ((ResourceResponse) response) - .getPortletOutputStream(); - final PrintWriter outWriter = new PrintWriter(new BufferedWriter( - new OutputStreamWriter(out, "UTF-8"))); - outWriter.print("dummy page"); - outWriter.close(); - } else if (requestType == RequestType.STATIC_FILE) { - serveStaticResources((ResourceRequest) request, - (ResourceResponse) response); - } else { - Application application = null; - boolean transactionStarted = false; - boolean requestStarted = false; - boolean applicationRunning = false; - - try { - // TODO What about PARAM_UNLOADBURST & redirectToApplication?? - - /* Find out which application this request is related to */ - application = findApplicationInstance(wrappedRequest, - requestType); - if (application == null) { - return; - } - Application.setCurrent(application); - - /* - * Get or create an application context and an application - * manager for the session - */ - PortletApplicationContext2 applicationContext = getApplicationContext(request - .getPortletSession()); - applicationContext.setResponse(response); - applicationContext.setPortletConfig(getPortletConfig()); - - PortletCommunicationManager applicationManager = applicationContext - .getApplicationManager(application); - - if (requestType == RequestType.CONNECTOR_RESOURCE) { - applicationManager.serveConnectorResource(wrappedRequest, - wrappedResponse); - return; - } else if (requestType == RequestType.HEARTBEAT) { - applicationManager.handleHeartbeatRequest(wrappedRequest, - wrappedResponse, application); - return; - } - - /* Update browser information from request */ - applicationContext.getBrowser().updateRequestDetails( - wrappedRequest); - - /* - * Call application requestStart before Application.init() is - * called (bypasses the limitation in TransactionListener) - */ - if (application instanceof PortletRequestListener) { - ((PortletRequestListener) application).onRequestStart( - request, response); - requestStarted = true; - } - - /* Start the newly created application */ - startApplication(request, application, applicationContext); - applicationRunning = true; - - /* - * Transaction starts. Call transaction listeners. Transaction - * end is called in the finally block below. - */ - applicationContext.startTransaction(application, request); - transactionStarted = true; - - /* Notify listeners */ - - // Finds the window within the application - UI uI = null; - synchronized (application) { - if (application.isRunning()) { - switch (requestType) { - case RENDER: - case ACTION: - // Both action requests and render requests are ok - // without a UI as they render the initial HTML - // and then do a second request - uI = application.getUIForRequest(wrappedRequest); - break; - case BROWSER_DETAILS: - // Should not try to find a UI here as the - // combined request details might change the UI - break; - case FILE_UPLOAD: - // no window - break; - case APPLICATION_RESOURCE: - // use main window - should not need any window - // UI = application.getUI(); - break; - default: - uI = application.getUIForRequest(wrappedRequest); - } - // if window not found, not a problem - use null - } - } - - // TODO Should this happen before or after the transaction - // starts? - if (request instanceof RenderRequest) { - applicationContext.firePortletRenderRequest(application, - uI, (RenderRequest) request, - (RenderResponse) response); - } else if (request instanceof ActionRequest) { - applicationContext.firePortletActionRequest(application, - uI, (ActionRequest) request, - (ActionResponse) response); - } else if (request instanceof EventRequest) { - applicationContext.firePortletEventRequest(application, uI, - (EventRequest) request, (EventResponse) response); - } else if (request instanceof ResourceRequest) { - applicationContext.firePortletResourceRequest(application, - uI, (ResourceRequest) request, - (ResourceResponse) response); - } - - /* Handle the request */ - if (requestType == RequestType.FILE_UPLOAD) { - // UI is resolved in handleFileUpload by - // PortletCommunicationManager - applicationManager.handleFileUpload(application, - wrappedRequest, wrappedResponse); - return; - } else if (requestType == RequestType.BROWSER_DETAILS) { - applicationManager.handleBrowserDetailsRequest( - wrappedRequest, wrappedResponse, application); - return; - } else if (requestType == RequestType.UIDL) { - // Handles AJAX UIDL requests - applicationManager.handleUidlRequest(wrappedRequest, - wrappedResponse, portletWrapper, uI); - return; - } else { - /* - * Removes the application if it has stopped - */ - if (!application.isRunning()) { - endApplication(request, response, application); - return; - } - - handleOtherRequest(wrappedRequest, wrappedResponse, - requestType, application, applicationContext, - applicationManager); - } - } catch (final SessionExpiredException e) { - // TODO Figure out a better way to deal with - // SessionExpiredExceptions - getLogger().finest("A user session has expired"); - } catch (final GeneralSecurityException e) { - // TODO Figure out a better way to deal with - // GeneralSecurityExceptions - getLogger() - .fine("General security exception, the security key was probably incorrect."); - } catch (final Throwable e) { - handleServiceException(wrappedRequest, wrappedResponse, - application, e); - } finally { - - if (applicationRunning) { - application.closeInactiveUIs(); - } - - // Notifies transaction end - try { - if (transactionStarted) { - ((PortletApplicationContext2) application.getContext()) - .endTransaction(application, request); - } - } finally { - try { - if (requestStarted) { - ((PortletRequestListener) application) - .onRequestEnd(request, response); - - } - } finally { - UI.setCurrent(null); - Application.setCurrent(null); - - PortletSession session = request - .getPortletSession(false); - if (session != null) { - requestTimer.stop(getApplicationContext(session)); - } - } - } - } - } - } - - /** - * Wraps the request in a (possibly portal specific) wrapped portlet - * request. - * - * @param request - * The original PortletRequest - * @return A wrapped version of the PorletRequest - */ - protected WrappedPortletRequest createWrappedRequest(PortletRequest request) { - String portalInfo = request.getPortalContext().getPortalInfo() - .toLowerCase(); - if (portalInfo.contains("liferay")) { - return new WrappedLiferayRequest(request, - getDeploymentConfiguration()); - } else if (portalInfo.contains("gatein")) { - return new WrappedGateinRequest(request, - getDeploymentConfiguration()); - } else { - return new WrappedPortletRequest(request, - getDeploymentConfiguration()); - } - - } - - protected DeploymentConfiguration getDeploymentConfiguration() { - return deploymentConfiguration; - } - - private void handleUnknownRequest(PortletRequest request, - PortletResponse response) { - getLogger().warning("Unknown request type"); - } - - /** - * Handle a portlet request that is not for static files, UIDL or upload. - * Also render requests are handled here. - * - * This method is called after starting the application and calling portlet - * and transaction listeners. - * - * @param request - * @param response - * @param requestType - * @param application - * @param applicationContext - * @param applicationManager - * @throws PortletException - * @throws IOException - * @throws MalformedURLException - */ - private void handleOtherRequest(WrappedPortletRequest request, - WrappedResponse response, RequestType requestType, - Application application, - PortletApplicationContext2 applicationContext, - PortletCommunicationManager applicationManager) - throws PortletException, IOException, MalformedURLException { - if (requestType == RequestType.APPLICATION_RESOURCE - || requestType == RequestType.RENDER) { - if (!applicationManager.handleApplicationRequest(request, response)) { - response.sendError(HttpServletResponse.SC_NOT_FOUND, - "Not found"); - } - } else if (requestType == RequestType.EVENT) { - // nothing to do, listeners do all the work - } else if (requestType == RequestType.ACTION) { - // nothing to do, listeners do all the work - } else { - throw new IllegalStateException( - "handleRequest() without anything to do - should never happen!"); - } - } - - @Override - public void processEvent(EventRequest request, EventResponse response) - throws PortletException, IOException { - handleRequest(request, response); - } - - private void serveStaticResources(ResourceRequest request, - ResourceResponse response) throws IOException, PortletException { - final String resourceID = request.getResourceID(); - final PortletContext pc = getPortletContext(); - - InputStream is = pc.getResourceAsStream(resourceID); - if (is != null) { - final String mimetype = pc.getMimeType(resourceID); - if (mimetype != null) { - response.setContentType(mimetype); - } - final OutputStream os = response.getPortletOutputStream(); - final byte buffer[] = new byte[DEFAULT_BUFFER_SIZE]; - int bytes; - while ((bytes = is.read(buffer)) >= 0) { - os.write(buffer, 0, bytes); - } - } else { - getLogger().info( - "Requested resource [" + resourceID - + "] could not be found"); - response.setProperty(ResourceResponse.HTTP_STATUS_CODE, - Integer.toString(HttpServletResponse.SC_NOT_FOUND)); - } - } - - @Override - public void processAction(ActionRequest request, ActionResponse response) - throws PortletException, IOException { - handleRequest(request, response); - } - - @Override - protected void doDispatch(RenderRequest request, RenderResponse response) - throws PortletException, IOException { - try { - // try to let super handle - it'll call methods annotated for - // handling, the default doXYZ(), or throw if a handler for the mode - // is not found - super.doDispatch(request, response); - - } catch (PortletException e) { - if (e.getCause() == null) { - // No cause interpreted as 'unknown mode' - pass that trough - // so that the application can handle - handleRequest(request, response); - - } else { - // Something else failed, pass on - throw e; - } - } - } - - @Override - public void serveResource(ResourceRequest request, ResourceResponse response) - throws PortletException, IOException { - handleRequest(request, response); - } - - boolean requestCanCreateApplication(PortletRequest request, - RequestType requestType) { - if (requestType == RequestType.UIDL && isRepaintAll(request)) { - return true; - } else if (requestType == RequestType.RENDER) { - // In most cases the first request is a render request that renders - // the HTML fragment. This should create an application instance. - return true; - } else if (requestType == RequestType.EVENT) { - // A portlet can also be sent an event even though it has not been - // rendered, e.g. portlet on one page sends an event to a portlet on - // another page and then moves the user to that page. - return true; - } - return false; - } - - private boolean isRepaintAll(PortletRequest request) { - return (request.getParameter(URL_PARAMETER_REPAINT_ALL) != null) - && (request.getParameter(URL_PARAMETER_REPAINT_ALL).equals("1")); - } - - private void startApplication(PortletRequest request, - Application application, PortletApplicationContext2 context) - throws PortletException, MalformedURLException { - if (!application.isRunning()) { - Locale locale = request.getLocale(); - application.setLocale(locale); - // No application URL when running inside a portlet - application.start(new ApplicationStartEvent(null, - getDeploymentConfiguration(), context)); - addonContext.fireApplicationStarted(application); - } - } - - private void endApplication(PortletRequest request, - PortletResponse response, Application application) - throws IOException { - final PortletSession session = request.getPortletSession(); - if (session != null) { - getApplicationContext(session).removeApplication(application); - } - // Do not send any redirects when running inside a portlet. - } - - private Application findApplicationInstance( - WrappedPortletRequest wrappedRequest, RequestType requestType) - throws PortletException, SessionExpiredException, - MalformedURLException { - PortletRequest request = wrappedRequest.getPortletRequest(); - - boolean requestCanCreateApplication = requestCanCreateApplication( - request, requestType); - - /* Find an existing application for this request. */ - Application application = getExistingApplication(request, - requestCanCreateApplication); - - if (application != null) { - /* - * There is an existing application. We can use this as long as the - * user not specifically requested to close or restart it. - */ - - final boolean restartApplication = (wrappedRequest - .getParameter(URL_PARAMETER_RESTART_APPLICATION) != null); - final boolean closeApplication = (wrappedRequest - .getParameter(URL_PARAMETER_CLOSE_APPLICATION) != null); - - if (restartApplication) { - closeApplication(application, request.getPortletSession(false)); - return createApplication(request); - } else if (closeApplication) { - closeApplication(application, request.getPortletSession(false)); - return null; - } else { - return application; - } - } - - // No existing application was found - - if (requestCanCreateApplication) { - return createApplication(request); - } else { - throw new SessionExpiredException(); - } - } - - private void closeApplication(Application application, - PortletSession session) { - if (application == null) { - return; - } - - application.close(); - if (session != null) { - PortletApplicationContext2 context = getApplicationContext(session); - context.removeApplication(application); - } - } - - private Application createApplication(PortletRequest request) - throws PortletException, MalformedURLException { - Application newApplication = getNewApplication(request); - final PortletApplicationContext2 context = getApplicationContext(request - .getPortletSession()); - context.addApplication(newApplication, request.getWindowID()); - return newApplication; - } - - private Application getExistingApplication(PortletRequest request, - boolean allowSessionCreation) throws MalformedURLException, - SessionExpiredException { - - final PortletSession session = request - .getPortletSession(allowSessionCreation); - - if (session == null) { - throw new SessionExpiredException(); - } - - PortletApplicationContext2 context = getApplicationContext(session); - Application application = context.getApplicationForWindowId(request - .getWindowID()); - if (application == null) { - return null; - } - if (application.isRunning()) { - return application; - } - // application found but not running - context.removeApplication(application); - - return null; - } - - protected abstract Class getApplicationClass() - throws ClassNotFoundException; - - protected Application getNewApplication(PortletRequest request) - throws PortletException { - try { - final Application application = getApplicationClass().newInstance(); - return application; - } catch (final IllegalAccessException e) { - throw new PortletException("getNewApplication failed", e); - } catch (final InstantiationException e) { - throw new PortletException("getNewApplication failed", e); - } catch (final ClassNotFoundException e) { - throw new PortletException("getNewApplication failed", e); - } - } - - /** - * Get system messages from the current application class - * - * @return - */ - protected SystemMessages getSystemMessages() { - return ServletPortletHelper.DEFAULT_SYSTEM_MESSAGES; - } - - private void handleServiceException(WrappedPortletRequest request, - WrappedPortletResponse response, Application application, - Throwable e) throws IOException, PortletException { - // TODO Check that this error handler is working when running inside a - // portlet - - // if this was an UIDL request, response UIDL back to client - if (getRequestType(request) == RequestType.UIDL) { - SystemMessages ci = getSystemMessages(); - criticalNotification(request, response, - ci.getInternalErrorCaption(), ci.getInternalErrorMessage(), - null, ci.getInternalErrorURL()); - if (application != null) { - application.getErrorHandler() - .terminalError(new RequestError(e)); - } else { - throw new PortletException(e); - } - } else { - // Re-throw other exceptions - throw new PortletException(e); - } - - } - - @SuppressWarnings("serial") - public class RequestError implements Terminal.ErrorEvent, Serializable { - - private final Throwable throwable; - - public RequestError(Throwable throwable) { - this.throwable = throwable; - } - - @Override - public Throwable getThrowable() { - return throwable; - } - - } - - /** - * Send notification to client's application. Used to notify client of - * critical errors and session expiration due to long inactivity. Server has - * no knowledge of what application client refers to. - * - * @param request - * the Portlet request instance. - * @param response - * the Portlet response to write to. - * @param caption - * for the notification - * @param message - * for the notification - * @param details - * a detail message to show in addition to the passed message. - * Currently shown directly but could be hidden behind a details - * drop down. - * @param url - * url to load after message, null for current page - * @throws IOException - * if the writing failed due to input/output error. - */ - void criticalNotification(WrappedPortletRequest request, - WrappedPortletResponse response, String caption, String message, - String details, String url) throws IOException { - - // clients JS app is still running, but server application either - // no longer exists or it might fail to perform reasonably. - // send a notification to client's application and link how - // to "restart" application. - - if (caption != null) { - caption = "\"" + caption + "\""; - } - if (details != null) { - if (message == null) { - message = details; - } else { - message += "

        " + details; - } - } - if (message != null) { - message = "\"" + message + "\""; - } - if (url != null) { - url = "\"" + url + "\""; - } - - // Set the response type - response.setContentType("application/json; charset=UTF-8"); - final OutputStream out = response.getOutputStream(); - final PrintWriter outWriter = new PrintWriter(new BufferedWriter( - new OutputStreamWriter(out, "UTF-8"))); - outWriter.print("for(;;);[{\"changes\":[], \"meta\" : {" - + "\"appError\": {" + "\"caption\":" + caption + "," - + "\"message\" : " + message + "," + "\"url\" : " + url - + "}}, \"resources\": {}, \"locales\":[]}]"); - outWriter.close(); - } - - /** - * - * Gets the application context for a PortletSession. If no context is - * currently stored in a session a new context is created and stored in the - * session. - * - * @param portletSession - * the portlet session. - * @return the application context for the session. - */ - protected PortletApplicationContext2 getApplicationContext( - PortletSession portletSession) { - return PortletApplicationContext2.getApplicationContext(portletSession); - } - - private static final Logger getLogger() { - return Logger.getLogger(AbstractApplicationPortlet.class.getName()); - } - -} diff --git a/server/src/com/vaadin/server/ApplicationPortlet2.java b/server/src/com/vaadin/server/ApplicationPortlet2.java deleted file mode 100644 index 3708d8fe0d..0000000000 --- a/server/src/com/vaadin/server/ApplicationPortlet2.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2011 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.server; - -import javax.portlet.PortletConfig; -import javax.portlet.PortletException; - -import com.vaadin.Application; -import com.vaadin.server.ServletPortletHelper.ApplicationClassException; - -/** - * TODO Write documentation, fix JavaDoc tags. - * - * @author peholmst - */ -public class ApplicationPortlet2 extends AbstractApplicationPortlet { - - private Class applicationClass; - - @Override - public void init(PortletConfig config) throws PortletException { - super.init(config); - try { - applicationClass = ServletPortletHelper - .getApplicationClass(getDeploymentConfiguration()); - } catch (ApplicationClassException e) { - throw new PortletException(e); - } - } - - @Override - protected Class getApplicationClass() { - return applicationClass; - } - -} diff --git a/server/src/com/vaadin/server/PortletApplicationContext2.java b/server/src/com/vaadin/server/PortletApplicationContext2.java index 366c5b160d..cea97bc939 100644 --- a/server/src/com/vaadin/server/PortletApplicationContext2.java +++ b/server/src/com/vaadin/server/PortletApplicationContext2.java @@ -254,7 +254,7 @@ public class PortletApplicationContext2 extends ApplicationContext { } /** - * This is for use by {@link AbstractApplicationPortlet} only. + * This is for use by {@link VaadinPortlet} only. * * TODO cleaner implementation, now "semi-static"! * diff --git a/server/src/com/vaadin/server/PortletCommunicationManager.java b/server/src/com/vaadin/server/PortletCommunicationManager.java index 697095d691..cb3a8eab80 100644 --- a/server/src/com/vaadin/server/PortletCommunicationManager.java +++ b/server/src/com/vaadin/server/PortletCommunicationManager.java @@ -97,8 +97,7 @@ public class PortletCommunicationManager extends AbstractCommunicationManager { ResourceURL portletResourceUrl = getRenderResponse(context) .createResourceURL(); - portletResourceUrl - .setResourceID(AbstractApplicationPortlet.RESOURCE_URL_ID); + portletResourceUrl.setResourceID(VaadinPortlet.RESOURCE_URL_ID); defaults.put(ApplicationConstants.PORTLET_RESOUCE_URL_BASE, portletResourceUrl.toString()); @@ -113,10 +112,9 @@ public class PortletCommunicationManager extends AbstractCommunicationManager { throws JSONException, IOException { // fixed base theme to use - all portal pages with Vaadin // applications will load this exactly once - String portalTheme = WrappedPortletRequest - .cast(context.getRequest()) - .getPortalProperty( - AbstractApplicationPortlet.PORTAL_PARAMETER_VAADIN_THEME); + String portalTheme = WrappedPortletRequest.cast( + context.getRequest()).getPortalProperty( + VaadinPortlet.PORTAL_PARAMETER_VAADIN_THEME); if (portalTheme != null && !portalTheme.equals(context.getThemeName())) { String portalThemeUri = getThemeUri(context, portalTheme); @@ -133,8 +131,7 @@ public class PortletCommunicationManager extends AbstractCommunicationManager { DeploymentConfiguration deploymentConfiguration = context .getRequest().getDeploymentConfiguration(); return deploymentConfiguration.getApplicationOrSystemProperty( - AbstractApplicationPortlet.PORTLET_PARAMETER_STYLE, - null); + VaadinPortlet.PORTLET_PARAMETER_STYLE, null); } @Override @@ -164,8 +161,8 @@ public class PortletCommunicationManager extends AbstractCommunicationManager { PortletContext portletContext = context.getPortletSession() .getPortletContext(); return portletContext.getResourceAsStream("/" - + AbstractApplicationPortlet.THEME_DIRECTORY_PATH + themeName - + "/" + resource); + + VaadinPortlet.THEME_DIRECTORY_PATH + themeName + "/" + + resource); } } diff --git a/server/src/com/vaadin/server/VaadinPortlet.java b/server/src/com/vaadin/server/VaadinPortlet.java new file mode 100644 index 0000000000..25512e9237 --- /dev/null +++ b/server/src/com/vaadin/server/VaadinPortlet.java @@ -0,0 +1,1045 @@ +/* + * Copyright 2011 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.server; + +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.io.Serializable; +import java.lang.reflect.Method; +import java.net.MalformedURLException; +import java.security.GeneralSecurityException; +import java.util.Enumeration; +import java.util.Locale; +import java.util.Map; +import java.util.Properties; +import java.util.logging.Logger; + +import javax.portlet.ActionRequest; +import javax.portlet.ActionResponse; +import javax.portlet.EventRequest; +import javax.portlet.EventResponse; +import javax.portlet.GenericPortlet; +import javax.portlet.PortletConfig; +import javax.portlet.PortletContext; +import javax.portlet.PortletException; +import javax.portlet.PortletRequest; +import javax.portlet.PortletResponse; +import javax.portlet.PortletSession; +import javax.portlet.RenderRequest; +import javax.portlet.RenderResponse; +import javax.portlet.ResourceRequest; +import javax.portlet.ResourceResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; +import javax.servlet.http.HttpServletResponse; + +import com.liferay.portal.kernel.util.PortalClassInvoker; +import com.liferay.portal.kernel.util.PropsUtil; +import com.vaadin.Application; +import com.vaadin.Application.ApplicationStartEvent; +import com.vaadin.server.AbstractCommunicationManager.Callback; +import com.vaadin.server.ServletPortletHelper.ApplicationClassException; +import com.vaadin.ui.UI; + +/** + * Portlet 2.0 base class. This replaces the servlet in servlet/portlet 1.0 + * deployments and handles various portlet requests from the browser. + * + * TODO Document me! + * + * @author peholmst + */ +public class VaadinPortlet extends GenericPortlet implements + Constants { + + public static final String RESOURCE_URL_ID = "APP"; + + public static class WrappedHttpAndPortletRequest extends + WrappedPortletRequest { + + public WrappedHttpAndPortletRequest(PortletRequest request, + HttpServletRequest originalRequest, + DeploymentConfiguration deploymentConfiguration) { + super(request, deploymentConfiguration); + this.originalRequest = originalRequest; + } + + private final HttpServletRequest originalRequest; + + @Override + public String getParameter(String name) { + String parameter = super.getParameter(name); + if (parameter == null) { + parameter = originalRequest.getParameter(name); + } + return parameter; + } + + @Override + public String getRemoteAddr() { + return originalRequest.getRemoteAddr(); + } + + @Override + public String getHeader(String name) { + String header = super.getHeader(name); + if (header == null) { + header = originalRequest.getHeader(name); + } + return header; + } + + @Override + public Map getParameterMap() { + Map parameterMap = super.getParameterMap(); + if (parameterMap == null) { + parameterMap = originalRequest.getParameterMap(); + } + return parameterMap; + } + } + + public static class WrappedGateinRequest extends + WrappedHttpAndPortletRequest { + public WrappedGateinRequest(PortletRequest request, + DeploymentConfiguration deploymentConfiguration) { + super(request, getOriginalRequest(request), deploymentConfiguration); + } + + private static final HttpServletRequest getOriginalRequest( + PortletRequest request) { + try { + Method getRealReq = request.getClass().getMethod( + "getRealRequest"); + HttpServletRequestWrapper origRequest = (HttpServletRequestWrapper) getRealReq + .invoke(request); + return origRequest; + } catch (Exception e) { + throw new IllegalStateException("GateIn request not detected", + e); + } + } + } + + public static class WrappedLiferayRequest extends + WrappedHttpAndPortletRequest { + + public WrappedLiferayRequest(PortletRequest request, + DeploymentConfiguration deploymentConfiguration) { + super(request, getOriginalRequest(request), deploymentConfiguration); + } + + @Override + public String getPortalProperty(String name) { + return PropsUtil.get(name); + } + + private static HttpServletRequest getOriginalRequest( + PortletRequest request) { + try { + // httpRequest = PortalUtil.getHttpServletRequest(request); + HttpServletRequest httpRequest = (HttpServletRequest) PortalClassInvoker + .invoke("com.liferay.portal.util.PortalUtil", + "getHttpServletRequest", request); + + // httpRequest = + // PortalUtil.getOriginalServletRequest(httpRequest); + httpRequest = (HttpServletRequest) PortalClassInvoker.invoke( + "com.liferay.portal.util.PortalUtil", + "getOriginalServletRequest", httpRequest); + return httpRequest; + } catch (Exception e) { + throw new IllegalStateException("Liferay request not detected", + e); + } + } + + } + + public static class AbstractApplicationPortletWrapper implements Callback { + + private final VaadinPortlet portlet; + + public AbstractApplicationPortletWrapper( + VaadinPortlet portlet) { + this.portlet = portlet; + } + + @Override + public void criticalNotification(WrappedRequest request, + WrappedResponse response, String cap, String msg, + String details, String outOfSyncURL) throws IOException { + portlet.criticalNotification(WrappedPortletRequest.cast(request), + (WrappedPortletResponse) response, cap, msg, details, + outOfSyncURL); + } + } + + /** + * This portlet parameter is used to add styles to the main element. E.g + * "height:500px" generates a style="height:500px" to the main element. + */ + public static final String PORTLET_PARAMETER_STYLE = "style"; + + /** + * This portal parameter is used to define the name of the Vaadin theme that + * is used for all Vaadin applications in the portal. + */ + public static final String PORTAL_PARAMETER_VAADIN_THEME = "vaadin.theme"; + + public static final String WRITE_AJAX_PAGE_SCRIPT_WIDGETSET_SHOULD_WRITE = "writeAjaxPageScriptWidgetsetShouldWrite"; + + // TODO some parts could be shared with AbstractApplicationServlet + + // TODO Can we close the application when the portlet is removed? Do we know + // when the portlet is removed? + + private DeploymentConfiguration deploymentConfiguration; + private AddonContext addonContext; + + @Override + public void init(PortletConfig config) throws PortletException { + super.init(config); + Properties applicationProperties = new Properties(); + + // Read default parameters from the context + final PortletContext context = config.getPortletContext(); + for (final Enumeration e = context.getInitParameterNames(); e + .hasMoreElements();) { + final String name = e.nextElement(); + applicationProperties.setProperty(name, + context.getInitParameter(name)); + } + + // Override with application settings from portlet.xml + for (final Enumeration e = config.getInitParameterNames(); e + .hasMoreElements();) { + final String name = e.nextElement(); + applicationProperties.setProperty(name, + config.getInitParameter(name)); + } + + deploymentConfiguration = new AbstractDeploymentConfiguration( + getClass(), applicationProperties) { + @Override + public String getConfiguredWidgetset(WrappedRequest request) { + + String widgetset = getApplicationOrSystemProperty( + PARAMETER_WIDGETSET, null); + + if (widgetset == null) { + // If no widgetset defined for the application, check the + // portal property + widgetset = WrappedPortletRequest.cast(request) + .getPortalProperty( + PORTAL_PARAMETER_VAADIN_WIDGETSET); + } + + if (widgetset == null) { + // If no widgetset defined for the portal, use the default + widgetset = DEFAULT_WIDGETSET; + } + + return widgetset; + } + + @Override + public String getConfiguredTheme(WrappedRequest request) { + + // is the default theme defined by the portal? + String themeName = WrappedPortletRequest.cast(request) + .getPortalProperty( + Constants.PORTAL_PARAMETER_VAADIN_THEME); + + if (themeName == null) { + // no, using the default theme defined by Vaadin + themeName = DEFAULT_THEME_NAME; + } + + return themeName; + } + + @Override + public boolean isStandalone(WrappedRequest request) { + return false; + } + + /* + * (non-Javadoc) + * + * @see + * com.vaadin.terminal.DeploymentConfiguration#getStaticFileLocation + * (com.vaadin.terminal.WrappedRequest) + * + * Return the URL from where static files, e.g. the widgetset and + * the theme, are served. In a standard configuration the VAADIN + * folder inside the returned folder is what is used for widgetsets + * and themes. + * + * @return The location of static resources (inside which there + * should be a VAADIN directory). Does not end with a slash (/). + */ + + @Override + public String getStaticFileLocation(WrappedRequest request) { + String staticFileLocation = WrappedPortletRequest + .cast(request) + .getPortalProperty( + Constants.PORTAL_PARAMETER_VAADIN_RESOURCE_PATH); + if (staticFileLocation != null) { + // remove trailing slash if any + while (staticFileLocation.endsWith(".")) { + staticFileLocation = staticFileLocation.substring(0, + staticFileLocation.length() - 1); + } + return staticFileLocation; + } else { + // default for Liferay + return "/html"; + } + } + + @Override + public String getMimeType(String resourceName) { + return getPortletContext().getMimeType(resourceName); + } + + @Override + public SystemMessages getSystemMessages() { + return VaadinPortlet.this.getSystemMessages(); + } + }; + + addonContext = new AddonContext(deploymentConfiguration); + addonContext.init(); + } + + @Override + public void destroy() { + super.destroy(); + + addonContext.destroy(); + } + + protected enum RequestType { + FILE_UPLOAD, UIDL, RENDER, STATIC_FILE, APPLICATION_RESOURCE, DUMMY, EVENT, ACTION, UNKNOWN, BROWSER_DETAILS, CONNECTOR_RESOURCE, HEARTBEAT; + } + + protected RequestType getRequestType(WrappedPortletRequest wrappedRequest) { + PortletRequest request = wrappedRequest.getPortletRequest(); + if (request instanceof RenderRequest) { + return RequestType.RENDER; + } else if (request instanceof ResourceRequest) { + ResourceRequest resourceRequest = (ResourceRequest) request; + if (ServletPortletHelper.isUIDLRequest(wrappedRequest)) { + return RequestType.UIDL; + } else if (isBrowserDetailsRequest(resourceRequest)) { + return RequestType.BROWSER_DETAILS; + } else if (ServletPortletHelper.isFileUploadRequest(wrappedRequest)) { + return RequestType.FILE_UPLOAD; + } else if (ServletPortletHelper + .isConnectorResourceRequest(wrappedRequest)) { + return RequestType.CONNECTOR_RESOURCE; + } else if (ServletPortletHelper + .isApplicationResourceRequest(wrappedRequest)) { + return RequestType.APPLICATION_RESOURCE; + } else if (ServletPortletHelper.isHeartbeatRequest(wrappedRequest)) { + return RequestType.HEARTBEAT; + } else if (isDummyRequest(resourceRequest)) { + return RequestType.DUMMY; + } else { + return RequestType.STATIC_FILE; + } + } else if (request instanceof ActionRequest) { + return RequestType.ACTION; + } else if (request instanceof EventRequest) { + return RequestType.EVENT; + } + return RequestType.UNKNOWN; + } + + private boolean isBrowserDetailsRequest(ResourceRequest request) { + return request.getResourceID() != null + && request.getResourceID().equals("browserDetails"); + } + + private boolean isDummyRequest(ResourceRequest request) { + return request.getResourceID() != null + && request.getResourceID().equals("DUMMY"); + } + + /** + * Returns true if the portlet is running in production mode. Production + * mode disables all debug facilities. + * + * @return true if in production mode, false if in debug mode + */ + public boolean isProductionMode() { + return deploymentConfiguration.isProductionMode(); + } + + protected void handleRequest(PortletRequest request, + PortletResponse response) throws PortletException, IOException { + RequestTimer requestTimer = new RequestTimer(); + requestTimer.start(); + + AbstractApplicationPortletWrapper portletWrapper = new AbstractApplicationPortletWrapper( + this); + + WrappedPortletRequest wrappedRequest = createWrappedRequest(request); + + WrappedPortletResponse wrappedResponse = new WrappedPortletResponse( + response, getDeploymentConfiguration()); + + RequestType requestType = getRequestType(wrappedRequest); + + if (requestType == RequestType.UNKNOWN) { + handleUnknownRequest(request, response); + } else if (requestType == RequestType.DUMMY) { + /* + * This dummy page is used by action responses to redirect to, in + * order to prevent the boot strap code from being rendered into + * strange places such as iframes. + */ + ((ResourceResponse) response).setContentType("text/html"); + final OutputStream out = ((ResourceResponse) response) + .getPortletOutputStream(); + final PrintWriter outWriter = new PrintWriter(new BufferedWriter( + new OutputStreamWriter(out, "UTF-8"))); + outWriter.print("dummy page"); + outWriter.close(); + } else if (requestType == RequestType.STATIC_FILE) { + serveStaticResources((ResourceRequest) request, + (ResourceResponse) response); + } else { + Application application = null; + boolean transactionStarted = false; + boolean requestStarted = false; + boolean applicationRunning = false; + + try { + // TODO What about PARAM_UNLOADBURST & redirectToApplication?? + + /* Find out which application this request is related to */ + application = findApplicationInstance(wrappedRequest, + requestType); + if (application == null) { + return; + } + Application.setCurrent(application); + + /* + * Get or create an application context and an application + * manager for the session + */ + PortletApplicationContext2 applicationContext = getApplicationContext(request + .getPortletSession()); + applicationContext.setResponse(response); + applicationContext.setPortletConfig(getPortletConfig()); + + PortletCommunicationManager applicationManager = applicationContext + .getApplicationManager(application); + + if (requestType == RequestType.CONNECTOR_RESOURCE) { + applicationManager.serveConnectorResource(wrappedRequest, + wrappedResponse); + return; + } else if (requestType == RequestType.HEARTBEAT) { + applicationManager.handleHeartbeatRequest(wrappedRequest, + wrappedResponse, application); + return; + } + + /* Update browser information from request */ + applicationContext.getBrowser().updateRequestDetails( + wrappedRequest); + + /* + * Call application requestStart before Application.init() is + * called (bypasses the limitation in TransactionListener) + */ + if (application instanceof PortletRequestListener) { + ((PortletRequestListener) application).onRequestStart( + request, response); + requestStarted = true; + } + + /* Start the newly created application */ + startApplication(request, application, applicationContext); + applicationRunning = true; + + /* + * Transaction starts. Call transaction listeners. Transaction + * end is called in the finally block below. + */ + applicationContext.startTransaction(application, request); + transactionStarted = true; + + /* Notify listeners */ + + // Finds the window within the application + UI uI = null; + synchronized (application) { + if (application.isRunning()) { + switch (requestType) { + case RENDER: + case ACTION: + // Both action requests and render requests are ok + // without a UI as they render the initial HTML + // and then do a second request + uI = application.getUIForRequest(wrappedRequest); + break; + case BROWSER_DETAILS: + // Should not try to find a UI here as the + // combined request details might change the UI + break; + case FILE_UPLOAD: + // no window + break; + case APPLICATION_RESOURCE: + // use main window - should not need any window + // UI = application.getUI(); + break; + default: + uI = application.getUIForRequest(wrappedRequest); + } + // if window not found, not a problem - use null + } + } + + // TODO Should this happen before or after the transaction + // starts? + if (request instanceof RenderRequest) { + applicationContext.firePortletRenderRequest(application, + uI, (RenderRequest) request, + (RenderResponse) response); + } else if (request instanceof ActionRequest) { + applicationContext.firePortletActionRequest(application, + uI, (ActionRequest) request, + (ActionResponse) response); + } else if (request instanceof EventRequest) { + applicationContext.firePortletEventRequest(application, uI, + (EventRequest) request, (EventResponse) response); + } else if (request instanceof ResourceRequest) { + applicationContext.firePortletResourceRequest(application, + uI, (ResourceRequest) request, + (ResourceResponse) response); + } + + /* Handle the request */ + if (requestType == RequestType.FILE_UPLOAD) { + // UI is resolved in handleFileUpload by + // PortletCommunicationManager + applicationManager.handleFileUpload(application, + wrappedRequest, wrappedResponse); + return; + } else if (requestType == RequestType.BROWSER_DETAILS) { + applicationManager.handleBrowserDetailsRequest( + wrappedRequest, wrappedResponse, application); + return; + } else if (requestType == RequestType.UIDL) { + // Handles AJAX UIDL requests + applicationManager.handleUidlRequest(wrappedRequest, + wrappedResponse, portletWrapper, uI); + return; + } else { + /* + * Removes the application if it has stopped + */ + if (!application.isRunning()) { + endApplication(request, response, application); + return; + } + + handleOtherRequest(wrappedRequest, wrappedResponse, + requestType, application, applicationContext, + applicationManager); + } + } catch (final SessionExpiredException e) { + // TODO Figure out a better way to deal with + // SessionExpiredExceptions + getLogger().finest("A user session has expired"); + } catch (final GeneralSecurityException e) { + // TODO Figure out a better way to deal with + // GeneralSecurityExceptions + getLogger() + .fine("General security exception, the security key was probably incorrect."); + } catch (final Throwable e) { + handleServiceException(wrappedRequest, wrappedResponse, + application, e); + } finally { + + if (applicationRunning) { + application.closeInactiveUIs(); + } + + // Notifies transaction end + try { + if (transactionStarted) { + ((PortletApplicationContext2) application.getContext()) + .endTransaction(application, request); + } + } finally { + try { + if (requestStarted) { + ((PortletRequestListener) application) + .onRequestEnd(request, response); + + } + } finally { + UI.setCurrent(null); + Application.setCurrent(null); + + PortletSession session = request + .getPortletSession(false); + if (session != null) { + requestTimer.stop(getApplicationContext(session)); + } + } + } + } + } + } + + /** + * Wraps the request in a (possibly portal specific) wrapped portlet + * request. + * + * @param request + * The original PortletRequest + * @return A wrapped version of the PorletRequest + */ + protected WrappedPortletRequest createWrappedRequest(PortletRequest request) { + String portalInfo = request.getPortalContext().getPortalInfo() + .toLowerCase(); + if (portalInfo.contains("liferay")) { + return new WrappedLiferayRequest(request, + getDeploymentConfiguration()); + } else if (portalInfo.contains("gatein")) { + return new WrappedGateinRequest(request, + getDeploymentConfiguration()); + } else { + return new WrappedPortletRequest(request, + getDeploymentConfiguration()); + } + + } + + protected DeploymentConfiguration getDeploymentConfiguration() { + return deploymentConfiguration; + } + + private void handleUnknownRequest(PortletRequest request, + PortletResponse response) { + getLogger().warning("Unknown request type"); + } + + /** + * Handle a portlet request that is not for static files, UIDL or upload. + * Also render requests are handled here. + * + * This method is called after starting the application and calling portlet + * and transaction listeners. + * + * @param request + * @param response + * @param requestType + * @param application + * @param applicationContext + * @param applicationManager + * @throws PortletException + * @throws IOException + * @throws MalformedURLException + */ + private void handleOtherRequest(WrappedPortletRequest request, + WrappedResponse response, RequestType requestType, + Application application, + PortletApplicationContext2 applicationContext, + PortletCommunicationManager applicationManager) + throws PortletException, IOException, MalformedURLException { + if (requestType == RequestType.APPLICATION_RESOURCE + || requestType == RequestType.RENDER) { + if (!applicationManager.handleApplicationRequest(request, response)) { + response.sendError(HttpServletResponse.SC_NOT_FOUND, + "Not found"); + } + } else if (requestType == RequestType.EVENT) { + // nothing to do, listeners do all the work + } else if (requestType == RequestType.ACTION) { + // nothing to do, listeners do all the work + } else { + throw new IllegalStateException( + "handleRequest() without anything to do - should never happen!"); + } + } + + @Override + public void processEvent(EventRequest request, EventResponse response) + throws PortletException, IOException { + handleRequest(request, response); + } + + private void serveStaticResources(ResourceRequest request, + ResourceResponse response) throws IOException, PortletException { + final String resourceID = request.getResourceID(); + final PortletContext pc = getPortletContext(); + + InputStream is = pc.getResourceAsStream(resourceID); + if (is != null) { + final String mimetype = pc.getMimeType(resourceID); + if (mimetype != null) { + response.setContentType(mimetype); + } + final OutputStream os = response.getPortletOutputStream(); + final byte buffer[] = new byte[DEFAULT_BUFFER_SIZE]; + int bytes; + while ((bytes = is.read(buffer)) >= 0) { + os.write(buffer, 0, bytes); + } + } else { + getLogger().info( + "Requested resource [" + resourceID + + "] could not be found"); + response.setProperty(ResourceResponse.HTTP_STATUS_CODE, + Integer.toString(HttpServletResponse.SC_NOT_FOUND)); + } + } + + @Override + public void processAction(ActionRequest request, ActionResponse response) + throws PortletException, IOException { + handleRequest(request, response); + } + + @Override + protected void doDispatch(RenderRequest request, RenderResponse response) + throws PortletException, IOException { + try { + // try to let super handle - it'll call methods annotated for + // handling, the default doXYZ(), or throw if a handler for the mode + // is not found + super.doDispatch(request, response); + + } catch (PortletException e) { + if (e.getCause() == null) { + // No cause interpreted as 'unknown mode' - pass that trough + // so that the application can handle + handleRequest(request, response); + + } else { + // Something else failed, pass on + throw e; + } + } + } + + @Override + public void serveResource(ResourceRequest request, ResourceResponse response) + throws PortletException, IOException { + handleRequest(request, response); + } + + boolean requestCanCreateApplication(PortletRequest request, + RequestType requestType) { + if (requestType == RequestType.UIDL && isRepaintAll(request)) { + return true; + } else if (requestType == RequestType.RENDER) { + // In most cases the first request is a render request that renders + // the HTML fragment. This should create an application instance. + return true; + } else if (requestType == RequestType.EVENT) { + // A portlet can also be sent an event even though it has not been + // rendered, e.g. portlet on one page sends an event to a portlet on + // another page and then moves the user to that page. + return true; + } + return false; + } + + private boolean isRepaintAll(PortletRequest request) { + return (request.getParameter(URL_PARAMETER_REPAINT_ALL) != null) + && (request.getParameter(URL_PARAMETER_REPAINT_ALL).equals("1")); + } + + private void startApplication(PortletRequest request, + Application application, PortletApplicationContext2 context) + throws PortletException, MalformedURLException { + if (!application.isRunning()) { + Locale locale = request.getLocale(); + application.setLocale(locale); + // No application URL when running inside a portlet + application.start(new ApplicationStartEvent(null, + getDeploymentConfiguration(), context)); + addonContext.fireApplicationStarted(application); + } + } + + private void endApplication(PortletRequest request, + PortletResponse response, Application application) + throws IOException { + final PortletSession session = request.getPortletSession(); + if (session != null) { + getApplicationContext(session).removeApplication(application); + } + // Do not send any redirects when running inside a portlet. + } + + private Application findApplicationInstance( + WrappedPortletRequest wrappedRequest, RequestType requestType) + throws PortletException, SessionExpiredException, + MalformedURLException { + PortletRequest request = wrappedRequest.getPortletRequest(); + + boolean requestCanCreateApplication = requestCanCreateApplication( + request, requestType); + + /* Find an existing application for this request. */ + Application application = getExistingApplication(request, + requestCanCreateApplication); + + if (application != null) { + /* + * There is an existing application. We can use this as long as the + * user not specifically requested to close or restart it. + */ + + final boolean restartApplication = (wrappedRequest + .getParameter(URL_PARAMETER_RESTART_APPLICATION) != null); + final boolean closeApplication = (wrappedRequest + .getParameter(URL_PARAMETER_CLOSE_APPLICATION) != null); + + if (restartApplication) { + closeApplication(application, request.getPortletSession(false)); + return createApplication(request); + } else if (closeApplication) { + closeApplication(application, request.getPortletSession(false)); + return null; + } else { + return application; + } + } + + // No existing application was found + + if (requestCanCreateApplication) { + return createApplication(request); + } else { + throw new SessionExpiredException(); + } + } + + private void closeApplication(Application application, + PortletSession session) { + if (application == null) { + return; + } + + application.close(); + if (session != null) { + PortletApplicationContext2 context = getApplicationContext(session); + context.removeApplication(application); + } + } + + private Application createApplication(PortletRequest request) + throws PortletException, MalformedURLException { + Application newApplication = getNewApplication(request); + final PortletApplicationContext2 context = getApplicationContext(request + .getPortletSession()); + context.addApplication(newApplication, request.getWindowID()); + return newApplication; + } + + private Application getExistingApplication(PortletRequest request, + boolean allowSessionCreation) throws MalformedURLException, + SessionExpiredException { + + final PortletSession session = request + .getPortletSession(allowSessionCreation); + + if (session == null) { + throw new SessionExpiredException(); + } + + PortletApplicationContext2 context = getApplicationContext(session); + Application application = context.getApplicationForWindowId(request + .getWindowID()); + if (application == null) { + return null; + } + if (application.isRunning()) { + return application; + } + // application found but not running + context.removeApplication(application); + + return null; + } + + protected Class getApplicationClass() + throws ApplicationClassException { + return ServletPortletHelper + .getApplicationClass(getDeploymentConfiguration()); + } + + protected Application getNewApplication(PortletRequest request) + throws PortletException { + try { + final Application application = getApplicationClass().newInstance(); + return application; + } catch (final IllegalAccessException e) { + throw new PortletException("getNewApplication failed", e); + } catch (final InstantiationException e) { + throw new PortletException("getNewApplication failed", e); + } catch (final ApplicationClassException e) { + throw new PortletException("getNewApplication failed", e); + } + } + + /** + * Get system messages from the current application class + * + * @return + */ + protected SystemMessages getSystemMessages() { + return ServletPortletHelper.DEFAULT_SYSTEM_MESSAGES; + } + + private void handleServiceException(WrappedPortletRequest request, + WrappedPortletResponse response, Application application, + Throwable e) throws IOException, PortletException { + // TODO Check that this error handler is working when running inside a + // portlet + + // if this was an UIDL request, response UIDL back to client + if (getRequestType(request) == RequestType.UIDL) { + SystemMessages ci = getSystemMessages(); + criticalNotification(request, response, + ci.getInternalErrorCaption(), ci.getInternalErrorMessage(), + null, ci.getInternalErrorURL()); + if (application != null) { + application.getErrorHandler() + .terminalError(new RequestError(e)); + } else { + throw new PortletException(e); + } + } else { + // Re-throw other exceptions + throw new PortletException(e); + } + + } + + @SuppressWarnings("serial") + public class RequestError implements Terminal.ErrorEvent, Serializable { + + private final Throwable throwable; + + public RequestError(Throwable throwable) { + this.throwable = throwable; + } + + @Override + public Throwable getThrowable() { + return throwable; + } + + } + + /** + * Send notification to client's application. Used to notify client of + * critical errors and session expiration due to long inactivity. Server has + * no knowledge of what application client refers to. + * + * @param request + * the Portlet request instance. + * @param response + * the Portlet response to write to. + * @param caption + * for the notification + * @param message + * for the notification + * @param details + * a detail message to show in addition to the passed message. + * Currently shown directly but could be hidden behind a details + * drop down. + * @param url + * url to load after message, null for current page + * @throws IOException + * if the writing failed due to input/output error. + */ + void criticalNotification(WrappedPortletRequest request, + WrappedPortletResponse response, String caption, String message, + String details, String url) throws IOException { + + // clients JS app is still running, but server application either + // no longer exists or it might fail to perform reasonably. + // send a notification to client's application and link how + // to "restart" application. + + if (caption != null) { + caption = "\"" + caption + "\""; + } + if (details != null) { + if (message == null) { + message = details; + } else { + message += "

        " + details; + } + } + if (message != null) { + message = "\"" + message + "\""; + } + if (url != null) { + url = "\"" + url + "\""; + } + + // Set the response type + response.setContentType("application/json; charset=UTF-8"); + final OutputStream out = response.getOutputStream(); + final PrintWriter outWriter = new PrintWriter(new BufferedWriter( + new OutputStreamWriter(out, "UTF-8"))); + outWriter.print("for(;;);[{\"changes\":[], \"meta\" : {" + + "\"appError\": {" + "\"caption\":" + caption + "," + + "\"message\" : " + message + "," + "\"url\" : " + url + + "}}, \"resources\": {}, \"locales\":[]}]"); + outWriter.close(); + } + + /** + * + * Gets the application context for a PortletSession. If no context is + * currently stored in a session a new context is created and stored in the + * session. + * + * @param portletSession + * the portlet session. + * @return the application context for the session. + */ + protected PortletApplicationContext2 getApplicationContext( + PortletSession portletSession) { + return PortletApplicationContext2.getApplicationContext(portletSession); + } + + private static final Logger getLogger() { + return Logger.getLogger(VaadinPortlet.class.getName()); + } + +} diff --git a/server/src/com/vaadin/server/WrappedPortletRequest.java b/server/src/com/vaadin/server/WrappedPortletRequest.java index 40a5c5f509..47a8e2c358 100644 --- a/server/src/com/vaadin/server/WrappedPortletRequest.java +++ b/server/src/com/vaadin/server/WrappedPortletRequest.java @@ -101,7 +101,7 @@ public class WrappedPortletRequest implements WrappedRequest { if (request instanceof ResourceRequest) { ResourceRequest resourceRequest = (ResourceRequest) request; String resourceID = resourceRequest.getResourceID(); - if (AbstractApplicationPortlet.RESOURCE_URL_ID.equals(resourceID)) { + if (VaadinPortlet.RESOURCE_URL_ID.equals(resourceID)) { String resourcePath = resourceRequest .getParameter(ApplicationConstants.V_RESOURCE_PATH); return resourcePath; diff --git a/server/tests/src/com/vaadin/tests/server/TestClassesSerializable.java b/server/tests/src/com/vaadin/tests/server/TestClassesSerializable.java index e98be99cbc..0d3dea28ac 100644 --- a/server/tests/src/com/vaadin/tests/server/TestClassesSerializable.java +++ b/server/tests/src/com/vaadin/tests/server/TestClassesSerializable.java @@ -40,8 +40,7 @@ public class TestClassesSerializable extends TestCase { "com\\.vaadin\\.event\\.FieldEvents", // "com\\.vaadin\\.event\\.LayoutEvents", // "com\\.vaadin\\.event\\.MouseEvents", // - "com\\.vaadin\\.server\\.AbstractApplicationPortlet", // - "com\\.vaadin\\.server\\.ApplicationPortlet2", // + "com\\.vaadin\\.server\\.VaadinPortlet", // "com\\.vaadin\\.server\\.Constants", // "com\\.vaadin\\.util\\.SerializerHelper", // fully static // class level filtering, also affecting nested classes and -- cgit v1.2.3