View Javadoc
1   /*
2    * #%L
3    * wcm.io
4    * %%
5    * Copyright (C) 2014 wcm.io
6    * %%
7    * Licensed under the Apache License, Version 2.0 (the "License");
8    * you may not use this file except in compliance with the License.
9    * You may obtain a copy of the License at
10   *
11   *      http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   * #L%
19   */
20  package io.wcm.caravan.pipeline.extensions.hal.client.action;
21  
22  import static io.wcm.caravan.io.http.request.CaravanHttpRequest.CORRELATION_ID_HEADER_NAME;
23  
24  import java.util.Collection;
25  import java.util.Map;
26  
27  import org.apache.commons.lang3.StringUtils;
28  import org.apache.http.client.methods.HttpGet;
29  import org.osgi.annotation.versioning.ProviderType;
30  
31  import com.google.common.collect.Multimap;
32  
33  import io.wcm.caravan.hal.resource.Link;
34  import io.wcm.caravan.io.http.request.CaravanHttpRequest;
35  import io.wcm.caravan.io.http.request.CaravanHttpRequestBuilder;
36  import io.wcm.caravan.pipeline.JsonPipeline;
37  import io.wcm.caravan.pipeline.JsonPipelineContext;
38  import io.wcm.caravan.pipeline.JsonPipelineExceptionHandler;
39  import io.wcm.caravan.pipeline.JsonPipelineOutput;
40  import io.wcm.caravan.pipeline.cache.CacheControlUtils;
41  import io.wcm.caravan.pipeline.cache.CacheStrategy;
42  import io.wcm.caravan.pipeline.extensions.hal.client.ServiceIdExtractor;
43  import rx.Observable;
44  
45  /**
46   * Action to load a HAL link.
47   */
48  @ProviderType
49  public final class LoadLink extends AbstractHalClientAction {
50  
51    private final ServiceIdExtractor serviceId;
52    private final Link link;
53    private final Map<String, Object> parameters;
54    private String httpMethod = HttpGet.METHOD_NAME;
55  
56    /**
57     * @param serviceId Service ID
58     * @param link Link to load
59     * @param parameters URI parameters
60     */
61    public LoadLink(String serviceId, Link link, Map<String, Object> parameters) {
62      this.serviceId = (path) -> serviceId;
63      this.link = link;
64      this.parameters = parameters;
65    }
66  
67    /**
68     * @param serviceId a function to extract the serviceid from a path
69     * @param link Link to load
70     * @param parameters URI parameters
71     */
72    public LoadLink(ServiceIdExtractor serviceId, Link link, Map<String, Object> parameters) {
73      this.serviceId = serviceId;
74      this.link = link;
75      this.parameters = parameters;
76    }
77  
78    @Override
79    public String getId() {
80      return "LOAD-LINK(" + httpMethod + "-" + serviceId.getServiceId(link.getHref()) + '-' + StringUtils.defaultIfBlank(link.getName(), "") + '-'
81          + parameters.hashCode() + ")";
82    }
83  
84    @Override
85    public Observable<JsonPipelineOutput> execute(JsonPipelineOutput previousStepOutput, JsonPipelineContext pipelineContext) {
86  
87      CaravanHttpRequest request = createRequest(previousStepOutput);
88      JsonPipeline pipeline = createPipeline(pipelineContext, request);
89      return pipeline.getOutput().map(jsonPipelineOutput -> {
90  
91        int maxAge = CacheControlUtils.getLowestMaxAge(jsonPipelineOutput, previousStepOutput);
92        return jsonPipelineOutput.withMaxAge(maxAge);
93  
94      });
95  
96    }
97  
98    /**
99     * @param httpMethodToUse the HTTP method to use when loading the link
100    * @return this
101    */
102   public HalClientAction withHttpMethod(String httpMethodToUse) {
103     this.httpMethod = httpMethodToUse;
104     return this;
105   }
106 
107   private CaravanHttpRequest createRequest(JsonPipelineOutput previousStepOutput) {
108 
109     CaravanHttpRequestBuilder builder = getRequestBuilder();
110     builder = setCacheControlHeaderIfExists(builder, previousStepOutput);
111     builder = setCorrelationIdIfExists(builder, previousStepOutput);
112     builder = setAdditionalHttpHeadersIfExists(builder);
113     builder = builder.method(httpMethod);
114     return builder.build(parameters);
115 
116   }
117 
118   private JsonPipeline createPipeline(JsonPipelineContext context, CaravanHttpRequest request) {
119 
120     if (hasLogger()) {
121       getLogger().debug("Execute request: " + request);
122     }
123     JsonPipeline pipeline = context.getFactory().create(request, context.getProperties());
124     pipeline = setCacheStrategyIfExists(pipeline);
125     pipeline = addExceptionHandlers(pipeline);
126     return pipeline;
127 
128   }
129 
130   /**
131    * @return URL of the current link and parameters.
132    */
133   public String getUrl() {
134     return getRequestBuilder().build(parameters).getUrl();
135   }
136 
137   private CaravanHttpRequestBuilder getRequestBuilder() {
138     return new CaravanHttpRequestBuilder(serviceId.getServiceId(link.getHref())).append(link.getHref());
139   }
140 
141   /**
142    * Adds a cache point to the given JSON pipeline if provided and the http request method is GET.
143    * @param pipeline JSON pipeline
144    * @return JSON pipeline with or without cache point
145    */
146   private JsonPipeline setCacheStrategyIfExists(JsonPipeline pipeline) {
147     if (!HttpGet.METHOD_NAME.equals(httpMethod)) {
148       return pipeline;
149     }
150     else {
151       CacheStrategy cacheStrategy = getCacheStrategy();
152       return cacheStrategy == null ? pipeline : pipeline.addCachePoint(cacheStrategy);
153     }
154   }
155 
156   /**
157    * Sets the HTTP Cache-Control header to the given request builder if provided by the previous JSON pipeline output.
158    * @param builder HTTP request builder
159    * @param previousStepOutput Previous JSON output
160    * @return HTTP request builder with or without HTTP Cache-Control header
161    */
162   private CaravanHttpRequestBuilder setCacheControlHeaderIfExists(CaravanHttpRequestBuilder builder, JsonPipelineOutput previousStepOutput) {
163 
164     if (previousStepOutput.getRequests().isEmpty()) {
165       return builder;
166     }
167     CaravanHttpRequest previousRequest = previousStepOutput.getRequests().get(0);
168     Collection<String> cacheControlHeader = previousRequest.getHeaders().get("Cache-Control");
169     if (cacheControlHeader == null || cacheControlHeader.isEmpty()) {
170       return builder;
171     }
172     return builder.header("Cache-Control", cacheControlHeader);
173 
174   }
175 
176   /**
177    * Sets the {@code correlation-id} HTTP header to the given request builder if provided by the previous JSON pipeline
178    * output.
179    * @param builder HTTP request builder
180    * @param previousStepOutput Previous JSON output
181    * @return HTTP request builder with or without {@code correlation-id} HTTP header
182    */
183   private CaravanHttpRequestBuilder setCorrelationIdIfExists(CaravanHttpRequestBuilder builder, JsonPipelineOutput previousStepOutput) {
184     return previousStepOutput.getCorrelationId() == null ? builder : builder.header(CORRELATION_ID_HEADER_NAME, previousStepOutput.getCorrelationId());
185   }
186 
187   /**
188    * Adds exception handler(s) to the given JSON pipeline if provided.
189    * @param pipeline JSON pipeline
190    * @return JSON pipeline with or without exception handler(s)
191    */
192   private JsonPipeline addExceptionHandlers(JsonPipeline pipeline) {
193 
194     JsonPipeline newPipeline = pipeline;
195     for (JsonPipelineExceptionHandler handler : getExceptionHandlers()) {
196       newPipeline = newPipeline.handleException(handler);
197     }
198     return newPipeline;
199 
200   }
201 
202   /**
203    * Sets additional HTTP headers to the given request builder if provided.
204    * @param builder HTTP request builder
205    * @return HTTP request builder with or without headers
206    */
207   private CaravanHttpRequestBuilder setAdditionalHttpHeadersIfExists(CaravanHttpRequestBuilder builder) {
208     getHttpHeaders().keySet().forEach(name -> builder.header(name, getHttpHeaders().get(name)));
209     return builder;
210   }
211 
212 }