/*
* Copyright 2000-2016 Vaadin Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.vaadin.server;
import java.io.Serializable;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.GeneralSecurityException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.vaadin.server.ClientConnector.ConnectorErrorEvent;
import com.vaadin.shared.ApplicationConstants;
import com.vaadin.shared.JavaScriptConnectorState;
import com.vaadin.shared.JavaScriptExtensionState;
import com.vaadin.shared.communication.SharedState;
import com.vaadin.shared.ui.JavaScriptComponentState;
import com.vaadin.ui.Component;
import com.vaadin.ui.ConnectorTracker;
import com.vaadin.ui.HasComponents;
import com.vaadin.ui.SelectiveRenderer;
import com.vaadin.ui.UI;
import elemental.json.JsonObject;
import elemental.json.JsonValue;
/**
* This is a common base class for the server-side implementations of the
* communication system between the client code (compiled with GWT into
* JavaScript) and the server side components. Its client side counterpart is
* {@link com.vaadin.client.ApplicationConnection}.
* <p>
* TODO Document better!
*
* @deprecated As of 7.0. Will likely change or be removed in a future version
*/
@Deprecated
@SuppressWarnings("serial")
public class LegacyCommunicationManager implements Serializable {
// TODO Refactor (#11410)
private final HashMap<Integer, ClientCache> uiToClientCache = new HashMap<>();
/**
* The session this communication manager is used for
*/
private final VaadinSession session;
// TODO Refactor (#11413)
private final Map<String, Class<?>> publishedFileContexts = new HashMap<>();
/**
* TODO New constructor - document me!
*
* @param session
*/
public LegacyCommunicationManager(VaadinSession session) {
this.session = session;
}
protected VaadinSession getSession() {
return session;
}
private static final ConcurrentHashMap<Class<? extends SharedState>, JsonValue> referenceDiffStates = new ConcurrentHashMap<>();
/**
* @deprecated As of 7.1. See #11411.
*/
@Deprecated
public static JsonObject encodeState(ClientConnector connector,
SharedState state) {
UI uI = connector.getUI();
ConnectorTracker connectorTracker = uI.getConnectorTracker();
Class<? extends SharedState> stateType = connector.getStateType();
JsonValue diffState = connectorTracker.getDiffState(connector);
if (diffState == null) {
// Use an empty state object as reference for full
// repaints
diffState = referenceDiffStates.get(stateType);
if (diffState == null) {
diffState = createReferenceDiffStateState(stateType);
referenceDiffStates.put(stateType, diffState);
}
}
EncodeResult encodeResult = JsonCodec.encode(state, diffState,
stateType, uI.getConnectorTracker());
connectorTracker.setDiffState(connector,
(JsonObject) encodeResult.getEncodedValue());
return (JsonObject) encodeResult.getDiff();
}
private static JsonValue createReferenceDiffStateState(
Class<? extends SharedState> stateType) {
if (JavaScriptConnectorState.class.isAssignableFrom(stateType)) {
/*
* For JS state types, we should only include the framework-provided
* state fields in the reference diffstate since other fields are
* not know by the client and would therefore not get the right
* initial value if it would be recorded in the diffstate.
*/
stateType = findJsStateReferenceType(stateType);
}
try {
SharedState referenceState = stateType.newInstance();
EncodeResult encodeResult = JsonCodec.encode(referenceState, null,
stateType, null);
return encodeResult.getEncodedValue();
} catch (Exception e) {
getLogger().log(Level.WARNING,
"Error creating reference object for state of type {0}",
stateType.getName());
return null;
}
}
/**
* Finds the highest super class which implements
* {@link JavaScriptConnectorState}. In practice, this finds either
* {@link JavaScriptComponentState} or {@link JavaScriptExtensionState}.
* This is used to find which state properties the client side knows
* something about.
*
* @param stateType
* the state type for which the reference type should be found
* @return the found reference type
*/
private static Class<? extends SharedState> findJsStateReferenceType(
Class<? extends SharedState> stateType) {
assert JavaScriptConnectorState.class.isAssignableFrom(stateType);
Class<?> type = stateType;
while (type != null) {
Class<?> superclass = type.getSuperclass();
if (!JavaScriptConnectorState.class.isAssignableFrom(superclass)) {
break;
}
type = superclass;
}
return type.asSubclass(SharedState.class);
}
/**
* Resolves a dependency URI, registering the URI with this
* {@code LegacyCommunicationManager} if needed and returns a fully
* qualified URI.
*
* @deprecated As of 7.1. See #11413.
*/
@Deprecated
public String registerDependency(String resourceUri, Class<?> context) {
try {
URI uri = new URI(resourceUri);
String protocol = uri.getScheme();
if (ApplicationConstants.PUBLISHED_PROTOCOL_NAME.equals(protocol)) {
// Strip initial slash
String resourceName = uri.getPath().substring(1);
return registerPublishedFile(resourceName, context);
}
if (protocol != null || uri.getHost() != null) {
return resourceUri;
}
// Bare path interpreted as published file
return registerPublishedFile(resourceUri, context);
} catch (URISyntaxException e) {
getLogger().log(Level.WARNING,
"Could not parse resource url " + resourceUri, e);
return resourceUri;
}
}
/**
* @deprecated As of 7.1. See #11413.
*/
@Deprecated
public Map<String, Class<?>> getDependencies() {
return publishedFileContexts;
}
private String registerPublishedFile(String name, Class<?> context) {
// Add to map of names accepted by servePublishedFile
if (publishedFileContexts.containsKey(name)) {
Class<?> oldContext = publishedFileContexts.get(name);
if (oldContext != context) {
getLogger().log(Level.WARNING,
"{0} published by both {1} and {2}. File from {2} will be used.",
new Object[] { name, context, oldContext });
}
} else {
publishedFileContexts.put(name, context);
}
return ApplicationConstants.PUBLISHED_PROTOCOL_PREFIX + "/" + name;
}
/**
* @deprecated As of 7.1. See #11410.
*/
@Deprecated
public ClientCache getClientCache(UI uI) {
Integer uiId = Integer.valueOf(uI.getUIId());
ClientCache cache = uiToClientCache.get(uiId);
if (cache == null) {
cache = new ClientCache();
uiToClientCache.put(uiId, cache);
}
return cache;
}
/**
* Checks if the connector is visible in context. For Components,
* {@link #isComponentVisibleToClient(Component)} is used. For other types
* of connectors, the contextual visibility of its first Component ancestor
* is used. If no Component ancestor is found, the connector is not visible.
*
* @deprecated As of 7.1. See #11411.
*
* @param connector
* The connector to check
* @return <code>true</code> if the connector is visible to the client,
* <code>false</code> otherwise
*/
@Deprecated
public static boolean isConnectorVisibleToClient(
ClientConnector connector) {
if (connector instanceof Component) {
return isComponentVisibleToClient((Component) connector);
} else {
ClientConnector parent = connector.getParent();
if (parent == null) {
return false;
} else {
return isConnectorVisibleToClient(parent);
}
}
}
/**
* Checks if the component should be visible to the client. Returns false if
* the child should not be sent to the client, true otherwise.
*
* @deprecated As of 7.1. See #11411.
*
* @param child
* The child to check
* @return true if the child is visible to the client, false otherwise
*/
@Deprecated
public static boolean isComponentVisibleToClient(Component child) {
if (!child.isVisible()) {
return false;
}
HasComponents parent = child.getParent();
if (parent instanceof SelectiveRenderer) {
if (!((SelectiveRenderer) parent).isRendered(child)) {
return false;
}
}
if (parent != null) {
return isComponentVisibleToClient(parent);
} else {
if (child instanceof UI) {
// UI has no parent and visibility was checked above
return true;
} else {
// Component which is not attached to any UI
return false;
}
}
}
/**
* @deprecated As of 7.1. In 7.2 and later, use
* {@link ConnectorTracker#getConnector(String)
* uI.getConnectorTracker().getConnector(connectorId)} instead.
* See ticket #11411.
*/
@Deprecated
public ClientConnector getConnector(UI uI, String connectorId) {
return uI.getConnectorTracker().getConnector(connectorId);
}
/**
* @deprecated As of 7.1. Will be removed in the future.
*/
@Deprecated
public static class InvalidUIDLSecurityKeyException
extends GeneralSecurityException {
public InvalidUIDLSecurityKeyException(String message) {
super(message);
}
}
private final HashMap<Class<? extends ClientConnector>, Integer> typeToKey = new HashMap<>();
private int nextTypeKey = 0;
/**
* @deprecated As of 7.1. Will be removed in the future.
*/
@Deprecated
public String getTagForType(Class<? extends ClientConnector> class1) {
Integer id = typeToKey.get(class1);
if (id == null) {
id = nextTypeKey++;
typeToKey.put(class1, id);
if (getLogger().isLoggable(Level.FINE)) {
getLogger().log(Level.FINE, "Mapping {0} to {1}",
new Object[] { class1.getName(), id });
}
}
return id.toString();
}
/**
* Helper class for terminal to keep track of data that client is expected
* to know.
*
* TODO make customlayout templates (from theme) to be cached here.
*
* @deprecated As of 7.1. See #11410.
*/
@Deprecated
public class ClientCache implements Serializable {
private final Set<Object> res = new HashSet<>();
/**
*
* @param object
* @return true if the given class was added to cache
*/
public boolean cache(Object object) {
return res.add(object);
}
public void clear() {
res.clear();
}
public boolean isEmpty() {
return res.isEmpty();
}
}
/**
* @deprecated As of 7.1. See #11411.
*/
@Deprecated
public String getStreamVariableTargetUrl(ClientConnector owner, String name,
StreamVariable value) {
/*
* We will use the same APP/* URI space as ApplicationResources but
* prefix url with UPLOAD
*
* eg. APP/UPLOAD/[UIID]/[PID]/[NAME]/[SECKEY]
*
* SECKEY is created on each paint to make URL's unpredictable (to
* prevent CSRF attacks).
*
* NAME and PID from URI forms a key to fetch StreamVariable when
* handling post
*/
String paintableId = owner.getConnectorId();
UI ui = owner.getUI();
int uiId = ui.getUIId();
String key = uiId + "/" + paintableId + "/" + name;
ConnectorTracker connectorTracker = ui.getConnectorTracker();
connectorTracker.addStreamVariable(paintableId, name, value);
String seckey = connectorTracker.getSeckey(value);
return ApplicationConstants.APP_PROTOCOL_PREFIX
+ ServletPortletHelper.UPLOAD_URL_PREFIX + key + "/" + seckey;
}
/**
* Handles an exception related to a connector by invoking the appropriate
* error handler.
*
* @deprecated As of 7.1. See #11411.
*
* @param throwable
* the exception to handle
* @param connector
* the connector that the exception is related to
*/
@Deprecated
public void handleConnectorRelatedException(ClientConnector connector,
Throwable throwable) {
ErrorEvent errorEvent = new ConnectorErrorEvent(connector, throwable);
ErrorHandler handler = ErrorEvent.findErrorHandler(connector);
handler.error(errorEvent);
}
/**
* Requests that the given UI should be fully re-rendered on the client
* side.
*
* @since 7.1 @deprecated. As of 7.1. Should be refactored once locales are
* fixed (#11378)
*/
@Deprecated
public void repaintAll(UI ui) {
getClientCache(ui).clear();
ui.getConnectorTracker().markAllConnectorsDirty();
ui.getConnectorTracker().markAllClientSidesUninitialized();
}
private static final Logger getLogger() {
return Logger.getLogger(LegacyCommunicationManager.class.getName());
}
}