View Javadoc
1   /*
2    * ====================================================================
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  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,
14   * software distributed under the License is distributed on an
15   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16   * KIND, either express or implied.  See the License for the
17   * specific language governing permissions and limitations
18   * under the License.
19   * ====================================================================
20   *
21   * This software consists of voluntary contributions made by many
22   * individuals on behalf of the Apache Software Foundation.  For more
23   * information on the Apache Software Foundation, please see
24   * <http://www.apache.org/>.
25   *
26   */
27  package org.apache.http.impl.client.cache;
28  
29  import static org.mockito.Matchers.isA;
30  import static org.mockito.Mockito.doThrow;
31  import static org.mockito.Mockito.mock;
32  import static org.mockito.Mockito.times;
33  import static org.mockito.Mockito.verify;
34  import static org.mockito.Mockito.when;
35  
36  import java.io.IOException;
37  import java.util.concurrent.RejectedExecutionException;
38  import java.util.concurrent.TimeUnit;
39  
40  import org.apache.http.Header;
41  import org.apache.http.HttpHost;
42  import org.apache.http.HttpRequest;
43  import org.apache.http.client.cache.HeaderConstants;
44  import org.apache.http.client.cache.HttpCacheEntry;
45  import org.apache.http.client.methods.HttpExecutionAware;
46  import org.apache.http.client.methods.HttpGet;
47  import org.apache.http.client.methods.HttpRequestWrapper;
48  import org.apache.http.client.protocol.HttpClientContext;
49  import org.apache.http.conn.routing.HttpRoute;
50  import org.apache.http.message.BasicHeader;
51  import org.junit.Assert;
52  import org.junit.Before;
53  import org.junit.Test;
54  import org.mockito.ArgumentCaptor;
55  
56  @SuppressWarnings({"boxing","static-access"}) // test code
57  public class TestAsynchronousValidator {
58  
59      private AsynchronousValidator impl;
60  
61      private CachingExec mockClient;
62      private HttpRoute route;
63      private HttpRequestWrapper request;
64      private HttpClientContext context;
65      private HttpExecutionAware mockExecAware;
66      private HttpCacheEntry mockCacheEntry;
67  
68      private SchedulingStrategy mockSchedulingStrategy;
69  
70      @Before
71      public void setUp() {
72          mockClient = mock(CachingExec.class);
73          route = new HttpRoute(new HttpHost("foo.example.com", 80));
74          request = HttpRequestWrapper.wrap(new HttpGet("/"));
75          context = HttpClientContext.create();
76          context.setTargetHost(new HttpHost("foo.example.com"));
77          mockExecAware = mock(HttpExecutionAware.class);
78          mockCacheEntry = mock(HttpCacheEntry.class);
79          mockSchedulingStrategy = mock(SchedulingStrategy.class);
80      }
81  
82      @Test
83      public void testRevalidateCacheEntrySchedulesExecutionAndPopulatesIdentifier() {
84          impl = new AsynchronousValidator(mockSchedulingStrategy);
85  
86          when(mockCacheEntry.hasVariants()).thenReturn(false);
87  
88          impl.revalidateCacheEntry(mockClient, route, request, context, mockExecAware, mockCacheEntry);
89  
90          verify(mockCacheEntry).hasVariants();
91          verify(mockSchedulingStrategy).schedule(isA(AsynchronousValidationRequest.class));
92  
93          Assert.assertEquals(1, impl.getScheduledIdentifiers().size());
94      }
95  
96      @Test
97      public void testMarkCompleteRemovesIdentifier() {
98          impl = new AsynchronousValidator(mockSchedulingStrategy);
99  
100         when(mockCacheEntry.hasVariants()).thenReturn(false);
101 
102         impl.revalidateCacheEntry(mockClient, route, request, context, mockExecAware, mockCacheEntry);
103 
104         final ArgumentCaptor<AsynchronousValidationRequest> cap = ArgumentCaptor.forClass(AsynchronousValidationRequest.class);
105         verify(mockCacheEntry).hasVariants();
106         verify(mockSchedulingStrategy).schedule(cap.capture());
107 
108         Assert.assertEquals(1, impl.getScheduledIdentifiers().size());
109 
110         impl.markComplete(cap.getValue().getIdentifier());
111 
112         Assert.assertEquals(0, impl.getScheduledIdentifiers().size());
113     }
114 
115     @Test
116     public void testRevalidateCacheEntryDoesNotPopulateIdentifierOnRejectedExecutionException() {
117         impl = new AsynchronousValidator(mockSchedulingStrategy);
118 
119         when(mockCacheEntry.hasVariants()).thenReturn(false);
120         doThrow(new RejectedExecutionException()).when(mockSchedulingStrategy).schedule(isA(AsynchronousValidationRequest.class));
121 
122         impl.revalidateCacheEntry(mockClient, route, request, context, mockExecAware, mockCacheEntry);
123 
124         verify(mockCacheEntry).hasVariants();
125 
126         Assert.assertEquals(0, impl.getScheduledIdentifiers().size());
127         verify(mockSchedulingStrategy).schedule(isA(AsynchronousValidationRequest.class));
128     }
129 
130     @Test
131     public void testRevalidateCacheEntryProperlyCollapsesRequest() {
132         impl = new AsynchronousValidator(mockSchedulingStrategy);
133 
134         when(mockCacheEntry.hasVariants()).thenReturn(false);
135 
136         impl.revalidateCacheEntry(mockClient, route, request, context, mockExecAware, mockCacheEntry);
137         impl.revalidateCacheEntry(mockClient, route, request, context, mockExecAware, mockCacheEntry);
138 
139         verify(mockCacheEntry, times(2)).hasVariants();
140         verify(mockSchedulingStrategy).schedule(isA(AsynchronousValidationRequest.class));
141 
142         Assert.assertEquals(1, impl.getScheduledIdentifiers().size());
143     }
144 
145     @Test
146     public void testVariantsBothRevalidated() {
147         impl = new AsynchronousValidator(mockSchedulingStrategy);
148 
149         final HttpRequest req1 = new HttpGet("/");
150         req1.addHeader(new BasicHeader("Accept-Encoding", "identity"));
151 
152         final HttpRequest req2 = new HttpGet("/");
153         req2.addHeader(new BasicHeader("Accept-Encoding", "gzip"));
154 
155         final Header[] variantHeaders = new Header[] {
156                 new BasicHeader(HeaderConstants.VARY, "Accept-Encoding")
157         };
158 
159         when(mockCacheEntry.hasVariants()).thenReturn(true);
160         when(mockCacheEntry.getHeaders(HeaderConstants.VARY)).thenReturn(variantHeaders);
161         mockSchedulingStrategy.schedule(isA(AsynchronousValidationRequest.class));
162 
163         impl.revalidateCacheEntry(mockClient, route, HttpRequestWrapper.wrap(req1), context, mockExecAware, mockCacheEntry);
164         impl.revalidateCacheEntry(mockClient, route, HttpRequestWrapper.wrap(req2), context, mockExecAware, mockCacheEntry);
165 
166         verify(mockCacheEntry, times(2)).hasVariants();
167         verify(mockCacheEntry, times(2)).getHeaders(HeaderConstants.VARY);
168         verify(mockSchedulingStrategy, times(2)).schedule(isA(AsynchronousValidationRequest.class));
169 
170         Assert.assertEquals(2, impl.getScheduledIdentifiers().size());
171     }
172 
173     @Test
174     public void testRevalidateCacheEntryEndToEnd() throws Exception {
175         final CacheConfig config = CacheConfig.custom()
176             .setAsynchronousWorkersMax(1)
177             .setAsynchronousWorkersCore(1)
178             .build();
179         final ImmediateSchedulingStrategy schedulingStrategy = new ImmediateSchedulingStrategy(config);
180         impl = new AsynchronousValidator(schedulingStrategy);
181 
182         when(mockCacheEntry.hasVariants()).thenReturn(false);
183         when(mockClient.revalidateCacheEntry(
184                 route, request, context, mockExecAware, mockCacheEntry)).thenReturn(null);
185 
186         impl.revalidateCacheEntry(mockClient, route, request, context, mockExecAware, mockCacheEntry);
187 
188         try {
189             // shut down backend executor and make sure all finishes properly, 1 second should be sufficient
190             schedulingStrategy.close();
191             schedulingStrategy.awaitTermination(1, TimeUnit.SECONDS);
192         } catch (final InterruptedException ie) {
193 
194         } finally {
195             verify(mockCacheEntry).hasVariants();
196             verify(mockClient).revalidateCacheEntry(route, request, context, mockExecAware, mockCacheEntry);
197 
198             Assert.assertEquals(0, impl.getScheduledIdentifiers().size());
199         }
200     }
201 
202     @Test
203     public void testSchedulingStrategyShutdownOnClose() throws IOException {
204         impl = new AsynchronousValidator(mockSchedulingStrategy);
205 
206         impl.close();
207 
208         verify(mockSchedulingStrategy).close();
209     }
210 }