/* | |
* Licensed to the Apache Software Foundation (ASF) under one | |
* or more contributor license agreements. See the NOTICE file | |
* distributed with this work for additional information | |
* regarding copyright ownership. The ASF licenses this file | |
* to you 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.apache.myfaces.extensions.validator.core.validation.parameter; | |
import org.apache.myfaces.extensions.validator.internal.UsageInformation; | |
import org.apache.myfaces.extensions.validator.internal.UsageCategory; | |
import org.apache.myfaces.extensions.validator.internal.ToDo; | |
import org.apache.myfaces.extensions.validator.internal.Priority; | |
import java.util.Map; | |
import java.util.List; | |
import java.util.HashMap; | |
import java.util.ArrayList; | |
import java.util.logging.Logger; | |
import java.util.logging.Level; | |
import java.lang.annotation.Annotation; | |
import java.lang.reflect.Field; | |
import java.lang.reflect.Method; | |
import java.lang.reflect.Type; | |
import java.lang.reflect.ParameterizedType; | |
import java.lang.reflect.GenericArrayType; | |
import java.lang.reflect.WildcardType; | |
import java.lang.reflect.Modifier; | |
/** | |
* {@inheritDoc} | |
* | |
* @since x.x.3 | |
*/ | |
@UsageInformation(UsageCategory.INTERNAL) | |
public class DefaultValidationParameterExtractor implements ValidationParameterExtractor | |
{ | |
protected final Logger logger = Logger.getLogger(getClass().getName()); | |
public Map<Object, List<Object>> extract(Annotation annotation) | |
{ | |
return extractById(annotation, null); | |
} | |
public List<Object> extract(Annotation annotation, Object key) | |
{ | |
return extractById(annotation, key, null); | |
} | |
public <T> List<T> extract(Annotation annotation, Object key, Class<T> valueType) | |
{ | |
return extractById(annotation, key, valueType, null); | |
} | |
public <T> T extract(Annotation annotation, Object key, Class<T> valueType, Class valueId) | |
{ | |
List<T> results = extractById(annotation, key, valueType, valueId); | |
if(results.iterator().hasNext()) | |
{ | |
return results.iterator().next(); | |
} | |
return null; | |
} | |
@SuppressWarnings({"unchecked"}) | |
public <T> List<T> extractById(Annotation annotation, Object key, Class<T> valueType, Class valueId) | |
{ | |
List<Object> result = new ArrayList<Object>(); | |
for(Object entry : extractById(annotation, key, valueId)) | |
{ | |
if(valueType.isAssignableFrom(entry.getClass())) | |
{ | |
result.add(entry); | |
} | |
} | |
return (List<T>)result; | |
} | |
public List<Object> extractById(Annotation annotation, Object key, Class valueId) | |
{ | |
Map<Object, List<Object>> fullResult = extractById(annotation, valueId); | |
if(fullResult.containsKey(key)) | |
{ | |
return fullResult.get(key); | |
} | |
return new ArrayList<Object>(); | |
} | |
@ToDo(value = Priority.MEDIUM, description = "add web.xml parameter for performance tuning to deactivate the scan") | |
public Map<Object, List<Object>> extractById(Annotation annotation, Class valueId) | |
{ | |
Map<Object, List<Object>> result = new HashMap<Object, List<Object>>(); | |
for(Method currentAnnotationAttribute : annotation.annotationType().getDeclaredMethods()) | |
{ | |
try | |
{ | |
if(!isValidationParameter(currentAnnotationAttribute.getGenericReturnType())) | |
{ | |
continue; | |
} | |
Object parameterValue = currentAnnotationAttribute.invoke(annotation); | |
if(parameterValue instanceof Class[]) | |
{ | |
for(Class currentParameterValue : (Class[])parameterValue) | |
{ | |
//keep check so that following is true: | |
//if at least one parameter is found which tells that it isn't a blocking error, let it pass | |
processParameterValue(annotation, currentParameterValue, result, valueId); | |
} | |
} | |
else if(parameterValue instanceof Class) | |
{ | |
//keep check so that following is true: | |
//if at least one parameter is found which tells that it isn't a blocking error, let it pass | |
processParameterValue(annotation, (Class)parameterValue, result, valueId); | |
} | |
} | |
catch (Exception e) | |
{ | |
this.logger.log(Level.WARNING, "invalid method", e); | |
} | |
} | |
return result; | |
} | |
/* | |
* don't use the Introspector in this case | |
* if you have a better solution which supports all supported parameter styles (see extval wiki), | |
* you can impl. it and use it (exchange the impls. via the ExtValContext). | |
* furthermore, you can provide the fix for the community | |
*/ | |
private void processParameterValue( | |
Annotation annotation, Class paramClass, Map<Object, List<Object>> result, Class valueId) throws Exception | |
{ | |
Object key = null; | |
List<Object> parameterValues = new ArrayList<Object>(); | |
if(ValidationParameter.class.isAssignableFrom(paramClass)) | |
{ | |
List<Field> processedFields = new ArrayList<Field>(); | |
List<Method> processedMethods = new ArrayList<Method>(); | |
Class currentParamClass = paramClass; | |
while (currentParamClass != null && !Object.class.getName().equals(currentParamClass.getName())) | |
{ | |
/* | |
* process class | |
*/ | |
//support pure interface approach e.g. ViolationSeverity.Warn.class | |
for(Field currentField : currentParamClass.getDeclaredFields()) | |
{ | |
if(!processedFields.contains(currentField)) | |
{ | |
key = processFoundField(annotation, currentField, parameterValues, key, valueId); | |
processedFields.add(currentField); | |
} | |
} | |
//inspect the other methods of the implementing class | |
for(Method currentMethod : currentParamClass.getDeclaredMethods()) | |
{ | |
if(!processedMethods.contains(currentMethod)) | |
{ | |
key = processFoundMethod(currentParamClass, currentMethod, parameterValues, key, valueId); | |
processedMethods.add(currentMethod); | |
} | |
} | |
/* | |
* process interfaces | |
*/ | |
for(Class currentInterface : currentParamClass.getInterfaces()) | |
{ | |
if(!ValidationParameter.class.isAssignableFrom(currentInterface)) | |
{ | |
continue; | |
} | |
//support interface + impl. approach e.g. MyParamImpl.class | |
//(MyParamImpl implements MyParam | |
//MyParam extends ValidationParameter | |
//methods in the interface have to be marked with @ParameterValue and @ParameterKey | |
for(Method currentMethod : currentInterface.getDeclaredMethods()) | |
{ | |
if(!processedMethods.contains(currentMethod)) | |
{ | |
key = processFoundMethod(currentParamClass, currentMethod, parameterValues, key, valueId); | |
processedMethods.add(currentMethod); | |
} | |
} | |
for(Field currentField : currentInterface.getDeclaredFields()) | |
{ | |
if(!processedFields.contains(currentField)) | |
{ | |
key = processFoundField(annotation, currentField, parameterValues, key, valueId); | |
processedFields.add(currentField); | |
} | |
} | |
} | |
currentParamClass = currentParamClass.getSuperclass(); | |
} | |
} | |
key = createDefaultKey(key, paramClass); | |
if(parameterValues.isEmpty()) | |
{ | |
//@ParameterValue is optional as well | |
parameterValues.add(key); | |
} | |
if(result.containsKey(key)) | |
{ | |
result.get(key).addAll(parameterValues); | |
} | |
else | |
{ | |
result.put(key, parameterValues); | |
} | |
} | |
private Object createDefaultKey(Object key, Class currentClass) | |
{ | |
if(key == null) | |
{ | |
//check for super-interface (exclude ValidationParameter itself) | |
for(Class interfaceClass : currentClass.getInterfaces()) | |
{ | |
if(ValidationParameter.class.isAssignableFrom(interfaceClass) && | |
(!interfaceClass.getName().equals(ValidationParameter.class.getName()))) | |
{ | |
key = interfaceClass; | |
break; | |
} | |
} | |
} | |
if(key == null) | |
{ | |
key = currentClass; | |
} | |
return key; | |
} | |
private Object processFoundField( | |
Object instance, Field currentField, List<Object> paramValues, Object key, Class valueId) | |
{ | |
Object newKey = null; | |
if(key == null && currentField.isAnnotationPresent(ParameterKey.class)) | |
{ | |
try | |
{ | |
newKey = currentField.get(instance); | |
} | |
catch (Exception e) | |
{ | |
this.logger.log(Level.WARNING, "invalid field", e); | |
} | |
} | |
//no "else if" to allow both at one field | |
if(currentField.isAnnotationPresent(ParameterValue.class)) | |
{ | |
if(valueId == null || valueId.equals(currentField.getAnnotation(ParameterValue.class).id())) | |
{ | |
currentField.setAccessible(true); | |
try | |
{ | |
paramValues.add(currentField.get(instance)); | |
} | |
catch (Exception e) | |
{ | |
this.logger.log(Level.WARNING, "invalid field", e); | |
} | |
} | |
} | |
return newKey != null ? newKey : key; | |
} | |
private Object processFoundMethod( | |
Class paramClass, Method currentMethod, List<Object> parameterValues, Object key, Class valueId) | |
{ | |
Object newKey = null; | |
if(key == null && currentMethod.isAnnotationPresent(ParameterKey.class)) | |
{ | |
try | |
{ | |
if(!(Modifier.isAbstract(paramClass.getModifiers()) || Modifier.isInterface(paramClass.getModifiers()))) | |
{ | |
newKey = currentMethod.invoke(paramClass.newInstance()); | |
} | |
} | |
catch (Exception e) | |
{ | |
this.logger.log(Level.WARNING, "invalid method", e); | |
} | |
} | |
//no "else if" to allow both at one field | |
if(currentMethod.isAnnotationPresent(ParameterValue.class)) | |
{ | |
if(valueId == null || valueId.equals(currentMethod.getAnnotation(ParameterValue.class).id())) | |
{ | |
currentMethod.setAccessible(true); | |
try | |
{ | |
parameterValues.add(currentMethod.invoke(paramClass.newInstance())); | |
} | |
catch (Exception e) | |
{ | |
//check if it's a none-static inner class -> return this class | |
if(paramClass.getEnclosingClass() != null) | |
{ | |
parameterValues.add(paramClass); | |
} | |
else | |
{ | |
this.logger.log(Level.WARNING, "invalid method", e); | |
} | |
} | |
} | |
} | |
return newKey != null ? newKey : key; | |
} | |
private boolean isValidationParameter(Type genericReturnType) | |
{ | |
if(genericReturnType instanceof GenericArrayType) | |
{ | |
if(((GenericArrayType)genericReturnType).getGenericComponentType() instanceof ParameterizedType) | |
{ | |
return analyzeParameterizedType( | |
(ParameterizedType)((GenericArrayType)genericReturnType).getGenericComponentType()); | |
} | |
} | |
else if(genericReturnType instanceof ParameterizedType) | |
{ | |
return analyzeParameterizedType( | |
(ParameterizedType)genericReturnType); | |
} | |
return false; | |
} | |
private boolean analyzeParameterizedType(ParameterizedType parameterizedType) | |
{ | |
for(Type type : parameterizedType.getActualTypeArguments()) | |
{ | |
if(type instanceof WildcardType) | |
{ | |
for(Type upperBounds : ((WildcardType)type).getUpperBounds()) | |
{ | |
if(upperBounds instanceof Class && | |
//for attributes like: Class<? extends InheritedFromValidationParameter> value(); | |
ValidationParameter.class.isAssignableFrom((Class)upperBounds)) | |
{ | |
return true; | |
} | |
} | |
} | |
} | |
return false; | |
} | |
} |