com.kelveden.rastajax.representation.flat.FlatRepresentationBuilder.java Source code

Java tutorial

Introduction

Here is the source code for com.kelveden.rastajax.representation.flat.FlatRepresentationBuilder.java

Source

/**
 * Copyright 2012 Alistair Dutton
 *
 * 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.kelveden.rastajax.representation.flat;

import com.kelveden.rastajax.core.raw.*;
import com.kelveden.rastajax.core.RepresentationBuilder;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;

/**
 * {@link com.kelveden.rastajax.core.RepresentationBuilder} that generates a representation where sub-resources are denormalised into a flat
 * structure.
 */
public class FlatRepresentationBuilder implements RepresentationBuilder<Set<FlatResource>> {

    private static final Logger LOGGER = LoggerFactory.getLogger(FlatRepresentationBuilder.class);
    private static final int UNDERLINE_LENGTH = 60;
    private static final Comparator<FlatResource> RESOURCE_COMPARATOR = new Comparator<FlatResource>() {
        @Override
        public int compare(final FlatResource flatResource1, final FlatResource flatResource2) {
            return flatResource1.getUriTemplate().compareTo(flatResource2.getUriTemplate());
        }
    };

    @Override
    public Set<FlatResource> buildRepresentationFor(final ResourceClass resourceClass) {

        final TreeSet<FlatResource> result = new TreeSet<FlatResource>(RESOURCE_COMPARATOR);

        LOGGER.debug(StringUtils.repeat("-", UNDERLINE_LENGTH));
        LOGGER.debug("Building representation for resource with URI template {}...",
                resourceClass.getUriTemplate());
        LOGGER.debug(StringUtils.repeat("-", UNDERLINE_LENGTH));

        if (resourceClass.isRootResource()) {

            LOGGER.debug("This resource is a root resource.");
            LOGGER.debug("Analyzing methods...");

            final MultiValuedMap<String, ResourceClassMethod> resourceClassMethodsByPath = groupResourceClassMethodsByUriTemplate(
                    resourceClass, " |-");

            final List<FlatResource> methodsAsResources = representResourceClassMethods(resourceClass,
                    resourceClassMethodsByPath);
            result.addAll(methodsAsResources);

            LOGGER.debug(
                    "Finished analyzing methods: flattened methods to {} distinct resource(s) in representation.",
                    methodsAsResources.size());

        } else {
            LOGGER.debug("This resource is NOT a root resource - skipping.");
        }

        return result;
    }

    @Override
    public Set<FlatResource> buildRepresentationFor(final Set<ResourceClass> resourceClasses) {

        final TreeSet<FlatResource> result = new TreeSet<FlatResource>(RESOURCE_COMPARATOR);

        for (ResourceClass rawResource : resourceClasses) {
            result.addAll(buildRepresentationFor(rawResource));
        }

        LOGGER.info("Representation completed with {} resources.", result.size());

        return result;
    }

