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

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

/*
 * Copyright (c) 1997, 2020 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0, which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * This Source Code may also be made available under the following Secondary
 * Licenses when the conditions for such availability set forth in the
 * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
 * version 2 with the GNU Classpath Exception, which is available at
 * https://www.gnu.org/software/classpath/license.html.
 *
 * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
 */

package jakarta.faces.component;

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

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

import jakarta.el.ValueExpression;
import jakarta.faces.component.behavior.ClientBehaviorHolder;
import jakarta.faces.context.ExternalContext;
import jakarta.faces.context.FacesContext;
import jakarta.faces.push.Push;
import jakarta.faces.push.PushContext;
import jakarta.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 "jakarta.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 = "jakarta.faces.Websocket"; /** *

* The standard component family for this component. *

*/ public static final String COMPONENT_FAMILY = "jakarta.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" + "Jakarta Expression Language 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 Jakarta Expression Language 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 Jakarta Expression Language 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