InjectLinkFieldDescriptor.java

/*
 * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0, which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * This Source Code may also be made available under the following Secondary
 * Licenses when the conditions for such availability set forth in the
 * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
 * version 2 with the GNU Classpath Exception, which is available at
 * https://www.gnu.org/software/classpath/license.html.
 *
 * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
 */

package org.glassfish.jersey.linking;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URI;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.ws.rs.BeanParam;
import javax.ws.rs.HttpMethod;
import javax.ws.rs.Path;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Link;

import org.glassfish.jersey.linking.mapping.ResourceMappingContext;
import org.glassfish.jersey.server.model.AnnotatedMethod;
import org.glassfish.jersey.server.model.MethodList;

/**
 * Utility class for working with {@link InjectLink} annotated fields.
 *
 * @author Mark Hadley
 * @author Gerard Davison (gerard.davison at oracle.com)
 */
class InjectLinkFieldDescriptor extends FieldDescriptor implements InjectLinkDescriptor {

    private InjectLink link;
    private Class<?> type;
    private Map<String, String> bindings;

    /**
     * C'tor
     *
     * @param f the field to inject
     * @param l the InjectLink annotation
     * @param t the class that contains field f
     */
    InjectLinkFieldDescriptor(Field f, InjectLink l, Class<?> t) {
        super(f);
        link = l;
        type = t;
        bindings = new HashMap<>();
        for (Binding binding : l.bindings()) {
            bindings.put(binding.name(), binding.value());
        }
    }

    /**
     * Injects the uri into the field.
     *
     * @param instance the target for the injection
     * @param uri the value to inject
     */
    void setPropertyValue(Object instance, URI uri) {
        setAccessibleField(field);
        try {

            Object value;
            if (Objects.equals(URI.class, type)) {
                value = uri;
            } else if (Link.class.isAssignableFrom(type)) {

                // Make a link with the correct bindings
                value = getLink(uri);
            } else if (Objects.equals(String.class, type)) {
                value = uri.toString();
            } else {
                throw new IllegalArgumentException("Field type " + type + " not one of supported String,URI and Link");
            }

            field.set(instance, value);
        } catch (IllegalArgumentException | IllegalAccessException ex) {
            Logger.getLogger(InjectLinkFieldDescriptor.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    /**
     * Simple delegate to {@link InjectLink#style()}
     * @return {@link InjectLink#style()}
     */
    @Override
    public InjectLink.Style getLinkStyle() {
        return link.style();
    }

    /**
     * Returns the template based on the {@link ResourceMappingContext}
     *
     * @param rmc the context
     * @return the link template
     */
    @Override
    public String getLinkTemplate(ResourceMappingContext rmc) {
        return getLinkTemplate(rmc, link);
    }


    /**
     * Returns the template based on the {@link ResourceMappingContext}
     *
     * @param rmc the context
     * @param link the link
     * @return the link template
     */
    static String getLinkTemplate(ResourceMappingContext rmc, InjectLink link) {
        String template = null;
        if (Objects.equals(link.resource(), Class.class)) {
            template = link.value();
        } else {
            ResourceMappingContext.Mapping map = rmc.getMapping(link.resource());
            if (map != null) {
                template = map.getTemplate().getTemplate();
            } else {
                // extract template from specified class' @Path annotation
                Path path = link.resource().getAnnotation(Path.class);
                template = path == null ? "" : path.value();
            }

            // extract template from specified class' @Path annotation
            if (!link.method().isEmpty()) {
                // append value of method's @Path annotation
                MethodList methods = new MethodList(link.resource());
                methods = methods.withMetaAnnotation(HttpMethod.class);
                for (AnnotatedMethod method : methods) {
                    if (!Objects.equals(method.getMethod().getName(), link.method())) {
                        continue;
                    }
                    StringBuilder builder = new StringBuilder();
                    builder.append(template);

                    Path methodPath = method.getAnnotation(Path.class);
                    if (methodPath != null) {
                        String methodTemplate = methodPath.value();

                        if (!(template.endsWith("/") || methodTemplate.startsWith("/"))) {
                            builder.append("/");
                        }
                        builder.append(methodTemplate);
                    }

                    CharSequence querySubString = extractQueryParams(method);

                    if (querySubString.length() > 0) {
                        builder.append("{?");
                        builder.append(querySubString);
                        builder.append("}");
                    }

                    template = builder.toString();
                    break;
                }
            }
        }

        return template;
    }

    static StringBuilder extractQueryParams(AnnotatedMethod method) throws SecurityException {
        // append query parameters
        StringBuilder querySubString = new StringBuilder();
        int parameterIndex = 0;
        for (Annotation[] paramAnns : method.getParameterAnnotations()) {
            for (Annotation ann : paramAnns) {
                if (Objects.equals(ann.annotationType(), QueryParam.class)) {
                    querySubString.append(((QueryParam) ann).value());
                    querySubString.append(',');
                }
                if (Objects.equals(ann.annotationType(), BeanParam.class)) {
                    Class<?> beanParamType = method.getParameterTypes()[parameterIndex];
                    Field[] fields = beanParamType.getFields();
                    for (Field field : fields) {
                        QueryParam queryParam = field.getAnnotation(QueryParam.class);
                        if (queryParam != null) {
                            querySubString.append(queryParam.value());
                            querySubString.append(',');
                        }
                    }
                    Method[] beanMethods = beanParamType.getMethods();
                    for (Method beanMethod : beanMethods) {
                        QueryParam queryParam = beanMethod.getAnnotation(QueryParam.class);
                        if (queryParam != null) {
                            querySubString.append(queryParam.value());
                            querySubString.append(',');
                        }
                    }
                }
            }
            parameterIndex++;
        }

        return querySubString;
    }

    /**
     * Creates a link from uri combined with {@link InjectLink}.
     *
     * @param uri uri for the link
     * @return Link instance
     */
    Link getLink(URI uri) {
        return InjectLink.Util.buildLinkFromUri(uri, link);
    }

    /**
     * Gets the binding by name
     *
     * @param name name of the binding
     * @return the binding
     */
    public String getBinding(String name) {
        return bindings.get(name);
    }


    /**
     * Returns the condition of {@link InjectLink}.
     * @return {@link InjectLink#condition()}
     */
    public String getCondition() {
        return link.condition();
    }
}