    private MultiValuedMap<String, ResourceClassMethod> groupResourceClassMethodsByUriTemplate(
            final ResourceClass resourceClass, final String logPrefix) {

        final MultiValuedMap<String, ResourceClassMethod> resourceClassMethodsByUriTemplate = new MultiValuedMap<String, ResourceClassMethod>();

        for (ResourceClassMethod resourceClassMethod : resourceClass.getMethods()) {

            final String uriTemplate = buildResourceMethodUriTemplateFrom(resourceClass, resourceClassMethod);

            LOGGER.debug("{} Found method '{}'.", logPrefix, resourceClassMethod.getName());

            if (resourceClassMethod instanceof SubResourceMethod) {

                final SubResourceMethod subResourceMethod = (SubResourceMethod) resourceClassMethod;
                LOGGER.debug(
                        "{} Method is a sub-resource method with URI template '{}' and request method designator '{}'.",
                        logPrefix, subResourceMethod.getUriTemplate(),
                        subResourceMethod.getRequestMethodDesignator());

                resourceClassMethodsByUriTemplate.putSingleValue(uriTemplate, resourceClassMethod);

            } else if (resourceClassMethod instanceof ResourceMethod) {

                final ResourceMethod resourceMethod = (ResourceMethod) resourceClassMethod;
                LOGGER.debug("{} Method is a resource method with request method designator '{}'.", logPrefix,
                        resourceMethod.getRequestMethodDesignator());

                resourceClassMethodsByUriTemplate.putSingleValue(uriTemplate, resourceClassMethod);

            } else if (resourceClassMethod instanceof SubResourceLocator) {

                final SubResourceLocator subResourceLocator = (SubResourceLocator) resourceClassMethod;
                LOGGER.debug(
                        "{} Method is a sub-resource locator with URI template '{}'. Will now analyze class indicated by sub-resource locator for methods.",
                        logPrefix, subResourceLocator.getUriTemplate());

                final ResourceClass subResource = subResourceLocator.getSubResource();

                if (subResource != null) {
                    LOGGER.debug("{} Analyzing methods on the class '{}' indicated by the sub-resource locator.",
                            logPrefix, subResource.getRawClass());

                    final MultiValuedMap<String, ResourceClassMethod> subResourceClassMethodsByUriTemplate = getSubResourceLocatorMethodsAsResourceMethods(
                            subResource, uriTemplate, logPrefix);
                    resourceClassMethodsByUriTemplate.mergeIn(subResourceClassMethodsByUriTemplate);

                    LOGGER.debug("{} Finished analyzing sub-resource locator with URI template {}.", logPrefix,
                            subResourceLocator.getUriTemplate());

                } else {
                    LOGGER.debug("{} Could not find sub-resource class indicated by sub-resource locator.",
                            logPrefix);
                }
            }
        }

        return resourceClassMethodsByUriTemplate;
    }

    private MultiValuedMap<String, ResourceClassMethod> getSubResourceLocatorMethodsAsResourceMethods(
            final ResourceClass subResourceLocatorSubResource, final String resourceClassUriTemplate,
            final String logPrefix) {

        final MultiValuedMap<String, ResourceClassMethod> subResourceUriTemplateToMethodsMap = groupResourceClassMethodsByUriTemplate(
                subResourceLocatorSubResource, logPrefix + "--");

        final MultiValuedMap<String, ResourceClassMethod> result = new MultiValuedMap<String, ResourceClassMethod>();

        for (Map.Entry<String, List<ResourceClassMethod>> entry : subResourceUriTemplateToMethodsMap.entrySet()) {
            final String methodUriTemplate = entry.getKey();
            final String fullUriTemplate = resourceClassUriTemplate
                    + (methodUriTemplate == null ? "" : "/" + methodUriTemplate);

            final List<ResourceClassMethod> resourceClassMethods = entry.getValue();
            result.get(fullUriTemplate).addAll(resourceClassMethods);
        }

        return result;
    }

    private String buildResourceMethodUriTemplateFrom(final ResourceClass resource,
            final ResourceClassMethod method) {

        String fullUriTemplateRoot = "";
        if (resource.getUriTemplate() != null) {
            fullUriTemplateRoot = resource.getUriTemplate() + "/";
        }

        if (method instanceof SubResourceLocator) {
            return fullUriTemplateRoot + ((SubResourceLocator) method).getUriTemplate();

        } else if (method instanceof SubResourceMethod) {
            return fullUriTemplateRoot + ((SubResourceMethod) method).getUriTemplate();

        } else if (method instanceof ResourceMethod) {
            return resource.getUriTemplate();

        } else {
            throw new UnsupportedOperationException(
                    "ResourceClassMethod of type '" + method.getClass().getName() + "' is not supported.");
        }
    }

    private String representRequestMethodDesignator(final ResourceClassMethod resourceClassMethod) {

        if (resourceClassMethod instanceof SubResourceMethod) {
            return ((SubResourceMethod) resourceClassMethod).getRequestMethodDesignator();

        } else if (resourceClassMethod instanceof ResourceMethod) {
            return ((ResourceMethod) resourceClassMethod).getRequestMethodDesignator();

        } else {
            throw new UnsupportedOperationException(
                    "A resource class method of type '" + resourceClassMethod.getClass().getName()
                            + "' is not supported here. This most likely represents a bug in Rastjax.");
        }
    }

    private MultiValuedMap<String, FlatResourceMethodParameter> representParameters(
            final List<Parameter> resourceClassMethodParameters) {

        final MultiValuedMap<String, FlatResourceMethodParameter> parameters = new MultiValuedMap<String, FlatResourceMethodParameter>();

        for (Parameter resourceClassMethodParameter : resourceClassMethodParameters) {
            final String parameterType = toCamelCase(
                    resourceClassMethodParameter.getJaxRsAnnotationType().getSimpleName());

            parameters.putSingleValue(parameterType,
                    new FlatResourceMethodParameter(resourceClassMethodParameter.getName(),
                            resourceClassMethodParameter.getType().getSimpleName()));
        }

        return parameters;
    }

    private List<FlatResource> representResourceClassMethods(final ResourceClass resourceClass,
            final MultiValuedMap<String, ResourceClassMethod> resourceClassMethodsByUriTemplate) {

        final List<FlatResource> result = new ArrayList<FlatResource>();

        for (Map.Entry<String, List<ResourceClassMethod>> subResourceMethodsGroupedByPath : resourceClassMethodsByUriTemplate
                .entrySet()) {
            final String uriTemplate = subResourceMethodsGroupedByPath.getKey();

            final List<FlatResourceMethod> flatResourceMethods = new ArrayList<FlatResourceMethod>();
            for (ResourceClassMethod rawMethod : subResourceMethodsGroupedByPath.getValue()) {
                flatResourceMethods
                        .add(representResourceClassMethod(resourceClass, rawMethod.getResourceClass(), rawMethod));
            }

            result.add(representResource(uriTemplate, flatResourceMethods));
        }

        return result;
    }

    private FlatResourceMethod representResourceClassMethod(final ResourceClass resourceClass,
            final ResourceClass resourceClassContainingMethod, final ResourceClassMethod resourceClassMethod) {

        final List<String> produces = representMediaTypeListForMethod(resourceClassMethod.getProduces(),
                resourceClassContainingMethod.getProduces());
        final List<String> consumes = representMediaTypeListForMethod(resourceClassMethod.getConsumes(),
                resourceClassContainingMethod.getConsumes());

        final Map<String, List<FlatResourceMethodParameter>> parameters = representParameters(
                resourceClassMethod.getParameters());
        parameters.putAll(representParameters(resourceClassContainingMethod.getFields()));

        if (resourceClass == resourceClassContainingMethod) {
            parameters.putAll(representParameters(resourceClass.getFields()));
        }

        final String requestMethodDesignator = representRequestMethodDesignator(resourceClassMethod);

        return new FlatResourceMethod(resourceClassMethod.getName(), requestMethodDesignator, parameters, consumes,
                produces, resourceClassContainingMethod.getRawClass().getName());
    }

    private FlatResource representResource(final String uriTemplate,
            final List<FlatResourceMethod> resourceMethods) {

        final String cleanUriTemplate = cleanupUriTemplate(uriTemplate);

        LOGGER.info("Added resource with URI template '{}' to representation with {} resource methods.",
                cleanUriTemplate, resourceMethods.size());

        return new FlatResource(cleanUriTemplate, resourceMethods);
    }

    private List<String> representMediaTypeListForMethod(final List<String> methodLevelMediaTypes,
            final List<String> classLevelMediaTypes) {

        if (methodLevelMediaTypes.isEmpty()) {
            return classLevelMediaTypes;
        } else {
            return methodLevelMediaTypes;
        }
    }

    private String cleanupUriTemplate(final String uriTemplate) {
        return uriTemplate.replaceAll("/+", "/");
    }

    private String toCamelCase(final String string) {
        return string.substring(0, 1).toLowerCase(Locale.getDefault()) + string.substring(1);
    }

    private static class MultiValuedMap<K, V> extends HashMap<K, List<V>> {

        private static final long serialVersionUID = 1L;

        public void putSingleValue(final K key, final V value) {
            get(key).add(value);
        }

        public void mergeIn(final MultiValuedMap<K, V> input) {

            for (Map.Entry<K, List<V>> entry : input.entrySet()) {
                final K key = entry.getKey();
                final List<V> values = entry.getValue();

                final List<V> listToAddTo = get(key);
                listToAddTo.addAll(values);
            }
        }

        @Override
        public List<V> get(final Object key) {
            List<V> raw = super.get(key);

            if (raw == null) {
                raw = new ArrayList<V>();

                super.put((K) key, raw);
            }

            return raw;
        }
    }
}