/*
* Copyright 2017 OmniFaces
*
* 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 org.omnifaces.util;
import static org.omnifaces.util.Faces.getApplication;
import static org.omnifaces.util.Faces.getCurrentPhaseId;
import static org.omnifaces.util.Faces.getViewRoot;
import javax.faces.application.Application;
import javax.faces.component.UIComponent;
import javax.faces.component.UIViewRoot;
import javax.faces.event.ComponentSystemEvent;
import javax.faces.event.ComponentSystemEventListener;
import javax.faces.event.PhaseEvent;
import javax.faces.event.PhaseId;
import javax.faces.event.PhaseListener;
import javax.faces.event.SystemEvent;
import javax.faces.event.SystemEventListener;
import org.omnifaces.eventlistener.CallbackPhaseListener;
import org.omnifaces.eventlistener.DefaultPhaseListener;
import org.omnifaces.eventlistener.DefaultViewEventListener;
/**
* <p>
* Collection of utility methods for the JSF API with respect to working with system and phase events.
*
* <h3>Usage</h3>
* <p>
* Some examples:
* <pre>
* // Add a callback to the current view which should run during every after phase of the render response on same view.
* Events.subscribeToViewAfterPhase(PhaseId.RENDER_RESPONSE, new Callback.Void() {
* @Override
* public void invoke() {
* // ...
* }
* });
* </pre>
* <pre>
* // Add a callback to the current request which should run during before phase of the render response on current request.
* Events.subscribeToRequestBeforePhase(PhaseId.RENDER_RESPONSE, new Callback.Void() {
* @Override
* public void invoke() {
* // ...
* }
* });
* </pre>
* <pre>
* // Add a callback to the current view which should run during the pre render view event.
* Events.subscribeToViewEvent(PreRenderViewEvent.class, new Callback.SerializableVoid() {
* @Override
* public void invoke() {
* // ...
* }
* });
* </pre>
* <p>
* Note that you can specify any phase ID or system event to your choice.
*
* @author Arjan Tijms
* @author Bauke Scholtz
* @see CallbackPhaseListener
*/
public final class Events {
// Constants ------------------------------------------------------------------------------------------------------
private static final String ERROR_UNSUBSCRIBE_TOO_LATE =
"The render response phase is too late to unsubscribe the view event listener. Do it in an earlier phase.";
// Constructors ---------------------------------------------------------------------------------------------------
private Events() {
// Hide constructor.
}
// Application scoped event listeners -----------------------------------------------------------------------------
/**
* Subscribe the given system event listener to the current application that get invoked every time when the given
* system event type is published in the current application.
* @param type The system event type to be observed.
* @param listener The system event listener to be subscribed.
* @since 2.0
* @see Application#subscribeToEvent(Class, SystemEventListener)
*/
public static void subscribeToApplicationEvent(Class<? extends SystemEvent> type, SystemEventListener listener) {
getApplication().subscribeToEvent(type, listener);
}
/**
* Subscribe the given callback to the current application that get invoked every time when the given
* system event type is published in the current application.
* @param type The system event type to be observed.
* @param callback The callback to be invoked.
* @since 2.0
* @see #subscribeToApplicationEvent(Class, SystemEventListener)
*/
public static void subscribeToApplicationEvent(Class<? extends SystemEvent> type, Callback.SerializableVoid callback) {
subscribeToApplicationEvent(type, createSystemEventListener(Events.<SystemEvent>wrap(callback)));
}
/**
* Subscribe the given callback to the current application that get invoked every time when the given
* system event type is published in the current application.
* @param type The system event type to be observed.
* @param callback The callback to be invoked.
* @since 2.0
* @see #subscribeToApplicationEvent(Class, SystemEventListener)
*/
public static void subscribeToApplicationEvent(Class<? extends SystemEvent> type, Callback.SerializableWithArgument<SystemEvent> callback) {
subscribeToApplicationEvent(type, createSystemEventListener(callback));
}
// View scoped event listeners ------------------------------------------------------------------------------------
/**
* Subscribe the given system event listener to the current view that get invoked every time when the given
* system event type is published on the current view.
* @param type The system event type to be observed.
* @param listener The system event listener to be subscribed.
* @since 1.2
* @see UIViewRoot#subscribeToViewEvent(Class, SystemEventListener)
*/
public static void subscribeToViewEvent(Class<? extends SystemEvent> type, SystemEventListener listener) {
getViewRoot().subscribeToViewEvent(type, listener);
}
/**
* Subscribe the given callback to the current view that get invoked every time when the given
* system event type is published on the current view.
* @param type The system event type to be observed.
* @param callback The callback to be invoked.
* @since 1.2
* @see #subscribeToViewEvent(Class, SystemEventListener)
*/
public static void subscribeToViewEvent(Class<? extends SystemEvent> type, Callback.SerializableVoid callback) {
subscribeToViewEvent(type, createSystemEventListener(Events.<SystemEvent>wrap(callback)));
}
/**
* Subscribe the given callback to the current view that get invoked every time when the given
* system event type is published on the current view.
* @param type The system event type to be observed.
* @param callback The callback to be invoked.
* @since 2.0
* @see #subscribeToViewEvent(Class, SystemEventListener)
*/
public static void subscribeToViewEvent(Class<? extends SystemEvent> type, Callback.SerializableWithArgument<SystemEvent> callback) {
subscribeToViewEvent(type, createSystemEventListener(callback));
}
// View scoped phase listeners ------------------------------------------------------------------------------------
/**
* Adds the given phase listener to the current view.
* The difference with {@link #addRequestPhaseListener(PhaseListener)} is that the given phase listener is invoked
* during every (postback) request on the same view instead of only during the current request.
* @param listener The phase listener to be added to the current view.
* @since 2.0
* @see UIViewRoot#addPhaseListener(PhaseListener)
*/
public static void addViewPhaseListener(PhaseListener listener) {
getViewRoot().addPhaseListener(listener);
}
/**
* Subscribe the given callback instance to the current view that get invoked every time before given phase ID.
* @param phaseId The phase ID to be observed.
* @param callback The callback to be invoked.
* @since 2.0
* @see #addViewPhaseListener(PhaseListener)
*/
public static void subscribeToViewBeforePhase(PhaseId phaseId, Callback.Void callback) {
addViewPhaseListener(createBeforePhaseListener(phaseId, Events.<PhaseEvent>wrap(callback)));
}
/**
* Subscribe the given callback instance to the current view that get invoked every time before given phase ID.
* @param phaseId The phase ID to be observed.
* @param callback The callback to be invoked.
* @since 2.0
* @see #addViewPhaseListener(PhaseListener)
*/
public static void subscribeToViewBeforePhase(PhaseId phaseId, Callback.WithArgument<PhaseEvent> callback) {
addViewPhaseListener(createBeforePhaseListener(phaseId, callback));
}
/**
* Subscribe the given callback instance to the current view that get invoked every time after given phase ID.
* @param phaseId The phase ID to be observed.
* @param callback The callback to be invoked.
* @since 2.0
* @see #addViewPhaseListener(PhaseListener)
*/
public static void subscribeToViewAfterPhase(PhaseId phaseId, Callback.Void callback) {
addViewPhaseListener(createAfterPhaseListener(phaseId, Events.<PhaseEvent>wrap(callback)));
}
/**
* Subscribe the given callback instance to the current view that get invoked every time after given phase ID.
* @param phaseId The phase ID to be observed.
* @param callback The callback to be invoked.
* @since 2.0
* @see #addViewPhaseListener(PhaseListener)
*/
public static void subscribeToViewAfterPhase(PhaseId phaseId, Callback.WithArgument<PhaseEvent> callback) {
addViewPhaseListener(createAfterPhaseListener(phaseId, callback));
}
// Request scoped component event listeners -----------------------------------------------------------------------
/**
* Subscribe the given callback instance to the given component that get invoked only in the current request when
* the given component system event type is published on the given component. The difference with
* {@link UIComponent#subscribeToEvent(Class, ComponentSystemEventListener)} is that this listener is request
* scoped instead of view scoped as component system event listeners are by default saved in JSF state and thus
* inherently view scoped.
* @param component The component to subscribe the given callback instance to.
* @param type The system event type to be observed.
* @param callback The callback to be invoked.
* @since 2.1
* @see UIComponent#subscribeToEvent(Class, ComponentSystemEventListener)
* @see #unsubscribeFromComponentEvent(UIComponent, Class, ComponentSystemEventListener)
*/
public static void subscribeToRequestComponentEvent(final UIComponent component,
final Class<? extends ComponentSystemEvent> type, final Callback.WithArgument<ComponentSystemEvent> callback)
{
component.subscribeToEvent(type, new ComponentSystemEventListener() {
@Override
public void processEvent(ComponentSystemEvent event) {
unsubscribeFromComponentEvent(component, type, this); // Prevent it from being saved in JSF state.
callback.invoke(event);
}
});
}
// Request scoped phase listeners ---------------------------------------------------------------------------------
/**
* Adds the given phase listener to the current request.
* The difference with {@link #addViewPhaseListener(PhaseListener)} is that the given phase listener is invoked
* only during the current request instead of during every (postback) request on the same view.
* @param listener The phase listener to be added to the current request.
* @since 2.0
* @see CallbackPhaseListener
*/
public static void addRequestPhaseListener(PhaseListener listener) {
CallbackPhaseListener.add(listener);
}
/**
* Subscribe the given callback instance to the current request that get invoked before given phase ID.
* @param phaseId The phase ID to be observed.
* @param callback The callback to be invoked.
* @since 2.0
* @see #addRequestPhaseListener(PhaseListener)
*/
public static void subscribeToRequestBeforePhase(PhaseId phaseId, Callback.Void callback) {
addRequestPhaseListener(createBeforePhaseListener(phaseId, Events.<PhaseEvent>wrap(callback)));
}
/**
* Subscribe the given callback instance to the current request that get invoked before given phase ID.
* @param phaseId The phase ID to be observed.
* @param callback The callback to be invoked.
* @since 2.0
* @see #addRequestPhaseListener(PhaseListener)
*/
public static void subscribeToRequestBeforePhase(PhaseId phaseId, Callback.WithArgument<PhaseEvent> callback) {
addRequestPhaseListener(createBeforePhaseListener(phaseId, callback));
}
/**
* Subscribe the given callback instance to the current request that get invoked after given phase ID.
* @param phaseId The phase ID to be observed.
* @param callback The callback to be invoked.
* @since 2.0
* @see #addRequestPhaseListener(PhaseListener)
*/
public static void subscribeToRequestAfterPhase(PhaseId phaseId, Callback.Void callback) {
addRequestPhaseListener(createAfterPhaseListener(phaseId, Events.<PhaseEvent>wrap(callback)));
}
/**
* Subscribe the given callback instance to the current request that get invoked after given phase ID.
* @param phaseId The phase ID to be observed.
* @param callback The callback to be invoked.
* @since 2.0
* @see #addRequestPhaseListener(PhaseListener)
*/
public static void subscribeToRequestAfterPhase(PhaseId phaseId, Callback.WithArgument<PhaseEvent> callback) {
addRequestPhaseListener(createAfterPhaseListener(phaseId, callback));
}
// Component scoped event listeners -------------------------------------------------------------------------------
/**
* Unsubscribe the given event listener on the given event from the given component. Normally, you would use
* {@link UIComponent#unsubscribeFromEvent(Class, ComponentSystemEventListener)} for this, but this wouldn't work
* when executed inside {@link ComponentSystemEventListener#processEvent(javax.faces.event.ComponentSystemEvent)},
* as it would otherwise end up in a <code>ConcurrentModificationException</code> while JSF is iterating over all
* system event listeners. The trick is to perform the unsubscribe during the after phase of the current request
* phase {@link #subscribeToRequestAfterPhase(PhaseId, org.omnifaces.util.Callback.Void)}.
* @param component The component to unsubscribe the given event listener from.
* @param event The event associated with the given event listener.
* @param listener The event listener to be unsubscribed from the given component.
* @throws IllegalStateException When this method is invoked during render response phase, because it would be too
* late to remove it from the view state.
* @since 2.1
* @see #subscribeToRequestAfterPhase(PhaseId, org.omnifaces.util.Callback.Void)
* @see UIComponent#unsubscribeFromEvent(Class, ComponentSystemEventListener)
*/
public static void unsubscribeFromComponentEvent
(final UIComponent component, final Class<? extends SystemEvent> event, final ComponentSystemEventListener listener)
{
PhaseId currentPhaseId = getCurrentPhaseId();
if (currentPhaseId == PhaseId.RENDER_RESPONSE) {
throw new IllegalStateException(ERROR_UNSUBSCRIBE_TOO_LATE);
}
subscribeToRequestAfterPhase(currentPhaseId, new Callback.Void() {
@Override
public void invoke() {
component.unsubscribeFromEvent(event, listener);
}
});
}
// Helpers --------------------------------------------------------------------------------------------------------
private static <A> Callback.WithArgument<A> wrap(final Callback.Void callback) {
return new Callback.WithArgument<A>() {
@Override
public void invoke(A argument) {
callback.invoke();
}
};
}
private static <A> Callback.SerializableWithArgument<A> wrap(final Callback.SerializableVoid callback) {
return new Callback.SerializableWithArgument<A>() {
private static final long serialVersionUID = 1L;
@Override
public void invoke(A argument) {
callback.invoke();
}
};
}
private static SystemEventListener createSystemEventListener(final Callback.SerializableWithArgument<SystemEvent> callback) {
return new DefaultViewEventListener() {
@Override
public void processEvent(SystemEvent event) {
callback.invoke(event);
}
};
}
private static PhaseListener createBeforePhaseListener(PhaseId phaseId, final Callback.WithArgument<PhaseEvent> callback) {
return new DefaultPhaseListener(phaseId) {
private static final long serialVersionUID = 1L;
@Override
public void beforePhase(PhaseEvent event) {
callback.invoke(event);
}
};
}
private static PhaseListener createAfterPhaseListener(PhaseId phaseId, final Callback.WithArgument<PhaseEvent> callback) {
return new DefaultPhaseListener(phaseId) {
private static final long serialVersionUID = 1L;
@Override
public void afterPhase(PhaseEvent event) {
callback.invoke(event);
}
};
}
}