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

com.google.api.tools.framework.aspects.http.model.HttpAttribute Maven / Gradle / Ivy

/*
 * Copyright (C) 2016 Google Inc.
 *
 * 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.google.api.tools.framework.aspects.http.model;

import com.google.api.HttpRule;
import com.google.api.tools.framework.model.Field;
import com.google.api.tools.framework.model.FieldSelector;
import com.google.api.tools.framework.model.MessageType;
import com.google.common.base.Function;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableList.Builder;
import com.google.inject.Key;
import java.util.List;

/**
 * Describes the mapping of a method to HTTP. Attached by the aspects to methods which do have an
 * http configuration.
 */
public class HttpAttribute {

  /** Key used to access this attribute. */
  public static final Key KEY = Key.get(HttpAttribute.class);

  /** Base class for path segments. */
  public abstract static class PathSegment {

    /** Returns the syntax of this segment. */
    public abstract String syntax();

    @Override
    public String toString() {
      return syntax();
    }

    /** Returns the syntax for a full path given as a list of segments. */
    public static String toSyntax(Iterable parts) {
      return toSyntax(parts, true);
    }

    static String toSyntax(Iterable parts, boolean leadingSeparator) {
      StringBuilder result = new StringBuilder();
      for (PathSegment part : parts) {
        if (leadingSeparator || result.length() > 0) {
          result.append(part.separator());
        }
        result.append(part.syntax());
      }
      return result.toString();
    }

    /** Returns the separator to be used before this path segment. Defaults to '/'. */
    String separator() {
      return "/";
    }
  }

  /**
   * A path segment representing a wildcard, which is bounded (matches 1 segment) or unbounded
   * (matches 1 or more segments).
   */
  public static class WildcardSegment extends PathSegment {

    private final boolean unbounded;

    public WildcardSegment(boolean unbounded) {
      this.unbounded = unbounded;
    }

    public boolean isUnbounded() {
      return unbounded;
    }

    @Override
    public String syntax() {
      return unbounded ? "**" : "*";
    }
  }

  /** A path segment representing a literal. */
  public static class LiteralSegment extends PathSegment {

    private final String literal;
    private final boolean isTrailingCustomVerb;

    public LiteralSegment(String literal) {
      this.literal = literal;
      this.isTrailingCustomVerb = false;
    }

    public LiteralSegment(String literal, boolean isTrailingCustomVerb) {
      this.literal = literal;
      this.isTrailingCustomVerb = isTrailingCustomVerb;
    }

    public String getLiteral() {
      return literal;
    }

    public boolean isTrailingCustomVerb() {
      return isTrailingCustomVerb;
    }

    @Override
    public String syntax() {
      return literal;
    }

    @Override
    String separator() {
      return isTrailingCustomVerb ? ":" : "/";
    }
  }

  /** A path segment representing a field reference. */
  public static class FieldSegment extends PathSegment {

    private final String fieldPath;
    private final ImmutableList subPath;

    private FieldSelector selector;

    public FieldSegment(String fieldPath, ImmutableList subPath) {
      this.fieldPath = Preconditions.checkNotNull(fieldPath);
      this.subPath = Preconditions.checkNotNull(subPath);
    }

    public String getFieldPath() {
      return null == selector ? fieldPath : selector.toString();
    }

    public ImmutableList getSubPath() {
      return subPath;
    }

    /** Gets the field this segment links to. */
    public FieldSelector getFieldSelector() {
      return selector;
    }

    /** Sets the field this segment links to. */
    public void setFieldSelector(FieldSelector selector) {
      this.selector = selector;
    }

    @Override
    public String syntax() {
      StringBuilder result = new StringBuilder();
      result.append('{');
      result.append(getFieldPath());
      if (!subPath.isEmpty()) {
        result.append('=');
        result.append(PathSegment.toSyntax(subPath, false));
      }
      result.append('}');
      return result.toString();
    }
  }

  private HttpRule currentRule;
  private final MethodKind methodKind;
  private final MessageType message;
  private final ImmutableList path;
  private final String body;
  private final boolean isFromIdl;
  private final ImmutableList additionalBindings;
  private final boolean isPrimary;
  private RestMethod restMethod;

  private ImmutableList flattenedPath;

  /**
   * Constructs an http binding for the given method kind, message, path, and body. See
   * documentation of the getters for the meaning of those values.
   */
  public HttpAttribute(
      HttpRule rule,
      MethodKind methodKind,
      MessageType message,
      ImmutableList path,
      String body,
      boolean isFromIdl,
      ImmutableList additionalBindings,
      boolean isPrimary) {

    this.currentRule = Preconditions.checkNotNull(rule);
    this.methodKind = Preconditions.checkNotNull(methodKind);
    this.message = Preconditions.checkNotNull(message);
    this.path = Preconditions.checkNotNull(path);
    this.body = body;
    this.isFromIdl = isFromIdl;
    this.additionalBindings = Preconditions.checkNotNull(additionalBindings);
    this.isPrimary = isPrimary;
  }

  /** Creates an empty binding for a method which is not exposed via http. */
  public static HttpAttribute noConfig(MessageType message) {
    HttpAttribute config =
        new HttpAttribute(
            HttpRule.getDefaultInstance(),
            MethodKind.NONE,
            message,
            ImmutableList.of(),
            null,
            false,
            ImmutableList.of(),
            true);
    config.setFields(ImmutableList.of(), ImmutableList.of());
    return config;
  }

  /** Returns the name of the any field specified in the underlying http rule. */
  public String getAnySpecifiedFieldInHttpRule() {
    String fieldName;
    if (Strings.isNullOrEmpty(getHttpRule().getSelector())) {
      fieldName = "post";
      switch (getMethodKind()) {
        case GET:
          fieldName = "get";
          break;
        case PUT:
          fieldName = "put";
          break;
        case POST:
          fieldName = "post";
          break;
        case PATCH:
          fieldName = "patch";
          break;
        case DELETE:
          fieldName = "delete";
          break;
        default:
          fieldName = "post";
          break;
      }
    } else {
      fieldName = "selector";
    }
    return fieldName;
  }

  /** Returns true if this is the primary binding. */
  public boolean isPrimary() {
    return isPrimary;
  }

  /** Returns the associated rest method. Each HTTP binding has one. */
  public RestMethod getRestMethod() {
    return restMethod;
  }

  /** Sets the rest method. */
  public void setRestMethod(RestMethod method) {
    this.restMethod = method;
  }

  /**
   * Create a new HTTP binding where the HTTP paths are rooted underneath the provided path.
   * replaces the first segment in the path by the list of literal segments obtained from the
   * newRoot parameter.
   */
  public HttpAttribute reroot(final String newRoot) {
    // Compute new path.
    ImmutableList.Builder changedPath = ImmutableList.builder();
    for (String comp : Splitter.on('/').omitEmptyStrings().trimResults().split(newRoot)) {
      changedPath.add(new LiteralSegment(comp));
    }
    changedPath.addAll(path.subList(1, path.size()));

    // Change rule.
    HttpRule.Builder changedRule = currentRule.toBuilder();
    String changedPathPattern = PathSegment.toSyntax(changedPath.build());
    switch (currentRule.getPatternCase()) {
      case GET:
        changedRule.setGet(changedPathPattern);
        break;
      case PUT:
        changedRule.setPut(changedPathPattern);
        break;
      case POST:
        changedRule.setPost(changedPathPattern);
        break;
      case PATCH:
        changedRule.setPatch(changedPathPattern);
        break;
      case DELETE:
        changedRule.setDelete(changedPathPattern);
        break;
      default:
        // TODO Shouldn't this have a case for CUSTOM?  How is this reroot() used?
        break;
    }

    // Change additional bindings.
    ImmutableList changedAdditionalBindings =
        FluentIterable.from(additionalBindings)
            .transform(
                new Function() {
                  @Override
                  public HttpAttribute apply(HttpAttribute attrib) {
                    return attrib.reroot(newRoot);
                  }
                })
            .toList();

    // Return new binding.
    HttpAttribute attrib =
        new HttpAttribute(
            changedRule.build(),
            methodKind,
            message,
            changedPath.build(),
            body,
            isFromIdl,
            changedAdditionalBindings,
            isPrimary);
    attrib.pathSelectors = pathSelectors;
    attrib.bodySelectors = bodySelectors;
    attrib.paramSelectors = paramSelectors;
    return attrib;
  }

  /**
   * Returns an {@link Iterable} that includes the primary binding (the current one) and all
   * additional bindings. The primary binding is always the first item in the {@link Iterable}.
   */
  public Iterable getAllBindings() {
    return FluentIterable.from(ImmutableList.of(this)).append(additionalBindings);
  }

  // -------------------------------------------------------------------------
  // Syntax

  /** Gets the underlying http rule. */
  public HttpRule getHttpRule() {
    return currentRule;
  }

  /** Gets the http method kind. */
  public MethodKind getMethodKind() {
    return methodKind;
  }

  /** Gets the request message this configuration is associated with. */
  public MessageType getMessage() {
    return message;
  }

  /** Gets the path as a list of segments. */
  public ImmutableList getPath() {
    return path;
  }

  /** Gets the flattened path, where all FieldSegments have been replaced by their sub-paths. */
  public ImmutableList getFlatPath() {
    if (flattenedPath != null) {
      return flattenedPath;
    }
    ImmutableList.Builder builder = ImmutableList.builder();
    flatten(builder, path);
    return flattenedPath = builder.build();
  }

  private void flatten(Builder builder, ImmutableList path) {
    for (PathSegment segm : path) {
      if (segm instanceof FieldSegment) {
        FieldSegment fieldSegm = (FieldSegment) segm;
        if (fieldSegm.subPath.isEmpty()) {
          // looking at {name}, will be replaced by '*'.
          builder.add(new WildcardSegment(false));
        } else {
          flatten(builder, ((FieldSegment) segm).getSubPath());
        }
      } else {
        builder.add(segm);
      }
    }
  }

  /** Gets the body or null if none specified. */
  public String getBody() {
    return body;
  }

  /** Return true if the http binding information is from IDL. */
  public boolean isFromIdl() {
    return isFromIdl;
  }

  /** Returns true if the body is configured to include unbound fields. */
  public boolean bodyCapturesUnboundFields() {
    return "*".equals(body);
  }

  // -------------------------------------------------------------------------
  // Attributes belonging to merged stage

  private ImmutableList pathSelectors;
  private ImmutableList paramSelectors;
  private ImmutableList bodySelectors;

  /** Gets the fields which are bound via the path. */
  public ImmutableList getPathSelectors() {
    return pathSelectors;
  }

  /** Gets fields which are bound via parameters. */
  public ImmutableList getParamSelectors() {
    return paramSelectors;
  }

  /** Get fields which are bound via the body. */
  public ImmutableList getBodySelectors() {
    return bodySelectors;
  }

  /**
   * Sets the parameter and body fields. Also derives the path fields from the path, assuming they
   * have been resolved.
   */
  public void setFields(
      ImmutableList paramFields, ImmutableList bodyFields) {
    this.paramSelectors = paramFields;
    this.bodySelectors = bodyFields;
    this.pathSelectors =
        FluentIterable.from(path)
            .filter(FieldSegment.class)
            .filter(
                new Predicate() {
                  @Override
                  public boolean apply(FieldSegment seg) {
                    return seg.getFieldSelector() != null;
                  }
                })
            .transform(
                new Function() {
                  @Override
                  public FieldSelector apply(FieldSegment seg) {
                    return seg.getFieldSelector();
                  }
                })
            .toList();
  }

  // -------------------------------------------------------------------------
  // Attributes belonging to scoped stage

  private ImmutableList visiblePathSelectors;
  private ImmutableList visibleParamSelectors;
  private ImmutableList visibleBodySelectors;

  /** Gets visible fields which are bound via the path. */
  public ImmutableList getVisiblePathSelectors() {
    if (visiblePathSelectors == null) {
      visiblePathSelectors = buildVisibleSelectors(pathSelectors);
    }
    return visiblePathSelectors;
  }

  /** Gets visible fields which are bound via parameters. */
  public ImmutableList getVisibleParamSelectors() {
    if (visibleParamSelectors == null) {
      visibleParamSelectors = buildVisibleSelectors(paramSelectors);
    }
    return visibleParamSelectors;
  }

  /** Gets visible fields which are bound via body. */
  public ImmutableList getVisibleBodySelectors() {
    if (visibleBodySelectors == null) {
      visibleBodySelectors = buildVisibleSelectors(bodySelectors);
    }
    return visibleBodySelectors;
  }

  private ImmutableList buildVisibleSelectors(List selectors) {
    ImmutableList.Builder listBuilder = ImmutableList.builder();
    for (FieldSelector selector : selectors) {
      boolean hasInvisibleField = false;
      for (Field field : selector.getFields()) {
        if (!field.isReachable()) {
          hasInvisibleField = true;
          break;
        }
      }
      // Only include FieldSelector that has no invisible field.
      if (!hasInvisibleField) {
        listBuilder.add(selector);
      }
    }
    return listBuilder.build();
  }

  public ImmutableList getAdditionalBindings() {
    return additionalBindings;
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy