com.ocpsoft.pretty.faces.config.annotation.PrettyAnnotationHandler Maven / Gradle / Ivy
/*
* Copyright 2010 Lincoln Baxter, III
*
* 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 com.ocpsoft.pretty.faces.config.annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.ocpsoft.pretty.faces.annotation.URLAction;
import com.ocpsoft.pretty.faces.annotation.URLAction.PhaseId;
import com.ocpsoft.pretty.faces.annotation.URLActions;
import com.ocpsoft.pretty.faces.annotation.URLBeanName;
import com.ocpsoft.pretty.faces.annotation.URLMapping;
import com.ocpsoft.pretty.faces.annotation.URLMappings;
import com.ocpsoft.pretty.faces.annotation.URLQueryParameter;
import com.ocpsoft.pretty.faces.annotation.URLValidator;
import com.ocpsoft.pretty.faces.config.PrettyConfigBuilder;
import com.ocpsoft.pretty.faces.config.mapping.PathValidator;
import com.ocpsoft.pretty.faces.config.mapping.QueryParameter;
import com.ocpsoft.pretty.faces.config.mapping.UrlAction;
import com.ocpsoft.pretty.faces.config.mapping.UrlMapping;
import com.ocpsoft.pretty.faces.el.ConstantExpression;
import com.ocpsoft.pretty.faces.el.LazyBeanNameFinder;
import com.ocpsoft.pretty.faces.el.LazyExpression;
import com.ocpsoft.pretty.faces.el.PrettyExpression;
@SuppressWarnings("unchecked")
public class PrettyAnnotationHandler
{
/**
* The logger
*/
private static final Log log = LogFactory.getLog(PrettyAnnotationHandler.class);
/**
* A map assigning mapping IDs to {@link UrlMapping} instances
*/
private final Map urlMappings = new HashMap();
/**
* A map to resolve the bean name for a {@link Class} object
*/
private final Map beanNameMap = new HashMap();
/**
* The {@link ActionSpec} objects generated from annotation scanning
*/
private final List urlActions = new ArrayList();
/**
* The {@link QueryParamSpec} objects generated from annotation scanning
*/
private final List queryParamList = new ArrayList();
/**
* Reference to the {@link LazyBeanNameFinder}
*/
private final LazyBeanNameFinder beanNameFinder;
/**
* Constructor
*/
public PrettyAnnotationHandler(LazyBeanNameFinder beanNameFinder)
{
this.beanNameFinder = beanNameFinder;
}
/**
* This method scans the supplied class for PrettyFaces annotations. The
* method must be called for every class that should be scanner before
* finally calling {@link #build(PrettyConfigBuilder)}.
*
* @param clazz The class to scan
*/
public void processClass(Class clazz)
{
// log class name on trace level
if (log.isTraceEnabled())
{
log.trace("Analyzing class: " + clazz.getName());
}
try
{
// scan for PrettyAnnotation class
// returns the mapping ID, if an annotation was found
String[] classMappingIds = processClassMappingAnnotations(clazz);
// scan for PrettyBean annotation
processPrettyBeanAnnotation(clazz);
// process annotations on public methods
for (Method method : clazz.getMethods())
{
processMethodAnnotations(method, classMappingIds);
}
// loop over fields to find URLQueryParameter annotations
for (Field field : clazz.getDeclaredFields())
{
processFieldAnnotations(field, classMappingIds);
}
}
catch (NoClassDefFoundError e)
{
// reference to another class unknown to the classloader
log.debug("Unable to process class '" + clazz.getName() + "': " + e.toString());
}
}
/**
* Checks for PrettyFaces mapping annotations on a single class
*
* @param clazz
* Class to scan
* @return The IDs of the mappings found on the class
*/
public String[] processClassMappingAnnotations(Class clazz)
{
// list of all mapping IDs found on the class
List classMappingIds = new ArrayList();
// get reference to @URLMapping annotation
URLMapping mappingAnnotation = (URLMapping) clazz.getAnnotation(URLMapping.class);
// process annotation if it exists
if (mappingAnnotation != null)
{
String mappingId = processPrettyMappingAnnotation(clazz, mappingAnnotation);
classMappingIds.add(mappingId);
}
// container annotation
URLMappings mappingsAnnotation = (URLMappings) clazz.getAnnotation(URLMappings.class);
if (mappingsAnnotation != null)
{
// process all contained @URLMapping annotations
for (URLMapping child : mappingsAnnotation.mappings())
{
String mappingId = processPrettyMappingAnnotation(clazz, child);
classMappingIds.add(mappingId);
}
}
// return list of mappings found
return classMappingIds.toArray(new String[classMappingIds.size()]);
}
/**
* Process a single {@link URLMapping} annotation.
*
* @param clazz The class that the annotation was found on
* @param mappingAnnotation The annotation to process
* @return The mapping ID of the mapping found
*/
private String processPrettyMappingAnnotation(Class clazz, URLMapping mappingAnnotation)
{
// log class name
if (log.isTraceEnabled())
{
log.trace("Found @URLMapping annotation on class: " + clazz.getName());
}
// create UrlMapping from annotation
UrlMapping mapping = new UrlMapping();
mapping.setId(mappingAnnotation.id());
mapping.setParentId(mappingAnnotation.parentId());
mapping.setPattern(mappingAnnotation.pattern());
mapping.setViewId(mappingAnnotation.viewId());
mapping.setOutbound(mappingAnnotation.outbound());
mapping.setOnPostback(mappingAnnotation.onPostback());
// register mapping
Object existingMapping = urlMappings.put(mapping.getId(), mapping);
// fail if a mapping with this ID already existed
if (existingMapping != null)
{
throw new IllegalArgumentException("Duplicated mapping id: " + mapping.getId());
}
// At bean name to lookup map if it has been specified
if ((mappingAnnotation.beanName() != null) && (mappingAnnotation.beanName().length() > 0))
{
beanNameMap.put(clazz, mappingAnnotation.beanName());
}
// process validations
for (URLValidator validationAnnotation : mappingAnnotation.validation())
{
// index attribute is required in this case
if (validationAnnotation.index() < 0)
{
throw new IllegalArgumentException(
"Please set the index of the path parameter you want to validate with the @URLValidator specified on mapping: " + mapping.getId());
}
// prepare PathValidator
PathValidator pathValidator = new PathValidator();
pathValidator.setIndex(validationAnnotation.index());
pathValidator.setOnError(validationAnnotation.onError());
pathValidator.setValidatorIds(join(validationAnnotation.validatorIds(), " "));
// optional validator method
if (!isBlank(validationAnnotation.validator()))
{
pathValidator.setValidatorExpression(new ConstantExpression(validationAnnotation.validator()));
}
// add PathValidator to the mapping
mapping.getPathValidators().add(pathValidator);
}
// return mapping id
return mapping.getId().trim();
}
/**
* Checks the class for a {@link URLBeanName} annotation.
*
* @param clazz Class to scan
*/
private void processPrettyBeanAnnotation(Class clazz)
{
// get reference to @URLMapping annotation
URLBeanName prettyBean = (URLBeanName) clazz.getAnnotation(URLBeanName.class);
// process annotation if it exists
if (prettyBean != null)
{
// log class name
if (log.isTraceEnabled())
{
log.trace("Found @URLBeanName annotation on class: " + clazz.getName());
}
// add bean to map
beanNameMap.put(clazz, prettyBean.value());
}
}
/**
* Searches for {@link URLAction} or {@link URLActions} annotations on a
* method.
*
* @param method Method to scan
* @param classMappingIds The mapping IDs of the class this method belongs to
*/
private void processMethodAnnotations(Method method, String[] classMappingIds)
{
// is there a @URLAction annotation on the class?
URLAction actionAnnotation = method.getAnnotation(URLAction.class);
if (actionAnnotation != null)
{
processPrettyActionAnnotation(actionAnnotation, method, classMappingIds);
}
// is there a @URLAction container annotation on the class?
URLActions actionsAnnotation = method.getAnnotation(URLActions.class);
if (actionsAnnotation != null)
{
// process all @URLAction annotations
for (URLAction child : actionsAnnotation.actions())
{
processPrettyActionAnnotation(child, method, classMappingIds);
}
}
}
/**
* Searches for {@link URLQueryParameter} annotations on a single field.
*
* @param field Field to scan
* @param classMappingIds The mapping IDs of the class this method belongs to
*/
private void processFieldAnnotations(Field field, String[] classMappingIds)
{
// Is there a @URLQueryParameter annotation?
URLQueryParameter queryParamAnnotation = field.getAnnotation(URLQueryParameter.class);
if (queryParamAnnotation != null)
{
// create a QueryParamSpec from the annotation
QueryParamSpec queryParam = new QueryParamSpec();
queryParam.setFieldName(field.getName());
queryParam.setOwnerClass(field.getDeclaringClass());
queryParam.setName(queryParamAnnotation.value());
queryParam.setOnPostback(queryParamAnnotation.onPostback());
// check which mapping the action belongs to
if (!isBlank(queryParamAnnotation.mappingId()))
{
// action belongs to the mapping mentioned with mappingId attribute
queryParam.setMappingIds(new String[] { queryParamAnnotation.mappingId().trim() });
}
else if (classMappingIds != null && classMappingIds.length > 0)
{
// use the mappings found on the class
queryParam.setMappingIds(classMappingIds);
}
else
{
throw new IllegalArgumentException("Unable to find a suitable mapping "
+ "for the query-parameter definied on field '" + field.getName() + "' in class '"
+ field.getDeclaringClass().getName() + "'. Either place a @URLMapping annotation on the "
+ "class or reference a foreign mapping using the 'mappingId' attribute.");
}
// check if there is also a validation annotation placed on the field
URLValidator validationAnnotation = field.getAnnotation(URLValidator.class);
// check if annotation has been found
if (validationAnnotation != null)
{
// set validation options on the QueryParamSpec object
queryParam.setValidatorIds(validationAnnotation.validatorIds());
queryParam.setOnError(validationAnnotation.onError());
queryParam.setValidator(validationAnnotation.validator());
}
// add the new spec object to the list of specs
queryParamList.add(queryParam);
}
}
/**
* Creates a {@link UrlAction} object from the supplied {@link URLAction}
* annotation
*
* @param actionAnnotation The annotation
* @param method The method that was annotated
* @param classMappingIds the mapping IDs of the current class
*/
private void processPrettyActionAnnotation(URLAction actionAnnotation, Method method, String[] classMappingIds)
{
// Create ActionSpec
ActionSpec actionSpec = new ActionSpec();
actionSpec.setMethod(method);
actionSpec.setOnPostback(actionAnnotation.onPostback());
actionSpec.setPhaseId(actionAnnotation.phaseId());
// check which mapping the action belongs to
if (!isBlank(actionAnnotation.mappingId()))
{
// action belongs to the mapping mentioned with mappingId attribute
actionSpec.setMappingIds(new String[] { actionAnnotation.mappingId().trim() });
}
else if ( classMappingIds != null && classMappingIds.length > 0 )
{
// use the mapping found on the class
actionSpec.setMappingIds( classMappingIds );
}
else
{
// No mapping found... throw an exception..
throw new IllegalArgumentException("Unable to find a suitable mapping "
+ "for the action definied on method '" + method.getName() + "' in class '"
+ method.getDeclaringClass().getName() + "'. Either place a @URLMapping annotation on the "
+ "class or reference a foreign mapping using the 'mappingId' attribute.");
}
// add action to list of actions
urlActions.add(actionSpec);
}
/**
* Returns true
for "blank" strings.
*
* @param str Input string
* @return true
if string is null
or trimmed value
* is empty
*/
private static boolean isBlank(String str)
{
return (str == null) || (str.trim().length() == 0);
}
/**
* This methods adds all mappings found to the supplied
* {@link PrettyConfigBuilder}. It should be called after all classes has
* been scanned via {@link #processClass(Class)}.
*
* @param builder The builder to add the mappings to
*/
public void build(PrettyConfigBuilder builder)
{
// process all actions found
for (ActionSpec actionSpec : urlActions)
{
// create an action for each referenced mapping
for (String mappingId : actionSpec.getMappingIds())
{
// Get the mapping references by the action
UrlMapping mapping = urlMappings.get(mappingId);
/*
* Fail for unresolved mappings. This may happen when the user places
* invalid mapping IDs in the mappingId attribute of
*
* @URLAction or @URLQueryParameter
*/
if (mapping == null)
{
throw new IllegalArgumentException("Unable to find the mapping '" + mappingId
+ "' referenced at method '" + actionSpec.getMethod().getName() + "' in class '"
+ actionSpec.getMethod().getDeclaringClass().getName() + "'.");
}
// build UrlMapping
UrlAction urlAction = new UrlAction();
urlAction.setPhaseId(actionSpec.getPhaseId());
urlAction.setOnPostback(actionSpec.isOnPostback());
// try to get bean name
Class clazz = actionSpec.getMethod().getDeclaringClass();
// build expression
PrettyExpression expression = buildPrettyExpression(clazz, actionSpec.getMethod().getName());
urlAction.setAction(expression);
// trace
if (log.isTraceEnabled())
{
log.trace("Adding action expression '" + urlAction.getAction() + "' to mapping: " + mapping.getId());
}
// register this action
mapping.addAction(urlAction);
}
}
for (QueryParamSpec queryParamSpec : queryParamList)
{
// create a query param for each referenced mapping
for (String mappingId : queryParamSpec.getMappingIds())
{
// Get the mapping references by the query param
UrlMapping mapping = urlMappings.get(mappingId);
// fail for unresolved mappings
if (mapping == null)
{
throw new IllegalArgumentException("Unable to find the mapping '" + mappingId
+ "' referenced at field '" + queryParamSpec.getFieldName() + "' in class '"
+ queryParamSpec.getOwnerClass().getName() + "'.");
}
// build UrlMapping
QueryParameter queryParam = new QueryParameter();
queryParam.setName(queryParamSpec.getName());
queryParam.setOnError(queryParamSpec.getOnError());
queryParam.setValidatorIds(join(queryParamSpec.getValidatorIds(), " "));
queryParam.setOnPostback(queryParamSpec.isOnPostback());
// optional validator method
if (!isBlank(queryParamSpec.getValidator()))
{
queryParam.setValidatorExpression(new ConstantExpression(queryParamSpec.getValidator()));
}
// try to get bean name
Class> clazz = queryParamSpec.getOwnerClass();
// build expression
PrettyExpression expression = buildPrettyExpression(clazz, queryParamSpec.getFieldName());
queryParam.setExpression(expression);
// trace
if (log.isTraceEnabled())
{
log.trace("Registered query-param '" + queryParam.getName() + "' to '" + expression + "' in mapping: "
+ mapping.getId());
}
// register this action
mapping.addQueryParam(queryParam);
}
}
// finally register all mappings
for (UrlMapping mapping : urlMappings.values())
{
builder.addMapping(mapping);
}
}
/**
* Creates a {@link PrettyExpression} for a class and component. This method
* may return a {@link ConstantExpression} or a {@link LazyExpression}.
*
* @param clazz The class of the bean
* @param component the component (property or method name)
* @return The expression
*/
private PrettyExpression buildPrettyExpression(Class> clazz, String component)
{
if (log.isTraceEnabled())
{
log.trace("Searching name of bean: " + clazz.getName());
}
// get name from internal map build from @URLBeanName annotations and
// previously resolved names
String beanName = beanNameMap.get(clazz);
// return a constant expression
if (beanName != null)
{
if (log.isTraceEnabled())
{
log.trace("Got bean name from @URLBeanName annotation: " + beanName);
}
return new ConstantExpression("#{" + beanName + "." + component + "}");
}
// build a lazy expression
else
{
if (log.isTraceEnabled())
{
log.trace("Name of bean not found. Building lazy expression for: " + clazz.getName());
}
return new LazyExpression(beanNameFinder, clazz, component);
}
}
/**
* Joins the list of values.
*
* @param values values to join
* @param separator the separator to use
* @return joined list of values
*/
private static String join(String[] values, String separator)
{
StringBuilder result = new StringBuilder();
if (values != null)
{
for (int i = 0; i < values.length; i++)
{
if (i > 0)
{
result.append(separator);
}
result.append(values[i]);
}
}
return result.toString();
}
/**
* Internal class to hold parameters of a {@link URLAction} annotation.
*
* @author Christian Kaltepoth
*/
private static class ActionSpec
{
private Method method;
private boolean onPostback;
private PhaseId phaseId;
private String[] mappingIds;
public boolean isOnPostback()
{
return onPostback;
}
public void setOnPostback(boolean onPostback)
{
this.onPostback = onPostback;
}
public PhaseId getPhaseId()
{
return phaseId;
}
public void setPhaseId(PhaseId phaseId)
{
this.phaseId = phaseId;
}
public Method getMethod()
{
return method;
}
public void setMethod(Method method)
{
this.method = method;
}
public String[] getMappingIds()
{
return mappingIds;
}
public void setMappingIds(String[] mappingIds)
{
this.mappingIds = mappingIds;
}
}
/**
* Internal class to hold parameters of a Pretty annotation.
*
* @author Christian Kaltepoth
*/
private static class QueryParamSpec
{
private String fieldName;
private Class> ownerClass;
private String[] mappingIds;
private String name;
private String onError;
private String[] validatorIds = {};
private String validator;
private boolean onPostback;
public String getValidator()
{
return validator;
}
public void setValidator(String validator)
{
this.validator = validator;
}
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public String getFieldName()
{
return fieldName;
}
public void setFieldName(String fieldName)
{
this.fieldName = fieldName;
}
public Class> getOwnerClass()
{
return ownerClass;
}
public void setOwnerClass(Class> ownerClass)
{
this.ownerClass = ownerClass;
}
public String getOnError()
{
return onError;
}
public void setOnError(String onError)
{
this.onError = onError;
}
public String[] getValidatorIds()
{
return validatorIds;
}
public void setValidatorIds(String[] validatorIds)
{
this.validatorIds = validatorIds;
}
public boolean isOnPostback()
{
return onPostback;
}
public void setOnPostback(boolean onPostback)
{
this.onPostback = onPostback;
}
public String[] getMappingIds()
{
return mappingIds;
}
public void setMappingIds(String[] mappingIds)
{
this.mappingIds = mappingIds;
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy