• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2020 Google LLC
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are
6  * met:
7  *
8  *     * Redistributions of source code must retain the above copyright
9  * notice, this list of conditions and the following disclaimer.
10  *     * Redistributions in binary form must reproduce the above
11  * copyright notice, this list of conditions and the following disclaimer
12  * in the documentation and/or other materials provided with the
13  * distribution.
14  *     * Neither the name of Google LLC nor the names of its
15  * contributors may be used to endorse or promote products derived from
16  * this software without specific prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  */
30 package com.google.api.gax.httpjson;
31 
32 import com.google.api.core.BetaApi;
33 import com.google.api.core.InternalApi;
34 import com.google.api.pathtemplate.PathTemplate;
35 import com.google.protobuf.Message;
36 import java.util.Arrays;
37 import java.util.List;
38 import java.util.Map;
39 import java.util.stream.Collectors;
40 
41 /** Creates parts of a HTTP request from a protobuf message. */
42 public class ProtoMessageRequestFormatter<RequestT extends Message>
43     implements HttpRequestFormatter<RequestT> {
44 
45   private final FieldsExtractor<RequestT, String> requestBodyExtractor;
46   // Using of triple nested generics (which is not pretty) is predetermined by the
47   // Map<String, List<String>> returned value type of the getQueryParamNames interface method
48   // implemented by this class.
49   private final FieldsExtractor<RequestT, Map<String, List<String>>> queryParamsExtractor;
50   private final String rawPath;
51   private final PathTemplate pathTemplate;
52   private final FieldsExtractor<RequestT, Map<String, String>> pathVarsExtractor;
53   private final List<String> additionalRawPaths;
54   private final List<PathTemplate> additionalPathTemplates;
55 
ProtoMessageRequestFormatter( FieldsExtractor<RequestT, String> requestBodyExtractor, FieldsExtractor<RequestT, Map<String, List<String>>> queryParamsExtractor, String rawPath, PathTemplate pathTemplate, FieldsExtractor<RequestT, Map<String, String>> pathVarsExtractor, List<String> additionalRawPaths, List<PathTemplate> additionalPathTemplates)56   private ProtoMessageRequestFormatter(
57       FieldsExtractor<RequestT, String> requestBodyExtractor,
58       FieldsExtractor<RequestT, Map<String, List<String>>> queryParamsExtractor,
59       String rawPath,
60       PathTemplate pathTemplate,
61       FieldsExtractor<RequestT, Map<String, String>> pathVarsExtractor,
62       List<String> additionalRawPaths,
63       List<PathTemplate> additionalPathTemplates) {
64     this.requestBodyExtractor = requestBodyExtractor;
65     this.queryParamsExtractor = queryParamsExtractor;
66     this.rawPath = rawPath;
67     this.pathTemplate = pathTemplate;
68     this.pathVarsExtractor = pathVarsExtractor;
69     this.additionalRawPaths = additionalRawPaths;
70     this.additionalPathTemplates = additionalPathTemplates;
71   }
72 
73   public static <RequestT extends Message>
newBuilder()74       ProtoMessageRequestFormatter.Builder<RequestT> newBuilder() {
75     return new Builder<RequestT>().setAdditionalPaths();
76   }
77 
toBuilder()78   public Builder<RequestT> toBuilder() {
79     return new Builder<RequestT>()
80         .setPath(rawPath, pathVarsExtractor)
81         .setAdditionalPaths(additionalRawPaths.toArray(new String[] {}))
82         .setQueryParamsExtractor(queryParamsExtractor)
83         .setRequestBodyExtractor(requestBodyExtractor);
84   }
85 
86   /* {@inheritDoc} */
87   @Override
getQueryParamNames(RequestT apiMessage)88   public Map<String, List<String>> getQueryParamNames(RequestT apiMessage) {
89     return queryParamsExtractor.extract(apiMessage);
90   }
91 
92   /* {@inheritDoc} */
93   @Override
getRequestBody(RequestT apiMessage)94   public String getRequestBody(RequestT apiMessage) {
95     return requestBodyExtractor.extract(apiMessage);
96   }
97 
98   /**
99    * Returns the relative URL path created from the path parameters from the given message. Attempts
100    * to match the with the default PathTemplate. If there is not match, it attempts to match with
101    * the templates in the additionalPathTemplates.
102    *
103    * @param apiMessage Request object to extract fields from
104    * @return Path of a matching valid URL or the default Path URL
105    */
106   @Override
getPath(RequestT apiMessage)107   public String getPath(RequestT apiMessage) {
108     Map<String, String> pathVarsMap = pathVarsExtractor.extract(apiMessage);
109     String path = pathTemplate.instantiate(pathVarsMap);
110     if (pathTemplate.matches(path)) {
111       return path;
112     }
113     for (PathTemplate additionalPathTemplate : additionalPathTemplates) {
114       String additionalPath = additionalPathTemplate.instantiate(pathVarsMap);
115       if (additionalPathTemplate.matches(additionalPath)) {
116         return additionalPath;
117       }
118     }
119     // If there are no matches, we return the default path, this is for backwards compatibility.
120     // TODO: Log this scenario once we implemented the Cloud SDK logging.
121     return path;
122   }
123 
124   @BetaApi
125   @Override
getAdditionalPathTemplates()126   public List<PathTemplate> getAdditionalPathTemplates() {
127     return additionalPathTemplates;
128   }
129 
130   /* {@inheritDoc} */
131   @Override
getPathTemplate()132   public PathTemplate getPathTemplate() {
133     return pathTemplate;
134   }
135 
136   // This has class has compound setter methods (multiple arguments in setters), that is why not
137   // using @AutoValue.
138   public static class Builder<RequestT extends Message> {
139     private FieldsExtractor<RequestT, String> requestBodyExtractor;
140     private FieldsExtractor<RequestT, Map<String, List<String>>> queryParamsExtractor;
141     private String rawPath;
142     private FieldsExtractor<RequestT, Map<String, String>> pathVarsExtractor;
143     private List<String> rawAdditionalPaths;
144 
setRequestBodyExtractor( FieldsExtractor<RequestT, String> requestBodyExtractor)145     public Builder<RequestT> setRequestBodyExtractor(
146         FieldsExtractor<RequestT, String> requestBodyExtractor) {
147       this.requestBodyExtractor = requestBodyExtractor;
148       return this;
149     }
150 
setQueryParamsExtractor( FieldsExtractor<RequestT, Map<String, List<String>>> queryParamsExtractor)151     public Builder<RequestT> setQueryParamsExtractor(
152         FieldsExtractor<RequestT, Map<String, List<String>>> queryParamsExtractor) {
153       this.queryParamsExtractor = queryParamsExtractor;
154       return this;
155     }
156 
setPath( String rawPath, FieldsExtractor<RequestT, Map<String, String>> pathVarsExtractor)157     public Builder<RequestT> setPath(
158         String rawPath, FieldsExtractor<RequestT, Map<String, String>> pathVarsExtractor) {
159       this.rawPath = rawPath;
160       this.pathVarsExtractor = pathVarsExtractor;
161       return this;
162     }
163 
164     @BetaApi
setAdditionalPaths(String... rawAdditionalPaths)165     public Builder<RequestT> setAdditionalPaths(String... rawAdditionalPaths) {
166       this.rawAdditionalPaths = Arrays.asList(rawAdditionalPaths);
167       return this;
168     }
169 
170     @InternalApi
updateRawPath(String rawPath)171     public Builder<RequestT> updateRawPath(String rawPath) {
172       this.rawPath = rawPath;
173       return this;
174     }
175 
176     @InternalApi
updateRawPath(String target, String replacement)177     public Builder<RequestT> updateRawPath(String target, String replacement) {
178       this.rawPath = this.rawPath.replace(target, replacement);
179       return this;
180     }
181 
build()182     public ProtoMessageRequestFormatter<RequestT> build() {
183       return new ProtoMessageRequestFormatter<>(
184           requestBodyExtractor,
185           queryParamsExtractor,
186           rawPath,
187           PathTemplate.create(rawPath),
188           pathVarsExtractor,
189           rawAdditionalPaths,
190           rawAdditionalPaths.stream().map(PathTemplate::create).collect(Collectors.toList()));
191     }
192   }
193 }
194