All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.jayway.jaxrs.hateoas.DefaultHateoasContext Maven / Gradle / Ivy

There is a newer version: 0.4.5
Show newest version
/*
 * Copyright 2011 the original author or authors.
 * 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.
 */
package com.jayway.jaxrs.hateoas;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.Validate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.ws.rs.*;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * Default implementation of {@link HateoasContext}. Not intended for external use. This class is configured by the
 * Application classes in the core package.
 *
 * @author Mattias Hellborg Arthursson
 * @author Kalle Stenflo
 */
public class DefaultHateoasContext implements HateoasContext {

    private static final ReadWriteLock LOCK = new ReentrantReadWriteLock();

    private static final String[] DEFAULT_MEDIA_TYPE = {"*/*"};

    private final static Logger logger = LoggerFactory
            .getLogger(DefaultHateoasContext.class);

    private final Map linkableMapping = new LinkedHashMap();

    private final Set> initializedClasses = new HashSet>();

    /*
      * (non-Javadoc)
      *
      * @see com.jayway.jaxrs.hateoas.HateoasContext#mapClass(java.lang.Class)
      */
    @Override
    public void mapClass(Class clazz) {
        if (clazz.isAnnotationPresent(Path.class)) {
            String rootPath = clazz.getAnnotation(Path.class).value();
            mapClass(clazz, rootPath);
        } else {
            logger.debug("Class {} is not annotated with @Path", clazz);
        }
    }

    /*
      * (non-Javadoc)
      * 
      * @see
      * com.jayway.jaxrs.hateoas.HateoasContext#getLinkableInfo(java.lang.String)
      */
    @Override
    public LinkableInfo getLinkableInfo(String link) {
        LinkableInfo linkableInfo = linkableMapping.get(link);
        Validate.notNull(linkableInfo, "Invalid link: " + link);

        return linkableInfo;
    }

    private void mapClass(Class clazz, String path) {


        if (!isInitialized(clazz)) {
            logger.info("Mapping class {}", clazz);

            if (path.endsWith("/")) {
                path = StringUtils.removeEnd(path, "/");
            }

            Method[] methods = clazz.getMethods();
            for (Method method : methods) {
                if (!method.getDeclaringClass().equals(Object.class)) {
                    mapMethod(clazz, path, method);
                }
            }
        } else {
            logger.info("Class {} already mapped. Skipped mapping.", clazz);
        }
    }

    private void mapMethod(Class clazz, String rootPath, Method method) {
        String httpMethod = findHttpMethod(method);

        if (httpMethod != null) {
            String path = getPath(rootPath, method);
            String[] consumes = getConsumes(method);
            String[] produces = getProduces(method);

            if (method.isAnnotationPresent(Linkable.class)) {
                Linkable linkAnnotation = method.getAnnotation(Linkable.class);
                String id = linkAnnotation.value();
                if (linkableMapping.containsKey(id)) {
                    throw new IllegalArgumentException("Id '" + id
                            + "' mapped in class " + clazz
                            + " is already mapped from another class");
                }


                LinkableParameterInfo[] parameterInfo = extractMethodParameterInfo(method);

                LinkableInfo linkableInfo = new LinkableInfo(id, path,
                        httpMethod, consumes, produces,
                        linkAnnotation.label(), linkAnnotation.description(),
                        linkAnnotation.templateClass(), parameterInfo);

                linkableMapping.put(id, linkableInfo);
            } else {
                logger.warn("Method {} is missing Linkable annotation", method);
            }
        } else {
            //this might be a sub resource method
            if (method.isAnnotationPresent(Path.class)) {
                String path = method.getAnnotation(Path.class).value();
                if (path.endsWith("/")) {
                    path = StringUtils.removeEnd(path, "/");
                }

                Class subResourceType = method.getReturnType();

                mapClass(subResourceType, rootPath + path);
            }
        }
    }

    private LinkableParameterInfo[] extractMethodParameterInfo(Method method) {
        List parameterInfoList = new LinkedList();
        Class[] parameterTypes = method.getParameterTypes();
        Annotation[][] parameterAnnotations = method.getParameterAnnotations();

        for (int i = 0; i < parameterTypes.length; i++) {
            Annotation[] annotations = parameterAnnotations[i];

            String  parameterName = null;
            String defaultValue = null;

            for (Annotation annotation : annotations) {
                if(annotation instanceof QueryParam){
                    parameterName = ((QueryParam)annotation).value();
                }
                if(annotation instanceof DefaultValue){
                    defaultValue = ((DefaultValue)annotation).value();
                }
            }

            if(parameterName != null){
                logger.debug("Found QueryParam: {}", parameterName);

                parameterInfoList.add(new LinkableParameterInfo(parameterName, defaultValue));
            }
            if(defaultValue != null){
                logger.debug("Found DefaultValue: {}", defaultValue);
            }
        }
        return parameterInfoList.toArray(new LinkableParameterInfo[0]);
    }

    private String[] getConsumes(Method method) {
        if (method.isAnnotationPresent(Consumes.class)) {
            return method.getAnnotation(Consumes.class).value();
        }

        return DEFAULT_MEDIA_TYPE;
    }

    private String[] getProduces(Method method) {
        if (method.isAnnotationPresent(Produces.class)) {
            return method.getAnnotation(Produces.class).value();
        }

        return DEFAULT_MEDIA_TYPE;
    }

    private String getPath(String rootPath, Method method) {
        if (method.isAnnotationPresent(Path.class)) {
            Path pathAnnotation = method.getAnnotation(Path.class);
            return rootPath + pathAnnotation.value();
        }

        return rootPath.isEmpty() ? "/" : rootPath;
    }

    private String findHttpMethod(Method method) {
        if (method.isAnnotationPresent(GET.class)) {
            return HttpMethod.GET;
        }
        if (method.isAnnotationPresent(POST.class)) {
            return HttpMethod.POST;
        }
        if (method.isAnnotationPresent(PUT.class)) {
            return HttpMethod.PUT;
        }
        if (method.isAnnotationPresent(DELETE.class)) {
            return HttpMethod.DELETE;
        }
        if (method.isAnnotationPresent(OPTIONS.class)) {
            return HttpMethod.OPTIONS;
        }
        return null;
    }

    @Override
    public String toString() {
        Set> entrySet = linkableMapping.entrySet();
        StringBuilder sb = new StringBuilder();
        for (Entry relInfo : entrySet) {
            sb.append(relInfo.toString()).append("
"); } return sb.toString(); } /** * Checks if the specified class has been registered. If not add the class to the set mapping * registred classes. * * @param clazz the class to check * @return true if the class has already been registered, * false otherwise. */ public boolean isInitialized(Class clazz) { Lock readLock = LOCK.readLock(); try { readLock.lock(); if (initializedClasses.contains(clazz)) { return true; } } finally { readLock.unlock(); } Lock writeLock = LOCK.writeLock(); try { writeLock.lock(); if (!initializedClasses.contains(clazz)) { // check again - things might have changed initializedClasses.add(clazz); return false; } } finally { writeLock.unlock(); } return true; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy