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

de.escalon.hypermedia.affordance.PartialUriTemplate Maven / Gradle / Ivy

There is a newer version: 0.4.2
Show newest version
/*
 * Copyright (c) 2014. Escalon System-Entwicklung, Dietrich Schulten
 *
 * 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 de.escalon.hypermedia.affordance;

import de.escalon.hypermedia.spring.AffordanceBuilder;
import org.springframework.hateoas.TemplateVariable;
import org.springframework.util.Assert;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * URI template with the ability to be partially expanded, no matter if its variables are required or not. Unsatisfied
 * variables are kept as variables. Other implementations either remove all unsatisfied variables or fail when required
 * variables are unsatisfied. This behavior is required due to the way an Affordance is created by {@link
 * AffordanceBuilder}, see package info for an overview of affordance creation.
 *
 * @author Dietrich Schulten
 * @see de.escalon.hypermedia.spring
 */
public class PartialUriTemplate {

    private static final Pattern VARIABLE_REGEX = Pattern.compile("\\{([\\?\\&#/]?)([\\w\\,\\.]+)(:??.*?)\\}");

    private final List urlComponents = new ArrayList();

    private final List> variableIndices = new ArrayList>();
    private List variables = new ArrayList();
    private List variableNames = new ArrayList();

    /**
     * Creates a new {@link PartialUriTemplate} using the given template string.
     *
     * @param template
     *         must not be {@literal null} or empty.
     */
    public PartialUriTemplate(String template) {
        Assert.hasText(template, "Template must not be null or empty!");

        Matcher matcher = VARIABLE_REGEX.matcher(template);
        // first group is the variable start without leading {: "", "/", "?", "#",
        // second group is the comma-separated name list without the trailing } of the variable
        int endOfPart = 0;
        while (matcher.find()) {

            // 0 is the current match, i.e. the entire variable expression
            int startOfPart = matcher.start(0);
            // add part before current match
            if (endOfPart < startOfPart) {
                final String partWithoutVariables = template.substring(endOfPart, startOfPart);
                final StringTokenizer stringTokenizer = new StringTokenizer(partWithoutVariables, "?", true);
                boolean inQuery = false;
                while (stringTokenizer.hasMoreTokens()) {
                    final String token = stringTokenizer.nextToken();
                    if ("?".equals(token)) {
                        inQuery = true;
                    } else {
                        if (!inQuery) {
                            urlComponents.add(token);
                        } else {
                            urlComponents.add("?" + token);
                        }
                        variableIndices.add(Collections.emptyList());
                    }
                }
            }
            endOfPart = matcher.end(0);

            // add current match as part
            final String variablePart = template.substring(startOfPart, endOfPart);
            urlComponents.add(variablePart);

            // collect variablesInPart and track for each part which variables it contains
            // group(1) is the variable head without the leading {
            TemplateVariable.VariableType type = TemplateVariable.VariableType.from(matcher.group(1));
            // group(2) are the variable names
            String[] names = matcher.group(2)
                    .split(",");
            List variablesInPart = new ArrayList();
            for (String name : names) {
                TemplateVariable variable = new TemplateVariable(name, type);
                variablesInPart.add(variables.size());
                variables.add(variable);
                variableNames.add(name);
            }
            variableIndices.add(variablesInPart);
        }
        // finish off remaining part
        if (endOfPart < template.length()) {
            urlComponents.add(template.substring(endOfPart));
            variableIndices.add(Collections.emptyList());
        }
    }

    public List getVariableNames() {
        return variableNames;
    }


    /**
     * Returns the template as uri components, without variable expansion.
     *
     * @return components of the Uri
     */
    public PartialUriTemplateComponents asComponents() {
        return getUriTemplateComponents(Collections.emptyMap(), Collections.emptyList());
    }


    /**
     * Expands the template using given parameters
     *
     * @param parameters
     *         for expansion in the order of appearance in the template, must not be empty
     * @return expanded template
     */
    public PartialUriTemplateComponents expand(Object... parameters) {
        List variableNames = getVariableNames();
        Map parameterMap = new LinkedHashMap();

        int i = 0;
        for (String variableName : variableNames) {
            if (i < parameters.length) {
                parameterMap.put(variableName, parameters[i++]);
            } else {
                break;
            }
        }
        return getUriTemplateComponents(parameterMap, Collections.emptyList());
    }

