blob: 33937881c692963398eef185ac1011356a0a292c [file] [log] [blame]
/*
* 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.tomee.security;
import javax.el.ELProcessor;
import javax.enterprise.inject.spi.BeanManager;
import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class TomEEELInvocationHandler implements InvocationHandler {
private final Annotation annotation;
private final ELProcessor processor;
public TomEEELInvocationHandler(final Annotation annotation, final ELProcessor processor) {
this.annotation = annotation;
this.processor = processor;
}
@Override
public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
// todo optimize and cache methods
// avoid stack overflow because of infinite loop (See bellow)
// if method is already the expression one, just invoke it
if (method.getName().endsWith("Expression") && method.getReturnType().equals(String.class)) {
return method.invoke(annotation, args);
}
try {
final Method expressionMethod = annotation.getClass().getDeclaredMethod(method.getName() + "Expression", method.getParameterTypes());
final String expression = (String) expressionMethod.invoke(proxy, args);
// if there is an expression, it takes precedence over the static one
if (!expression.isEmpty()) {
return eval(expression, method.getReturnType()); // use the return type of the static method instead
} else {
return method.invoke(annotation, args);
}
} catch (final NoSuchMethodException e) { // expression equivalent not found for the given method
return method.invoke(annotation, args);
} catch (final InvocationTargetException e) { // unwrap the invocation target exception so we get the actual error
throw e.getTargetException();
}
}
private Object eval(final String expression, final Class<?> expectedType) {
// expression maybe #{expression} instead of ${expression}
// the ELProcessor anyways wraps it with ${}
final String sanitizedExpression = expression.replaceAll("^[#$]\\{(.+)}$", "$1");
// ELProcessor does not do a good job with enums, so try to be a bit better (not sure)
// otherwise, let the EL processor do its best to convert into the expected value
if (!isEnumOrArrayOfEnums(expectedType)) {
return processor.getValue(sanitizedExpression, expectedType);
}
final Object value = processor.getValue(sanitizedExpression, Object.class);
// Convert single enum name to single enum
if (expectedType.isEnum() && value instanceof String) {
// yeah could use Enum.valueOf ....
return of(expectedType, value);
}
// Convert single enum name to enum array (multiple enum values not supported)
if (expectedType.isArray() && value instanceof String) {
final Class<?> enumType = expectedType.getComponentType();
if (enumType.isEnum()) { // just in case
final Enum<?> enumConstant = (Enum<?>) of(enumType, value);
final Enum<?>[] outcomeArray = (Enum<?>[]) Array.newInstance(enumType, 1);
outcomeArray[0] = enumConstant;
return outcomeArray;
}
// else not sure what else we can do but let the Object go
}
return value;
}
private boolean isEnumOrArrayOfEnums(final Class type) {
if (type.isEnum()) {
return true;
}
if (type.isArray()) {
final Class componentType = type.getComponentType();
return componentType.isEnum();
}
return false;
}
private <T /*extends Enum<T>*/> T of(final Class<T> type, final Object name) {
try {
return (T) type.getDeclaredMethod("valueOf", String.class).invoke(null, name);
} catch (final Exception e) {
// this will most likely result in a conversion error, but at least we know
// it won't be swallowed
return (T) name;
}
}
public static <T extends Annotation> T of(final Class<T> annotationClass, final T annotation, final BeanManager beanManager) {
final ELProcessor elProcessor = new ELProcessor();
elProcessor.getELManager().addELResolver(beanManager.getELResolver());
return (T) Proxy.newProxyInstance(annotation.getClass().getClassLoader(),
new Class[]{annotationClass},
new TomEEELInvocationHandler(annotation, elProcessor));
}
public static <T extends Annotation> T of(final Class<T> annotationClass, final T annotation, final ELProcessor elProcessor) {
return (T) Proxy.newProxyInstance(annotation.getClass().getClassLoader(),
new Class[]{annotationClass},
new TomEEELInvocationHandler(annotation, elProcessor));
}
}