/*
* #%L
* Wisdom-Framework
* %%
* Copyright (C) 2013 - 2014 Wisdom Framework
* %%
* 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.
* #L%
*/
package org.wisdom.router.parameter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.wisdom.api.content.ParameterFactories;
import org.wisdom.api.http.Context;
import org.wisdom.api.router.parameters.ActionParameter;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
/**
* Handles the {@link org.wisdom.api.annotations.BeanParameter} annotated parameters.
*/
public class BeanHandler implements RouteParameterHandler {
private static final java.lang.String SETTER_PREFIX = "set";
private static final Logger LOGGER = LoggerFactory.getLogger(BeanHandler.class);
/**
* Creates the parameter's value.
*
* @param argument the argument
* @param context the current HTTP context
* @param engine the converter
* @return the created object
*/
@Override
public Object create(ActionParameter argument, Context context, ParameterFactories engine) {
Object object = createNewInstance(argument.getRawType(), context, engine);
for (Method method : argument.getRawType().getMethods()) {
if (method.getName().startsWith(SETTER_PREFIX)) {
if (method.getParameterTypes().length != 1) {
LOGGER.warn("The class {} has a setter method called {} but with too many parameters to be " +
"injected with the 'BeanParameter' annotation", argument.getRawType().getName(),
method.getName());
continue;
}
// Only 1 parameter
Annotation[] annotation = method.getParameterAnnotations()[0];
Class<?> typesOfParameter = method.getParameterTypes()[0];
Type genericTypeOfParameter = method.getGenericParameterTypes()[0];
ActionParameter parameter = ActionParameter.from(method, annotation, typesOfParameter,
genericTypeOfParameter);
// An exception is thrown if we can't build the parameter object.
Object value = Bindings.create(parameter, context, engine);
if (value != null) {
inject(object, method, value);
}
}
}
return object;
}
private void inject(Object object, Method method, Object value) {
try {
if (!method.isAccessible()) {
method.setAccessible(true);
}
method.invoke(object, value);
} catch (IllegalAccessException e) {
throw new IllegalArgumentException("Cannot inject the value " + value + " in the method " + method
.getName() + " from " + object.getClass().getName(), e);
} catch (InvocationTargetException e) { //NOSONAR
throw new IllegalArgumentException("Cannot inject the value " + value + " in the method " + method
.getName() + " from " + object.getClass().getName(), e.getTargetException());
}
}
public static List<ActionParameter> buildActionParameterList(Constructor cst) {
List<ActionParameter> arguments = new ArrayList<>();
Annotation[][] annotations = cst.getParameterAnnotations();
Class<?>[] typesOfParameters = cst.getParameterTypes();
Type[] genericTypeOfParameters = cst.getGenericParameterTypes();
for (int i = 0; i < annotations.length; i++) {
arguments.add(ActionParameter.from(cst, annotations[i], typesOfParameters[i],
genericTypeOfParameters[i]));
}
return arguments;
}
private Object createNewInstance(Class<?> rawType, Context context, ParameterFactories engine) {
try {
// If we have an empty constructor use it.
Constructor<?> cst = getNoArgConstructor(rawType);
if (cst != null) {
return cst.newInstance();
}
// Try to get a constructor with annotated parameters.
cst = findConstructor(rawType);
if (cst == null) {
throw new IllegalArgumentException("Cannot build an instance of '" + rawType.getName() + "', " +
"cannot find a suitable constructor.");
}
List<ActionParameter> parameters = buildActionParameterList(cst);
Object[] values = new Object[parameters.size()];
for (int i = 0; i < parameters.size(); i++) {
ActionParameter argument = parameters.get(i);
values[i] = Bindings.create(argument, context, engine);
}
return cst.newInstance(values);
} catch (InstantiationException | IllegalAccessException e) {
throw new IllegalArgumentException("Cannot build an instance of '" + rawType.getName(), e);
} catch (InvocationTargetException e) { //NOSONAR
throw new IllegalArgumentException("Cannot build an instance of '" + rawType.getName(), e.getTargetException());
}
}
private Constructor<?> findConstructor(Class<?> rawType) {
for (Constructor constructor : rawType.getConstructors()) {
Annotation[][] annotations = constructor.getParameterAnnotations();
// Just check that all parameters are annotated, a more in-depth check is done during the creation of the
// actual parameter.
for (Annotation[] param : annotations) {
if (param.length == 0) {
// Not suitable.
continue;
}
return constructor;
}
}
return null;
}
private static Constructor<?> getNoArgConstructor(Class<?> rawType) {
try {
return rawType.getConstructor();
} catch (NoSuchMethodException e) { // NOSONAR
// Ignore it.
}
return null;
}
}