/* * Copyright 2004-2012 the original author or authors. * * 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.springframework.faces.model; import java.lang.reflect.Method; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import javax.faces.component.UIComponent; import javax.faces.component.UIData; import javax.faces.component.UIViewRoot; import javax.faces.event.AbortProcessingException; import javax.faces.event.ActionEvent; import javax.faces.event.ActionListener; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.faces.webflow.FlowActionListener; import org.springframework.util.ClassUtils; import org.springframework.util.ReflectionUtils; /** * Custom {@link ActionListener} that inspects the {@link UIComponent} that signaled the current {@link ActionEvent} to * determine whether it is a child of any iterator type of component (such as {@link UIData}) that uses a * {@link SelectionAware} data model implementation. If a containing SelectionAware model is found, the row containing * the event-signaling component instance will be selected. This enables convenient access to the selected model state * at any time through EL expressions such as #{model.selectedRow.id} without having to rely on the whether or not the * current row index is pointing to the desired row as it would need to be to use an expression such as * #{model.rowData.id} * * @author Jeremy Grelle */ public class SelectionTrackingActionListener implements ActionListener { private static final Method NO_MATCH = ClassUtils.getMethodIfAvailable(System.class, "currentTimeMillis", (Class<?>[]) null); private static final Log logger = LogFactory.getLog(FlowActionListener.class); private final ActionListener delegate; private final Map<Class<?>, Method> valueMethodCache = new ConcurrentHashMap<Class<?>, Method>(256); public SelectionTrackingActionListener(ActionListener delegate) { this.delegate = delegate; } public void processAction(ActionEvent event) throws AbortProcessingException { trackSelection(event.getComponent()); this.delegate.processAction(event); } private void trackSelection(UIComponent component) { // Find parent component with a SelectionAware model if it exists UIComponent currentComponent = component; while (currentComponent.getParent() != null && !(currentComponent.getParent() instanceof UIViewRoot)) { UIComponent parent = currentComponent.getParent(); Method valueAccessor = getValueMethod(parent.getClass()); if (valueAccessor != null) { Object value = ReflectionUtils.invokeMethod(valueAccessor, parent); if (value != null && value instanceof SelectionAware) { ((SelectionAware<?>) value).setCurrentRowSelected(true); if (logger.isDebugEnabled()) { logger.debug("Row selection has been set on the current SelectionAware data model."); } break; } } currentComponent = currentComponent.getParent(); } } private Method getValueMethod(Class<?> parentClass) { Method method = this.valueMethodCache.get(parentClass); if (method == null) { method = ReflectionUtils.findMethod(parentClass, "getValue"); this.valueMethodCache.put(parentClass, (method != null) ? method : NO_MATCH); } return (method != NO_MATCH) ? method : null; } }