/*******************************************************************************
* Copyright (c) 2012-2016 Codenvy, S.A.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.everrest.core.impl;
import com.google.common.base.MoreObjects;
import org.everrest.core.ApplicationContext;
import org.everrest.core.DependencySupplier;
import org.everrest.core.FieldInjector;
import org.everrest.core.impl.method.ParameterResolverFactory;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.Encoded;
import javax.ws.rs.MatrixParam;
import javax.ws.rs.PathParam;
import javax.ws.rs.QueryParam;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.Provider;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.util.List;
import static javax.ws.rs.core.Response.Status.BAD_REQUEST;
import static javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR;
import static javax.ws.rs.core.Response.Status.NOT_FOUND;
import static org.everrest.core.impl.method.ParameterHelper.PROVIDER_FIELDS_ANNOTATIONS;
import static org.everrest.core.impl.method.ParameterHelper.RESOURCE_FIELDS_ANNOTATIONS;
/**
* @author andrew00x
*/
public class FieldInjectorImpl implements FieldInjector {
/** All annotations including JAX-RS annotation. */
private final Annotation[] annotations;
/** JAX-RS annotation. */
private final Annotation annotation;
/**
* Default value for this parameter, default value can be used if there is not found required parameter in request. See {@link
* javax.ws.rs.DefaultValue}.
*/
private final String defaultValue;
/** See {@link javax.ws.rs.Encoded}. */
private final boolean encoded;
private final Field field;
private final ParameterResolverFactory parameterResolverFactory;
/**
* Setter for field. If setter available it will be used for field initialization. Otherwise field initialized directly.
*/
private final Method setter;
/**
* @param field
* java.lang.reflect.Field
*/
public FieldInjectorImpl(Field field, ParameterResolverFactory parameterResolverFactory) {
this.field = field;
this.parameterResolverFactory = parameterResolverFactory;
this.annotations = field.getDeclaredAnnotations();
final Class<?> declaringClass = field.getDeclaringClass();
this.setter = getSetter(declaringClass, field);
Annotation annotation = null;
String defaultValue = null;
boolean encoded = false;
final boolean isProvider = declaringClass.getAnnotation(Provider.class) != null;
final List<String> allowedAnnotation = isProvider ? PROVIDER_FIELDS_ANNOTATIONS : RESOURCE_FIELDS_ANNOTATIONS;
for (int i = 0, length = annotations.length; i < length; i++) {
Class<?> annotationType = annotations[i].annotationType();
if (allowedAnnotation.contains(annotationType.getName())) {
if (annotation != null) {
throw new RuntimeException(
String.format("JAX-RS annotations on one of fields %s are equivocality. Annotations: %s and %s can't be applied to one field. ",
field, annotation, annotations[i]));
}
annotation = annotations[i];
} else if (annotationType == Encoded.class && !isProvider) {
encoded = true;
} else if (annotationType == DefaultValue.class && !isProvider) {
defaultValue = ((DefaultValue)annotations[i]).value();
}
}
this.defaultValue = defaultValue;
this.annotation = annotation;
this.encoded = encoded || declaringClass.getAnnotation(Encoded.class) != null;
}
private Method getSetter(Class<?> clazz, Field field) {
Method setter = null;
try {
String name = field.getName();
String setterName = "set" + Character.toUpperCase(name.charAt(0)) + name.substring(1);
setter = clazz.getMethod(setterName, field.getType());
} catch (NoSuchMethodException ignored) {
}
return setter;
}
@Override
public Annotation getAnnotation() {
return annotation;
}
@Override
public Annotation[] getAnnotations() {
return annotations;
}
@Override
public String getDefaultValue() {
return defaultValue;
}
@Override
public Class<?> getParameterClass() {
return field.getType();
}
@Override
public Type getGenericType() {
return field.getGenericType();
}
@Override
public boolean isEncoded() {
return encoded;
}
@Override
public String getName() {
return field.getName();
}
@Override
public void inject(Object resource, ApplicationContext context) {
try {
Object value = null;
if (annotation != null) {
value = parameterResolverFactory.createParameterResolver(annotation).resolve(this, context);
} else {
DependencySupplier dependencies = context.getDependencySupplier();
if (dependencies != null) {
value = dependencies.getInstance(this);
}
}
if (value != null) {
if (setter != null) {
setter.invoke(resource, value);
} else {
if (!Modifier.isPublic(field.getModifiers())) {
field.setAccessible(true);
}
field.set(resource, value);
}
}
} catch (Exception e) {
if (annotation != null) {
Class<?> annotationType = annotation.annotationType();
if (annotationType == PathParam.class || annotationType == QueryParam.class || annotationType == MatrixParam.class) {
throw new WebApplicationException(e, Response.status(NOT_FOUND).build());
}
throw new WebApplicationException(e, Response.status(BAD_REQUEST).build());
}
throw new WebApplicationException(e, Response.status(INTERNAL_SERVER_ERROR).build());
}
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("name", getName())
.add("annotation", annotation)
.add("type", getParameterClass())
.add("encoded", encoded)
.add("defaultValue", defaultValue)
.omitNullValues()
.toString();
}
}