    /**
     * Expands the template using given parameters
     *
     * @param parameters
     *         for expansion, must not be empty
     * @return expanded template
     */
    public PartialUriTemplateComponents expand(Map parameters) {
        return getUriTemplateComponents(parameters, Collections.emptyList());
    }

    /**
     * Applies parameters to template variables.
     *
     * @param parameters
     *         to apply to variables
     * @param requiredArgs
     *         if not empty, retains given requiredArgs
     * @return uri components
     */
    private PartialUriTemplateComponents getUriTemplateComponents(Map parameters, List
            requiredArgs) {
        Assert.notNull(parameters, "Parameters must not be null!");

        final StringBuilder baseUrl = new StringBuilder(urlComponents.get(0));
        final StringBuilder queryHead = new StringBuilder();
        final StringBuilder queryTail = new StringBuilder();
        final StringBuilder fragmentIdentifier = new StringBuilder();
        for (int i = 1; i < urlComponents.size(); i++) {
            final String part = urlComponents.get(i);
            final List variablesInPart = variableIndices.get(i);
            if (variablesInPart.isEmpty()) {
                if (part.startsWith("?") || part.startsWith("&")) {
                    queryHead.append(part);
                } else if (part.startsWith("#")) {
                    fragmentIdentifier.append(part);
                } else {
                    baseUrl.append(part);
                }
            } else {
                for (Integer variableInPart : variablesInPart) {
                    final TemplateVariable variable = variables.get(variableInPart);
                    final Object value = parameters.get(variable.getName());
                    if (value == null) {
                        switch (variable.getType()) {
                            case REQUEST_PARAM:
                            case REQUEST_PARAM_CONTINUED:
                                if (requiredArgs.isEmpty() || requiredArgs.contains(variable.getName())) {
                                    // query vars without value always go last (query tail)
                                    if (queryTail.length() > 0) {
                                        queryTail.append(',');
                                    }
                                    queryTail.append(variable.getName());
                                }
                                break;
                            case FRAGMENT:
                                fragmentIdentifier.append(variable.toString());
                                break;
                            case PATH_VARIABLE:
                                if (queryHead.length() != 0) {
                                    // level 1 variable in query
                                    queryHead.append(variable.toString());
                                } else {
                                    baseUrl.append(variable.toString());
                                }
                                break;
                            case SEGMENT:
                                baseUrl.append(variable.toString());
                        }
                    } else {
                        switch (variable.getType()) {
                            case REQUEST_PARAM:
                            case REQUEST_PARAM_CONTINUED:
                                if (queryHead.length() == 0) {
                                    queryHead.append('?');
                                } else {
                                    queryHead.append('&');
                                }
                                queryHead.append(variable.getName())
                                        .append('=')
                                        .append(urlEncode(value.toString()));
                                break;
                            case SEGMENT:
                                baseUrl.append('/');
                                // fall through
                            case PATH_VARIABLE:
                                if (queryHead.length() != 0) {
                                    // level 1 variable in query
                                    queryHead.append(urlEncode(value.toString()));
                                } else {
                                    baseUrl.append(urlEncode(value.toString()));
                                }
                                break;
                            case FRAGMENT:
                                fragmentIdentifier.append('#');
                                fragmentIdentifier.append(urlEncode(value.toString()));
                                break;
                        }
                    }
                }
            }
        }

        return new PartialUriTemplateComponents(baseUrl.toString(), queryHead.toString(), queryTail.toString(),
                fragmentIdentifier.toString(), variableNames);
    }

    private String urlEncode(String s) {
        try {
            return URLEncoder.encode(s, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            throw new IllegalStateException("failed to urlEncode " + s, e);
        }
    }

    /**
     * Strips all variables which are not required by any of the given action descriptors. If no action descriptors are
     * given, nothing will be stripped.
     *
     * @param actionDescriptors
     *         to decide which variables are optional, may be empty
     * @return partial uri template components without optional variables, if actionDescriptors was not empty
     */
    public PartialUriTemplateComponents stripOptionalVariables(List actionDescriptors) {
        return getUriTemplateComponents(Collections.emptyMap(), getRequiredArgNames(actionDescriptors));
    }

    private List getRequiredArgNames(List actionDescriptors) {
        List ret = new ArrayList();
        for (ActionDescriptor actionDescriptor : actionDescriptors) {
            Map required = actionDescriptor.getRequiredParameters();
            for (ActionInputParameter actionInputParameter : required.values()) {
                ret.add(actionInputParameter.getParameterName());
            }
        }
        return ret;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy