<scope>provided</scope>
<optional>true</optional>
</dependency>
+
+ <!-- Google App Engine -->
+ <dependency>
+ <groupId>com.google.appengine</groupId>
+ <artifactId>appengine-api-1.0-sdk</artifactId>
+ <scope>provided</scope>
+ </dependency>
</dependencies>
<build>
--- /dev/null
+/*
+ * Copyright 2000-2016 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:
+ *
+ * <pre>
+ * <servlet>
+ * <servlet-name>HelloWorld</servlet-name>
+ * <servlet-class>com.vaadin.server.GAEApplicationServlet</servlet-class>
+ * <init-param>
+ * <param-name>UI</param-name>
+ * <param-value>com.vaadin.demo.HelloWorld</param-value>
+ * </init-param>
+ * </servlet>
+ * </pre>
+ *
+ * Session support must be enabled in appengine-web.xml:
+ *
+ * <pre>
+ * <sessions-enabled>true</sessions-enabled>
+ * </pre>
+ *
+ * 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):
+ *
+ * <pre>
+ * <cronentries>
+ * <cron>
+ * <url>/HelloWorld/CLEAN</url>
+ * <description>Clean up sessions</description>
+ * <schedule>every 2 hours</schedule>
+ * </cron>
+ * </cronentries>
+ * </pre>
+ *
+ * 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:
+ *
+ * <pre>
+ * <static-files>
+ * <include path="/VAADIN/**" />
+ * </static-files>
+ * </pre>
+ *
+ * Additional limitations:
+ * <ul>
+ * <li/>Do not change application state when serving an ApplicationResource.
+ * <li/>Avoid changing application state in transaction handlers, unless you're
+ * confident you fully understand the synchronization issues in App Engine.
+ * <li/>The application remains locked while uploading - no progressbar is
+ * possible.
+ * </ul>
+ *
+ * @deprecated No longer supported with Vaadin 8.0
+ */
+@Deprecated
+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";
+
+ // sessions with undefined (-1) expiration are limited to this, but explicit
+ // longer timeouts can be used
+ private static final int DEFAULT_MAX_INACTIVE_INTERVAL = 24 * 3600;
+
+ protected void sendDeadlineExceededNotification(
+ VaadinServletRequest request, VaadinServletResponse 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(VaadinServletRequest request,
+ VaadinServletResponse 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(VaadinServletRequest request,
+ VaadinServletResponse 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 {
+ VaadinServletRequest request = new VaadinServletRequest(
+ unwrappedRequest, getService());
+ VaadinServletResponse response = new VaadinServletResponse(
+ unwrappedResponse, getService());
+
+ if (isCleanupRequest(request)) {
+ cleanDatastore();
+ return;
+ }
+
+ if (isStaticResourceRequest(request)) {
+ // no locking needed, let superclass handle
+ super.service(request, response);
+ cleanSession(request);
+ return;
+ }
+
+ if (ServletPortletHelper.isAppRequest(request)) {
+ // no locking needed, let superclass handle
+ getApplicationContext(request,
+ MemcacheServiceFactory.getMemcacheService());
+ super.service(request, response);
+ cleanSession(request);
+ return;
+ }
+
+ final HttpSession session = request
+ .getSession(getService().requestCanCreateSession(request));
+ if (session == null) {
+ try {
+ getService().handleSessionExpired(request, response);
+ } catch (ServiceException e) {
+ throw new ServletException(e);
+ }
+ 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 = System.currentTimeMillis();
+ while (System.currentTimeMillis()
+ - started < MAX_UIDL_WAIT_MILLISECONDS) {
+ locked = memcache.put(mutex, 1, Expiration.byDeltaSeconds(40),
+ MemcacheService.SetPolicy.ADD_ONLY_IF_NOT_PRESENT);
+ if (locked || ServletPortletHelper.isUIDLRequest(request)) {
+ /*
+ * Done if we got a lock. Will also avoid retrying if
+ * there's a UIDL request because those are retried from the
+ * client without keeping the server thread stalled.
+ */
+ 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
+ VaadinSession 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 + getMaxInactiveIntervalSeconds(session) * 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().log(Level.WARNING, "DeadlineExceeded for {0}",
+ 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);
+ }
+ }
+
+ /**
+ * Returns the maximum inactive time for a session. This is used for
+ * handling the expiration of session related information in caches etc.
+ *
+ * @param session
+ * @return inactive timeout in seconds, greater than zero
+ */
+ protected int getMaxInactiveIntervalSeconds(final HttpSession session) {
+ int interval = session.getMaxInactiveInterval();
+ if (interval <= 0) {
+ getLogger().log(Level.FINE,
+ "Undefined session expiration time, using default value instead.");
+ return DEFAULT_MAX_INACTIVE_INTERVAL;
+ }
+ return interval;
+ }
+
+ protected VaadinSession getApplicationContext(HttpServletRequest request,
+ MemcacheService memcache) throws ServletException {
+ 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(
+ getMaxInactiveIntervalSeconds(session)),
+ MemcacheService.SetPolicy.ADD_ONLY_IF_NOT_PRESENT);
+ }
+ }
+ if (serializedAC != null) {
+ ByteArrayInputStream bais = new ByteArrayInputStream(serializedAC);
+ ObjectInputStream ois;
+ try {
+ ois = new ObjectInputStream(bais);
+ VaadinSession vaadinSession = (VaadinSession) ois.readObject();
+ getService().storeSession(vaadinSession,
+ new WrappedHttpSession(session));
+ } catch (IOException | 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
+ try {
+ return getService().findVaadinSession(createVaadinRequest(request));
+ } catch (Exception e) {
+ throw new ServletException(e);
+ }
+ }
+
+ private boolean isCleanupRequest(HttpServletRequest request) {
+ String path = request.getPathInfo();
+ 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(VaadinServletRequest request) {
+ // Should really be replaced with a session storage API...
+ WrappedSession wrappedSession = request.getWrappedSession(false);
+ if (wrappedSession == null) {
+ return;
+ }
+ VaadinSession serviceSession = getService().loadSession(wrappedSession);
+ if (serviceSession == null) {
+ return;
+ }
+
+ /*
+ * Inform VaadinSession.valueUnbound that it should not kill the session
+ * even though it gets unbound.
+ */
+ serviceSession.setAttribute(
+ VaadinService.PRESERVE_UNBOUND_SESSION_ATTRIBUTE, Boolean.TRUE);
+ getService().removeSession(serviceSession.getSession());
+
+ // Remove preservation marker
+ serviceSession.setAttribute(
+ VaadinService.PRESERVE_UNBOUND_SESSION_ATTRIBUTE, null);
+ }
+
+ /**
+ * 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<Entity> entities = pq
+ .asList(Builder.withLimit(CLEANUP_LIMIT));
+ if (entities != null) {
+ getLogger().log(Level.INFO,
+ "Vaadin cleanup deleting {0} expired Vaadin sessions.",
+ entities.size());
+ List<Key> 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<Entity> entities = pq
+ .asList(Builder.withLimit(CLEANUP_LIMIT));
+ if (entities != null) {
+ getLogger().log(Level.INFO,
+ "Vaadin cleanup deleting {0} expired appengine sessions.",
+ entities.size());
+ List<Key> 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());
+ }
+}
import java.util.logging.Logger;
import com.vaadin.shared.util.SharedUtil;
-import com.vaadin.util.SerializerHelper;
import com.vaadin.v7.data.Property;
+import com.vaadin.v7.util.SerializerHelper;
/**
* <p>
import java.util.logging.Logger;
import com.vaadin.util.ReflectTools;
-import com.vaadin.util.SerializerHelper;
import com.vaadin.v7.data.Property;
+import com.vaadin.v7.util.SerializerHelper;
/**
* Property descriptor that is able to create simple {@link MethodProperty}
--- /dev/null
+/*
+ * Copyright 2000-2016 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.v7.util;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+
+/**
+ * Helper class for performing serialization. Most of the methods are here are
+ * workarounds for problems in Google App Engine. Used internally by Vaadin and
+ * should not be used by application developers. Subject to change at any time.
+ *
+ * @since 6.0
+ * @deprecated Only used for compatibility-server
+ */
+@Deprecated
+public class SerializerHelper {
+
+ /**
+ * Serializes the class reference so {@link #readClass(ObjectInputStream)}
+ * can deserialize it. Supports null class references.
+ *
+ * @param out
+ * The {@link ObjectOutputStream} to serialize to.
+ * @param cls
+ * A class or null.
+ * @throws IOException
+ * Rethrows any IOExceptions from the ObjectOutputStream
+ */
+ public static void writeClass(ObjectOutputStream out, Class<?> cls)
+ throws IOException {
+ if (cls == null) {
+ out.writeObject(null);
+ } else {
+ out.writeObject(cls.getName());
+ }
+
+ }
+
+ /**
+ * Serializes the class references so
+ * {@link #readClassArray(ObjectInputStream)} can deserialize it. Supports
+ * null class arrays.
+ *
+ * @param out
+ * The {@link ObjectOutputStream} to serialize to.
+ * @param classes
+ * An array containing class references or null.
+ * @throws IOException
+ * Rethrows any IOExceptions from the ObjectOutputStream
+ */
+ public static void writeClassArray(ObjectOutputStream out,
+ Class<?>[] classes) throws IOException {
+ if (classes == null) {
+ out.writeObject(null);
+ } else {
+ String[] classNames = new String[classes.length];
+ for (int i = 0; i < classes.length; i++) {
+ classNames[i] = classes[i].getName();
+ }
+ out.writeObject(classNames);
+ }
+ }
+
+ /**
+ * Deserializes a class references serialized by
+ * {@link #writeClassArray(ObjectOutputStream, Class[])}. Supports null
+ * class arrays.
+ *
+ * @param in
+ * {@link ObjectInputStream} to read from.
+ * @return Class array with the class references or null.
+ * @throws ClassNotFoundException
+ * If one of the classes could not be resolved.
+ * @throws IOException
+ * Rethrows IOExceptions from the ObjectInputStream
+ */
+ public static Class<?>[] readClassArray(ObjectInputStream in)
+ throws ClassNotFoundException, IOException {
+ String[] classNames = (String[]) in.readObject();
+ if (classNames == null) {
+ return null;
+ }
+ Class<?>[] classes = new Class<?>[classNames.length];
+ for (int i = 0; i < classNames.length; i++) {
+ classes[i] = resolveClass(classNames[i]);
+ }
+
+ return classes;
+ }
+
+ /**
+ * List of primitive classes. Google App Engine has problems
+ * serializing/deserializing these (#3064).
+ */
+ private static final Class<?>[] primitiveClasses = new Class<?>[] {
+ byte.class, short.class, int.class, long.class, float.class,
+ double.class, boolean.class, char.class };
+
+ /**
+ * Resolves the class given by {@code className}.
+ *
+ * @param className
+ * The fully qualified class name.
+ * @return A {@code Class} reference.
+ * @throws ClassNotFoundException
+ * If the class could not be resolved.
+ */
+ public static Class<?> resolveClass(String className)
+ throws ClassNotFoundException {
+ for (Class<?> c : primitiveClasses) {
+ if (className.equals(c.getName())) {
+ return c;
+ }
+ }
+
+ return Class.forName(className);
+ }
+
+ /**
+ * Deserializes a class reference serialized by
+ * {@link #writeClass(ObjectOutputStream, Class)}. Supports null class
+ * references.
+ *
+ * @param in
+ * {@code ObjectInputStream} to read from.
+ * @return Class reference to the resolved class
+ * @throws ClassNotFoundException
+ * If the class could not be resolved.
+ * @throws IOException
+ * Rethrows IOExceptions from the ObjectInputStream
+ */
+ public static Class<?> readClass(ObjectInputStream in)
+ throws IOException, ClassNotFoundException {
+ String className = (String) in.readObject();
+ if (className == null) {
+ return null;
+ } else {
+ return resolveClass(className);
+
+ }
+
+ }
+
+ private SerializerHelper() {
+ }
+
+}
<scope>provided</scope>
</dependency>
- <!-- Google App Engine -->
- <dependency>
- <groupId>com.google.appengine</groupId>
- <artifactId>appengine-api-1.0-sdk</artifactId>
- <scope>provided</scope>
- </dependency>
-
<!-- Bean Validation API -->
<dependency>
<groupId>javax.validation</groupId>
+++ /dev/null
-/*
- * Copyright 2000-2016 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:
- *
- * <pre>
- * <servlet>
- * <servlet-name>HelloWorld</servlet-name>
- * <servlet-class>com.vaadin.server.GAEApplicationServlet</servlet-class>
- * <init-param>
- * <param-name>UI</param-name>
- * <param-value>com.vaadin.demo.HelloWorld</param-value>
- * </init-param>
- * </servlet>
- * </pre>
- *
- * Session support must be enabled in appengine-web.xml:
- *
- * <pre>
- * <sessions-enabled>true</sessions-enabled>
- * </pre>
- *
- * 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):
- *
- * <pre>
- * <cronentries>
- * <cron>
- * <url>/HelloWorld/CLEAN</url>
- * <description>Clean up sessions</description>
- * <schedule>every 2 hours</schedule>
- * </cron>
- * </cronentries>
- * </pre>
- *
- * 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:
- *
- * <pre>
- * <static-files>
- * <include path="/VAADIN/**" />
- * </static-files>
- * </pre>
- *
- * Additional limitations:
- * <ul>
- * <li/>Do not change application state when serving an ApplicationResource.
- * <li/>Avoid changing application state in transaction handlers, unless you're
- * confident you fully understand the synchronization issues in App Engine.
- * <li/>The application remains locked while uploading - no progressbar is
- * possible.
- * </ul>
- */
-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";
-
- // sessions with undefined (-1) expiration are limited to this, but explicit
- // longer timeouts can be used
- private static final int DEFAULT_MAX_INACTIVE_INTERVAL = 24 * 3600;
-
- protected void sendDeadlineExceededNotification(
- VaadinServletRequest request, VaadinServletResponse 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(VaadinServletRequest request,
- VaadinServletResponse 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(VaadinServletRequest request,
- VaadinServletResponse 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 {
- VaadinServletRequest request = new VaadinServletRequest(
- unwrappedRequest, getService());
- VaadinServletResponse response = new VaadinServletResponse(
- unwrappedResponse, getService());
-
- if (isCleanupRequest(request)) {
- cleanDatastore();
- return;
- }
-
- if (isStaticResourceRequest(request)) {
- // no locking needed, let superclass handle
- super.service(request, response);
- cleanSession(request);
- return;
- }
-
- if (ServletPortletHelper.isAppRequest(request)) {
- // no locking needed, let superclass handle
- getApplicationContext(request,
- MemcacheServiceFactory.getMemcacheService());
- super.service(request, response);
- cleanSession(request);
- return;
- }
-
- final HttpSession session = request
- .getSession(getService().requestCanCreateSession(request));
- if (session == null) {
- try {
- getService().handleSessionExpired(request, response);
- } catch (ServiceException e) {
- throw new ServletException(e);
- }
- 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 = System.currentTimeMillis();
- while (System.currentTimeMillis()
- - started < MAX_UIDL_WAIT_MILLISECONDS) {
- locked = memcache.put(mutex, 1, Expiration.byDeltaSeconds(40),
- MemcacheService.SetPolicy.ADD_ONLY_IF_NOT_PRESENT);
- if (locked || ServletPortletHelper.isUIDLRequest(request)) {
- /*
- * Done if we got a lock. Will also avoid retrying if
- * there's a UIDL request because those are retried from the
- * client without keeping the server thread stalled.
- */
- 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
- VaadinSession 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 + (getMaxInactiveIntervalSeconds(session) * 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().log(Level.WARNING, "DeadlineExceeded for {0}",
- 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);
- }
- }
-
- /**
- * Returns the maximum inactive time for a session. This is used for
- * handling the expiration of session related information in caches etc.
- *
- * @param session
- * @return inactive timeout in seconds, greater than zero
- */
- protected int getMaxInactiveIntervalSeconds(final HttpSession session) {
- int interval = session.getMaxInactiveInterval();
- if (interval <= 0) {
- getLogger().log(Level.FINE,
- "Undefined session expiration time, using default value instead.");
- return DEFAULT_MAX_INACTIVE_INTERVAL;
- }
- return interval;
- }
-
- protected VaadinSession getApplicationContext(HttpServletRequest request,
- MemcacheService memcache) throws ServletException {
- 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(
- getMaxInactiveIntervalSeconds(session)),
- MemcacheService.SetPolicy.ADD_ONLY_IF_NOT_PRESENT);
- }
- }
- if (serializedAC != null) {
- ByteArrayInputStream bais = new ByteArrayInputStream(serializedAC);
- ObjectInputStream ois;
- try {
- ois = new ObjectInputStream(bais);
- VaadinSession vaadinSession = (VaadinSession) ois.readObject();
- getService().storeSession(vaadinSession,
- new WrappedHttpSession(session));
- } catch (IOException | 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
- try {
- return getService().findVaadinSession(createVaadinRequest(request));
- } catch (Exception e) {
- throw new ServletException(e);
- }
- }
-
- private boolean isCleanupRequest(HttpServletRequest request) {
- String path = request.getPathInfo();
- 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(VaadinServletRequest request) {
- // Should really be replaced with a session storage API...
- WrappedSession wrappedSession = request.getWrappedSession(false);
- if (wrappedSession == null) {
- return;
- }
- VaadinSession serviceSession = getService().loadSession(wrappedSession);
- if (serviceSession == null) {
- return;
- }
-
- /*
- * Inform VaadinSession.valueUnbound that it should not kill the session
- * even though it gets unbound.
- */
- serviceSession.setAttribute(
- VaadinService.PRESERVE_UNBOUND_SESSION_ATTRIBUTE, Boolean.TRUE);
- getService().removeSession(serviceSession.getSession());
-
- // Remove preservation marker
- serviceSession.setAttribute(
- VaadinService.PRESERVE_UNBOUND_SESSION_ATTRIBUTE, null);
- }
-
- /**
- * 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<Entity> entities = pq
- .asList(Builder.withLimit(CLEANUP_LIMIT));
- if (entities != null) {
- getLogger().log(Level.INFO,
- "Vaadin cleanup deleting {0} expired Vaadin sessions.",
- entities.size());
- List<Key> 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<Entity> entities = pq
- .asList(Builder.withLimit(CLEANUP_LIMIT));
- if (entities != null) {
- getLogger().log(Level.INFO,
- "Vaadin cleanup deleting {0} expired appengine sessions.",
- entities.size());
- List<Key> 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());
- }
-}
* Gets the currently used Vaadin servlet. The current servlet is
* automatically defined when initializing the servlet and when processing
* requests to the server (see {@link ThreadLocal}) and in
- * {@link VaadinSession#access(Runnable)} and {@link UI#access(Runnable)}. In
- * other cases, (e.g. from background threads), the current servlet is not
- * automatically defined.
+ * {@link VaadinSession#access(Runnable)} and {@link UI#access(Runnable)}.
+ * In other cases, (e.g. from background threads), the current servlet is
+ * not automatically defined.
* <p>
* The current servlet is derived from the current service using
* {@link VaadinService#getCurrent()}
* if the writing failed due to input/output error.
*
* @deprecated As of 7.0. This method is retained only for backwards
- * compatibility and for {@link GAEVaadinServlet}.
+ * compatibility and for GAEVaadinServlet.
*/
@Deprecated
protected void criticalNotification(VaadinServletRequest request,
}
response.setHeader("Cache-Control", cacheControl);
response.setDateHeader("Expires",
- System.currentTimeMillis() + (resourceCacheTime * 1000));
+ System.currentTimeMillis() + resourceCacheTime * 1000);
// Find the modification timestamp
long lastModifiedTime = 0;
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.isSecure() && request.getServerPort() == 443
+ || !request.isSecure() && request.getServerPort() == 80
+ ? "" : ":" + request.getServerPort())
+ request.getRequestURI());
String servletPath = "";
if (request
+++ /dev/null
-/*
- * Copyright 2000-2016 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.util;
-
-import java.io.IOException;
-import java.io.ObjectInputStream;
-import java.io.ObjectOutputStream;
-
-/**
- * Helper class for performing serialization. Most of the methods are here are
- * workarounds for problems in Google App Engine. Used internally by Vaadin and
- * should not be used by application developers. Subject to change at any time.
- *
- * @since 6.0
- */
-public class SerializerHelper {
-
- /**
- * Serializes the class reference so {@link #readClass(ObjectInputStream)}
- * can deserialize it. Supports null class references.
- *
- * @param out
- * The {@link ObjectOutputStream} to serialize to.
- * @param cls
- * A class or null.
- * @throws IOException
- * Rethrows any IOExceptions from the ObjectOutputStream
- */
- public static void writeClass(ObjectOutputStream out, Class<?> cls)
- throws IOException {
- if (cls == null) {
- out.writeObject(null);
- } else {
- out.writeObject(cls.getName());
- }
-
- }
-
- /**
- * Serializes the class references so
- * {@link #readClassArray(ObjectInputStream)} can deserialize it. Supports
- * null class arrays.
- *
- * @param out
- * The {@link ObjectOutputStream} to serialize to.
- * @param classes
- * An array containing class references or null.
- * @throws IOException
- * Rethrows any IOExceptions from the ObjectOutputStream
- */
- public static void writeClassArray(ObjectOutputStream out,
- Class<?>[] classes) throws IOException {
- if (classes == null) {
- out.writeObject(null);
- } else {
- String[] classNames = new String[classes.length];
- for (int i = 0; i < classes.length; i++) {
- classNames[i] = classes[i].getName();
- }
- out.writeObject(classNames);
- }
- }
-
- /**
- * Deserializes a class references serialized by
- * {@link #writeClassArray(ObjectOutputStream, Class[])}. Supports null
- * class arrays.
- *
- * @param in
- * {@link ObjectInputStream} to read from.
- * @return Class array with the class references or null.
- * @throws ClassNotFoundException
- * If one of the classes could not be resolved.
- * @throws IOException
- * Rethrows IOExceptions from the ObjectInputStream
- */
- public static Class<?>[] readClassArray(ObjectInputStream in)
- throws ClassNotFoundException, IOException {
- String[] classNames = (String[]) in.readObject();
- if (classNames == null) {
- return null;
- }
- Class<?>[] classes = new Class<?>[classNames.length];
- for (int i = 0; i < classNames.length; i++) {
- classes[i] = resolveClass(classNames[i]);
- }
-
- return classes;
- }
-
- /**
- * List of primitive classes. Google App Engine has problems
- * serializing/deserializing these (#3064).
- */
- private static final Class<?>[] primitiveClasses = new Class<?>[] { byte.class,
- short.class, int.class, long.class, float.class, double.class,
- boolean.class, char.class };
-
- /**
- * Resolves the class given by {@code className}.
- *
- * @param className
- * The fully qualified class name.
- * @return A {@code Class} reference.
- * @throws ClassNotFoundException
- * If the class could not be resolved.
- */
- public static Class<?> resolveClass(String className)
- throws ClassNotFoundException {
- for (Class<?> c : primitiveClasses) {
- if (className.equals(c.getName())) {
- return c;
- }
- }
-
- return Class.forName(className);
- }
-
- /**
- * Deserializes a class reference serialized by
- * {@link #writeClass(ObjectOutputStream, Class)}. Supports null class
- * references.
- *
- * @param in
- * {@code ObjectInputStream} to read from.
- * @return Class reference to the resolved class
- * @throws ClassNotFoundException
- * If the class could not be resolved.
- * @throws IOException
- * Rethrows IOExceptions from the ObjectInputStream
- */
- public static Class<?> readClass(ObjectInputStream in)
- throws IOException, ClassNotFoundException {
- String className = (String) in.readObject();
- if (className == null) {
- return null;
- } else {
- return resolveClass(className);
-
- }
-
- }
-
- private SerializerHelper() {
- }
-
-}
rev="1.0.0.GA" conf="build -> default,sources" />
<dependency org="org.hibernate" name="hibernate-validator"
rev="4.2.0.Final" conf="build -> default" />
- <!-- Google App Engine -->
- <dependency org="com.google.appengine" name="appengine-api-1.0-sdk"
- rev="1.7.7" conf="build-provided -> default" />
<!-- LIBRARY DEPENDENCIES (compile time) -->
<!-- Project modules -->
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
</dependency>
- <!-- Google App Engine -->
- <dependency>
- <groupId>com.google.appengine</groupId>
- <artifactId>appengine-api-1.0-sdk</artifactId>
- <scope>provided</scope>
- </dependency>
<!-- jetty-servlets needed by ProxyTest, but not by jetty-runner -->
<!-- Jetty before vaadin-* on the classpath to make Eclipse use the
+++ /dev/null
-package com.vaadin.tests.appengine;
-
-import com.google.apphosting.api.DeadlineExceededException;
-import com.vaadin.server.ClassResource;
-import com.vaadin.server.DownloadStream;
-import com.vaadin.server.LegacyApplication;
-import com.vaadin.ui.Button;
-import com.vaadin.ui.Button.ClickEvent;
-import com.vaadin.ui.Embedded;
-import com.vaadin.ui.GridLayout;
-import com.vaadin.ui.Label;
-import com.vaadin.ui.LegacyWindow;
-import com.vaadin.ui.Notification;
-import com.vaadin.v7.data.Property;
-import com.vaadin.v7.data.Property.ValueChangeEvent;
-import com.vaadin.v7.ui.TextField;
-
-public class GAESyncTest extends LegacyApplication {
-
- /**
- *
- */
- private static final long serialVersionUID = -3724319151122707926l;
-
- @Override
- public void init() {
- setMainWindow(new IntrWindow(this));
-
- }
-
- @Override
- public void error(com.vaadin.server.ErrorEvent event) {
- Throwable t = event.getThrowable();
- // Was this caused by a GAE timeout?
- while (t != null) {
- if (t instanceof DeadlineExceededException) {
- getMainWindow().showNotification("Bugger!", "Deadline Exceeded",
- Notification.TYPE_ERROR_MESSAGE);
- return;
- }
- t = t.getCause();
- }
-
- super.error(event);
-
- }
-
- private class IntrWindow extends LegacyWindow {
- private int n = 0;
- private static final long serialVersionUID = -6521351715072191625l;
- TextField tf;
- Label l;
- LegacyApplication app;
- GridLayout gl;
-
- private IntrWindow(LegacyApplication app) {
-
- this.app = app;
- tf = new TextField("Echo thingie");
- tf.setImmediate(true);
- tf.addListener(new Property.ValueChangeListener() {
- @Override
- public void valueChange(ValueChangeEvent event) {
- IntrWindow.this.showNotification(
- (String) event.getProperty().getValue());
-
- }
-
- });
- addComponent(tf);
-
- l = new Label("" + n);
- addComponent(l);
-
- {
- Button b = new Button("Slow", new Button.ClickListener() {
- @Override
- public void buttonClick(ClickEvent event) {
- try {
- Thread.sleep(15000);
- } catch (InterruptedException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
-
- });
- addComponent(b);
- }
-
- {
- Button b = new Button("Add", new Button.ClickListener() {
-
- @Override
- public void buttonClick(ClickEvent event) {
- if (getUI() == getMainWindow()) {
- getUI().getPage()
- .showNotification(new Notification("main"));
- try {
- Thread.sleep((5000));
- } catch (InterruptedException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
- addImage();
- }
-
- });
- addComponent(b);
- }
-
- gl = new GridLayout(30, 50);
- addComponent(gl);
-
- }
-
- private void addImage() {
- ClassResource res = new ClassResource("img1.png") {
-
- private static final long serialVersionUID = 1L;
-
- @Override
- public DownloadStream getStream() {
- try {
- Thread.sleep((long) (Math.random() * 5000));
- } catch (InterruptedException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- return super.getStream();
- }
-
- };
- res.setCacheTime(0);
- Embedded emb = new Embedded("" + n, res);
- emb.setWidth("30px");
- emb.setHeight("5px");
- gl.addComponent(emb);
- l.setValue("" + n++);
- }
-
- }
-
- @Override
- public LegacyWindow getWindow(String name) {
- LegacyWindow w = super.getWindow(name);
- if (w == null) {
- w = new IntrWindow(this);
- addWindow(w);
- }
- return w;
-
- }
-
-}