/*
* Copyright 2010-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.springfaces.internal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Map;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.core.GenericTypeResolver;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.annotation.Order;
import org.springframework.springfaces.FacesWrapperFactory;
import org.springframework.springfaces.SpringFacesIntegration;
import org.springframework.util.Assert;
import org.springframework.web.context.WebApplicationContext;
/**
* Utility class that can wrap JSF objects by consulting all {@link FacesWrapperFactory} objects registered within the
* {@link WebApplicationContext} containing the {@link SpringFacesIntegration} bean.
* <p>
* Wrapping will be re-applied if whenever the {@link WebApplicationContext} is reloaded. If no
* {@link SpringFacesIntegration} is {@link SpringFacesIntegration#isInstalled(ExternalContext) installed} then the
* original delegate is returned as the wrapped instance.
*
* @author Phillip Webb
* @param <T> The JSF type being managed
* @see #getWrapped()
*/
class WrapperHandler<T> {
private final Log logger = LogFactory.getLog(getClass());
/**
* The type of JSF object being managed.
*/
private Class<?> typeClass;
/**
* access to the wrapped instance
*/
private WrappedAccessor<T> wrappedAccessor;
/**
* The fully wrapped implementation. This is late binding.
* @see #getWrapped()
*/
private T wrapped;
/**
* The date that the application context used to create the wrapped object was last refreshed.
*/
private Date lastRefreshedDate;
private boolean warnOnMissingSpringFaces;
/**
* Create a mew WrapperHandler.
* @param typeClass The JSF type being wrapped
* @param wrapped The root delegate
*/
public WrapperHandler(Class<T> typeClass, T wrapped) {
Assert.notNull(typeClass, "TypeClass must not be null");
Assert.notNull(wrapped, "Delegate must not be null");
this.typeClass = typeClass;
this.wrappedAccessor = new DirectAccessor<T>(wrapped);
}
/**
* Create a mew WrapperHandler.
* @param typeClass The JSF type being wrapped
* @param delegate Access to the root delegate
*/
public WrapperHandler(Class<T> typeClass, WrappedAccessor<T> delegate) {
Assert.notNull(typeClass, "TypeClass must not be null");
Assert.notNull(delegate, "Delegate must not be null");
this.typeClass = typeClass;
this.wrappedAccessor = delegate;
}
/**
* Set if a warning message is logged due to {@link SpringFacesIntegration} not being installed. Defaults to false
* as most wrappers can be instantiated before Spring.
* @param warnOnMissingSpringFaces if a warning message should be logged
*/
public void setWarnOnMissingSpringFaces(boolean warnOnMissingSpringFaces) {
this.warnOnMissingSpringFaces = warnOnMissingSpringFaces;
}
/**
* Creates a fully wrapped implementation of the delegate by consulting all {@link FacesWrapperFactory factories}
* registered with Spring.
* @return a wrapped implementation
*/
public T getWrapped() {
FacesContext facesContext = FacesContext.getCurrentInstance();
if (facesContext == null) {
// Calls to wrapped instances can occur when there is no faces context if JSF has not yet completely
// intialized. We allow these early calls to proceed to the delegate.
this.wrapped = null;
return this.wrappedAccessor.getWrapped(WrappedAccessType.WRAP);
}
ExternalContext externalContext = facesContext.getExternalContext();
if ((this.wrapped == null)
|| (SpringFacesIntegration.isInstalled(externalContext) && (!SpringFacesIntegration
.getLastRefreshedDate(externalContext).equals(this.lastRefreshedDate)))) {
WrappedAccessType accessType = (this.wrapped == null ? WrappedAccessType.WRAP : WrappedAccessType.REWRAP);
if (this.logger.isDebugEnabled()) {
this.logger.debug((accessType == WrappedAccessType.WRAP ? "Wrapping " : "Rewrapping ")
+ this.wrappedAccessor.getDescription());
}
this.wrapped = wrap(externalContext, this.wrappedAccessor.getWrapped(accessType));
if (SpringFacesIntegration.isInstalled(externalContext)) {
this.lastRefreshedDate = SpringFacesIntegration.getLastRefreshedDate(externalContext);
}
}
return this.wrapped;
}
/**
* Wrap the specified delegate by consulting all {@link FacesWrapperFactory factories} registered with Spring.
* @param externalContext the external context
* @param delegate the root delegate
* @return a wrapped implementation
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
private T wrap(ExternalContext externalContext, T delegate) {
if (!SpringFacesIntegration.isInstalled(externalContext)) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("SpringFacesSupport is not yet installed, wrapping will be deferred");
}
if (this.logger.isWarnEnabled() && this.warnOnMissingSpringFaces) {
this.logger
.warn("SpringFacesSupport is not installed, full Spring/JSF integration may not be availble");
}
return delegate;
}
ApplicationContext applicationContext = SpringFacesIntegration.getCurrentInstance(externalContext)
.getApplicationContext();
List<Map.Entry<String, FacesWrapperFactory>> orderdBeans = new ArrayList<Map.Entry<String, FacesWrapperFactory>>();
orderdBeans.addAll(BeanFactoryUtils
.beansOfTypeIncludingAncestors(applicationContext, FacesWrapperFactory.class).entrySet());
Collections.sort(orderdBeans, new OrderedMapEntryComparator());
T rtn = delegate;
for (Map.Entry<String, FacesWrapperFactory> entry : orderdBeans) {
FacesWrapperFactory factory = entry.getValue();
if (isFactorySupported(factory)) {
T wrapper = (T) factory.newWrapper(this.typeClass, rtn);
if (wrapper != null) {
Assert.isInstanceOf(this.typeClass, wrapper, "FacesWrapperFactory " + entry.getValue()
+ " returned incorrect type ");
if (this.logger.isDebugEnabled()) {
this.logger.debug("Wrapping " + this.typeClass.getSimpleName() + " with " + wrapper.getClass()
+ " obtained from FacesWrapperFactory " + entry.getValue());
}
postProcessWrapper(wrapper);
rtn = wrapper;
}
}
}
return rtn;
}
/**
* Determine if a given {@link FacesWrapperFactory} is suitable by resolving generic arguments.
* @param factory the factory to test
* @return <tt>true</tt> if the <tt>factory</tt> is supported, otherwise <tt>false</tt>
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
private boolean isFactorySupported(FacesWrapperFactory factory) {
Class typeArg = GenericTypeResolver.resolveTypeArgument(factory.getClass(), FacesWrapperFactory.class);
if (typeArg == null) {
Class targetClass = AopUtils.getTargetClass(factory);
if (targetClass != factory.getClass()) {
typeArg = GenericTypeResolver.resolveTypeArgument(targetClass, FacesWrapperFactory.class);
}
}
return (typeArg == null || typeArg.isAssignableFrom(this.typeClass));
}
/**
* Strategy method called after a wrapped instance has been created. Subclasses can implement custom post-processing
* as required.
* @param wrapped the newly created wrapped instance
*/
protected void postProcessWrapper(T wrapped) {
}
/**
* Convenience factory method to create a {@link WrapperHandler} with the generic type obtained from
* <tt>typeClass</tt>
* @param <T> the JSF type being managed
* @param typeClass the JSF type being managed
* @param delegate the delegate
* @return a {@link WrapperHandler}
*/
public static <T> WrapperHandler<T> get(Class<T> typeClass, T delegate) {
return new WrapperHandler<T>(typeClass, delegate);
}
/**
* {@link Comparator} implementation to sort {@link Map.Entry} values by {@link org.springframework.core.Ordered} as
* well as the {@link Order} annotation.
*/
private static class OrderedMapEntryComparator extends AnnotationAwareOrderComparator {
@Override
public int compare(Object o1, Object o2) {
return super.compare(((Map.Entry<?, ?>) o1).getValue(), ((Map.Entry<?, ?>) o2).getValue());
}
}
/**
* The various reasons that a delegate can be accessed.
*/
public enum WrappedAccessType {
/**
* The delegate is required for an initial wrap.
*/
WRAP,
/**
* The delegate is required for a re-wrap. This can occur if the application context has been refreshed.
*/
REWRAP
};
/**
* Interface to provide access to the underlying wrapped delegate. Implementations can return a different delegate
* if required.
* @param <T> The wrapped delegate type
*/
public static interface WrappedAccessor<T> {
/**
* Returns a description of the wrapped item.
* @return the description
*/
public String getDescription();
/**
* Returns the actual delegate to use.
* @param accessType the reason that the delegate is being accessed
* @return the delegate
*/
public T getWrapped(WrappedAccessType accessType);
}
/**
* Implementation of {@link WrapperHandler.WrappedAccessor} that simple returns an object instance.
* @param <T> the data type
*/
private static class DirectAccessor<T> implements WrappedAccessor<T> {
private T wrapped;
public DirectAccessor(T delegate) {
this.wrapped = delegate;
}
public String getDescription() {
return this.wrapped.getClass().getName();
}
public T getWrapped(WrappedAccessType accessType) {
return this.wrapped;
}
}
}