diff options
34 files changed, 1198 insertions, 554 deletions
diff --git a/src/com/vaadin/Application.java b/src/com/vaadin/Application.java index dbf71408a9..3da314a11e 100644 --- a/src/com/vaadin/Application.java +++ b/src/com/vaadin/Application.java @@ -18,7 +18,6 @@ import java.util.EventObject; import java.util.HashMap; import java.util.HashSet; import java.util.Hashtable; -import java.util.Iterator; import java.util.LinkedList; import java.util.Locale; import java.util.Map; @@ -48,14 +47,12 @@ import com.vaadin.terminal.WrappedRequest; import com.vaadin.terminal.WrappedRequest.BrowserDetails; import com.vaadin.terminal.WrappedResponse; import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.Connector; import com.vaadin.terminal.gwt.server.AbstractApplicationServlet; import com.vaadin.terminal.gwt.server.ChangeVariablesErrorEvent; import com.vaadin.terminal.gwt.server.ClientConnector; import com.vaadin.terminal.gwt.server.WebApplicationContext; import com.vaadin.ui.AbstractComponent; import com.vaadin.ui.AbstractField; -import com.vaadin.ui.Component; import com.vaadin.ui.Root; import com.vaadin.ui.Table; import com.vaadin.ui.Window; @@ -2359,8 +2356,6 @@ public class Application implements Terminal.ErrorListener, Serializable { return Collections.unmodifiableCollection(roots.values()); } - private final HashMap<String, ClientConnector> connectorIdToConnector = new HashMap<String, ClientConnector>(); - private int connectorIdSequence = 0; /** @@ -2372,53 +2367,7 @@ public class Application implements Terminal.ErrorListener, Serializable { * @return A new id for the connector */ public String createConnectorId(ClientConnector connector) { - String connectorId = String.valueOf(connectorIdSequence++); - Connector oldReference = connectorIdToConnector.put(connectorId, - connector); - if (oldReference != null) { - throw new RuntimeException( - "An error occured while generating connector ids. A connector with id " - + connectorId + " was already found!"); - } - return connectorId; - } - - /** - * Gets a connector by its id. - * - * @param connectorId - * The connector id to look for - * @return The connector with the given id or null if no connector has the - * given id - */ - public ClientConnector getConnector(String connectorId) { - return connectorIdToConnector.get(connectorId); - } - - /** - * Cleans the connector map from all connectors that are no longer attached - * to the application. This should only be called by the framework. - */ - public void cleanConnectorMap() { - // remove detached components from paintableIdMap so they - // can be GC'ed - Iterator<String> iterator = connectorIdToConnector.keySet().iterator(); - - while (iterator.hasNext()) { - String connectorId = iterator.next(); - Connector connector = connectorIdToConnector.get(connectorId); - if (connector instanceof Component) { - Component component = (Component) connector; - if (component.getApplication() != this) { - // If component is no longer part of this application, - // remove it from the map. If it is re-attached to the - // application at some point it will be re-added to this - // collection when sent to the client. - iterator.remove(); - } - } - } - + return String.valueOf(connectorIdSequence++); } private static final Logger getLogger() { diff --git a/src/com/vaadin/data/util/converter/ConverterUtil.java b/src/com/vaadin/data/util/converter/ConverterUtil.java new file mode 100644 index 0000000000..239baf6b6d --- /dev/null +++ b/src/com/vaadin/data/util/converter/ConverterUtil.java @@ -0,0 +1,165 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.data.util.converter; + +import java.io.Serializable; +import java.util.Locale; + +import com.vaadin.Application; + +public class ConverterUtil implements Serializable { + + /** + * Finds a converter that can convert from the given presentation type to + * the given model type and back. Uses the given application to find a + * {@link ConverterFactory} or, if application is null, uses the + * {@link Application#getCurrentApplication()}. + * + * @param <PRESENTATIONTYPE> + * The presentation type + * @param <MODELTYPE> + * The model type + * @param presentationType + * The presentation type + * @param modelType + * The model type + * @param application + * The application to use to find a ConverterFactory or null to + * use the current application + * @return A Converter capable of converting between the given types or null + * if no converter was found + */ + public static <PRESENTATIONTYPE, MODELTYPE> Converter<PRESENTATIONTYPE, MODELTYPE> getConverter( + Class<PRESENTATIONTYPE> presentationType, + Class<MODELTYPE> modelType, Application application) { + Converter<PRESENTATIONTYPE, MODELTYPE> converter = null; + if (application == null) { + application = Application.getCurrentApplication(); + } + + if (application != null) { + ConverterFactory factory = application.getConverterFactory(); + converter = factory.createConverter(presentationType, modelType); + } + return converter; + + } + + /** + * Convert the given value from the data source type to the UI type. + * + * @param modelValue + * The model value to convert + * @param presentationType + * The type of the presentation value + * @param converter + * The converter to (try to) use + * @param locale + * The locale to use for conversion + * @param <PRESENTATIONTYPE> + * Presentation type + * + * @return The converted value, compatible with the presentation type, or + * the original value if its type is compatible and no converter is + * set. + * @throws Converter.ConversionException + * if there is no converter and the type is not compatible with + * the model type. + */ + @SuppressWarnings("unchecked") + public static <PRESENTATIONTYPE, MODELTYPE> PRESENTATIONTYPE convertFromModel( + MODELTYPE modelValue, + Class<? extends PRESENTATIONTYPE> presentationType, + Converter<PRESENTATIONTYPE, MODELTYPE> converter, Locale locale) + throws Converter.ConversionException { + if (converter != null) { + return converter.convertToPresentation(modelValue, locale); + } + if (modelValue == null) { + return null; + } + + if (presentationType.isAssignableFrom(modelValue.getClass())) { + return (PRESENTATIONTYPE) modelValue; + } else { + throw new Converter.ConversionException( + "Unable to convert value of type " + + modelValue.getClass().getName() + + " to presentation type " + + presentationType + + ". No converter is set and the types are not compatible."); + } + } + + /** + * @param <MODELTYPE> + * @param <PRESENTATIONTYPE> + * @param presentationValue + * @param modelType + * @param converter + * @param locale + * @return + * @throws Converter.ConversionException + */ + public static <MODELTYPE, PRESENTATIONTYPE> MODELTYPE convertToModel( + PRESENTATIONTYPE presentationValue, Class<MODELTYPE> modelType, + Converter<PRESENTATIONTYPE, MODELTYPE> converter, Locale locale) + throws Converter.ConversionException { + if (converter != null) { + /* + * If there is a converter, always use it. It must convert or throw + * an exception. + */ + return converter.convertToModel(presentationValue, locale); + } + + if (presentationValue == null) { + // Null should always be passed through the converter but if there + // is no converter we can safely return null + return null; + } + + // check that the value class is compatible with the model type + if (modelType.isAssignableFrom(presentationValue.getClass())) { + return modelType.cast(presentationValue); + } else { + throw new Converter.ConversionException( + "Unable to convert value of type " + + presentationValue.getClass().getName() + + " to model type " + + modelType + + ". No converter is set and the types are not compatible."); + } + + } + + /** + * Checks if the given converter can handle conversion between the given + * presentation and model type + * + * @param converter + * The converter to check + * @param presentationType + * The presentation type + * @param modelType + * The model type + * @return true if the converter supports conversion between the given + * presentation and model type, false otherwise + */ + public static boolean canConverterHandle(Converter<?, ?> converter, + Class<?> presentationType, Class<?> modelType) { + if (converter == null) { + return false; + } + + if (!modelType.isAssignableFrom(converter.getModelType())) { + return false; + } + if (!presentationType.isAssignableFrom(converter.getPresentationType())) { + return false; + } + + return true; + } +} diff --git a/src/com/vaadin/navigator/Navigator.java b/src/com/vaadin/navigator/Navigator.java index 387f1d4eae..3ff727b504 100644 --- a/src/com/vaadin/navigator/Navigator.java +++ b/src/com/vaadin/navigator/Navigator.java @@ -1,9 +1,9 @@ +package com.vaadin.navigator; + /* -@VaadinApache2LicenseForJavaFiles@ + @VaadinApache2LicenseForJavaFiles@ */ -package com.vaadin.navigator; - import java.io.Serializable; import java.util.Iterator; import java.util.LinkedList; @@ -16,7 +16,6 @@ import com.vaadin.terminal.Page.FragmentChangedListener; import com.vaadin.ui.Component; import com.vaadin.ui.CssLayout; import com.vaadin.ui.CustomComponent; -import com.vaadin.ui.Root; /** * Navigator utility that allows switching of views in a part of an application. @@ -60,7 +59,7 @@ public class Navigator implements Serializable { } /** - * Fragment manager using URI fragments of a Root to track views and enable + * Fragment manager using URI fragments of a Page to track views and enable * listening to view changes. * * This class is mostly for internal use by Navigator, and is only public @@ -73,10 +72,10 @@ public class Navigator implements Serializable { /** * Create a new URIFragmentManager and attach it to listen to URI - * fragment changes of a {@link Root}. + * fragment changes of a {@link Page}. * - * @param root - * root whose URI fragment to get and modify + * @param page + * page whose URI fragment to get and modify * @param navigator * {@link Navigator} to notify of fragment changes (using * {@link Navigator#navigateTo(String)} @@ -93,8 +92,7 @@ public class Navigator implements Serializable { } public void setFragment(String fragment) { - // TODO ", false" ??? - page.setFragment(fragment); + page.setFragment(fragment, false); } public void fragmentChanged(FragmentChangedEvent event) { @@ -272,7 +270,7 @@ public class Navigator implements Serializable { /** * Create a navigator that is tracking the active view using URI fragments. * - * @param root + * @param page * whose URI fragments are used * @param display * where to display the views @@ -280,6 +278,7 @@ public class Navigator implements Serializable { public Navigator(Page page, ViewDisplay display) { this.display = display; fragmentManager = new UriFragmentManager(page, this); + navigateTo(page.getFragment()); } /** @@ -287,21 +286,25 @@ public class Navigator implements Serializable { * By default, a {@link SimpleViewDisplay} is used and can be obtained using * {@link #getDisplay()}. * - * @param root + * @param page * whose URI fragments are used */ public Navigator(Page page) { display = new SimpleViewDisplay(); fragmentManager = new UriFragmentManager(page, this); + navigateTo(page.getFragment()); } /** * Create a navigator. * * When a custom fragment manager is not needed, use the constructor - * {@link #Navigator(Root, ViewDisplay)} which uses a URI fragment based + * {@link #Navigator(Page, ViewDisplay)} which uses a URI fragment based * fragment manager. * + * Note that navigation to the initial view must be performed explicitly by + * the application after creating a Navigator using this constructor. + * * @param fragmentManager * fragment manager keeping track of the active view and enabling * bookmarking and direct navigation @@ -335,12 +338,13 @@ public class Navigator implements Serializable { * view name and parameters */ public void navigateTo(String viewAndParameters) { - String longestViewName = ""; + String longestViewName = null; View viewWithLongestName = null; for (ViewProvider provider : providers) { String viewName = provider.getViewName(viewAndParameters); if (null != viewName - && viewName.length() > longestViewName.length()) { + && (longestViewName == null || viewName.length() > longestViewName + .length())) { View view = provider.getView(viewName); if (null != view) { longestViewName = viewName; diff --git a/src/com/vaadin/terminal/AbstractClientConnector.java b/src/com/vaadin/terminal/AbstractClientConnector.java index 6a87f58c71..9de444d70e 100644 --- a/src/com/vaadin/terminal/AbstractClientConnector.java +++ b/src/com/vaadin/terminal/AbstractClientConnector.java @@ -72,7 +72,7 @@ public abstract class AbstractClientConnector implements ClientConnector { public void requestRepaint() { Root root = getRoot(); if (root != null) { - root.getDirtyConnectorTracker().markDirty(this); + root.getConnectorTracker().markDirty(this); } } @@ -455,9 +455,12 @@ public abstract class AbstractClientConnector implements ClientConnector { public void attach() { requestRepaint(); + getRoot().getConnectorTracker().registerConnector(this); + for (ClientConnector connector : getAllChildrenIterable(this)) { connector.attach(); } + } /** @@ -472,6 +475,8 @@ public abstract class AbstractClientConnector implements ClientConnector { for (ClientConnector connector : getAllChildrenIterable(this)) { connector.detach(); } + + getRoot().getConnectorTracker().unregisterConnector(this); } public boolean isConnectorEnabled() { diff --git a/src/com/vaadin/terminal/AbstractErrorMessage.java b/src/com/vaadin/terminal/AbstractErrorMessage.java index 1a625fc0e6..3f526f7339 100644 --- a/src/com/vaadin/terminal/AbstractErrorMessage.java +++ b/src/com/vaadin/terminal/AbstractErrorMessage.java @@ -4,6 +4,8 @@ package com.vaadin.terminal; +import java.io.PrintWriter; +import java.io.StringWriter; import java.util.ArrayList; import java.util.List; @@ -156,7 +158,10 @@ public abstract class AbstractErrorMessage implements ErrorMessage { } return error; } else { - return new SystemError(t); + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + t.printStackTrace(pw); + return new SystemError(sw.toString()); } } diff --git a/src/com/vaadin/terminal/gwt/client/LayoutManager.java b/src/com/vaadin/terminal/gwt/client/LayoutManager.java index 2281b4ab9c..74586a6def 100644 --- a/src/com/vaadin/terminal/gwt/client/LayoutManager.java +++ b/src/com/vaadin/terminal/gwt/client/LayoutManager.java @@ -120,7 +120,7 @@ public class LayoutManager { /** * Assigns a measured size to an element. Method defined as protected to - * allow separate implementation for IE8 in which delete not always works. + * allow separate implementation for IE8. * * @param element * the dom element to attach the measured size to @@ -138,7 +138,17 @@ public class LayoutManager { } }-*/; - private static native final MeasuredSize getMeasuredSize(Element element, + /** + * Gets the measured size for an element. Method defined as protected to + * allow separate implementation for IE8. + * + * @param element + * The element to get measured size for + * @param defaultSize + * The size to return if no measured size could be found + * @return The measured size for the element or {@literal defaultSize} + */ + protected native MeasuredSize getMeasuredSize(Element element, MeasuredSize defaultSize) /*-{ return element.vMeasuredSize || defaultSize; @@ -389,8 +399,13 @@ public class LayoutManager { ((PostLayoutListener) connector).postLayout(); } } - VConsole.log("Invoke post layout listeners in " - + (totalDuration.elapsedMillis() - postLayoutStart) + " ms"); + int postLayoutDone = (totalDuration.elapsedMillis() - postLayoutStart); + VConsole.log("Invoke post layout listeners in " + postLayoutDone + + " ms"); + + cleanMeasuredSizes(); + int cleaningDone = (totalDuration.elapsedMillis() - postLayoutDone); + VConsole.log("Cleaned old measured sizes in " + cleaningDone + "ms"); VConsole.log("Total layout phase time: " + totalDuration.elapsedMillis() + "ms"); @@ -480,7 +495,7 @@ public class LayoutManager { ComponentConnector[] connectors = ConnectorMap.get(connection) .getComponentConnectors(); for (ComponentConnector connector : connectors) { - measueConnector(connector); + measureConnector(connector); } for (ComponentConnector connector : connectors) { layoutDependencyTree.setNeedsMeasure(connector, false); @@ -492,7 +507,7 @@ public class LayoutManager { Collection<ComponentConnector> measureTargets = layoutDependencyTree .getMeasureTargets(); for (ComponentConnector connector : measureTargets) { - measueConnector(connector); + measureConnector(connector); measureCount++; } for (ComponentConnector connector : measureTargets) { @@ -502,7 +517,7 @@ public class LayoutManager { return measureCount; } - private void measueConnector(ComponentConnector connector) { + private void measureConnector(ComponentConnector connector) { Element element = connector.getWidget().getElement(); MeasuredSize measuredSize = getMeasuredSize(connector); MeasureResult measureResult = measuredAndUpdate(element, measuredSize); @@ -1190,4 +1205,11 @@ public class LayoutManager { public void setEverythingNeedsMeasure() { everythingNeedsMeasure = true; } + + /** + * Clean measured sizes which are no longer needed. Only for IE8. + */ + protected void cleanMeasuredSizes() { + } + } diff --git a/src/com/vaadin/terminal/gwt/client/LayoutManagerIE8.java b/src/com/vaadin/terminal/gwt/client/LayoutManagerIE8.java index 2b677985b5..742594671f 100644 --- a/src/com/vaadin/terminal/gwt/client/LayoutManagerIE8.java +++ b/src/com/vaadin/terminal/gwt/client/LayoutManagerIE8.java @@ -3,21 +3,46 @@ */ package com.vaadin.terminal.gwt.client; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + import com.google.gwt.dom.client.Element; +import com.google.gwt.user.client.ui.RootPanel; public class LayoutManagerIE8 extends LayoutManager { + private Map<Element, MeasuredSize> measuredSizes = new HashMap<Element, MeasuredSize>(); + + @Override + protected void setMeasuredSize(Element element, MeasuredSize measuredSize) { + if (measuredSize != null) { + measuredSizes.put(element, measuredSize); + } else { + measuredSizes.remove(element); + } + } + @Override - protected native void setMeasuredSize(Element element, - MeasuredSize measuredSize) - // IE8 cannot do delete element.vMeasuredSize, at least in the case when - // element is not attached to the document (e.g. when a caption is removed) - /*-{ - if (measuredSize) { - element.vMeasuredSize = measuredSize; + protected MeasuredSize getMeasuredSize(Element element, + MeasuredSize defaultSize) { + MeasuredSize measured = measuredSizes.get(element); + if (measured != null) { + return measured; } else { - element.vMeasuredSize = undefined; + return defaultSize; } - }-*/; + } + @Override + protected void cleanMeasuredSizes() { + Iterator<Element> i = measuredSizes.keySet().iterator(); + while (i.hasNext()) { + Element e = i.next(); + if (e.getOwnerDocument() != RootPanel.get().getElement() + .getOwnerDocument()) { + i.remove(); + } + } + } } diff --git a/src/com/vaadin/terminal/gwt/client/ui/label/ContentMode.java b/src/com/vaadin/terminal/gwt/client/ui/label/ContentMode.java index 2c232c9c38..4892c7e6bd 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/label/ContentMode.java +++ b/src/com/vaadin/terminal/gwt/client/ui/label/ContentMode.java @@ -6,7 +6,7 @@ package com.vaadin.terminal.gwt.client.ui.label; /** * Content modes defining how the client should interpret a Label's value. * - * @sine 7.0 + * @since 7.0.0 */ public enum ContentMode { /** diff --git a/src/com/vaadin/terminal/gwt/server/AbstractApplicationPortlet.java b/src/com/vaadin/terminal/gwt/server/AbstractApplicationPortlet.java index bf29144cc1..b7be6fd394 100644 --- a/src/com/vaadin/terminal/gwt/server/AbstractApplicationPortlet.java +++ b/src/com/vaadin/terminal/gwt/server/AbstractApplicationPortlet.java @@ -638,7 +638,7 @@ public abstract class AbstractApplicationPortlet extends GenericPortlet /* Handle the request */ if (requestType == RequestType.FILE_UPLOAD) { - applicationManager.handleFileUpload(wrappedRequest, + applicationManager.handleFileUpload(root, wrappedRequest, wrappedResponse); return; } else if (requestType == RequestType.BROWSER_DETAILS) { diff --git a/src/com/vaadin/terminal/gwt/server/AbstractApplicationServlet.java b/src/com/vaadin/terminal/gwt/server/AbstractApplicationServlet.java index 2179761d31..0e548f61c8 100644 --- a/src/com/vaadin/terminal/gwt/server/AbstractApplicationServlet.java +++ b/src/com/vaadin/terminal/gwt/server/AbstractApplicationServlet.java @@ -421,25 +421,28 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements /* Handle the request */ if (requestType == RequestType.FILE_UPLOAD) { - applicationManager.handleFileUpload(application, request, - response); + Root root = application.getRootForRequest(request); + if (root == null) { + throw new ServletException(ERROR_NO_ROOT_FOUND); + } + applicationManager.handleFileUpload(root, request, response); return; } else if (requestType == RequestType.UIDL) { - // Handles AJAX UIDL requests Root root = application.getRootForRequest(request); if (root == null) { - throw new ServletException(ERROR_NO_WINDOW_FOUND); - } + throw new ServletException(ERROR_NO_ROOT_FOUND); + }// Handles AJAX UIDL requests applicationManager.handleUidlRequest(request, response, servletWrapper, root); return; } else if (requestType == RequestType.BROWSER_DETAILS) { + // Browser details - not related to a specific root applicationManager.handleBrowserDetailsRequest(request, response, application); return; } - // Removes application if it has stopped (mayby by thread or + // Removes application if it has stopped (maybe by thread or // transactionlistener) if (!application.isRunning()) { endApplication(request, response, application); diff --git a/src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java b/src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java index ecd4fdd1ad..1ea713b4f6 100644 --- a/src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java +++ b/src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java @@ -71,10 +71,11 @@ import com.vaadin.terminal.gwt.client.communication.SharedState; import com.vaadin.terminal.gwt.client.communication.UidlValue; import com.vaadin.terminal.gwt.server.BootstrapHandler.BootstrapContext; import com.vaadin.terminal.gwt.server.ComponentSizeValidator.InvalidLayout; +import com.vaadin.terminal.gwt.server.RpcManager.RpcInvocationException; import com.vaadin.ui.AbstractComponent; import com.vaadin.ui.AbstractField; import com.vaadin.ui.Component; -import com.vaadin.ui.DirtyConnectorTracker; +import com.vaadin.ui.ConnectorTracker; import com.vaadin.ui.HasComponents; import com.vaadin.ui.Root; import com.vaadin.ui.Window; @@ -134,6 +135,9 @@ public abstract class AbstractCommunicationManager implements Serializable { private static final String GET_PARAM_ANALYZE_LAYOUTS = "analyzeLayouts"; + /** + * The application this communication manager is used for + */ private final Application application; private List<String> locales; @@ -517,7 +521,8 @@ public abstract class AbstractCommunicationManager implements Serializable { if (request.getParameter(GET_PARAM_HIGHLIGHT_COMPONENT) != null) { String pid = request .getParameter(GET_PARAM_HIGHLIGHT_COMPONENT); - highlightedConnector = root.getApplication().getConnector(pid); + highlightedConnector = root.getConnectorTracker().getConnector( + pid); highlightConnector(highlightedConnector); } } @@ -617,7 +622,7 @@ public abstract class AbstractCommunicationManager implements Serializable { protected void postPaint(Root root) { // Remove connectors that have been detached from the application during // handling of the request - root.getApplication().cleanConnectorMap(); + root.getConnectorTracker().cleanConnectorMap(); } protected void highlightConnector(Connector highlightedConnector) { @@ -763,8 +768,7 @@ public abstract class AbstractCommunicationManager implements Serializable { ArrayList<ClientConnector> dirtyVisibleConnectors = new ArrayList<ClientConnector>(); Application application = root.getApplication(); // Paints components - DirtyConnectorTracker rootConnectorTracker = root - .getDirtyConnectorTracker(); + ConnectorTracker rootConnectorTracker = root.getConnectorTracker(); getLogger().log(Level.FINE, "* Creating response to client"); if (repaintAll) { getClientCache(root).clear(); @@ -848,7 +852,7 @@ public abstract class AbstractCommunicationManager implements Serializable { } } Object stateJson = JsonCodec.encode(state, referenceState, - stateType, application); + stateType, root.getConnectorTracker()); sharedStates.put(connector.getConnectorId(), stateJson); } catch (JSONException e) { @@ -950,7 +954,7 @@ public abstract class AbstractCommunicationManager implements Serializable { // } paramJson.put(JsonCodec.encode( invocation.getParameters()[i], referenceParameter, - parameterType, application)); + parameterType, root.getConnectorTracker())); } invocationJson.put(paramJson); rpcCalls.put(invocationJson); @@ -1411,7 +1415,7 @@ public abstract class AbstractCommunicationManager implements Serializable { for (int bi = 1; bi < bursts.length; bi++) { // unescape any encoded separator characters in the burst final String burst = unescapeBurst(bursts[bi]); - success &= handleBurst(request, application2, burst); + success &= handleBurst(request, root, burst); // In case that there were multiple bursts, we know that this is // a special synchronous case for closing window. Thus we are @@ -1453,22 +1457,23 @@ public abstract class AbstractCommunicationManager implements Serializable { * directly. * * @param source - * @param app - * application receiving the burst + * @param root + * the root receiving the burst * @param burst * the content of the burst as a String to be parsed * @return true if the processing of the burst was successful and there were * no messages to non-existent components */ - public boolean handleBurst(Object source, Application app, + public boolean handleBurst(WrappedRequest source, Root root, final String burst) { boolean success = true; try { Set<Connector> enabledConnectors = new HashSet<Connector>(); - List<MethodInvocation> invocations = parseInvocations(burst); + List<MethodInvocation> invocations = parseInvocations( + root.getConnectorTracker(), burst); for (MethodInvocation invocation : invocations) { - final ClientConnector connector = getConnector(app, + final ClientConnector connector = getConnector(root, invocation.getConnectorId()); if (connector != null && connector.isConnectorEnabled()) { @@ -1479,7 +1484,7 @@ public abstract class AbstractCommunicationManager implements Serializable { for (int i = 0; i < invocations.size(); i++) { MethodInvocation invocation = invocations.get(i); - final ClientConnector connector = getConnector(app, + final ClientConnector connector = getConnector(root, invocation.getConnectorId()); if (connector == null) { @@ -1526,8 +1531,18 @@ public abstract class AbstractCommunicationManager implements Serializable { } if (invocation instanceof ServerRpcMethodInvocation) { - ServerRpcManager.applyInvocation(connector, - (ServerRpcMethodInvocation) invocation); + try { + ServerRpcManager.applyInvocation(connector, + (ServerRpcMethodInvocation) invocation); + } catch (RpcInvocationException e) { + Throwable realException = e.getCause(); + Component errorComponent = null; + if (connector instanceof Component) { + errorComponent = (Component) connector; + } + handleChangeVariablesError(root.getApplication(), + errorComponent, realException, null); + } } else { // All code below is for legacy variable changes @@ -1557,8 +1572,8 @@ public abstract class AbstractCommunicationManager implements Serializable { errorComponent = (Component) dropHandlerOwner; } } - handleChangeVariablesError(app, errorComponent, e, - changes); + handleChangeVariablesError(root.getApplication(), + errorComponent, e, changes); } } } @@ -1577,12 +1592,15 @@ public abstract class AbstractCommunicationManager implements Serializable { * Parse a message burst from the client into a list of MethodInvocation * instances. * + * @param root + * The root for this request * @param burst * message string (JSON) * @return list of MethodInvocation to perform * @throws JSONException */ - private List<MethodInvocation> parseInvocations(final String burst) + private List<MethodInvocation> parseInvocations( + ConnectorTracker connectorTracker, final String burst) throws JSONException { JSONArray invocationsJson = new JSONArray(burst); @@ -1595,7 +1613,7 @@ public abstract class AbstractCommunicationManager implements Serializable { JSONArray invocationJson = invocationsJson.getJSONArray(i); MethodInvocation invocation = parseInvocation(invocationJson, - previousInvocation); + previousInvocation, connectorTracker); if (invocation != null) { // Can be null iff the invocation was a legacy invocation and it // was merged with the previous one @@ -1607,7 +1625,8 @@ public abstract class AbstractCommunicationManager implements Serializable { } private MethodInvocation parseInvocation(JSONArray invocationJson, - MethodInvocation previousInvocation) throws JSONException { + MethodInvocation previousInvocation, + ConnectorTracker connectorTracker) throws JSONException { String connectorId = invocationJson.getString(0); String interfaceName = invocationJson.getString(1); String methodName = invocationJson.getString(2); @@ -1623,10 +1642,10 @@ public abstract class AbstractCommunicationManager implements Serializable { return parseLegacyChangeVariablesInvocation(connectorId, interfaceName, methodName, (LegacyChangeVariablesInvocation) previousInvocation, - parametersJson); + parametersJson, connectorTracker); } else { return parseServerRpcInvocation(connectorId, interfaceName, - methodName, parametersJson); + methodName, parametersJson, connectorTracker); } } @@ -1634,7 +1653,8 @@ public abstract class AbstractCommunicationManager implements Serializable { private LegacyChangeVariablesInvocation parseLegacyChangeVariablesInvocation( String connectorId, String interfaceName, String methodName, LegacyChangeVariablesInvocation previousInvocation, - JSONArray parametersJson) throws JSONException { + JSONArray parametersJson, ConnectorTracker connectorTracker) + throws JSONException { if (parametersJson.length() != 2) { throw new JSONException( "Invalid parameters in legacy change variables call. Expected 2, was " @@ -1642,7 +1662,7 @@ public abstract class AbstractCommunicationManager implements Serializable { } String variableName = parametersJson.getString(0); UidlValue uidlValue = (UidlValue) JsonCodec.decodeInternalType( - UidlValue.class, true, parametersJson.get(1), application); + UidlValue.class, true, parametersJson.get(1), connectorTracker); Object value = uidlValue.getValue(); @@ -1658,7 +1678,8 @@ public abstract class AbstractCommunicationManager implements Serializable { private ServerRpcMethodInvocation parseServerRpcInvocation( String connectorId, String interfaceName, String methodName, - JSONArray parametersJson) throws JSONException { + JSONArray parametersJson, ConnectorTracker connectorTracker) + throws JSONException { ServerRpcMethodInvocation invocation = new ServerRpcMethodInvocation( connectorId, interfaceName, methodName, parametersJson.length()); @@ -1670,7 +1691,7 @@ public abstract class AbstractCommunicationManager implements Serializable { Object parameterValue = parametersJson.get(j); Type parameterType = declaredRpcMethodParameterTypes[j]; parameters[j] = JsonCodec.decodeInternalOrCustomType(parameterType, - parameterValue, application); + parameterValue, connectorTracker); } invocation.setParameters(parameters); return invocation; @@ -1681,8 +1702,9 @@ public abstract class AbstractCommunicationManager implements Serializable { owner.changeVariables(source, m); } - protected ClientConnector getConnector(Application app, String connectorId) { - ClientConnector c = app.getConnector(connectorId); + protected ClientConnector getConnector(Root root, String connectorId) { + ClientConnector c = root.getConnectorTracker() + .getConnector(connectorId); if (c == null && connectorId.equals(getDragAndDropService().getConnectorId())) { return getDragAndDropService(); @@ -1764,10 +1786,10 @@ public abstract class AbstractCommunicationManager implements Serializable { * map from variable names to values */ private void handleChangeVariablesError(Application application, - Component owner, Exception e, Map<String, Object> m) { + Component owner, Throwable t, Map<String, Object> m) { boolean handled = false; ChangeVariablesErrorEvent errorEvent = new ChangeVariablesErrorEvent( - owner, e, m); + owner, t, m); if (owner instanceof AbstractField) { try { @@ -2037,9 +2059,9 @@ public abstract class AbstractCommunicationManager implements Serializable { * @return */ private ArrayList<ClientConnector> getDirtyVisibleConnectors( - DirtyConnectorTracker dirtyConnectorTracker) { + ConnectorTracker connectorTracker) { ArrayList<ClientConnector> dirtyConnectors = new ArrayList<ClientConnector>(); - for (ClientConnector c : dirtyConnectorTracker.getDirtyConnectors()) { + for (ClientConnector c : connectorTracker.getDirtyConnectors()) { if (isVisible(c)) { dirtyConnectors.add(c); } diff --git a/src/com/vaadin/terminal/gwt/server/CommunicationManager.java b/src/com/vaadin/terminal/gwt/server/CommunicationManager.java index 3dd2eb97fd..cc2981dc45 100644 --- a/src/com/vaadin/terminal/gwt/server/CommunicationManager.java +++ b/src/com/vaadin/terminal/gwt/server/CommunicationManager.java @@ -61,7 +61,8 @@ public class CommunicationManager extends AbstractCommunicationManager { /** * Handles file upload request submitted via Upload component. * - * @param application + * @param root + * The root for this request * * @see #getStreamVariableTargetUrl(ReceiverOwner, String, StreamVariable) * @@ -70,9 +71,9 @@ public class CommunicationManager extends AbstractCommunicationManager { * @throws IOException * @throws InvalidUIDLSecurityKeyException */ - public void handleFileUpload(Application application, - WrappedRequest request, WrappedResponse response) - throws IOException, InvalidUIDLSecurityKeyException { + public void handleFileUpload(Root root, WrappedRequest request, + WrappedResponse response) throws IOException, + InvalidUIDLSecurityKeyException { /* * URI pattern: APP/UPLOAD/[PID]/[NAME]/[SECKEY] See #createReceiverUrl @@ -86,14 +87,14 @@ public class CommunicationManager extends AbstractCommunicationManager { String uppUri = pathInfo.substring(startOfData); String[] parts = uppUri.split("/", 3); // 0 = pid, 1= name, 2 = sec key String variableName = parts[1]; - String paintableId = parts[0]; + String connectorId = parts[0]; StreamVariable streamVariable = pidToNameToStreamVariable.get( - paintableId).get(variableName); + connectorId).get(variableName); String secKey = streamVariableToSeckey.get(streamVariable); if (secKey.equals(parts[2])) { - Connector source = getConnector(application, paintableId); + Connector source = getConnector(root, connectorId); String contentType = request.getContentType(); if (contentType.contains("boundary")) { // Multipart requests contain boundary string @@ -117,13 +118,12 @@ public class CommunicationManager extends AbstractCommunicationManager { protected void postPaint(Root root) { super.postPaint(root); - Application application = root.getApplication(); if (pidToNameToStreamVariable != null) { Iterator<String> iterator = pidToNameToStreamVariable.keySet() .iterator(); while (iterator.hasNext()) { String connectorId = iterator.next(); - if (application.getConnector(connectorId) == null) { + if (root.getConnectorTracker().getConnector(connectorId) == null) { // Owner is no longer attached to the application Map<String, StreamVariable> removed = pidToNameToStreamVariable .get(connectorId); diff --git a/src/com/vaadin/terminal/gwt/server/Constants.java b/src/com/vaadin/terminal/gwt/server/Constants.java index 7c467aa7f4..9e6b2c775b 100644 --- a/src/com/vaadin/terminal/gwt/server/Constants.java +++ b/src/com/vaadin/terminal/gwt/server/Constants.java @@ -68,7 +68,7 @@ public interface Constants { // Widget set parameter name static final String PARAMETER_WIDGETSET = "widgetset"; - static final String ERROR_NO_WINDOW_FOUND = "No window found. Did you remember to setMainWindow()?"; + static final String ERROR_NO_ROOT_FOUND = "Application did not return a root for the request and did not request extra information either. Something is wrong."; static final String DEFAULT_THEME_NAME = "reindeer"; diff --git a/src/com/vaadin/terminal/gwt/server/JsonCodec.java b/src/com/vaadin/terminal/gwt/server/JsonCodec.java index 4fb7681e4a..d3a2ef56f8 100644 --- a/src/com/vaadin/terminal/gwt/server/JsonCodec.java +++ b/src/com/vaadin/terminal/gwt/server/JsonCodec.java @@ -24,7 +24,6 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Set; -import com.vaadin.Application; import com.vaadin.external.json.JSONArray; import com.vaadin.external.json.JSONException; import com.vaadin.external.json.JSONObject; @@ -32,6 +31,7 @@ import com.vaadin.terminal.gwt.client.Connector; import com.vaadin.terminal.gwt.client.communication.JsonEncoder; import com.vaadin.terminal.gwt.client.communication.UidlValue; import com.vaadin.ui.Component; +import com.vaadin.ui.ConnectorTracker; /** * Decoder for converting RPC parameters and other values from JSON in transfer @@ -110,16 +110,18 @@ public class JsonCodec implements Serializable { } public static Object decodeInternalOrCustomType(Type targetType, - Object value, Application application) throws JSONException { + Object value, ConnectorTracker connectorTracker) + throws JSONException { if (isInternalType(targetType)) { - return decodeInternalType(targetType, false, value, application); + return decodeInternalType(targetType, false, value, + connectorTracker); } else { - return decodeCustomType(targetType, value, application); + return decodeCustomType(targetType, value, connectorTracker); } } public static Object decodeCustomType(Type targetType, Object value, - Application application) throws JSONException { + ConnectorTracker connectorTracker) throws JSONException { if (isInternalType(targetType)) { throw new JSONException("decodeCustomType cannot be used for " + targetType + ", which is an internal type"); @@ -137,22 +139,23 @@ public class JsonCodec implements Serializable { // Legacy Object[] and String[] handled elsewhere, this takes care // of generic arrays return decodeArray((Class<?>) targetType, (JSONArray) value, - application); + connectorTracker); } else if (targetType == JSONObject.class || targetType == JSONArray.class) { return value; } else { - return decodeObject(targetType, (JSONObject) value, application); + return decodeObject(targetType, (JSONObject) value, + connectorTracker); } } private static Object decodeArray(Class<?> targetType, JSONArray value, - Application application) throws JSONException { + ConnectorTracker connectorTracker) throws JSONException { Class<?> componentType = targetType.getComponentType(); Object array = Array.newInstance(componentType, value.length()); for (int i = 0; i < value.length(); i++) { Object decodedValue = decodeInternalOrCustomType(componentType, - value.get(i), application); + value.get(i), connectorTracker); Array.set(array, i, decodedValue); } return array; @@ -184,7 +187,7 @@ public class JsonCodec implements Serializable { */ public static Object decodeInternalType(Type targetType, boolean restrictToInternalTypes, Object encodedJsonValue, - Application application) throws JSONException { + ConnectorTracker connectorTracker) throws JSONException { if (!isInternalType(targetType)) { throw new JSONException("Type " + targetType + " is not a supported internal type."); @@ -197,26 +200,27 @@ public class JsonCodec implements Serializable { // UidlValue if (targetType == UidlValue.class) { - return decodeUidlValue((JSONArray) encodedJsonValue, application); + return decodeUidlValue((JSONArray) encodedJsonValue, + connectorTracker); } // Collections if (JsonEncoder.VTYPE_LIST.equals(transportType)) { return decodeList(targetType, restrictToInternalTypes, - (JSONArray) encodedJsonValue, application); + (JSONArray) encodedJsonValue, connectorTracker); } else if (JsonEncoder.VTYPE_SET.equals(transportType)) { return decodeSet(targetType, restrictToInternalTypes, - (JSONArray) encodedJsonValue, application); + (JSONArray) encodedJsonValue, connectorTracker); } else if (JsonEncoder.VTYPE_MAP.equals(transportType)) { return decodeMap(targetType, restrictToInternalTypes, - encodedJsonValue, application); + encodedJsonValue, connectorTracker); } // Arrays if (JsonEncoder.VTYPE_ARRAY.equals(transportType)) { return decodeObjectArray(targetType, (JSONArray) encodedJsonValue, - application); + connectorTracker); } else if (JsonEncoder.VTYPE_STRINGARRAY.equals(transportType)) { return decodeStringArray((JSONArray) encodedJsonValue); @@ -227,7 +231,7 @@ public class JsonCodec implements Serializable { String stringValue = String.valueOf(encodedJsonValue); if (JsonEncoder.VTYPE_CONNECTOR.equals(transportType)) { - return application.getConnector(stringValue); + return connectorTracker.getConnector(stringValue); } // Legacy types @@ -250,11 +254,11 @@ public class JsonCodec implements Serializable { } private static UidlValue decodeUidlValue(JSONArray encodedJsonValue, - Application application) throws JSONException { + ConnectorTracker connectorTracker) throws JSONException { String type = encodedJsonValue.getString(0); Object decodedValue = decodeInternalType(getType(type), true, - encodedJsonValue.get(1), application); + encodedJsonValue.get(1), connectorTracker); return new UidlValue(decodedValue); } @@ -275,7 +279,7 @@ public class JsonCodec implements Serializable { private static Map<Object, Object> decodeMap(Type targetType, boolean restrictToInternalTypes, Object jsonMap, - Application application) throws JSONException { + ConnectorTracker connectorTracker) throws JSONException { if (jsonMap instanceof JSONArray) { // Client-side has no declared type information to determine // encoding method for empty maps, so these are handled separately. @@ -293,22 +297,22 @@ public class JsonCodec implements Serializable { .getActualTypeArguments()[1]; if (keyType == String.class) { return decodeStringMap(valueType, (JSONObject) jsonMap, - application); + connectorTracker); } else if (keyType == Connector.class) { return decodeConnectorMap(valueType, (JSONObject) jsonMap, - application); + connectorTracker); } else { return decodeObjectMap(keyType, valueType, (JSONArray) jsonMap, - application); + connectorTracker); } } else { return decodeStringMap(UidlValue.class, (JSONObject) jsonMap, - application); + connectorTracker); } } private static Map<Object, Object> decodeObjectMap(Type keyType, - Type valueType, JSONArray jsonMap, Application application) + Type valueType, JSONArray jsonMap, ConnectorTracker connectorTracker) throws JSONException { Map<Object, Object> map = new HashMap<Object, Object>(); @@ -319,9 +323,9 @@ public class JsonCodec implements Serializable { for (int i = 0; i < keys.length(); i++) { Object key = decodeInternalOrCustomType(keyType, keys.get(i), - application); + connectorTracker); Object value = decodeInternalOrCustomType(valueType, values.get(i), - application); + connectorTracker); map.put(key, value); } @@ -330,30 +334,32 @@ public class JsonCodec implements Serializable { } private static Map<Object, Object> decodeConnectorMap(Type valueType, - JSONObject jsonMap, Application application) throws JSONException { + JSONObject jsonMap, ConnectorTracker connectorTracker) + throws JSONException { Map<Object, Object> map = new HashMap<Object, Object>(); for (Iterator<?> iter = jsonMap.keys(); iter.hasNext();) { String key = (String) iter.next(); Object value = decodeInternalOrCustomType(valueType, - jsonMap.get(key), application); + jsonMap.get(key), connectorTracker); if (valueType == UidlValue.class) { value = ((UidlValue) value).getValue(); } - map.put(application.getConnector(key), value); + map.put(connectorTracker.getConnector(key), value); } return map; } private static Map<Object, Object> decodeStringMap(Type valueType, - JSONObject jsonMap, Application application) throws JSONException { + JSONObject jsonMap, ConnectorTracker connectorTracker) + throws JSONException { Map<Object, Object> map = new HashMap<Object, Object>(); for (Iterator<?> iter = jsonMap.keys(); iter.hasNext();) { String key = (String) iter.next(); Object value = decodeInternalOrCustomType(valueType, - jsonMap.get(key), application); + jsonMap.get(key), connectorTracker); if (valueType == UidlValue.class) { value = ((UidlValue) value).getValue(); } @@ -376,17 +382,18 @@ public class JsonCodec implements Serializable { */ private static Object decodeParametrizedType(Type targetType, boolean restrictToInternalTypes, int typeIndex, Object value, - Application application) throws JSONException { + ConnectorTracker connectorTracker) throws JSONException { if (!restrictToInternalTypes && targetType instanceof ParameterizedType) { Type childType = ((ParameterizedType) targetType) .getActualTypeArguments()[typeIndex]; // Only decode the given type - return decodeInternalOrCustomType(childType, value, application); + return decodeInternalOrCustomType(childType, value, + connectorTracker); } else { // Only UidlValue when not enforcing a given type to avoid security // issues UidlValue decodeInternalType = (UidlValue) decodeInternalType( - UidlValue.class, true, value, application); + UidlValue.class, true, value, connectorTracker); return decodeInternalType.getValue(); } } @@ -407,20 +414,21 @@ public class JsonCodec implements Serializable { } private static Object[] decodeObjectArray(Type targetType, - JSONArray jsonArray, Application application) throws JSONException { - List list = decodeList(List.class, true, jsonArray, application); + JSONArray jsonArray, ConnectorTracker connectorTracker) + throws JSONException { + List list = decodeList(List.class, true, jsonArray, connectorTracker); return list.toArray(new Object[list.size()]); } private static List<Object> decodeList(Type targetType, boolean restrictToInternalTypes, JSONArray jsonArray, - Application application) throws JSONException { + ConnectorTracker connectorTracker) throws JSONException { List<Object> list = new ArrayList<Object>(); for (int i = 0; i < jsonArray.length(); ++i) { // each entry always has two elements: type and value Object encodedValue = jsonArray.get(i); Object decodedChild = decodeParametrizedType(targetType, - restrictToInternalTypes, 0, encodedValue, application); + restrictToInternalTypes, 0, encodedValue, connectorTracker); list.add(decodedChild); } return list; @@ -428,10 +436,10 @@ public class JsonCodec implements Serializable { private static Set<Object> decodeSet(Type targetType, boolean restrictToInternalTypes, JSONArray jsonArray, - Application application) throws JSONException { + ConnectorTracker connectorTracker) throws JSONException { HashSet<Object> set = new HashSet<Object>(); set.addAll(decodeList(targetType, restrictToInternalTypes, jsonArray, - application)); + connectorTracker)); return set; } @@ -457,7 +465,7 @@ public class JsonCodec implements Serializable { } private static Object decodeObject(Type targetType, - JSONObject serializedObject, Application application) + JSONObject serializedObject, ConnectorTracker connectorTracker) throws JSONException { Class<?> targetClass = getClassForType(targetType); @@ -478,7 +486,7 @@ public class JsonCodec implements Serializable { Object encodedFieldValue = serializedObject.get(fieldName); Type fieldType = pd.getReadMethod().getGenericReturnType(); Object decodedFieldValue = decodeInternalOrCustomType( - fieldType, encodedFieldValue, application); + fieldType, encodedFieldValue, connectorTracker); pd.getWriteMethod().invoke(decodedObject, decodedFieldValue); } @@ -498,7 +506,8 @@ public class JsonCodec implements Serializable { } public static Object encode(Object value, Object referenceValue, - Type valueType, Application application) throws JSONException { + Type valueType, ConnectorTracker connectorTracker) + throws JSONException { if (valueType == null) { throw new IllegalArgumentException("type must be defined"); @@ -527,15 +536,15 @@ public class JsonCodec implements Serializable { } else if (value instanceof Collection) { Collection<?> collection = (Collection<?>) value; JSONArray jsonArray = encodeCollection(valueType, collection, - application); + connectorTracker); return jsonArray; } else if (valueType instanceof Class<?> && ((Class<?>) valueType).isArray()) { - JSONArray jsonArray = encodeArrayContents(value, application); + JSONArray jsonArray = encodeArrayContents(value, connectorTracker); return jsonArray; } else if (value instanceof Map) { Object jsonMap = encodeMap(valueType, (Map<?, ?>) value, - application); + connectorTracker); return jsonMap; } else if (value instanceof Connector) { Connector connector = (Connector) value; @@ -546,13 +555,13 @@ public class JsonCodec implements Serializable { } return connector.getConnectorId(); } else if (value instanceof Enum) { - return encodeEnum((Enum<?>) value, application); + return encodeEnum((Enum<?>) value, connectorTracker); } else if (value instanceof JSONArray || value instanceof JSONObject) { return value; } else { // Any object that we do not know how to encode we encode by looping // through fields - return encodeObject(value, referenceValue, application); + return encodeObject(value, referenceValue, connectorTracker); } } @@ -561,7 +570,7 @@ public class JsonCodec implements Serializable { } private static Object encodeObject(Object value, Object referenceValue, - Application application) throws JSONException { + ConnectorTracker connectorTracker) throws JSONException { JSONObject jsonMap = new JSONObject(); try { @@ -595,7 +604,7 @@ public class JsonCodec implements Serializable { jsonMap.put( fieldName, encode(fieldValue, referenceFieldValue, fieldType, - application)); + connectorTracker)); // } else { // System.out.println("Skipping field " + fieldName // + " of type " + fieldType.getName() @@ -629,46 +638,46 @@ public class JsonCodec implements Serializable { return false; } - private static String encodeEnum(Enum<?> e, Application application) - throws JSONException { + private static String encodeEnum(Enum<?> e, + ConnectorTracker connectorTracker) throws JSONException { return e.name(); } private static JSONArray encodeArrayContents(Object array, - Application application) throws JSONException { + ConnectorTracker connectorTracker) throws JSONException { JSONArray jsonArray = new JSONArray(); Class<?> componentType = array.getClass().getComponentType(); for (int i = 0; i < Array.getLength(array); i++) { jsonArray.put(encode(Array.get(array, i), null, componentType, - application)); + connectorTracker)); } return jsonArray; } private static JSONArray encodeCollection(Type targetType, - Collection collection, Application application) + Collection collection, ConnectorTracker connectorTracker) throws JSONException { JSONArray jsonArray = new JSONArray(); for (Object o : collection) { - jsonArray.put(encodeChild(targetType, 0, o, application)); + jsonArray.put(encodeChild(targetType, 0, o, connectorTracker)); } return jsonArray; } private static Object encodeChild(Type targetType, int typeIndex, Object o, - Application application) throws JSONException { + ConnectorTracker connectorTracker) throws JSONException { if (targetType instanceof ParameterizedType) { Type childType = ((ParameterizedType) targetType) .getActualTypeArguments()[typeIndex]; // Encode using the given type - return encode(o, null, childType, application); + return encode(o, null, childType, connectorTracker); } else { throw new JSONException("Collection is missing generics"); } } private static Object encodeMap(Type mapType, Map<?, ?> map, - Application application) throws JSONException { + ConnectorTracker connectorTracker) throws JSONException { Type keyType, valueType; if (mapType instanceof ParameterizedType) { @@ -685,24 +694,25 @@ public class JsonCodec implements Serializable { } if (keyType == String.class) { - return encodeStringMap(valueType, map, application); + return encodeStringMap(valueType, map, connectorTracker); } else if (keyType == Connector.class) { - return encodeConnectorMap(valueType, map, application); + return encodeConnectorMap(valueType, map, connectorTracker); } else { - return encodeObjectMap(keyType, valueType, map, application); + return encodeObjectMap(keyType, valueType, map, connectorTracker); } } private static JSONArray encodeObjectMap(Type keyType, Type valueType, - Map<?, ?> map, Application application) throws JSONException { + Map<?, ?> map, ConnectorTracker connectorTracker) + throws JSONException { JSONArray keys = new JSONArray(); JSONArray values = new JSONArray(); for (Entry<?, ?> entry : map.entrySet()) { Object encodedKey = encode(entry.getKey(), null, keyType, - application); + connectorTracker); Object encodedValue = encode(entry.getValue(), null, valueType, - application); + connectorTracker); keys.put(encodedKey); values.put(encodedValue); @@ -712,13 +722,13 @@ public class JsonCodec implements Serializable { } private static JSONObject encodeConnectorMap(Type valueType, Map<?, ?> map, - Application application) throws JSONException { + ConnectorTracker connectorTracker) throws JSONException { JSONObject jsonMap = new JSONObject(); for (Entry<?, ?> entry : map.entrySet()) { Connector key = (Connector) entry.getKey(); Object encodedValue = encode(entry.getValue(), null, valueType, - application); + connectorTracker); jsonMap.put(key.getConnectorId(), encodedValue); } @@ -726,13 +736,13 @@ public class JsonCodec implements Serializable { } private static JSONObject encodeStringMap(Type valueType, Map<?, ?> map, - Application application) throws JSONException { + ConnectorTracker connectorTracker) throws JSONException { JSONObject jsonMap = new JSONObject(); for (Entry<?, ?> entry : map.entrySet()) { String key = (String) entry.getKey(); Object encodedValue = encode(entry.getValue(), null, valueType, - application); + connectorTracker); jsonMap.put(key, encodedValue); } diff --git a/src/com/vaadin/terminal/gwt/server/PortletCommunicationManager.java b/src/com/vaadin/terminal/gwt/server/PortletCommunicationManager.java index 55f15da3d9..d3fbf4d988 100644 --- a/src/com/vaadin/terminal/gwt/server/PortletCommunicationManager.java +++ b/src/com/vaadin/terminal/gwt/server/PortletCommunicationManager.java @@ -43,12 +43,12 @@ public class PortletCommunicationManager extends AbstractCommunicationManager { super(application); } - public void handleFileUpload(WrappedRequest request, + public void handleFileUpload(Root root, WrappedRequest request, WrappedResponse response) throws IOException { String contentType = request.getContentType(); String name = request.getParameter("name"); String ownerId = request.getParameter("rec-owner"); - Connector owner = getConnector(getApplication(), ownerId); + Connector owner = getConnector(root, ownerId); StreamVariable streamVariable = ownerToNameToStreamVariable.get(owner) .get(name); @@ -73,7 +73,7 @@ public class PortletCommunicationManager extends AbstractCommunicationManager { .iterator(); while (iterator.hasNext()) { Connector owner = iterator.next(); - if (application.getConnector(owner.getConnectorId()) == null) { + if (getConnector(root, owner.getConnectorId()) == null) { // Owner is no longer attached to the application iterator.remove(); } diff --git a/src/com/vaadin/terminal/gwt/server/RpcManager.java b/src/com/vaadin/terminal/gwt/server/RpcManager.java index d240ab8467..026c847e2b 100644 --- a/src/com/vaadin/terminal/gwt/server/RpcManager.java +++ b/src/com/vaadin/terminal/gwt/server/RpcManager.java @@ -13,5 +13,36 @@ import java.io.Serializable; * @since 7.0 */ public interface RpcManager extends Serializable { - public void applyInvocation(ServerRpcMethodInvocation invocation); + public void applyInvocation(ServerRpcMethodInvocation invocation) + throws RpcInvocationException; + + /** + * Wrapper exception for exceptions which occur during invocation of an RPC + * call + * + * @author Vaadin Ltd + * @version @VERSION@ + * @since 7.0 + * + */ + public static class RpcInvocationException extends Exception { + + public RpcInvocationException() { + super(); + } + + public RpcInvocationException(String message, Throwable cause) { + super(message, cause); + } + + public RpcInvocationException(String message) { + super(message); + } + + public RpcInvocationException(Throwable cause) { + super(cause); + } + + } + } diff --git a/src/com/vaadin/terminal/gwt/server/ServerRpcManager.java b/src/com/vaadin/terminal/gwt/server/ServerRpcManager.java index 07f83864c2..d9931a9610 100644 --- a/src/com/vaadin/terminal/gwt/server/ServerRpcManager.java +++ b/src/com/vaadin/terminal/gwt/server/ServerRpcManager.java @@ -5,6 +5,7 @@ package com.vaadin.terminal.gwt.server; import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; @@ -66,9 +67,10 @@ public class ServerRpcManager<T> implements RpcManager { * non-null target of the RPC call * @param invocation * method invocation to perform + * @throws RpcInvocationException */ public static void applyInvocation(RpcTarget target, - ServerRpcMethodInvocation invocation) { + ServerRpcMethodInvocation invocation) throws RpcInvocationException { RpcManager manager = target.getRpcManager(invocation .getInterfaceClass()); if (manager != null) { @@ -109,7 +111,8 @@ public class ServerRpcManager<T> implements RpcManager { * @param invocation * method invocation to perform */ - public void applyInvocation(ServerRpcMethodInvocation invocation) { + public void applyInvocation(ServerRpcMethodInvocation invocation) + throws RpcInvocationException { Method method = invocation.getMethod(); Class<?>[] parameterTypes = method.getParameterTypes(); Object[] args = new Object[parameterTypes.length]; @@ -125,7 +128,7 @@ public class ServerRpcManager<T> implements RpcManager { try { method.invoke(implementation, args); } catch (Exception e) { - throw new RuntimeException("Unable to invoke method " + throw new RpcInvocationException("Unable to invoke method " + invocation.getMethodName() + " in " + invocation.getInterfaceName(), e); } diff --git a/src/com/vaadin/ui/AbstractField.java b/src/com/vaadin/ui/AbstractField.java index 4efed11e2c..21ca01f592 100644 --- a/src/com/vaadin/ui/AbstractField.java +++ b/src/com/vaadin/ui/AbstractField.java @@ -14,14 +14,14 @@ import java.util.LinkedList; import java.util.List; import java.util.logging.Logger; -import com.vaadin.Application; import com.vaadin.data.Buffered; import com.vaadin.data.Property; import com.vaadin.data.Validatable; import com.vaadin.data.Validator; import com.vaadin.data.Validator.InvalidValueException; import com.vaadin.data.util.converter.Converter; -import com.vaadin.data.util.converter.ConverterFactory; +import com.vaadin.data.util.converter.Converter.ConversionException; +import com.vaadin.data.util.converter.ConverterUtil; import com.vaadin.event.Action; import com.vaadin.event.ShortcutAction; import com.vaadin.event.ShortcutListener; @@ -701,8 +701,8 @@ public abstract class AbstractField<T> extends AbstractComponent implements // Check if the current converter is compatible. if (newDataSource != null - && (getConverter() == null || !newDataSource.getType() - .isAssignableFrom(getConverter().getModelType()))) { + && !ConverterUtil.canConverterHandle(getConverter(), getType(), + newDataSource.getType())) { // Changing from e.g. Number -> Double should set a new converter, // changing from Double -> Number can keep the old one (Property // accepts Number) @@ -760,15 +760,9 @@ public abstract class AbstractField<T> extends AbstractComponent implements * from */ public void setConverter(Class<?> datamodelType) { - Converter<T, ?> converter = null; - - Application app = Application.getCurrentApplication(); - if (app != null) { - ConverterFactory factory = app.getConverterFactory(); - converter = (Converter<T, ?>) factory.createConverter(getType(), - datamodelType); - } - setConverter(converter); + Converter<T, ?> c = (Converter<T, ?>) ConverterUtil.getConverter( + getType(), datamodelType, getApplication()); + setConverter(c); } /** @@ -782,26 +776,9 @@ public abstract class AbstractField<T> extends AbstractComponent implements * if there is no converter and the type is not compatible with * the data source type. */ - @SuppressWarnings("unchecked") - private T convertFromDataSource(Object newValue) - throws Converter.ConversionException { - if (converter != null) { - return converter.convertToPresentation(newValue, getLocale()); - } - if (newValue == null) { - return null; - } - - if (getType().isAssignableFrom(newValue.getClass())) { - return (T) newValue; - } else { - throw new Converter.ConversionException( - "Unable to convert value of type " - + newValue.getClass().getName() - + " to " - + getType() - + ". No converter is set and the types are not compatible."); - } + private T convertFromDataSource(Object newValue) { + return ConverterUtil.convertFromModel(newValue, getType(), + getConverter(), getLocale()); } /** @@ -817,38 +794,17 @@ public abstract class AbstractField<T> extends AbstractComponent implements */ private Object convertToDataSource(T fieldValue) throws Converter.ConversionException { - if (converter != null) { - /* - * If there is a converter, always use it. It must convert or throw - * an exception. - */ - try { - return converter.convertToModel(fieldValue, getLocale()); - } catch (com.vaadin.data.util.converter.Converter.ConversionException e) { - throw new Converter.ConversionException( - getConversionError(converter.getModelType()), e); + try { + Class<?> modelType = null; + Property pd = getPropertyDataSource(); + if (pd != null) { + modelType = pd.getType(); } - } - - if (fieldValue == null) { - // Null should always be passed through the converter but if there - // is no converter we can safely return null - return null; - } - - // check that the value class is compatible with the data source type - // (if data source set) or field type - Class<?> type; - if (getPropertyDataSource() != null) { - type = getPropertyDataSource().getType(); - } else { - type = getType(); - } - - if (type.isAssignableFrom(fieldValue.getClass())) { - return fieldValue; - } else { - throw new Converter.ConversionException(getConversionError(type)); + return ConverterUtil.convertToModel(fieldValue, + (Class<Object>) modelType, getConverter(), getLocale()); + } catch (ConversionException e) { + throw new ConversionException( + getConversionError(converter.getModelType()), e); } } diff --git a/src/com/vaadin/ui/Component.java b/src/com/vaadin/ui/Component.java index ce6df9854f..2394d0f307 100644 --- a/src/com/vaadin/ui/Component.java +++ b/src/com/vaadin/ui/Component.java @@ -497,21 +497,15 @@ public interface Component extends ClientConnector, Sizeable, Serializable { public void setIcon(Resource icon); /** - * Gets the parent window of the component. + * Gets the Root the component is attached to. * * <p> - * If the component is not attached to a window through a component + * If the component is not attached to a Root through a component * containment hierarchy, <code>null</code> is returned. * </p> * - * <p> - * The window can be either an application-level window or a sub-window. If - * the component is itself a window, it returns a reference to itself, not - * to its containing window (of a sub-window). - * </p> - * - * @return the parent window of the component or <code>null</code> if it is - * not attached to a window or is itself a window + * @return the Root of the component or <code>null</code> if it is not + * attached to a Root */ public Root getRoot(); @@ -520,11 +514,16 @@ public interface Component extends ClientConnector, Sizeable, Serializable { * * <p> * The method will return {@code null} if the component is not currently - * attached to an application. This is often a problem in constructors of - * regular components and in the initializers of custom composite - * components. A standard workaround is to move the problematic - * initialization to {@link #attach()}, as described in the documentation of - * the method. + * attached to an application. + * </p> + * + * <p> + * Getting a null value is often a problem in constructors of regular + * components and in the initializers of custom composite components. A + * standard workaround is to use {@link Application#getCurrentApplication()} + * to retrieve the application instance that the current request relates to. + * Another way is to move the problematic initialization to + * {@link #attach()}, as described in the documentation of the method. * </p> * * @return the parent application of the component or <code>null</code>. diff --git a/src/com/vaadin/ui/ConnectorTracker.java b/src/com/vaadin/ui/ConnectorTracker.java new file mode 100644 index 0000000000..75a75ad22a --- /dev/null +++ b/src/com/vaadin/ui/ConnectorTracker.java @@ -0,0 +1,229 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.ui; + +import java.io.Serializable; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.vaadin.terminal.AbstractClientConnector; +import com.vaadin.terminal.gwt.client.ServerConnector; +import com.vaadin.terminal.gwt.server.ClientConnector; + +/** + * A class which takes care of book keeping of {@link ClientConnector}s for one + * Root. + * <p> + * Provides {@link #getConnector(String)} which can be used to lookup a + * connector from its id. This is for framework use only and should not be + * needed in applications. + * </p> + * <p> + * Tracks which {@link ClientConnector}s are dirty so they can be updated to the + * client when the following response is sent. A connector is dirty when an + * operation has been performed on it on the server and as a result of this + * operation new information needs to be sent to its {@link ServerConnector}. + * </p> + * + * @author Vaadin Ltd + * @version @VERSION@ + * @since 7.0.0 + * + */ +public class ConnectorTracker implements Serializable { + + private final HashMap<String, ClientConnector> connectorIdToConnector = new HashMap<String, ClientConnector>(); + private Set<ClientConnector> dirtyConnectors = new HashSet<ClientConnector>(); + + private Root root; + + /** + * Gets a logger for this class + * + * @return A logger instance for logging within this class + * + */ + public static Logger getLogger() { + return Logger.getLogger(ConnectorTracker.class.getName()); + } + + public ConnectorTracker(Root root) { + this.root = root; + } + + /** + * Register the given connector. + * <p> + * The lookup method {@link #getConnector(String)} only returns registered + * connectors. + * </p> + * + * @param connector + * The connector to register. + */ + public void registerConnector(ClientConnector connector) { + String connectorId = connector.getConnectorId(); + ClientConnector previouslyRegistered = connectorIdToConnector + .get(connectorId); + if (previouslyRegistered == null) { + connectorIdToConnector.put(connectorId, connector); + getLogger().fine( + "Registered " + connector.getClass().getSimpleName() + " (" + + connectorId + ")"); + } else if (previouslyRegistered != connector) { + throw new RuntimeException("A connector with id " + connectorId + + " is already registered!"); + } else { + getLogger().warning( + "An already registered connector was registered again: " + + connector.getClass().getSimpleName() + " (" + + connectorId + ")"); + } + + } + + /** + * Unregister the given connector. + * + * <p> + * The lookup method {@link #getConnector(String)} only returns registered + * connectors. + * </p> + * + * @param connector + * The connector to unregister + */ + public void unregisterConnector(ClientConnector connector) { + String connectorId = connector.getConnectorId(); + if (!connectorIdToConnector.containsKey(connectorId)) { + getLogger().warning( + "Tried to unregister " + + connector.getClass().getSimpleName() + " (" + + connectorId + ") which is not registered"); + return; + } + if (connectorIdToConnector.get(connectorId) != connector) { + throw new RuntimeException("The given connector with id " + + connectorId + + " is not the one that was registered for that id"); + } + + getLogger().fine( + "Unregistered " + connector.getClass().getSimpleName() + " (" + + connectorId + ")"); + connectorIdToConnector.remove(connectorId); + } + + /** + * Gets a connector by its id. + * + * @param connectorId + * The connector id to look for + * @return The connector with the given id or null if no connector has the + * given id + */ + public ClientConnector getConnector(String connectorId) { + return connectorIdToConnector.get(connectorId); + } + + /** + * Cleans the connector map from all connectors that are no longer attached + * to the application. This should only be called by the framework. + */ + public void cleanConnectorMap() { + // remove detached components from paintableIdMap so they + // can be GC'ed + Iterator<String> iterator = connectorIdToConnector.keySet().iterator(); + + while (iterator.hasNext()) { + String connectorId = iterator.next(); + ClientConnector connector = connectorIdToConnector.get(connectorId); + if (connector instanceof Component) { + Component component = (Component) connector; + if (component.getRoot() != root) { + // If component is no longer part of this application, + // remove it from the map. If it is re-attached to the + // application at some point it will be re-added through + // registerConnector(connector) + iterator.remove(); + } + } + } + + } + + public void markDirty(ClientConnector connector) { + if (getLogger().isLoggable(Level.FINE)) { + if (!dirtyConnectors.contains(connector)) { + getLogger() + .fine(getDebugInfo(connector) + " " + "is now dirty"); + } + } + + dirtyConnectors.add(connector); + } + + public void markClean(ClientConnector connector) { + if (getLogger().isLoggable(Level.FINE)) { + if (dirtyConnectors.contains(connector)) { + getLogger().fine( + getDebugInfo(connector) + " " + "is no longer dirty"); + } + } + + dirtyConnectors.remove(connector); + } + + private String getDebugInfo(ClientConnector connector) { + String message = getObjectString(connector); + if (connector.getParent() != null) { + message += " (parent: " + getObjectString(connector.getParent()) + + ")"; + } + return message; + } + + private String getObjectString(Object connector) { + return connector.getClass().getName() + "@" + + Integer.toHexString(connector.hashCode()); + } + + public void markAllConnectorsDirty() { + markConnectorsDirtyRecursively(root); + getLogger().fine("All connectors are now dirty"); + } + + public void markAllConnectorsClean() { + dirtyConnectors.clear(); + getLogger().fine("All connectors are now clean"); + } + + /** + * Marks all visible connectors dirty, starting from the given connector and + * going downwards in the hierarchy. + * + * @param c + * The component to start iterating downwards from + */ + private void markConnectorsDirtyRecursively(ClientConnector c) { + if (c instanceof Component && !((Component) c).isVisible()) { + return; + } + markDirty(c); + for (ClientConnector child : AbstractClientConnector + .getAllChildrenIterable(c)) { + markConnectorsDirtyRecursively(child); + } + } + + public Collection<ClientConnector> getDirtyConnectors() { + return dirtyConnectors; + } + +} diff --git a/src/com/vaadin/ui/CustomField.java b/src/com/vaadin/ui/CustomField.java index 269f24fb2c..0998c11612 100644 --- a/src/com/vaadin/ui/CustomField.java +++ b/src/com/vaadin/ui/CustomField.java @@ -62,21 +62,18 @@ public abstract class CustomField<T> extends AbstractField<T> implements */ @Override public void attach() { - root = getContent(); - getContent().setParent(this); - fireComponentAttachEvent(getContent()); + // First call super attach to notify all children (none if content has + // not yet been created) super.attach(); - } - /** - * Notifies the content that the {@link CustomField} is detached from a - * window. - * - * @see com.vaadin.ui.Component#detach() - */ - @Override - public void detach() { - super.detach(); + // If the content has not yet been created, we create and attach it at + // this point. + if (root == null) { + // Ensure content is created and its parent is set. + // The getContent() call creates the content and attaches the + // content + fireComponentAttachEvent(getContent()); + } } /** @@ -87,6 +84,7 @@ public abstract class CustomField<T> extends AbstractField<T> implements protected Component getContent() { if (null == root) { root = initContent(); + root.setParent(this); } return root; } diff --git a/src/com/vaadin/ui/DirtyConnectorTracker.java b/src/com/vaadin/ui/DirtyConnectorTracker.java deleted file mode 100644 index ae47d87eae..0000000000 --- a/src/com/vaadin/ui/DirtyConnectorTracker.java +++ /dev/null @@ -1,113 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ -package com.vaadin.ui; - -import java.io.Serializable; -import java.util.Collection; -import java.util.HashSet; -import java.util.Set; -import java.util.logging.Level; -import java.util.logging.Logger; - -import com.vaadin.terminal.AbstractClientConnector; -import com.vaadin.terminal.gwt.server.ClientConnector; - -/** - * A class that tracks dirty {@link ClientConnector}s. A {@link ClientConnector} - * is dirty when an operation has been performed on it on the server and as a - * result of this operation new information needs to be sent to its client side - * counterpart. - * - * @author Vaadin Ltd - * @version @VERSION@ - * @since 7.0.0 - * - */ -public class DirtyConnectorTracker implements Serializable { - private Set<ClientConnector> dirtyConnectors = new HashSet<ClientConnector>(); - private Root root; - - /** - * Gets a logger for this class - * - * @return A logger instance for logging within this class - * - */ - public static Logger getLogger() { - return Logger.getLogger(DirtyConnectorTracker.class.getName()); - } - - public DirtyConnectorTracker(Root root) { - this.root = root; - } - - public void markDirty(ClientConnector connector) { - if (getLogger().isLoggable(Level.FINE)) { - if (!dirtyConnectors.contains(connector)) { - getLogger() - .fine(getDebugInfo(connector) + " " + "is now dirty"); - } - } - - dirtyConnectors.add(connector); - } - - public void markClean(ClientConnector connector) { - if (getLogger().isLoggable(Level.FINE)) { - if (dirtyConnectors.contains(connector)) { - getLogger().fine( - getDebugInfo(connector) + " " + "is no longer dirty"); - } - } - - dirtyConnectors.remove(connector); - } - - private String getDebugInfo(ClientConnector connector) { - String message = getObjectString(connector); - if (connector.getParent() != null) { - message += " (parent: " + getObjectString(connector.getParent()) - + ")"; - } - return message; - } - - private String getObjectString(Object connector) { - return connector.getClass().getName() + "@" - + Integer.toHexString(connector.hashCode()); - } - - public void markAllConnectorsDirty() { - markConnectorsDirtyRecursively(root); - getLogger().fine("All connectors are now dirty"); - } - - public void markAllConnectorsClean() { - dirtyConnectors.clear(); - getLogger().fine("All connectors are now clean"); - } - - /** - * Marks all visible connectors dirty, starting from the given connector and - * going downwards in the hierarchy. - * - * @param c - * The component to start iterating downwards from - */ - private void markConnectorsDirtyRecursively(ClientConnector c) { - if (c instanceof Component && !((Component) c).isVisible()) { - return; - } - markDirty(c); - for (ClientConnector child : AbstractClientConnector - .getAllChildrenIterable(c)) { - markConnectorsDirtyRecursively(child); - } - } - - public Collection<ClientConnector> getDirtyConnectors() { - return dirtyConnectors; - } - -} diff --git a/src/com/vaadin/ui/Form.java b/src/com/vaadin/ui/Form.java index 39b12e67e2..796ad17448 100644 --- a/src/com/vaadin/ui/Form.java +++ b/src/com/vaadin/ui/Form.java @@ -968,34 +968,6 @@ public class Form extends AbstractField<Object> implements Item.Editor, } /** - * Notifies the component that it is connected to an application - * - * @see com.vaadin.ui.Component#attach() - */ - @Override - public void attach() { - super.attach(); - getLayout().attach(); - if (getFooter() != null) { - getFooter().attach(); - } - } - - /** - * Notifies the component that it is detached from the application. - * - * @see com.vaadin.ui.Component#detach() - */ - @Override - public void detach() { - super.detach(); - getLayout().detach(); - if (getFooter() != null) { - getFooter().detach(); - } - } - - /** * Checks the validity of the Form and all of its fields. * * @see com.vaadin.data.Validatable#validate() diff --git a/src/com/vaadin/ui/Label.java b/src/com/vaadin/ui/Label.java index 99a0f89e5c..e1c64605d7 100644 --- a/src/com/vaadin/ui/Label.java +++ b/src/com/vaadin/ui/Label.java @@ -7,7 +7,8 @@ package com.vaadin.ui; import java.lang.reflect.Method; import com.vaadin.data.Property; -import com.vaadin.data.util.ObjectProperty; +import com.vaadin.data.util.converter.Converter; +import com.vaadin.data.util.converter.ConverterUtil; import com.vaadin.terminal.gwt.client.ui.label.ContentMode; import com.vaadin.terminal.gwt.client.ui.label.LabelState; @@ -36,10 +37,9 @@ import com.vaadin.terminal.gwt.client.ui.label.LabelState; * @since 3.0 */ @SuppressWarnings("serial") -// TODO generics for interface Property -public class Label extends AbstractComponent implements Property, +public class Label extends AbstractComponent implements Property<String>, Property.Viewer, Property.ValueChangeListener, - Property.ValueChangeNotifier, Comparable<Object> { + Property.ValueChangeNotifier, Comparable<Label> { /** * @deprecated From 7.0, use {@link ContentMode#TEXT} instead @@ -77,9 +77,13 @@ public class Label extends AbstractComponent implements Property, @Deprecated public static final ContentMode CONTENT_DEFAULT = ContentMode.TEXT; - private static final String DATASOURCE_MUST_BE_SET = "Datasource must be set"; + /** + * A converter used to convert from the data model type to the field type + * and vice versa. Label type is always String. + */ + private Converter<String, Object> converter = null; - private Property dataSource; + private Property<String> dataSource = null; /** * Creates an empty Label. @@ -114,7 +118,9 @@ public class Label extends AbstractComponent implements Property, * @param contentMode */ public Label(String content, ContentMode contentMode) { - this(new ObjectProperty<String>(content, String.class), contentMode); + setValue(content); + setContentMode(contentMode); + setWidth(100, Unit.PERCENTAGE); } /** @@ -127,15 +133,7 @@ public class Label extends AbstractComponent implements Property, public Label(Property contentSource, ContentMode contentMode) { setPropertyDataSource(contentSource); setContentMode(contentMode); - setWidth(100, UNITS_PERCENTAGE); - } - - @Override - public void updateState() { - super.updateState(); - // We don't know when the text is updated so update it here before - // sending the state to the client - getState().setText(getStringValue()); + setWidth(100, Unit.PERCENTAGE); } @Override @@ -149,25 +147,35 @@ public class Label extends AbstractComponent implements Property, * * @return the Value of the label. */ - public Object getValue() { - if (dataSource == null) { - throw new IllegalStateException(DATASOURCE_MUST_BE_SET); + public String getValue() { + if (getPropertyDataSource() == null) { + // Use internal value if we are running without a data source + return getState().getText(); } - return dataSource.getValue(); + return ConverterUtil.convertFromModel(getPropertyDataSource() + .getValue(), String.class, getConverter(), getLocale()); } /** * Set the value of the label. Value of the label is the XML contents of the * label. * - * @param newValue + * @param newStringValue * the New value of the label. */ - public void setValue(Object newValue) { - if (dataSource == null) { - throw new IllegalStateException(DATASOURCE_MUST_BE_SET); + public void setValue(Object newStringValue) { + if (newStringValue != null && newStringValue.getClass() != String.class) { + throw new Converter.ConversionException("Value of type " + + newStringValue.getClass() + " cannot be assigned to " + + String.class.getName()); + } + if (getPropertyDataSource() == null) { + getState().setText((String) newStringValue); + requestRepaint(); + } else { + throw new IllegalStateException( + "Label is only a Property.Viewer and cannot update its data source"); } - dataSource.setValue(newValue); } /** @@ -179,26 +187,7 @@ public class Label extends AbstractComponent implements Property, @Override public String toString() { throw new UnsupportedOperationException( - "Use Property.getValue() instead of Label.toString()"); - } - - /** - * Returns the value of the <code>Property</code> in human readable textual - * format. - * - * This method exists to help migration from previous Vaadin versions by - * providing a simple replacement for {@link #toString()}. However, it is - * normally better to use the value of the label directly. - * - * @return String representation of the value stored in the Property - * @since 7.0 - */ - public String getStringValue() { - if (dataSource == null) { - throw new IllegalStateException(DATASOURCE_MUST_BE_SET); - } - Object value = dataSource.getValue(); - return (null != value) ? value.toString() : null; + "Use getValue() instead of Label.toString()"); } /** @@ -206,11 +195,8 @@ public class Label extends AbstractComponent implements Property, * * @see com.vaadin.data.Property#getType() */ - public Class getType() { - if (dataSource == null) { - throw new IllegalStateException(DATASOURCE_MUST_BE_SET); - } - return dataSource.getType(); + public Class<String> getType() { + return String.class; } /** @@ -238,7 +224,13 @@ public class Label extends AbstractComponent implements Property, ((Property.ValueChangeNotifier) dataSource).removeListener(this); } - // Sets the new data source + if (!ConverterUtil.canConverterHandle(getConverter(), String.class, + newDataSource.getType())) { + // Try to find a converter + Converter<String, ?> c = ConverterUtil.getConverter(String.class, + newDataSource.getType(), getApplication()); + setConverter(c); + } dataSource = newDataSource; // Listens the new data source if possible @@ -354,7 +346,6 @@ public class Label extends AbstractComponent implements Property, protected void fireValueChange() { // Set the error message fireEvent(new Label.ValueChangeEvent(this)); - requestRepaint(); } /** @@ -363,9 +354,28 @@ public class Label extends AbstractComponent implements Property, * @see com.vaadin.data.Property.ValueChangeListener#valueChange(Property.ValueChangeEvent) */ public void valueChange(Property.ValueChangeEvent event) { + // Update the internal value from the data source + getState().setText(getValue()); + requestRepaint(); + fireValueChange(); } + private String getComparableValue() { + String stringValue = getValue(); + if (stringValue == null) { + stringValue = ""; + } + + if (getContentMode() == ContentMode.XHTML + || getContentMode() == ContentMode.XML) { + return stripTags(stringValue); + } else { + return stringValue; + } + + } + /** * Compares the Label to other objects. * @@ -387,27 +397,10 @@ public class Label extends AbstractComponent implements Property, * less than, equal to, or greater than the specified object. * @see java.lang.Comparable#compareTo(java.lang.Object) */ - public int compareTo(Object other) { - - String thisValue; - String otherValue; + public int compareTo(Label other) { - if (getContentMode() == ContentMode.XML - || getContentMode() == ContentMode.XHTML) { - thisValue = stripTags(getStringValue()); - } else { - thisValue = getStringValue(); - } - - if (other instanceof Label - && (((Label) other).getContentMode() == ContentMode.XML || ((Label) other) - .getContentMode() == ContentMode.XHTML)) { - otherValue = stripTags(((Label) other).getStringValue()); - } else { - // TODO not a good idea - and might assume that Field.toString() - // returns a string representation of the value - otherValue = other.toString(); - } + String thisValue = getComparableValue(); + String otherValue = other.getComparableValue(); return thisValue.compareTo(otherValue); } @@ -443,4 +436,26 @@ public class Label extends AbstractComponent implements Property, return res.toString(); } + /** + * Gets the converter used to convert the property data source value to the + * label value. + * + * @return The converter or null if none is set. + */ + public Converter<String, Object> getConverter() { + return converter; + } + + /** + * Sets the converter used to convert the label value to the property data + * source type. The converter must have a presentation type of String. + * + * @param converter + * The new converter to use. + */ + public void setConverter(Converter<String, ?> converter) { + this.converter = (Converter<String, Object>) converter; + requestRepaint(); + } + } diff --git a/src/com/vaadin/ui/Root.java b/src/com/vaadin/ui/Root.java index 50ad99571e..64bfaf57a5 100644 --- a/src/com/vaadin/ui/Root.java +++ b/src/com/vaadin/ui/Root.java @@ -560,8 +560,7 @@ public abstract class Root extends AbstractComponentContainer implements /** Identifies the click event */ private static final String CLICK_EVENT_ID = VRoot.CLICK_EVENT_ID; - private DirtyConnectorTracker dirtyConnectorTracker = new DirtyConnectorTracker( - this); + private ConnectorTracker connectorTracker = new ConnectorTracker(this); private Page page = new Page(this); @@ -1178,38 +1177,14 @@ public abstract class Root extends AbstractComponentContainer implements removeListener(CLICK_EVENT_ID, ClickEvent.class, listener); } - /** - * Notifies the child components and windows that the root is attached to - * the application. - */ - @Override - public void attach() { - super.attach(); - for (Window w : windows) { - w.attach(); - } - } - - /** - * Notifies the child components and windows that the root is detached from - * the application. - */ - @Override - public void detach() { - super.detach(); - for (Window w : windows) { - w.detach(); - } - } - @Override public boolean isConnectorEnabled() { // TODO How can a Root be invisible? What does it mean? return isVisible() && isEnabled(); } - public DirtyConnectorTracker getDirtyConnectorTracker() { - return dirtyConnectorTracker; + public ConnectorTracker getConnectorTracker() { + return connectorTracker; } public Page getPage() { diff --git a/src/com/vaadin/ui/Table.java b/src/com/vaadin/ui/Table.java index 5d4f919704..b74de7e180 100644 --- a/src/com/vaadin/ui/Table.java +++ b/src/com/vaadin/ui/Table.java @@ -21,13 +21,13 @@ import java.util.StringTokenizer; import java.util.logging.Level; import java.util.logging.Logger; -import com.vaadin.Application; import com.vaadin.data.Container; import com.vaadin.data.Item; import com.vaadin.data.Property; import com.vaadin.data.util.ContainerOrderedWrapper; import com.vaadin.data.util.IndexedContainer; import com.vaadin.data.util.converter.Converter; +import com.vaadin.data.util.converter.ConverterUtil; import com.vaadin.event.Action; import com.vaadin.event.Action.Handler; import com.vaadin.event.DataBoundTransferable; @@ -3664,12 +3664,8 @@ public class Table extends AbstractSelect implements Action.Container, if (hasConverter(colId)) { converter = getConverter(colId); } else { - Application app = Application.getCurrentApplication(); - if (app != null) { - converter = (Converter<String, Object>) app - .getConverterFactory().createConverter(String.class, - property.getType()); - } + ConverterUtil.getConverter(String.class, property.getType(), + getApplication()); } Object value = property.getValue(); if (converter != null) { diff --git a/tests/server-side/com/vaadin/tests/data/bean/Person.java b/tests/server-side/com/vaadin/tests/data/bean/Person.java index 2cb3a29368..f7bad31d0e 100644 --- a/tests/server-side/com/vaadin/tests/data/bean/Person.java +++ b/tests/server-side/com/vaadin/tests/data/bean/Person.java @@ -130,4 +130,14 @@ public class Person { this.birthDate = birthDate; } + public static Person createTestPerson1() { + return new Person("Foo", "Bar", "yeah@cool.com", 46, Sex.MALE, + new Address("Street", 1123, "Turku", Country.FINLAND)); + } + + public static Person createTestPerson2() { + return new Person("Maya", "Dinkelstein", "maya@foo.bar", 18, + Sex.FEMALE, new Address("Red street", 12, "Amsterdam", + Country.NETHERLANDS)); + } } diff --git a/tests/server-side/com/vaadin/tests/server/component/abstractfield/AbstractFieldValueConversions.java b/tests/server-side/com/vaadin/tests/server/component/abstractfield/AbstractFieldValueConversions.java index 050ab282a6..7305e022ee 100644 --- a/tests/server-side/com/vaadin/tests/server/component/abstractfield/AbstractFieldValueConversions.java +++ b/tests/server-side/com/vaadin/tests/server/component/abstractfield/AbstractFieldValueConversions.java @@ -4,6 +4,7 @@ import java.util.Locale; import junit.framework.TestCase; +import com.vaadin.Application; import com.vaadin.data.util.MethodProperty; import com.vaadin.data.util.converter.Converter; import com.vaadin.data.util.converter.StringToIntegerConverter; @@ -159,4 +160,45 @@ public class AbstractFieldValueConversions extends TestCase { } + public static class NumberBean { + private Number number; + + public Number getNumber() { + return number; + } + + public void setNumber(Number number) { + this.number = number; + } + + } + + public void testNumberDoubleConverterChange() { + final Application a = new Application(); + Application.setCurrentApplication(a); + TextField tf = new TextField() { + @Override + public Application getApplication() { + return a; + } + }; + NumberBean nb = new NumberBean(); + nb.setNumber(490); + + tf.setPropertyDataSource(new MethodProperty<Number>(nb, "number")); + assertEquals(490, tf.getPropertyDataSource().getValue()); + assertEquals("490", tf.getValue()); + + Converter c1 = tf.getConverter(); + + tf.setPropertyDataSource(new MethodProperty<Number>(nb, "number")); + Converter c2 = tf.getConverter(); + assertTrue( + "StringToNumber converter is ok for integer types and should stay even though property is changed", + c1 == c2); + assertEquals(490, tf.getPropertyDataSource().getValue()); + assertEquals("490", tf.getValue()); + + } + } diff --git a/tests/server-side/com/vaadin/tests/server/component/abstractfield/RemoveListenersOnDetach.java b/tests/server-side/com/vaadin/tests/server/component/abstractfield/RemoveListenersOnDetach.java index 851fece4d1..94385700d8 100644 --- a/tests/server-side/com/vaadin/tests/server/component/abstractfield/RemoveListenersOnDetach.java +++ b/tests/server-side/com/vaadin/tests/server/component/abstractfield/RemoveListenersOnDetach.java @@ -4,6 +4,7 @@ import static org.junit.Assert.assertEquals; import org.junit.Test; +import com.vaadin.Application; import com.vaadin.data.Property; import com.vaadin.data.util.AbstractProperty; import com.vaadin.data.util.converter.Converter.ConversionException; @@ -25,10 +26,13 @@ public class RemoveListenersOnDetach { } }; + private Application application = new Application() { + + }; @Override public Class<?> getType() { - return null; + return String.class; } @Override @@ -48,6 +52,11 @@ public class RemoveListenersOnDetach { public com.vaadin.ui.Root getRoot() { return root; }; + + @Override + public Application getApplication() { + return application; + }; }; Property property = new AbstractProperty() { @@ -61,7 +70,7 @@ public class RemoveListenersOnDetach { } public Class<?> getType() { - return null; + return String.class; } }; diff --git a/tests/server-side/com/vaadin/tests/server/component/label/LabelConverters.java b/tests/server-side/com/vaadin/tests/server/component/label/LabelConverters.java new file mode 100644 index 0000000000..e79bd84741 --- /dev/null +++ b/tests/server-side/com/vaadin/tests/server/component/label/LabelConverters.java @@ -0,0 +1,59 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.tests.server.component.label; + +import junit.framework.TestCase; + +import com.vaadin.Application; +import com.vaadin.data.Property; +import com.vaadin.data.util.MethodProperty; +import com.vaadin.tests.data.bean.Person; +import com.vaadin.ui.Label; + +public class LabelConverters extends TestCase { + + public void testLabelSetDataSourceLaterOn() { + Person p = Person.createTestPerson1(); + Label l = new Label("My label"); + assertEquals("My label", l.getValue()); + assertNull(l.getConverter()); + l.setPropertyDataSource(new MethodProperty<String>(p, "firstName")); + assertEquals(p.getFirstName(), l.getValue()); + p.setFirstName("123"); + assertEquals("123", l.getValue()); + } + + public void testIntegerDataSource() { + Application.setCurrentApplication(new Application()); + Label l = new Label("Foo"); + Property ds = new MethodProperty<Integer>(Person.createTestPerson1(), + "age"); + l.setPropertyDataSource(ds); + assertEquals(String.valueOf(Person.createTestPerson1().getAge()), + l.getValue()); + } + + public void testSetValueWithDataSource() { + try { + MethodProperty<String> property = new MethodProperty<String>( + Person.createTestPerson1(), "firstName"); + Label l = new Label(property); + l.setValue("Foo"); + fail("setValue should throw an exception when a data source is set"); + } catch (Exception e) { + } + + } + + public void testLabelWithoutDataSource() { + Label l = new Label("My label"); + assertEquals("My label", l.getValue()); + assertNull(l.getConverter()); + assertNull(l.getPropertyDataSource()); + l.setValue("New value"); + assertEquals("New value", l.getValue()); + assertNull(l.getConverter()); + assertNull(l.getPropertyDataSource()); + } +} diff --git a/tests/testbench/com/vaadin/tests/components/formlayout/FormLayouts.java b/tests/testbench/com/vaadin/tests/components/formlayout/FormLayouts.java index e247ce95f7..e247ce95f7 100755..100644 --- a/tests/testbench/com/vaadin/tests/components/formlayout/FormLayouts.java +++ b/tests/testbench/com/vaadin/tests/components/formlayout/FormLayouts.java diff --git a/tests/testbench/com/vaadin/tests/components/orderedlayout/LayoutResizeTest.java b/tests/testbench/com/vaadin/tests/components/orderedlayout/LayoutResizeTest.java new file mode 100644 index 0000000000..455c16c425 --- /dev/null +++ b/tests/testbench/com/vaadin/tests/components/orderedlayout/LayoutResizeTest.java @@ -0,0 +1,140 @@ +package com.vaadin.tests.components.orderedlayout; + +import com.vaadin.terminal.ThemeResource; +import com.vaadin.terminal.gwt.client.ui.label.ContentMode; +import com.vaadin.tests.components.TestBase; +import com.vaadin.ui.Alignment; +import com.vaadin.ui.Button; +import com.vaadin.ui.Button.ClickEvent; +import com.vaadin.ui.Embedded; +import com.vaadin.ui.HorizontalLayout; +import com.vaadin.ui.HorizontalSplitPanel; +import com.vaadin.ui.JavaScript; +import com.vaadin.ui.Label; +import com.vaadin.ui.Table; +import com.vaadin.ui.VerticalLayout; +import com.vaadin.ui.VerticalSplitPanel; +import com.vaadin.ui.themes.Reindeer; + +public class LayoutResizeTest extends TestBase { + + @Override + protected void setup() { + getLayout().setSizeFull(); + + HorizontalSplitPanel split1 = new HorizontalSplitPanel(); + split1.setSizeFull(); + addComponent(split1); + + VerticalLayout left = new VerticalLayout(); + left.setSizeFull(); + split1.setFirstComponent(left); + + left.setSpacing(true); + left.setMargin(true); + + left.addComponent(new Label("<h2>Layout resize test</h2>", + ContentMode.XHTML)); + + Button resize = new Button("Resize to 700x400", + new Button.ClickListener() { + public void buttonClick(ClickEvent event) { + JavaScript + .getCurrent() + .execute( + "setTimeout(function() {window.resizeTo(700,400)}, 500)"); + } + }); + left.addComponent(resize); + + resize = new Button("Resize to 900x600", new Button.ClickListener() { + public void buttonClick(ClickEvent event) { + JavaScript + .getCurrent() + .execute( + "setTimeout(function() {window.resizeTo(900,600)}, 500)"); + } + }); + left.addComponent(resize); + + left.addComponent(new Label( + "Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Proin vel ante a orci tempus eleifend ut et magna. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus luctus urna sed urna ultricies.")); + + Table table1 = new Table(); + table1.setSizeFull(); + table1.addContainerProperty("Column", String.class, ""); + for (int i = 1; i <= 100; i++) { + table1.addItem(new Object[] { "Value " + i }, i); + } + left.addComponent(table1); + left.setExpandRatio(table1, 1); + + VerticalSplitPanel split2 = new VerticalSplitPanel(); + split2.setSizeFull(); + split1.setSecondComponent(split2); + + Table table2 = new Table(); + table2.setSizeFull(); + table2.addContainerProperty("Column 1", String.class, ""); + table2.addContainerProperty("Column 2", String.class, ""); + table2.addContainerProperty("Column 3", String.class, ""); + table2.addContainerProperty("Column 4", String.class, ""); + for (int i = 1; i <= 100; i++) { + table2.addItem(new Object[] { "Value " + i, "Value " + i, + "Value " + i, "Value " + i }, i); + } + split2.setFirstComponent(table2); + + VerticalLayout rows = new VerticalLayout(); + rows.setWidth("100%"); + rows.setSpacing(true); + rows.setMargin(true); + for (int i = 1; i <= 100; i++) { + rows.addComponent(getRow(i)); + } + split2.setSecondComponent(rows); + } + + private HorizontalLayout getRow(int i) { + HorizontalLayout row = new HorizontalLayout(); + row.setWidth("100%"); + row.setSpacing(true); + + Embedded icon = new Embedded(null, new ThemeResource( + "../runo/icons/32/document.png")); + row.addComponent(icon); + row.setComponentAlignment(icon, Alignment.MIDDLE_LEFT); + + Label text = new Label( + "Row content #" + + i + + ". In pellentesque faucibus vestibulum. Nulla at nulla justo, eget luctus tortor. Nulla facilisi. Duis aliquet."); + row.addComponent(text); + row.setExpandRatio(text, 1); + + Button button = new Button("Edit"); + button.addStyleName(Reindeer.BUTTON_SMALL); + row.addComponent(button); + row.setComponentAlignment(button, Alignment.MIDDLE_LEFT); + + button = new Button("Delete"); + button.addStyleName(Reindeer.BUTTON_SMALL); + row.addComponent(button); + row.setComponentAlignment(button, Alignment.MIDDLE_LEFT); + + return row; + } + + @Override + protected String getDescription() { + // TODO Auto-generated method stub + return null; + } + + @Override + protected Integer getTicketNumber() { + // TODO Auto-generated method stub + return null; + } + +} diff --git a/tests/testbench/com/vaadin/tests/components/window/RepaintWindowContents.html b/tests/testbench/com/vaadin/tests/components/window/RepaintWindowContents.html new file mode 100644 index 0000000000..9cbcf1d5ea --- /dev/null +++ b/tests/testbench/com/vaadin/tests/components/window/RepaintWindowContents.html @@ -0,0 +1,57 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> +<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> +<link rel="selenium.base" href="http://arturwin.office.itmill.com:8888/" /> +<title>New Test</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> +<thead> +<tr><td rowspan="1" colspan="3">New Test</td></tr> +</thead><tbody> +<tr> + <td>open</td> + <td>/run/com.vaadin.tests.components.window.RepaintWindowContents?restartApplication</td> + <td></td> +</tr> +<tr> + <td>assertText</td> + <td>vaadin=runcomvaadintestscomponentswindowRepaintWindowContents::/VWindow[0]/FocusableScrollPanel[0]/VVerticalLayout[0]/VButton[0]/domChild[0]/domChild[0]</td> + <td>Button 1</td> +</tr> +<tr> + <td>click</td> + <td>vaadin=runcomvaadintestscomponentswindowRepaintWindowContents::/VWindow[0]/FocusableScrollPanel[0]/VVerticalLayout[0]/VButton[0]/domChild[0]/domChild[0]</td> + <td></td> +</tr> +<tr> + <td>assertText</td> + <td>vaadin=runcomvaadintestscomponentswindowRepaintWindowContents::/VWindow[0]/FocusableScrollPanel[0]/VVerticalLayout[0]/VButton[0]/domChild[0]/domChild[0]</td> + <td>Button 2</td> +</tr> +<tr> + <td>click</td> + <td>vaadin=runcomvaadintestscomponentswindowRepaintWindowContents::/VWindow[0]/FocusableScrollPanel[0]/VVerticalLayout[0]/VButton[0]/domChild[0]/domChild[0]</td> + <td></td> +</tr> +<tr> + <td>assertText</td> + <td>vaadin=runcomvaadintestscomponentswindowRepaintWindowContents::/VWindow[0]/FocusableScrollPanel[0]/VVerticalLayout[0]/VButton[0]/domChild[0]/domChild[0]</td> + <td>Button 1</td> +</tr> +<tr> + <td>click</td> + <td>vaadin=runcomvaadintestscomponentswindowRepaintWindowContents::/VWindow[0]/FocusableScrollPanel[0]/VVerticalLayout[0]/VButton[0]/domChild[0]</td> + <td></td> +</tr> +<tr> + <td>assertText</td> + <td>vaadin=runcomvaadintestscomponentswindowRepaintWindowContents::/VWindow[0]/FocusableScrollPanel[0]/VVerticalLayout[0]/VButton[0]/domChild[0]/domChild[0]</td> + <td>Button 2</td> +</tr> + +</tbody></table> +</body> +</html> diff --git a/tests/testbench/com/vaadin/tests/components/window/RepaintWindowContents.java b/tests/testbench/com/vaadin/tests/components/window/RepaintWindowContents.java new file mode 100644 index 0000000000..353eb535ca --- /dev/null +++ b/tests/testbench/com/vaadin/tests/components/window/RepaintWindowContents.java @@ -0,0 +1,56 @@ +package com.vaadin.tests.components.window; + +import com.vaadin.terminal.WrappedRequest; +import com.vaadin.tests.components.AbstractTestRoot; +import com.vaadin.ui.Button; +import com.vaadin.ui.Button.ClickEvent; +import com.vaadin.ui.Button.ClickListener; +import com.vaadin.ui.Layout; +import com.vaadin.ui.VerticalLayout; +import com.vaadin.ui.Window; + +public class RepaintWindowContents extends AbstractTestRoot { + private static final long serialVersionUID = 1L; + + @SuppressWarnings("serial") + @Override + protected void setup(WrappedRequest request) { + final Window window = new Window("Test window"); + addWindow(window); + + final Layout layout1 = new VerticalLayout(); + Button button1 = new Button("Button 1"); + layout1.addComponent(button1); + + final Layout layout2 = new VerticalLayout(); + Button button2 = new Button("Button 2"); + layout2.addComponent(button2); + + window.setContent(layout1); + + button1.addListener(new ClickListener() { + + public void buttonClick(ClickEvent event) { + window.setContent(layout2); + } + }); + + button2.addListener(new ClickListener() { + + public void buttonClick(ClickEvent event) { + window.setContent(layout1); + } + }); + } + + @Override + protected String getTestDescription() { + return "Clicking the button switches the content between content1 and content2"; + } + + @Override + protected Integer getTicketNumber() { + return 8832; + } + +}
\ No newline at end of file |