package com.atlassian.stash.internal.rest.inject;

import com.atlassian.stash.internal.rest.exception.ResourceContextInjectionFailedException;
import com.google.common.base.Throwables;
import com.sun.jersey.api.core.HttpContext;
import com.sun.jersey.server.impl.inject.AbstractHttpContextInjectable;

import java.lang.annotation.Annotation;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

/**
 * Simplifies exception handling for resource injectable implementations.
 * <p>
 * Any {@code RuntimeException}s thrown by {@link #doGetValue(HttpContext)} will be wrapped in a
 * {@link ResourceContextInjectionFailedException}.
 *
 * @since 2.4
 */
public abstract class AbstractResourceInjectable<T> extends AbstractHttpContextInjectable<T> {

    static final Annotation[] EMPTY_ANNOTATIONS = new Annotation[0];
    private final Type type;

    public AbstractResourceInjectable() {
        // Capture the type here so we can easily create a map in ResourceContextInjectableProvider
        type = ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
    }

    @Override
    public T getValue(HttpContext httpContext) {
        try {
            return doGetValue(httpContext);
        } catch (RuntimeException e) {
            // if already a ResourceContextInjectionFailedException, rethrow the exception as it is
            Throwables.propagateIfInstanceOf(e, ResourceContextInjectionFailedException.class);
            throw new ResourceContextInjectionFailedException(e);
        }
    }

    public Type getType() {
        return type;
    }

    /**
     * Implemented in derived classes to produce the value to be injected. Implementations may throw exceptions
     * normally; {@link #getValue(HttpContext)} will see to their handling.
     *
     * @param context the context for the request
     * @return the value to be injected, which may be {@code null} to not inject a value
     */
    protected abstract T doGetValue(HttpContext context);

}
