/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 1997-2017 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* https://glassfish.java.net/public/CDDL+GPL_1_1.html
* or packager/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package com.sun.faces.application.applicationimpl;
import static com.sun.faces.util.Util.notNull;
import static java.util.logging.Level.WARNING;
import static javax.faces.application.ProjectStage.Development;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
import javax.faces.application.Application;
import javax.faces.application.ProjectStage;
import javax.faces.component.UIViewRoot;
import javax.faces.context.FacesContext;
import javax.faces.event.AbortProcessingException;
import javax.faces.event.ExceptionQueuedEvent;
import javax.faces.event.ExceptionQueuedEventContext;
import javax.faces.event.SystemEvent;
import javax.faces.event.SystemEventListener;
import javax.faces.event.SystemEventListenerHolder;
import com.sun.faces.application.applicationimpl.events.ComponentSystemEventHelper;
import com.sun.faces.application.applicationimpl.events.EventInfo;
import com.sun.faces.application.applicationimpl.events.ReentrantLisneterInvocationGuard;
import com.sun.faces.application.applicationimpl.events.SystemEventHelper;
import com.sun.faces.util.FacesLogger;
public class Events {
private static final Logger LOGGER = FacesLogger.APPLICATION.getLogger();
private static final String CONTEXT = "context";
private static final String LISTENER = "listener";
private static final String SOURCE = "source";
private static final String SYSTEM_EVENT_CLASS = "systemEventClass";
private final SystemEventHelper systemEventHelper = new SystemEventHelper();
private final ComponentSystemEventHelper compSysEventHelper = new ComponentSystemEventHelper();
/*
* This class encapsulates the behavior to prevent infinite loops when the publishing of one
* event leads to the queueing of another event of the same type. Special provision is made to
* allow the case where this guaring mechanims happens on a per-FacesContext,
* per-SystemEvent.class type basis.
*/
private ReentrantLisneterInvocationGuard listenerInvocationGuard = new ReentrantLisneterInvocationGuard();
/**
* @see javax.faces.application.Application#publishEvent(FacesContext, Class, Object)
*/
public void publishEvent(FacesContext context, Class<? extends SystemEvent> systemEventClass, Object source, ProjectStage projectStage) {
publishEvent(context, systemEventClass, null, source, projectStage);
}
/**
* @see javax.faces.application.Application#publishEvent(FacesContext, Class, Object)
*/
public void publishEvent(FacesContext context, Class<? extends SystemEvent> systemEventClass, Class<?> sourceBaseType, Object source, ProjectStage projectStage) {
notNull(CONTEXT, context);
notNull(SYSTEM_EVENT_CLASS, systemEventClass);
notNull(SOURCE, source);
if (!needsProcessing(context, systemEventClass)) {
return;
}
// Source is not compatible with the provided base type.
// Log a warning that the types are incompatible and return.
if (projectStage == Development && sourceBaseType != null && !sourceBaseType.isInstance(source)) {
if (LOGGER.isLoggable(WARNING)) {
LOGGER.log(WARNING, "jsf.application.publish.event.base_type_mismatch", new Object[] { source.getClass().getName(), sourceBaseType.getName() });
}
return;
}
try {
// The side-effect of calling invokeListenersFor
// will create a SystemEvent object appropriate to event/source
// combination. This event will be passed on subsequent invocations
// of invokeListenersFor
// Look for and invoke any listeners stored on the source instance.
SystemEvent event = invokeComponentListenersFor(systemEventClass, source);
// Look for and invoke any 'view' listeners
event = invokeViewListenersFor(context, systemEventClass, event, source);
// Look for and invoke any listeners stored on the application using source type.
event = invokeListenersFor(systemEventClass, event, source, sourceBaseType, true);
// Look for and invoke any listeners not specific to the source class
invokeListenersFor(systemEventClass, event, source, null, false);
} catch (AbortProcessingException ape) {
context.getApplication().publishEvent(context, ExceptionQueuedEvent.class, new ExceptionQueuedEventContext(context, ape));
}
}
/**
* @see Application#subscribeToEvent(Class, javax.faces.event.SystemEventListener)
*/
public void subscribeToEvent(Class<? extends SystemEvent> systemEventClass, SystemEventListener listener) {
subscribeToEvent(systemEventClass, null, listener);
}
/**
* @see Application#subscribeToEvent(Class, Class, javax.faces.event.SystemEventListener)
*/
public void subscribeToEvent(Class<? extends SystemEvent> systemEventClass, Class<?> sourceClass, SystemEventListener listener) {
notNull(SYSTEM_EVENT_CLASS, systemEventClass);
notNull(LISTENER, listener);
getListeners(systemEventClass, sourceClass).add(listener);
}
/**
* @see Application#unsubscribeFromEvent(Class, Class, javax.faces.event.SystemEventListener)
*/
public void unsubscribeFromEvent(Class<? extends SystemEvent> systemEventClass, Class<?> sourceClass, SystemEventListener listener) {
notNull(SYSTEM_EVENT_CLASS, systemEventClass);
notNull(LISTENER, listener);
Set<SystemEventListener> listeners = getListeners(systemEventClass, sourceClass);
if (listeners != null) {
listeners.remove(listener);
}
}
/**
* @return the SystemEventListeners that should be used for the provided combination of
* SystemEvent and source.
*/
private Set<SystemEventListener> getListeners(Class<? extends SystemEvent> systemEvent, Class<?> sourceClass) {
Set<SystemEventListener> listeners = null;
EventInfo sourceInfo = systemEventHelper.getEventInfo(systemEvent, sourceClass);
if (sourceInfo != null) {
listeners = sourceInfo.getListeners();
}
return listeners;
}
private boolean needsProcessing(FacesContext context, Class<? extends SystemEvent> systemEventClass) {
return context.isProcessingEvents() || ExceptionQueuedEvent.class.isAssignableFrom(systemEventClass);
}
/**
* @return process any listeners for the specified SystemEventListenerHolder and return any
* SystemEvent that may have been created as a side-effect of processing the listeners.
*/
private SystemEvent invokeComponentListenersFor(Class<? extends SystemEvent> systemEventClass, Object source) {
if (source instanceof SystemEventListenerHolder) {
List<SystemEventListener> listeners = ((SystemEventListenerHolder) source).getListenersForEventClass(systemEventClass);
if (null == listeners) {
return null;
}
EventInfo eventInfo = compSysEventHelper.getEventInfo(systemEventClass, source.getClass());
return processListeners(listeners, null, source, eventInfo);
}
return null;
}
private SystemEvent invokeViewListenersFor(FacesContext ctx, Class<? extends SystemEvent> systemEventClass, SystemEvent event, Object source) {
SystemEvent result = event;
if (listenerInvocationGuard.isGuardSet(ctx, systemEventClass)) {
return result;
}
listenerInvocationGuard.setGuard(ctx, systemEventClass);
UIViewRoot root = ctx.getViewRoot();
try {
if (root != null) {
List<SystemEventListener> listeners = root.getViewListenersForEventClass(systemEventClass);
if (null == listeners) {
return null;
}
EventInfo rootEventInfo = systemEventHelper.getEventInfo(systemEventClass, UIViewRoot.class);
// process view listeners
result = processListenersAccountingForAdds(listeners, event, source, rootEventInfo);
}
} finally {
listenerInvocationGuard.clearGuard(ctx, systemEventClass);
}
return result;
}
/**
* Traverse the <code>List</code> of listeners and invoke any that are relevent for the
* specified source.
*
* @throws javax.faces.event.AbortProcessingException propagated from the listener invocation
*/
private SystemEvent invokeListenersFor(Class<? extends SystemEvent> systemEventClass, SystemEvent event, Object source, Class<?> sourceBaseType,
boolean useSourceLookup) throws AbortProcessingException {
EventInfo eventInfo = systemEventHelper.getEventInfo(systemEventClass, source, sourceBaseType, useSourceLookup);
if (eventInfo != null) {
Set<SystemEventListener> listeners = eventInfo.getListeners();
event = processListeners(listeners, event, source, eventInfo);
}
return event;
}
/**
* Iterate through and invoke the listeners. If the passed event was <code>null</code>, create
* the event, and return it.
*/
private SystemEvent processListeners(Collection<SystemEventListener> listeners, SystemEvent event, Object source, EventInfo eventInfo) {
if (listeners != null && !listeners.isEmpty()) {
ArrayList<SystemEventListener> list = new ArrayList<>(listeners);
for (SystemEventListener curListener : list) {
if (curListener != null && curListener.isListenerForSource(source)) {
if (event == null) {
event = eventInfo.createSystemEvent(source);
}
assert event != null;
if (event.isAppropriateListener(curListener)) {
event.processListener(curListener);
}
}
}
}
return event;
}
private SystemEvent processListenersAccountingForAdds(List<SystemEventListener> listeners, SystemEvent event, Object source, EventInfo eventInfo) {
if (listeners != null && !listeners.isEmpty()) {
// copy listeners
// go thru copy completely
// compare copy to original
// if original differs from copy, make a new copy.
// The new copy consists of the original list - processed
SystemEventListener listenersCopy[] = new SystemEventListener[listeners.size()];
int i = 0;
for (i = 0; i < listenersCopy.length; i++) {
listenersCopy[i] = listeners.get(i);
}
Map<SystemEventListener, Boolean> processedListeners = new HashMap<>(listeners.size());
boolean processedSomeEvents = false, originalDiffersFromCopy = false;
do {
i = 0;
originalDiffersFromCopy = false;
if (0 < listenersCopy.length) {
for (i = 0; i < listenersCopy.length; i++) {
SystemEventListener curListener = listenersCopy[i];
if (curListener != null && curListener.isListenerForSource(source)) {
if (event == null) {
event = eventInfo.createSystemEvent(source);
}
assert event != null;
if (!processedListeners.containsKey(curListener) && event.isAppropriateListener(curListener)) {
processedSomeEvents = true;
event.processListener(curListener);
processedListeners.put(curListener, Boolean.TRUE);
}
}
}
if (originalDiffersFromCopy(listeners, listenersCopy)) {
originalDiffersFromCopy = true;
listenersCopy = copyListWithExclusions(listeners, processedListeners);
}
}
} while (originalDiffersFromCopy && processedSomeEvents);
}
return event;
}
private boolean originalDiffersFromCopy(Collection<SystemEventListener> original, SystemEventListener copy[]) {
boolean foundDifference = false;
int i = 0, originalLen = original.size(), copyLen = copy.length;
if (originalLen == copyLen) {
SystemEventListener originalItem, copyItem;
Iterator<SystemEventListener> iter = original.iterator();
while (iter.hasNext() && !foundDifference) {
originalItem = iter.next();
copyItem = copy[i++];
foundDifference = originalItem != copyItem;
}
} else {
foundDifference = true;
}
return foundDifference;
}
private SystemEventListener[] copyListWithExclusions(Collection<SystemEventListener> original, Map<SystemEventListener, Boolean> excludes) {
SystemEventListener[] result = null, temp = new SystemEventListener[original.size()];
int i = 0;
for (SystemEventListener cur : original) {
if (!excludes.containsKey(cur)) {
temp[i++] = cur;
}
}
result = new SystemEventListener[i];
System.arraycopy(temp, 0, result, 0, i);
return result;
}
}