flex.messaging.endpoints.AbstractEndpoint Maven / Gradle / Ivy
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 flex.messaging.endpoints;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import flex.management.ManageableComponent;
import flex.management.runtime.messaging.MessageBrokerControl;
import flex.management.runtime.messaging.endpoints.EndpointControl;
import flex.messaging.FlexContext;
import flex.messaging.FlexSession;
import flex.messaging.MessageBroker;
import flex.messaging.MessageException;
import flex.messaging.Server;
import flex.messaging.client.FlexClient;
import flex.messaging.client.FlexClientOutboundQueueProcessor;
import flex.messaging.client.FlushResult;
import flex.messaging.client.PollFlushResult;
import flex.messaging.client.UserAgentSettings;
import flex.messaging.config.ChannelSettings;
import flex.messaging.config.ConfigMap;
import flex.messaging.config.ConfigurationConstants;
import flex.messaging.config.ConfigurationException;
import flex.messaging.config.SecurityConstraint;
import flex.messaging.io.ClassAliasRegistry;
import flex.messaging.io.SerializationContext;
import flex.messaging.io.TypeMarshaller;
import flex.messaging.io.TypeMarshallingContext;
import flex.messaging.io.amf.translator.ASTranslator;
import flex.messaging.log.Log;
import flex.messaging.log.LogCategories;
import flex.messaging.log.Logger;
import flex.messaging.messages.AcknowledgeMessage;
import flex.messaging.messages.AcknowledgeMessageExt;
import flex.messaging.messages.AsyncMessage;
import flex.messaging.messages.AsyncMessageExt;
import flex.messaging.messages.CommandMessage;
import flex.messaging.messages.CommandMessageExt;
import flex.messaging.messages.Message;
import flex.messaging.messages.SmallMessage;
import flex.messaging.security.SecurityException;
import flex.messaging.util.ClassUtil;
import flex.messaging.util.StringUtils;
import flex.messaging.util.UserAgentManager;
import flex.messaging.validators.DeserializationValidator;
/**
* This is the default implementation of Endpoint, which provides a convenient
* base for behavior and associations common to all endpoints.
*
* These properties that appear in the endpoint configuration are only used by the
* client, therefore they have to be set on the appropriate client classes: connect-timeout-seconds set on Channel.
*
* @see flex.messaging.endpoints.Endpoint
*/
public abstract class AbstractEndpoint extends ManageableComponent
implements Endpoint2, ConfigurationConstants
{
/** Log category for AbstractEndpoint
. */
public static final String LOG_CATEGORY = LogCategories.ENDPOINT_GENERAL;
/**
* HTTP header field names.
*/
public static final String HEADER_NAME_CACHE_CONTROL = "Cache-Control";
public static final String HEADER_NAME_EXPIRES = "Expires";
public static final String HEADER_NAME_PRAGMA = "Pragma";
// Errors
private static final int NONSECURE_PROTOCOL = 10066;
private static final int REQUIRES_FLEXCLIENT_SUPPORT = 10030;
private static final int ERR_MSG_INVALID_URL_SCHEME = 11100;
// XML Configuration Properties
private static final String SERIALIZATION = "serialization";
private static final String CREATE_ASOBJECT_FOR_MISSING_TYPE = "create-asobject-for-missing-type";
private static final String CUSTOM_DESERIALIZER = "custom-deserializer";
private static final String CUSTOM_SERIALIZER = "custom-serializer";
private static final String ENABLE_SMALL_MESSAGES = "enable-small-messages";
private static final String TYPE_MARSHALLER = "type-marshaller";
private static final String RESTORE_REFERENCES = "restore-references";
private static final String INSTANTIATE_TYPES = "instantiate-types";
private static final String SUPPORT_REMOTE_CLASS = "support-remote-class";
private static final String LEGACY_COLLECTION = "legacy-collection";
private static final String LEGACY_DICTIONARY = "legacy-dictionary";
private static final String LEGACY_MAP = "legacy-map";
private static final String LEGACY_XML = "legacy-xml";
private static final String LEGACY_XML_NAMESPACES = "legacy-xml-namespaces";
private static final String LEGACY_THROWABLE = "legacy-throwable";
private static final String LEGACY_BIG_NUMBERS = "legacy-big-numbers";
private static final String LEGACY_EXTERNALIZABLE = "legacy-externalizable";
private static final String LOG_PROPERTY_ERRORS = "log-property-errors";
private static final String IGNORE_PROPERTY_ERRORS = "ignore-property-errors";
private static final String INCLUDE_READ_ONLY = "include-read-only";
private static final String GLOBAL_INCLUDE_READ_ONLY = "global-include-read-only";
private static final String FLEX_CLIENT_OUTBOUND_QUEUE_PROCESSOR = "flex-client-outbound-queue-processor";
private static final String SHOW_STACKTRACES = "show-stacktraces";
private static final String MAX_OBJECT_NEST_LEVEL = "max-object-nest-level";
private static final String MAX_COLLECTION_NEST_LEVEL = "max-collection-nest-level";
private static final String PREFER_VECTORS = "prefer-vectors";
// Endpoint properties
protected Set clientLoadBalancingUrls;
protected String clientType;
protected int connectTimeoutSeconds;
protected int requestTimeoutSeconds;
protected FlexClientOutboundQueueProcessor flexClientOutboundQueueProcessor;
protected SerializationContext serializationContext;
protected Class> deserializerClass;
protected Class> serializerClass;
protected TypeMarshaller typeMarshaller;
protected int port;
private SecurityConstraint securityConstraint;
protected String url;
protected boolean recordMessageSizes;
protected boolean recordMessageTimes;
protected boolean remote;
protected Server server;
protected boolean serverOnly;
// Endpoint internal
protected String parsedUrl;
// Keeps track of what context path parsedUrl has been parsed for. If it is
// null, means parsedUrl has not been parsed already.
protected String parsedForContext;
protected boolean clientContextParsed;
protected String parsedClientUrl;
protected Logger log;
protected Class> flexClientOutboundQueueProcessClass;
protected ConfigMap flexClientOutboundQueueProcessorConfig;
// Supported messaging version
protected double messagingVersion = 1.0;
//--------------------------------------------------------------------------
//
// Constructor
//
//--------------------------------------------------------------------------
/**
* Constructs an unmanaged AbstractEndpoint
.
*/
public AbstractEndpoint()
{
this(false);
}
/**
* Constructs an AbstractEndpoint
with the indicated management.
*
* @param enableManagement true
if the AbstractEndpoint
* is manageable; false
otherwise.
*/
public AbstractEndpoint(boolean enableManagement)
{
super(enableManagement);
this.log = Log.getLogger(getLogCategory());
serializationContext = new SerializationContext();
}
//--------------------------------------------------------------------------
//
// Initialize, validate, start, and stop methods.
//
//--------------------------------------------------------------------------
/**
* Initializes the Endpoint
with the properties.
* If subclasses override this method, they must call super.initialize()
.
*
* @param id The ID of the Endpoint
.
* @param properties Properties for the Endpoint
.
*/
@Override
public void initialize(String id, ConfigMap properties)
{
super.initialize(id, properties);
if (properties == null || properties.size() == 0)
return;
// Client-targeted
initializeClientLoadBalancing(id, properties);
// Client-targeted
connectTimeoutSeconds = properties.getPropertyAsInt(CONNECT_TIMEOUT_SECONDS_ELEMENT, 0);
// Client-targeted
requestTimeoutSeconds = properties.getPropertyAsInt(REQUEST_TIMEOUT_SECONDS_ELEMENT, 0);
// Check for a custom FlexClient outbound queue processor.
ConfigMap outboundQueueConfig = properties.getPropertyAsMap(FLEX_CLIENT_OUTBOUND_QUEUE_PROCESSOR, null);
if (outboundQueueConfig != null)
{
// Get nested props for the processor.
flexClientOutboundQueueProcessorConfig = outboundQueueConfig.getPropertyAsMap(PROPERTIES_ELEMENT, null);
String pClassName = outboundQueueConfig.getPropertyAsString(CLASS_ATTR, null);
if (pClassName != null)
{
try
{
flexClientOutboundQueueProcessClass = createClass(pClassName);
// And now create an instance and initialize to make sure the properties are valid.
setFlexClientOutboundQueueProcessorConfig(flexClientOutboundQueueProcessorConfig);
}
catch (Throwable t)
{
if (Log.isWarn())
log.warn("Cannot register custom FlexClient outbound queue processor class {0}", new Object[]{pClassName}, t);
}
}
}
ConfigMap serialization = properties.getPropertyAsMap(SERIALIZATION, null);
if (serialization != null)
{
// Custom deserializers
List> deserializers = serialization.getPropertyAsList(CUSTOM_DESERIALIZER, null);
if (deserializers != null && Log.isWarn())
log.warn("Endpoint functionality is no longer available. Please remove this entry from your configuration.");
// Custom serializers
List> serializers = serialization.getPropertyAsList(CUSTOM_SERIALIZER, null);
if (serializers != null && Log.isWarn())
log.warn("Endpoint functionality is no longer available. Please remove this entry from your configuration.");
// Type Marshaller implementation
String typeMarshallerClassName = serialization.getPropertyAsString(TYPE_MARSHALLER, null);
if (typeMarshallerClassName != null && typeMarshallerClassName.length() > 0)
{
try
{
Class> tmc = createClass(typeMarshallerClassName);
typeMarshaller = (TypeMarshaller)ClassUtil.createDefaultInstance(tmc, TypeMarshaller.class);
}
catch (Throwable t)
{
if (Log.isWarn())
log.warn("Cannot register custom type marshaller for type {0}", new Object[]{typeMarshallerClassName}, t);
}
}
// Boolean Serialization Flags
serializationContext.createASObjectForMissingType = serialization.getPropertyAsBoolean(CREATE_ASOBJECT_FOR_MISSING_TYPE, false);
serializationContext.enableSmallMessages = serialization.getPropertyAsBoolean(ENABLE_SMALL_MESSAGES, true);
serializationContext.instantiateTypes = serialization.getPropertyAsBoolean(INSTANTIATE_TYPES, true);
serializationContext.supportRemoteClass = serialization.getPropertyAsBoolean(SUPPORT_REMOTE_CLASS, false);
serializationContext.legacyCollection = serialization.getPropertyAsBoolean(LEGACY_COLLECTION, false);
serializationContext.legacyDictionary = serialization.getPropertyAsBoolean(LEGACY_DICTIONARY, false);
serializationContext.legacyMap = serialization.getPropertyAsBoolean(LEGACY_MAP, false);
serializationContext.legacyXMLDocument = serialization.getPropertyAsBoolean(LEGACY_XML, false);
serializationContext.legacyXMLNamespaces = serialization.getPropertyAsBoolean(LEGACY_XML_NAMESPACES, false);
serializationContext.legacyThrowable = serialization.getPropertyAsBoolean(LEGACY_THROWABLE, false);
serializationContext.legacyBigNumbers = serialization.getPropertyAsBoolean(LEGACY_BIG_NUMBERS, false);
serializationContext.legacyExternalizable = serialization.getPropertyAsBoolean(LEGACY_EXTERNALIZABLE, false);
serializationContext.maxObjectNestLevel = (int)serialization.getPropertyAsLong(MAX_OBJECT_NEST_LEVEL, 512);
serializationContext.maxCollectionNestLevel = (int)serialization.getPropertyAsLong(MAX_COLLECTION_NEST_LEVEL, 15);
serializationContext.preferVectors = serialization.getPropertyAsBoolean(PREFER_VECTORS, false);
boolean showStacktraces = serialization.getPropertyAsBoolean(SHOW_STACKTRACES, false);
if (showStacktraces && Log.isWarn())
log.warn("The " + SHOW_STACKTRACES + " configuration option is deprecated and non-functional. Please remove this from your configuration file.");
serializationContext.restoreReferences = serialization.getPropertyAsBoolean(RESTORE_REFERENCES, false);
serializationContext.logPropertyErrors = serialization.getPropertyAsBoolean(LOG_PROPERTY_ERRORS, false);
serializationContext.ignorePropertyErrors = serialization.getPropertyAsBoolean(IGNORE_PROPERTY_ERRORS, true);
serializationContext.includeReadOnly = serialization.getPropertyAsBoolean(INCLUDE_READ_ONLY, false);
}
recordMessageSizes = properties.getPropertyAsBoolean(ConfigurationConstants.RECORD_MESSAGE_SIZES_ELEMENT, false);
if (recordMessageSizes && Log.isWarn())
log.warn("Setting to true affects application performance and should only be used for debugging");
recordMessageTimes = properties.getPropertyAsBoolean(ConfigurationConstants.RECORD_MESSAGE_TIMES_ELEMENT, false);
}
/**
* Starts the endpoint if its associated MessageBroker
is started,
* and if the endpoint is not already running. If subclasses override this method,
* they must call super.start()
.
*/
@Override
public void start()
{
if (isStarted())
return;
// Check if the MessageBroker is started
MessageBroker broker = getMessageBroker();
if (!broker.isStarted())
{
if (Log.isWarn())
{
Log.getLogger(getLogCategory()).warn("Endpoint with id '{0}' cannot be started" +
" when the MessageBroker is not started.",
new Object[]{getId()});
}
return;
}
// Set up management
if (isManaged() && broker.isManaged())
{
setupEndpointControl(broker);
MessageBrokerControl controller = (MessageBrokerControl)broker.getControl();
if (getControl() != null)
controller.addEndpoint(this);
}
// Setup Deserializer and Serializer for the SerializationContext
if (deserializerClass == null)
deserializerClass = createClass(getDeserializerClassName());
if (serializerClass == null)
serializerClass = createClass(getSerializerClassName());
serializationContext.setDeserializerClass(deserializerClass);
serializationContext.setSerializerClass(serializerClass);
// Setup endpoint features
ClassAliasRegistry registry = ClassAliasRegistry.getRegistry();
registry.registerAlias(AsyncMessageExt.CLASS_ALIAS, AsyncMessageExt.class.getName());
registry.registerAlias(AcknowledgeMessageExt.CLASS_ALIAS, AcknowledgeMessageExt.class.getName());
registry.registerAlias(CommandMessageExt.CLASS_ALIAS, CommandMessageExt.class.getName());
super.start();
}
/**
* Stops the endpoint if it is running. If subclasses override this method, they must
* call super.stop()
.
*/
@Override
public void stop()
{
if (!isStarted())
return;
super.stop();
// Remove management
if (isManaged() && getMessageBroker().isManaged())
{
if (getControl() != null)
{
getControl().unregister();
setControl(null);
}
setManaged(false);
}
}
//--------------------------------------------------------------------------
//
// Public Getters and Setters for AbstractEndpoint properties
//
//--------------------------------------------------------------------------
/**
* Adds a client-load-balancing URL.
*
* @param url A client-load-balancing URL.
* @return false
if the set already contains the URL, true
otherwise.
*
*/
public boolean addClientLoadBalancingUrl(String url)
{
if (clientLoadBalancingUrls == null)
clientLoadBalancingUrls = new HashSet();
if (url == null || url.length() == 0)
{
// Invalid {0} configuration for endpoint ''{1}''; cannot add empty url.
ConfigurationException ce = new ConfigurationException();
ce.setMessage(ERR_MSG_EMTPY_CLIENT_LOAD_BALACNING_URL, new Object[]{CLIENT_LOAD_BALANCING_ELEMENT, getId()});
throw ce;
}
return clientLoadBalancingUrls.add(url);
}
/**
* Removes the client-load-balancing URL.
*
* @param url The URL to remove.
* @return true
if the set contained the URL, false
otherwise.
*/
public boolean removeClientLoadBalancingUrl(String url)
{
if (clientLoadBalancingUrls != null)
return clientLoadBalancingUrls.remove(url);
return false;
}
/**
* Retrieves a snapshot of the current client-load-balancing URLs.
*
* @return A snapshot of the current client-load-balancing URLs, or null
if
* no URL exists.
*/
public Set getClientLoadBalancingUrls()
{
return clientLoadBalancingUrls == null? null : new HashSet(clientLoadBalancingUrls);
}
/**
* Retrieves the corresponding client channel type for the endpoint.
*
* @return The corresponding client channel type for the endpoint.
*/
public String getClientType()
{
return clientType;
}
/**
* Sets the corresponding client channel type for the endpoint.
*
* @param type The corresponding client channel type for the endpoint.
*/
public void setClientType(String type)
{
this.clientType = type;
}
/**
* Retrieves the FlexClientOutboundQueueProcessorClass
of the endpoint.
*
* @return The FlexClientOutboundQueueProcessorClass
of the endpoint.
*/
public Class> getFlexClientOutboundQueueProcessorClass()
{
return flexClientOutboundQueueProcessClass;
}
/**
* Sets the the FlexClientOutboundQueueProcessor
of the endpoint.
*
* @param flexClientOutboundQueueProcessorClass the Class of the Flex client outbound queue processor.
*/
public void setFlexClientOutboundQueueProcessorClass(Class> flexClientOutboundQueueProcessorClass)
{
this.flexClientOutboundQueueProcessClass = flexClientOutboundQueueProcessorClass;
if (flexClientOutboundQueueProcessClass != null && flexClientOutboundQueueProcessorConfig != null)
{
FlexClientOutboundQueueProcessor processor = (FlexClientOutboundQueueProcessor)ClassUtil.createDefaultInstance(flexClientOutboundQueueProcessClass, null);
processor.initialize(flexClientOutboundQueueProcessorConfig);
}
}
/**
* Retrieves the properties for the FlexClientOutboundQueueProcessor
of the endpoint.
*
* @return The properties for the FlexClientOutboundQueueProcessor
of the endpoint.
*/
public ConfigMap getFlexClientOutboundQueueProcessorConfig()
{
return flexClientOutboundQueueProcessorConfig;
}
/**
* Sets the properties for the FlexClientOutboundQueueProcessor
of the endpoint.
*
* @param flexClientOutboundQueueProcessorConfig The configuration map.
*/
public void setFlexClientOutboundQueueProcessorConfig(ConfigMap flexClientOutboundQueueProcessorConfig)
{
this.flexClientOutboundQueueProcessorConfig = flexClientOutboundQueueProcessorConfig;
if (flexClientOutboundQueueProcessorConfig != null && flexClientOutboundQueueProcessClass != null)
{
FlexClientOutboundQueueProcessor processor = (FlexClientOutboundQueueProcessor)ClassUtil.createDefaultInstance(flexClientOutboundQueueProcessClass, null);
processor.initialize(flexClientOutboundQueueProcessorConfig);
}
}
/**
* Sets the ID of the AbstractEndpoint
. If the AbstractEndpoint
* has a MessageBroker
assigned, it also updates the ID in the
* MessageBroker
.
*
* @param id The endpoint ID.
*/
@Override
public void setId(String id)
{
String oldId = getId();
if (oldId != null && oldId.equals(id))
return;
super.setId(id);
// Update the endpoint id in the broker
MessageBroker broker = getMessageBroker();
if (broker != null)
{
// broker must have the endpoint then
broker.removeEndpoint(oldId);
broker.addEndpoint(this);
}
}
/**
* Retrieves the MessageBroker
of the AbstractEndpoint
.
*
* @return The MessageBroker
of the AbstractEndpoint
.
*/
public MessageBroker getMessageBroker()
{
return (MessageBroker)getParent();
}
/**
* Sets the MessageBroker
of the AbstractEndpoint
.
* Removes the AbstractEndpoint
from the old broker
* (if there was one) and adds to the list of endpoints in the new broker.
*
* @param broker The MessageBroker
of the AbstractEndpoint
.
*/
public void setMessageBroker(MessageBroker broker)
{
MessageBroker oldBroker = getMessageBroker();
setParent(broker);
if (oldBroker != null)
oldBroker.removeEndpoint(getId());
// Add endpoint to the new broker if needed
if (broker.getEndpoint(getId()) != this)
broker.addEndpoint(this);
}
/**
* Return the highest messaging version currently available via this
* endpoint.
* @return double the messaging version
*/
public double getMessagingVersion()
{
return messagingVersion;
}
/**
* Retrieves the port of the URL of the endpoint.
* A return value of 0 denotes no port in the channel URL.
*
* @return The port of the URL of the endpoint, or 0 if the URL does not contain
* a port number.
*/
public int getPort()
{
return port;
}
/**
* Determines whether the endpoint is secure.
*
* @return false
by default.
*/
public boolean isSecure()
{
return false;
}
/**
* Determines if the endpoint clients connect to directly is mirrored and running
* on a remote host, in which case this local instance is not started and will service no direct
* client connections.
*
* @return true
if this endpoint will not process direct client connections and is just
* a local representation of a symmetric endpoint on a remote host that will, false
otherwise.
*/
public boolean isRemote()
{
return remote;
}
/**
* Sets the remote status for this endpoint.
*
* @param value true
if this endpoint will not process direct client connections and is just
* a local representation of a symmetric endpoint on a remote host that will, false
otherwise.
*/
public void setRemote(boolean value)
{
remote = value;
}
/**
* Retrieves the Server that the endpoint is using, or null
if
* no server has been assigned.
* @return Server The Server object the endpoint is using.
*/
public Server getServer()
{
return server;
}
/**
* Sets the Server that the endpoint will use.
* @param server The Server object.
*/
public void setServer(Server server)
{
this.server = server;
}
/**
* Determines whether the endpoint is server only.
*
* @return true
if the endpoint is server only, false
otherwise.
*/
public boolean getServerOnly()
{
return serverOnly;
}
/**
* Sets whether the endpoint is server only.
*
* @param serverOnly true
if the endpoint is server only, false
otherwise.
*/
public void setServerOnly(boolean serverOnly)
{
this.serverOnly = serverOnly;
}
/**
* Retrieves the SecurityConstraint
of the Endpoint
.
*
* @return The SecurityConstraint
of the Endpoint
.
*/
public SecurityConstraint getSecurityConstraint()
{
return securityConstraint;
}
/**
* Sets the SecurityConstraint
of the Endpoint
.
*
* @param securityConstraint The SecurityContraint object.
*/
public void setSecurityConstraint(SecurityConstraint securityConstraint)
{
this.securityConstraint = securityConstraint;
}
/**
* Retrieves the SerializationContext
of the endpoint.
*
* @return The SerializationContext
of the endpoint.
*/
public SerializationContext getSerializationContext()
{
return serializationContext;
}
/**
* Sets the SerializationContext
of the endpoint.
*
* @param serializationContext The SerializationContext object.
*/
public void setSerializationContext(SerializationContext serializationContext)
{
this.serializationContext = serializationContext;
}
/**
* Retrieves the TypeMarshaller
of the endpoint.
*
* @return The TypeMarshaller
of the endpoint.
*/
public TypeMarshaller getTypeMarshaller()
{
if (typeMarshaller == null)
typeMarshaller = new ASTranslator();
return typeMarshaller;
}
/**
* Sets the TypeMarshaller
of the endpoint.
*
* @param typeMarshaller The TypeMarshaller object.
*/
public void setTypeMarshaller(TypeMarshaller typeMarshaller)
{
this.typeMarshaller = typeMarshaller;
}
/**
* Retrieves the URL of the endpoint.
*
* @return The URL of the endpoint.
*/
public String getUrl()
{
return url;
}
/**
* Sets the URL of the endpoint.
*
* @param url The URL of the endpoint.
*/
public void setUrl(String url)
{
this.url = url;
port = internalParsePort(url);
parsedForContext = null;
clientContextParsed = false;
}
/**
* @exclude
* Returns the url of the endpoint parsed for the client.
*
* @return The url of the endpoint parsed for the client.
*/
public String getUrlForClient()
{
if (!clientContextParsed)
{
HttpServletRequest req = FlexContext.getHttpRequest();
if (req != null)
{
String contextPath = req.getContextPath();
parseClientUrl(contextPath);
}
else
{
return url;
}
}
return parsedClientUrl;
}
/**
* @exclude
* Returns the total throughput for the endpoint.
*
* @return The total throughput for the endpoint.
*/
public long getThroughput()
{
EndpointControl control = (EndpointControl)getControl();
return control.getBytesDeserialized().longValue() + control.getBytesSerialized().longValue();
}
//--------------------------------------------------------------------------
//
// Other Public APIs
//
//--------------------------------------------------------------------------
/** @exclude **/
public static void addNoCacheHeaders(HttpServletRequest req, HttpServletResponse res)
{
String userAgent = req.getHeader(UserAgentManager.USER_AGENT_HEADER_NAME);
// For MSIE over HTTPS, set additional Cache-Control values.
if (req.isSecure() && userAgent != null && userAgent.indexOf(UserAgentSettings.USER_AGENT_MSIE) != -1)
res.addHeader(HEADER_NAME_CACHE_CONTROL, "no-store, no-cache, must-revalidate, post-check=0, pre-check=0, no-transform, private");
else // For the rest, set no-cache header value only.
res.addHeader(HEADER_NAME_CACHE_CONTROL, "no-cache");
// Set an expiration date in the past as well.
res.setDateHeader(HEADER_NAME_EXPIRES, 946080000000L); //Approx Jan 1, 2000
// Set Pragma no-cache header if we're not MSIE over HTTPS
if (!(req.isSecure() && userAgent != null && userAgent.indexOf(UserAgentSettings.USER_AGENT_MSIE) != -1))
res.setHeader(HEADER_NAME_PRAGMA, "no-cache");
}
/**
* @exclude
*/
public Message convertToSmallMessage(Message message)
{
if (message instanceof SmallMessage)
{
Message smallMessage = ((SmallMessage)message).getSmallMessage();
if (smallMessage != null)
message = smallMessage;
}
return message;
}
/**
* Retrieves a ConfigMap
of the endpoint properties the client
* needs. Subclasses should add additional properties to super.describeDestination
,
* or return null
if they must not send their properties to the client.
*
* @return ConfigMap The ConfigMap object.
*/
public ConfigMap describeEndpoint()
{
ConfigMap channelConfig = new ConfigMap();
if (serverOnly) // Client does not need server only endpoints.
return channelConfig;
channelConfig.addProperty(ID_ATTR, getId());
channelConfig.addProperty(TYPE_ATTR, getClientType());
ConfigMap properties = new ConfigMap();
boolean containsClientLoadBalancing = clientLoadBalancingUrls != null && !clientLoadBalancingUrls.isEmpty();
if (containsClientLoadBalancing)
{
ConfigMap clientLoadBalancing = new ConfigMap();
for (Iterator iterator = clientLoadBalancingUrls.iterator(); iterator.hasNext();)
{
ConfigMap url = new ConfigMap();
// Adding as a value rather than attribute to the parent.
url.addProperty(EMPTY_STRING, iterator.next());
clientLoadBalancing.addProperty(URL_ATTR, url);
}
properties.addProperty(CLIENT_LOAD_BALANCING_ELEMENT, clientLoadBalancing);
}
// Add endpoint uri only if no client-load-balancing urls are defined.
if (!containsClientLoadBalancing)
{
ConfigMap endpointConfig = new ConfigMap();
endpointConfig.addProperty(URI_ATTR, getUrlForClient());
channelConfig.addProperty(ENDPOINT_ELEMENT, endpointConfig);
}
if (connectTimeoutSeconds > 0)
{
ConfigMap connectTimeoutConfig = new ConfigMap();
connectTimeoutConfig.addProperty(EMPTY_STRING, String.valueOf(connectTimeoutSeconds));
properties.addProperty(CONNECT_TIMEOUT_SECONDS_ELEMENT, connectTimeoutConfig);
}
if (requestTimeoutSeconds > 0)
{
ConfigMap requestTimeoutSeconds = new ConfigMap();
requestTimeoutSeconds.addProperty(EMPTY_STRING, String.valueOf(requestTimeoutSeconds));
properties.addProperty(REQUEST_TIMEOUT_SECONDS_ELEMENT, requestTimeoutSeconds);
}
if (recordMessageTimes)
{
ConfigMap recordMessageTimesMap = new ConfigMap();
// Adding as a value rather than attribute to the parent
recordMessageTimesMap.addProperty(EMPTY_STRING, TRUE_STRING);
properties.addProperty(RECORD_MESSAGE_TIMES_ELEMENT, recordMessageTimesMap);
}
if (recordMessageSizes)
{
ConfigMap recordMessageSizesMap = new ConfigMap();
// Adding as a value rather than attribute to the parent
recordMessageSizesMap.addProperty(EMPTY_STRING, TRUE_STRING);
properties.addProperty(RECORD_MESSAGE_SIZES_ELEMENT, recordMessageSizesMap);
}
ConfigMap serialization = new ConfigMap();
serialization.addProperty(ENABLE_SMALL_MESSAGES_ELEMENT, Boolean.toString(serializationContext.enableSmallMessages));
properties.addProperty(SERIALIZATION_ELEMENT, serialization);
if (properties.size() > 0)
channelConfig.addProperty(PROPERTIES_ELEMENT, properties);
return channelConfig;
}
/**
* @exclude
* Make sure this matches with ChannelSettings.getParsedUri.
*/
public String getParsedUrl(String contextPath)
{
parseUrl(contextPath);
return parsedUrl;
}
/**
* @exclude
*/
public void handleClientMessagingVersion(Number version)
{
if (version != null)
{
boolean clientSupportsSmallMessages = version.doubleValue() >= messagingVersion;
if (clientSupportsSmallMessages && getSerializationContext().enableSmallMessages)
{
FlexSession session = FlexContext.getFlexSession();
if (session != null)
session.setUseSmallMessages(true);
}
}
}
/**
* Default implementation of the Endpoint service
method.
* Subclasses should call super.service
before their custom
* code.
*
* @param req The HttpServletRequest object.
* @param res The HttpServletResponse object.
*/
public void service(HttpServletRequest req, HttpServletResponse res)
{
validateRequestProtocol(req);
}
/**
* Typically invoked by subclasses, this method transforms decoded message data
* into the appropriate Message object and routes the Message to the endpoint's broker.
*
* @param message The decoded message data.
* @return Message The transformed message.
*/
public Message serviceMessage(Message message)
{
if (isManaged())
{
((EndpointControl) getControl()).incrementServiceMessageCount();
}
try
{
FlexContext.setThreadLocalEndpoint(this);
Message ack = null;
// Make sure this message is timestamped.
if (message.getTimestamp() == 0)
{
message.setTimestamp(System.currentTimeMillis());
}
// Reset the endpoint header for inbound messages to the id for this endpoint
// to guarantee that it's correct. Don't allow clients to spoof this.
// However, if the endpoint id is passed as null we need to tag the message to
// skip channel/endpoint validation at the destination level (MessageBroker.inspectChannel()).
if (message.getHeader(Message.ENDPOINT_HEADER) != null)
message.setHeader(Message.VALIDATE_ENDPOINT_HEADER, Boolean.TRUE);
message.setHeader(Message.ENDPOINT_HEADER, getId());
if (message instanceof CommandMessage)
{
CommandMessage command = (CommandMessage)message;
// Apply channel endpoint level constraint; always allow login commands through.
int operation = command.getOperation();
if (operation != CommandMessage.LOGIN_OPERATION)
checkSecurityConstraint(message);
// Handle general (not Consumer specific) poll requests here.
// We need to fetch all outbound messages for client subscriptions over this endpoint.
// We identify these general poll messages by their operation and a null clientId.
if (operation == CommandMessage.POLL_OPERATION && message.getClientId() == null)
{
verifyFlexClientSupport(command);
FlexClient flexClient = FlexContext.getFlexClient();
ack = handleFlexClientPollCommand(flexClient, command);
}
else if (operation == CommandMessage.DISCONNECT_OPERATION)
{
ack = handleChannelDisconnect(command);
}
else if (operation == CommandMessage.TRIGGER_CONNECT_OPERATION)
{
ack = new AcknowledgeMessage();
((AcknowledgeMessage)ack).setCorrelationId(message.getMessageId());
boolean needsConfig = false;
if (command.getHeader(CommandMessage.NEEDS_CONFIG_HEADER) != null)
needsConfig = ((Boolean)(command.getHeader(CommandMessage.NEEDS_CONFIG_HEADER)));
// Send configuration information only if the client requested.
if (needsConfig)
{
ConfigMap serverConfig = getMessageBroker().describeServices(this);
if (serverConfig.size() > 0)
ack.setBody(serverConfig);
}
}
else
{
// Block a subset of commands for legacy clients that need to be recompiled to
// interop with a 2.5+ server.
if (operation == CommandMessage.SUBSCRIBE_OPERATION || operation == CommandMessage.POLL_OPERATION)
verifyFlexClientSupport(command);
ack = getMessageBroker().routeCommandToService((CommandMessage) message, this);
// Look for client advertised features on initial connect.
if (operation == CommandMessage.CLIENT_PING_OPERATION || operation == CommandMessage.LOGIN_OPERATION)
{
Number clientVersion = (Number)command.getHeader(CommandMessage.MESSAGING_VERSION);
handleClientMessagingVersion(clientVersion);
// Also respond by advertising the messaging version on the
// acknowledgement.
ack.setHeader(CommandMessage.MESSAGING_VERSION, new Double(messagingVersion));
}
}
}
else
{
// Block any AsyncMessages from a legacy client.
if (message instanceof AsyncMessage)
verifyFlexClientSupport(message);
// Apply channel endpoint level constraint.
checkSecurityConstraint(message);
ack = getMessageBroker().routeMessageToService(message, this);
}
return ack;
}
finally
{
FlexContext.setThreadLocalEndpoint(null);
}
}
/**
* Utility method that endpoint implementations (or associated classes)
* should invoke when they receive an incoming message from a client but before
* servicing it. This method looks up or creates the proper FlexClient instance
* based upon the client the message came from and places it in the FlexContext.
*
* @param message The incoming message to process.
*
* @return The FlexClient, or null
if the message did not contain a FlexClient ID value.
*/
public FlexClient setupFlexClient(Message message)
{
FlexClient flexClient = null;
if (message.getHeaders().containsKey(Message.FLEX_CLIENT_ID_HEADER))
{
String id = (String)message.getHeaders().get(Message.FLEX_CLIENT_ID_HEADER);
// If the id is null, reset to the special token value that let's us differentiate
// between legacy clients and 2.5+ clients.
if (id == null)
id = FlexClient.NULL_FLEXCLIENT_ID;
flexClient = setupFlexClient(id);
}
return flexClient;
}
/**
* Utility method that endpoint implementations (or associated classes)
* should invoke when they receive an incoming message from a client but before
* servicing it. This method looks up or creates the proper FlexClient instance
* based upon the FlexClient ID value received from the client.
* It also associates this FlexClient instance with the current FlexSession.
*
* @param id The FlexClient ID value from the client.
*
* @return The FlexClient or null if the provided ID was null
.
*/
public FlexClient setupFlexClient(String id)
{
FlexClient flexClient = null;
if (id != null)
{
// This indicates that we're dealing with a non-legacy client that hasn't been
// assigned a FlexClient Id yet. Reset to null to generate a fresh Id.
if (id.equals(FlexClient.NULL_FLEXCLIENT_ID))
id = null;
flexClient = getMessageBroker().getFlexClientManager().getFlexClient(id);
// Make sure the FlexClient and FlexSession are associated.
FlexSession session = FlexContext.getFlexSession();
flexClient.registerFlexSession(session);
// And place the FlexClient in FlexContext for this request.
FlexContext.setThreadLocalFlexClient(flexClient);
}
return flexClient;
}
/**
* @exclude
* Performance metrics gathering property
*/
public boolean isRecordMessageSizes()
{
return recordMessageSizes;
}
/**
* @exclude
* Performance metrics gathering property
*/
public boolean isRecordMessageTimes()
{
return recordMessageTimes;
}
/**
* @exclude
*/
public void setThreadLocals()
{
if (serializationContext != null)
{
SerializationContext context = (SerializationContext)serializationContext.clone();
// Get the latest deserialization validator from the broker.
MessageBroker broker = getMessageBroker();
DeserializationValidator validator = broker == null? null : broker.getDeserializationValidator();
context.setDeserializationValidator(validator);
SerializationContext.setSerializationContext(context);
}
TypeMarshallingContext.setTypeMarshaller(getTypeMarshaller());
}
/**
* @exclude
*/
public void clearThreadLocals()
{
SerializationContext.clearThreadLocalObjects();
TypeMarshallingContext.clearThreadLocalObjects();
}
//--------------------------------------------------------------------------
//
// Protected/private methods.
//
//--------------------------------------------------------------------------
/**
* Returns the log category of the AbstractEndpoint
. Subclasses
* can override to provide a more specific logging category.
*
* @return The log category.
*/
@Override
protected String getLogCategory()
{
return LOG_CATEGORY;
}
/**
* Hook method invoked when a disconnect command is received from a client channel.
* The response returned by this method is not guaranteed to get to the client, which
* is free to terminate its physical connection at any point.
*
* @param disconnectCommand The disconnect command.
* @return The response; by default an empty AcknowledgeMessage.
*/
protected Message handleChannelDisconnect(CommandMessage disconnectCommand)
{
return new AcknowledgeMessage();
}
/**
* Hook method for varying poll reply strategies for synchronous endpoints.
* The default behavior performs a non-waited, synchronous poll for the FlexClient
* and if any messages are currently queued they are returned immediately. If no
* messages are queued an empty response is returned immediately.
*
* @param flexClient The FlexClient that issued the poll request.
* @param pollCommand The poll command from the client.
* @return The FlushResult response.
*/
protected FlushResult handleFlexClientPoll(FlexClient flexClient, CommandMessage pollCommand)
{
return flexClient.poll(getId());
}
/**
* Handles a general poll request from a FlexClient to this endpoint.
* Subclasses may override to implement different poll handling strategies.
*
* @param flexClient The FlexClient that issued the poll request.
* @param pollCommand The poll command from the client.
* @return The poll response message; either for success or fault.
*/
protected Message handleFlexClientPollCommand(FlexClient flexClient, CommandMessage pollCommand)
{
if (Log.isDebug())
Log.getLogger(getMessageBroker().getLogCategory(pollCommand)).debug(
"Before handling general client poll request. " + StringUtils.NEWLINE +
" incomingMessage: " + pollCommand + StringUtils.NEWLINE);
FlushResult flushResult = handleFlexClientPoll(flexClient, pollCommand);
Message pollResponse = null;
// Generate a no-op poll response if necessary; prevents a single client from busy polling when the server
// is doing wait()-based long-polls.
if ((flushResult instanceof PollFlushResult) && ((PollFlushResult)flushResult).isClientProcessingSuppressed())
{
pollResponse = new CommandMessage(CommandMessage.CLIENT_SYNC_OPERATION);
pollResponse.setHeader(CommandMessage.NO_OP_POLL_HEADER, Boolean.TRUE);
}
if (pollResponse == null)
{
List messagesToReturn = (flushResult != null) ? flushResult.getMessages() : null;
if (messagesToReturn != null && !messagesToReturn.isEmpty())
{
pollResponse = new CommandMessage(CommandMessage.CLIENT_SYNC_OPERATION);
pollResponse.setBody(messagesToReturn.toArray());
}
else
{
pollResponse = new AcknowledgeMessage();
}
}
// Set the adaptive poll wait time if necessary.
if (flushResult != null)
{
int nextFlushWaitTime = flushResult.getNextFlushWaitTimeMillis();
if (nextFlushWaitTime > 0)
pollResponse.setHeader(CommandMessage.POLL_WAIT_HEADER, new Integer(nextFlushWaitTime));
}
if (Log.isDebug())
{
String debugPollResult = Log.getPrettyPrinter().prettify(pollResponse);
Log.getLogger(getMessageBroker().getLogCategory(pollCommand)).debug(
"After handling general client poll request. " + StringUtils.NEWLINE +
" reply: " + debugPollResult + StringUtils.NEWLINE);
}
return pollResponse;
}
/**
* Initializes the Endpoint
with the client-load-balancing urls.
*
* @param id Id of the Endpoint
.
* @param properties Properties for the Endpoint
.
*/
protected void initializeClientLoadBalancing(String id, ConfigMap properties)
{
if (!properties.containsKey(CLIENT_LOAD_BALANCING_ELEMENT))
return;
ConfigMap clientLoadBalancing = properties.getPropertyAsMap(CLIENT_LOAD_BALANCING_ELEMENT, null);
if (clientLoadBalancing == null)
{
// Invalid {0} configuration for endpoint ''{1}''; no urls defined.
ConfigurationException ce = new ConfigurationException();
ce.setMessage(ERR_MSG_EMPTY_CLIENT_LOAD_BALANCING_ELEMENT, new Object[]{CLIENT_LOAD_BALANCING_ELEMENT, getId()});
throw ce;
}
@SuppressWarnings("unchecked")
List urls = clientLoadBalancing.getPropertyAsList(URL_ATTR, null);
if (urls == null || urls.isEmpty())
{
// Invalid {0} configuration for endpoint ''{1}''; no urls defined.
ConfigurationException ce = new ConfigurationException();
ce.setMessage(ERR_MSG_EMPTY_CLIENT_LOAD_BALANCING_ELEMENT, new Object[]{CLIENT_LOAD_BALANCING_ELEMENT, getId()});
throw ce;
}
for (Iterator iterator = urls.iterator(); iterator.hasNext();)
{
String url = iterator.next();
if (!addClientLoadBalancingUrl(url) && Log.isWarn())
log.warn("Endpoint '{0}' is ignoring the url '{1}' as it's already in the set of client-load-balancing urls.", new Object[]{id, url});
}
}
protected void checkSecurityConstraint(Message message)
{
if (securityConstraint != null)
{
getMessageBroker().getLoginManager().checkConstraint(securityConstraint);
}
}
/**
* Returns the deserializer class name used by the endpoint.
*
* @return The deserializer class name used by the endpoint.
*/
protected abstract String getDeserializerClassName();
/**
* Returns the serializer class name used by the endpoint.
*
* @return The serializer class name used by the endpoint.
*/
protected abstract String getSerializerClassName();
/**
* Returns the secure protocol scheme for the endpoint.
*
* @return The secure protocol scheme for the endpoint.
*/
protected abstract String getSecureProtocolScheme();
/**
* Returns the insecure protocol scheme for the endpoint.
*
* @return The insecure protocol scheme for the endpoint.
*/
protected abstract String getInsecureProtocolScheme();
/**
* Invoked automatically to allow the AbstractEndpoint
to setup
* its corresponding MBean control. Subclasses should override to setup and
* register their MBean control. Manageable subclasses should override this
* template method.
*
* @param broker The MessageBroker
that manages this
* AbstractEndpoint
.
*/
protected abstract void setupEndpointControl(MessageBroker broker);
/**
* Validates the endpoint url scheme.
*/
protected void validateEndpointProtocol()
{
String scheme = isSecure()? getSecureProtocolScheme() : getInsecureProtocolScheme();
if (!url.startsWith(scheme))
{
ConfigurationException ce = new ConfigurationException();
ce.setMessage(ERR_MSG_INVALID_URL_SCHEME, new Object[] {url, scheme});
throw ce;
}
}
protected void validateRequestProtocol(HttpServletRequest req)
{
// Secure url can talk to secure or non-secure endpoint.
// Non-secure url can only talk to non-secure endpoint.
boolean secure = req.isSecure();
if (!secure && isSecure())
{
// Secure endpoints must be contacted via a secure protocol.
String endpointPath = req.getServletPath() + req.getPathInfo();
SecurityException se = new SecurityException();
se.setMessage(NONSECURE_PROTOCOL, new Object[]{endpointPath});
throw se;
}
}
/**
* @exclude
* Verifies that the remote client supports the FlexClient API.
* Legacy clients that do not support this receive a message fault for any messages they send.
*
* @param message The message to verify.
*/
protected void verifyFlexClientSupport(Message message)
{
if (FlexContext.getFlexClient() == null)
{
MessageException me = new MessageException();
me.setMessage(REQUIRES_FLEXCLIENT_SUPPORT, new Object[] {message.getDestination()});
throw me;
}
}
/**
* @exclude
*/
protected Class> createClass(String className)
{
return ClassUtil.createClass(className, FlexContext.getMessageBroker() == null ? null :
FlexContext.getMessageBroker().getClassLoader());
}
// This should match with ChannelSetting.parseClientUri
private void parseClientUrl(String contextPath)
{
if (!clientContextParsed)
{
String channelEndpoint = url.trim();
// either {context-root} or {context.root} is legal
channelEndpoint = StringUtils.substitute(channelEndpoint, "{context-root}", ConfigurationConstants.CONTEXT_PATH_TOKEN);
if ((contextPath == null) && (channelEndpoint.indexOf(ConfigurationConstants.CONTEXT_PATH_TOKEN) != -1))
{
// context root must be specified before it is used
ConfigurationException e = new ConfigurationException();
e.setMessage(ConfigurationConstants.UNDEFINED_CONTEXT_ROOT, new Object[]{getId()});
throw e;
}
// simplify the number of combinations to test by ensuring our
// context path always starts with a slash
if (contextPath != null && !contextPath.startsWith("/"))
{
contextPath = "/" + contextPath;
}
// avoid double-slashes from context root by replacing /{context.root}
// in a single replacement step
if (channelEndpoint.indexOf(ConfigurationConstants.SLASH_CONTEXT_PATH_TOKEN) != -1)
{
// but avoid double-slash for /{context.root}/etc when we have
// the default context root
if ("/".equals(contextPath) && !ConfigurationConstants.SLASH_CONTEXT_PATH_TOKEN.equals(channelEndpoint))
contextPath = "";
channelEndpoint = StringUtils.substitute(channelEndpoint, ConfigurationConstants.SLASH_CONTEXT_PATH_TOKEN, contextPath);
}
// otherwise we have something like {server.name}:{server.port}{context.root}...
else
{
// but avoid double-slash for {context.root}/etc when we have
// the default context root
if ("/".equals(contextPath) && !ConfigurationConstants.CONTEXT_PATH_TOKEN.equals(channelEndpoint))
contextPath = "";
channelEndpoint = StringUtils.substitute(channelEndpoint, ConfigurationConstants.CONTEXT_PATH_TOKEN, contextPath);
}
parsedClientUrl = channelEndpoint;
clientContextParsed = true;
}
}
private int internalParsePort(String url)
{
int port = ChannelSettings.parsePort(url);
// If there is no specified port, log an info message as urls without ports are supported
if (port == 0 && Log.isInfo())
log.info("No port specified in channel URL: {0}", new Object[]{url});
return port == -1? 0 : port; // Replace -1 with 0.
}
private void parseUrl(String contextPath)
{
// Parse again only if never parsed before or parsed for a different contextPath.
if (parsedForContext == null || !parsedForContext.equals(contextPath))
{
String channelEndpoint = url.toLowerCase().trim();
// Remove protocol and host info
String insecureProtocol = getInsecureProtocolScheme() + "://";
String secureProtocol = getSecureProtocolScheme() + "://";
if (channelEndpoint.startsWith(secureProtocol) || channelEndpoint.startsWith(insecureProtocol))
{
int nextSlash = channelEndpoint.indexOf('/', 8);
if (nextSlash > 0)
{
channelEndpoint = channelEndpoint.substring(nextSlash);
}
}
// either {context-root} or {context.root} is legal
channelEndpoint = StringUtils.substitute(channelEndpoint, "{context-root}", ConfigurationConstants.CONTEXT_PATH_TOKEN);
// Remove context path info
if (channelEndpoint.startsWith(ConfigurationConstants.CONTEXT_PATH_TOKEN))
{
channelEndpoint = channelEndpoint.substring(ConfigurationConstants.CONTEXT_PATH_TOKEN.length());
}
else if (channelEndpoint.startsWith(ConfigurationConstants.SLASH_CONTEXT_PATH_TOKEN))
{
channelEndpoint = channelEndpoint.substring(ConfigurationConstants.SLASH_CONTEXT_PATH_TOKEN.length());
}
else if (contextPath.length() > 0)
{
if (channelEndpoint.startsWith(contextPath.toLowerCase()))
{
channelEndpoint = channelEndpoint.substring(contextPath.length());
}
}
// We also don't match on trailing slashes
if (channelEndpoint.endsWith("/"))
{
channelEndpoint = channelEndpoint.substring(0, channelEndpoint.length() - 1);
}
parsedUrl = channelEndpoint;
parsedForContext = contextPath;
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy