All Downloads are FREE. Search and download functionalities are using the official Maven repository.

javax.faces.component.UIWebsocket Maven / Gradle / Ivy

There is a newer version: 8.0.1
Show newest version
package javax.faces.component;

import static java.util.Collections.unmodifiableList;
import static javax.faces.push.PushContext.ENABLE_WEBSOCKET_ENDPOINT_PARAM_NAME;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.regex.Pattern;

import javax.el.ValueExpression;
import javax.faces.component.behavior.ClientBehaviorHolder;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.faces.push.Push;
import javax.faces.push.PushContext;
import javax.websocket.CloseReason.CloseCodes;

/**
 * 

* The <f:websocket> tag opens an one-way (server to client) websocket based push connection in client * side which can be reached from server side via {@link PushContext} interface injected in any CDI/container managed * artifact via @{@link Push} annotation. *

* *

* By default, the rendererType property must be set to "javax.faces.Websocket". * This value can be changed by calling the setRendererType() method. *

* *

* For detailed usage instructions, see @{@link Push} javadoc. *

* * @see Push * @since 2.3 */ public class UIWebsocket extends UIComponentBase implements ClientBehaviorHolder { // ---------------------------------------------------------------------------------------------- Manifest Constants /** *

* The standard component type for this component. *

*/ public static final String COMPONENT_TYPE = "javax.faces.Websocket"; /** *

* The standard component family for this component. *

*/ public static final String COMPONENT_FAMILY = "javax.faces.Script"; /** *

* Properties that are tracked by state saving. *

*/ enum PropertyKeys { channel, scope, user, onopen, onmessage, onclose, connected; } private static final Pattern PATTERN_CHANNEL_NAME = Pattern.compile("[\\w.-]+"); private static final String ERROR_ENDPOINT_NOT_ENABLED = "f:websocket endpoint is not enabled." + " You need to set web.xml context param '" + ENABLE_WEBSOCKET_ENDPOINT_PARAM_NAME + "' with value 'true'."; private static final String ERROR_INVALID_CHANNEL = "f:websocket 'channel' attribute '%s' does not represent a valid channel name. It is required, it may not be an" + " EL expression and it may only contain alphanumeric characters, hyphens, underscores and periods."; private static final String ERROR_INVALID_USER = "f:websocket 'user' attribute '%s' does not represent a valid user identifier. It must implement Serializable and" + " preferably have low memory footprint. Suggestion: use #{request.remoteUser} or #{someLoggedInUser.id}."; // ---------------------------------------------------------------------------------------------------- Constructors /** *

* Create a new {@link UIWebsocket} instance with default property values. *

* * @throws IllegalStateException When Websocket endpoint is not enabled. */ public UIWebsocket() { ExternalContext externalContext = FacesContext.getCurrentInstance().getExternalContext(); if (!Boolean.parseBoolean(externalContext.getInitParameter(ENABLE_WEBSOCKET_ENDPOINT_PARAM_NAME))) { throw new IllegalStateException(ERROR_ENDPOINT_NOT_ENABLED); } } // --------------------------------------------------------------------------------------------- UIComponent Methods /** *

* Returns {@link UIWebsocket#COMPONENT_FAMILY}. *

*/ @Override public String getFamily() { return COMPONENT_FAMILY; } /** *

* Set the {@link ValueExpression} used to calculate the value for the specified attribute or property name, if any. * If a {@link ValueExpression} is set for the channel or scope property, regardless of * the value, throw an illegal argument exception. If a {@link ValueExpression} is set for the user * property, and the non-null value is not an instance of Serializable, throw an illegal argument * exception. *

* * @throws IllegalArgumentException If name is one of id, parent, * channel or scope, or it name is user and the non-null value * is not an instance of Serializable. * @throws NullPointerException If name is null. */ @Override public void setValueExpression(String name, ValueExpression binding) { if (PropertyKeys.channel.toString().equals(name) || PropertyKeys.scope.toString().equals(name)) { throw new IllegalArgumentException(name); } if (PropertyKeys.user.toString().equals(name)) { Object user = binding.getValue(getFacesContext().getELContext()); if (user != null && !(user instanceof Serializable)) { throw new IllegalArgumentException(String.format(ERROR_INVALID_USER, user)); } } super.setValueExpression(name, binding); } // ------------------------------------------------------------------------------------ ClientBehaviorHolder Methods /** *

* Returns a non-null, empty, unmodifiable Collection which returns true on any * Collection#contains() invocation, indicating that all client behavior event names are acceptable. *

*/ @Override public Collection getEventNames() { return CONTAINS_EVERYTHING; } private static final Collection CONTAINS_EVERYTHING = unmodifiableList(new ArrayList() { private static final long serialVersionUID = 1L; @Override public boolean contains(Object object) { return true; } }); // ------------------------------------------------------------------------------------------------------ Properties /** * Returns the name of the websocket channel. * @return The name of the websocket channel. */ public String getChannel() { return (String) getStateHelper().eval(PropertyKeys.channel); } /** * Sets the name of the websocket channel. * It may not be an EL expression and it may only contain alphanumeric characters, hyphens, underscores and periods. * All open websockets on the same channel will receive the same push message from the server. * @param channel The name of the websocket channel. * @throws IllegalArgumentException When the value does not represent a valid channel name. */ public void setChannel(String channel) { if (channel == null || !PATTERN_CHANNEL_NAME.matcher(channel).matches()) { throw new IllegalArgumentException(String.format(ERROR_INVALID_CHANNEL, channel)); } getStateHelper().put(PropertyKeys.channel, channel); } /** * Returns the scope of the websocket channel. * @return The scope of the websocket channel. */ public String getScope() { return (String) getStateHelper().eval(PropertyKeys.scope); } /** * Sets the scope of the websocket channel. * It may not be an EL expression and allowed values are application, session and * view, case insensitive. When the value is application, then all channels with the same * name throughout the application will receive the same push message. When the value is session, then * only the channels with the same name in the current user session will receive the same push message. When the * value is view, then only the channel in the current view will receive the push message. The default * scope is application. When the user attribute is specified, then the default scope is * session. * @param scope The scope of the websocket channel. */ public void setScope(String scope) { getStateHelper().put(PropertyKeys.scope, scope); } /** * Returns the user identifier of the websocket channel. * @return The user identifier of the websocket channel. */ public Serializable getUser() { return (Serializable) getStateHelper().eval(PropertyKeys.user); } /** * Sets the user identifier of the websocket channel, so that user-targeted push messages can be sent. * All open websockets on the same channel and user will receive the same push message from the server. * It must implement Serializable and preferably have low memory footprint. * Suggestion: use #{request.remoteUser} or #{someLoggedInUser.id}. * @param user The user identifier of the websocket channel. */ public void setUser(Serializable user) { getStateHelper().put(PropertyKeys.user, user); } /** * Returns the JavaScript event handler function that is invoked when the websocket is opened. * @return The JavaScript event handler function that is invoked when the websocket is opened. */ public String getOnopen() { return (String) getStateHelper().eval(PropertyKeys.onopen); } /** * Sets the JavaScript event handler function that is invoked when the websocket is opened. * The function will be invoked with one argument: the channel name. * @param onopen The JavaScript event handler function that is invoked when the websocket is opened. */ public void setOnopen(String onopen) { getStateHelper().put(PropertyKeys.onopen, onopen); } /** * Returns the JavaScript event handler function that is invoked when a push message is received from the server. * @return The JavaScript event handler function that is invoked when a push message is received from the server. */ public String getOnmessage() { return (String) getStateHelper().eval(PropertyKeys.onmessage); } /** * Sets the JavaScript event handler function that is invoked when a push message is received from the server. The * function will be invoked with three arguments: the push message, the channel name and the raw MessageEvent itself. * @param onmessage The JavaScript event handler function that is invoked when a push message is received from the server. */ public void setOnmessage(String onmessage) { getStateHelper().put(PropertyKeys.onmessage, onmessage); } /** * Returns the JavaScript event handler function that is invoked when the websocket is closed. * @return The JavaScript event handler function that is invoked when the websocket is closed. */ public String getOnclose() { return (String) getStateHelper().eval(PropertyKeys.onclose); } /** * Sets the JavaScript event handler function that is invoked when the websocket is closed. * The function will be invoked with three arguments: the close reason code, the channel name and the raw * CloseEvent itself. Note that this will also be invoked on errors and that you can inspect the close * reason code if an error occurred and which one (i.e. when the code is not 1000). See also * RFC 6455 section 7.4.1 and {@link CloseCodes} API * for an elaborate list of all close codes. * @param onclose The JavaScript event handler function that is invoked when the websocket is closed. */ public void setOnclose(String onclose) { getStateHelper().put(PropertyKeys.onclose, onclose); } /** * Returns whether to (auto)connect the websocket or not. * @return Whether to (auto)connect the websocket or not. */ public boolean isConnected() { return (Boolean) getStateHelper().eval(PropertyKeys.connected, Boolean.TRUE); } /** * Sets whether to (auto)connect the websocket or not. Defaults to true. It's interpreted as a * JavaScript instruction whether to open or close the websocket push connection. Note that this attribute is * re-evaluated on every ajax request. You can also explicitly set it to false and then manually * control in JavaScript by OmniFaces.Push.open("channelName") and * OmniFaces.Push.close("channelName"). * @param connected Whether to (auto)connect the websocket or not. */ public void setConnected(boolean connected) { getStateHelper().put(PropertyKeys.connected, connected); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy