| /* |
| * 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)); |
| } |
| |
| } |