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.hc.client5.http.impl.cache;
28  
29  import static org.mockito.ArgumentMatchers.eq;
30  import static org.mockito.Mockito.verify;
31  import static org.mockito.Mockito.when;
32  
33  import java.util.Arrays;
34  import java.util.Collection;
35  import java.util.HashMap;
36  import java.util.Map;
37  
38  import org.apache.hc.client5.http.cache.HttpCacheCASOperation;
39  import org.apache.hc.client5.http.cache.HttpCacheEntry;
40  import org.apache.hc.client5.http.cache.HttpCacheStorageEntry;
41  import org.apache.hc.client5.http.cache.HttpCacheUpdateException;
42  import org.apache.hc.client5.http.cache.ResourceIOException;
43  import org.hamcrest.CoreMatchers;
44  import org.junit.Assert;
45  import org.junit.Before;
46  import org.junit.Test;
47  import org.mockito.Answers;
48  import org.mockito.ArgumentCaptor;
49  import org.mockito.ArgumentMatchers;
50  import org.mockito.Mockito;
51  import org.mockito.invocation.InvocationOnMock;
52  import org.mockito.stubbing.Answer;
53  
54  @SuppressWarnings("boxing") // test code
55  public class TestAbstractSerializingCacheStorage {
56  
57      public static byte[] serialize(final String key, final HttpCacheEntry value) throws ResourceIOException {
58          return ByteArrayCacheEntrySerializer.INSTANCE.serialize(new HttpCacheStorageEntry(key, value));
59      }
60  
61      private AbstractBinaryCacheStorage<String> impl;
62  
63      @Before
64      @SuppressWarnings("unchecked")
65      public void setUp() {
66          impl = Mockito.mock(AbstractBinaryCacheStorage.class,
67                  Mockito.withSettings().defaultAnswer(Answers.CALLS_REAL_METHODS).useConstructor(3));
68      }
69  
70      @Test
71      public void testCachePut() throws Exception {
72          final String key = "foo";
73          final HttpCacheEntry value = HttpTestUtils.makeCacheEntry();
74  
75          when(impl.digestToStorageKey(key)).thenReturn("bar");
76  
77          impl.putEntry(key, value);
78  
79          final ArgumentCaptor<byte[]> argumentCaptor = ArgumentCaptor.forClass(byte[].class);
80          verify(impl).store(eq("bar"), argumentCaptor.capture());
81          Assert.assertArrayEquals(serialize(key, value), argumentCaptor.getValue());
82      }
83  
84      @Test
85      public void testCacheGetNullEntry() throws Exception {
86          final String key = "foo";
87  
88          when(impl.digestToStorageKey(key)).thenReturn("bar");
89          when(impl.restore("bar")).thenReturn(null);
90  
91          final HttpCacheEntry resultingEntry = impl.getEntry(key);
92  
93          verify(impl).restore("bar");
94  
95          Assert.assertThat(resultingEntry, CoreMatchers.nullValue());
96      }
97  
98      @Test
99      public void testCacheGet() throws Exception {
100         final String key = "foo";
101         final HttpCacheEntry value = HttpTestUtils.makeCacheEntry();
102 
103         when(impl.digestToStorageKey(key)).thenReturn("bar");
104         when(impl.restore("bar")).thenReturn(serialize(key, value));
105 
106         final HttpCacheEntry resultingEntry = impl.getEntry(key);
107 
108         verify(impl).restore("bar");
109 
110         Assert.assertThat(resultingEntry, HttpCacheEntryMatcher.equivalent(value));
111     }
112 
113     @Test
114     public void testCacheGetKeyMismatch() throws Exception {
115         final String key = "foo";
116         final HttpCacheEntry value = HttpTestUtils.makeCacheEntry();
117 
118         when(impl.digestToStorageKey(key)).thenReturn("bar");
119         when(impl.restore("bar")).thenReturn(serialize("not-foo", value));
120 
121         final HttpCacheEntry resultingEntry = impl.getEntry(key);
122 
123         verify(impl).restore("bar");
124 
125         Assert.assertThat(resultingEntry, CoreMatchers.nullValue());
126     }
127 
128     @Test
129     public void testCacheRemove()  throws Exception{
130         final String key = "foo";
131 
132         when(impl.digestToStorageKey(key)).thenReturn("bar");
133         impl.removeEntry(key);
134 
135         verify(impl).delete("bar");
136     }
137 
138     @Test
139     public void testCacheUpdateNullEntry() throws Exception {
140         final String key = "foo";
141         final HttpCacheEntry updatedValue = HttpTestUtils.makeCacheEntry();
142 
143         when(impl.digestToStorageKey(key)).thenReturn("bar");
144         when(impl.getForUpdateCAS("bar")).thenReturn(null);
145 
146         impl.updateEntry(key, new HttpCacheCASOperation() {
147 
148             @Override
149             public HttpCacheEntry execute(final HttpCacheEntry existing) throws ResourceIOException {
150                 Assert.assertThat(existing, CoreMatchers.nullValue());
151                 return updatedValue;
152             }
153 
154         });
155 
156         verify(impl).getForUpdateCAS("bar");
157         verify(impl).store(ArgumentMatchers.eq("bar"), ArgumentMatchers.<byte[]>any());
158     }
159 
160     @Test
161     public void testCacheCASUpdate() throws Exception {
162         final String key = "foo";
163         final HttpCacheEntry existingValue = HttpTestUtils.makeCacheEntry();
164         final HttpCacheEntry updatedValue = HttpTestUtils.makeCacheEntry();
165 
166         when(impl.digestToStorageKey(key)).thenReturn("bar");
167         when(impl.getForUpdateCAS("bar")).thenReturn("stuff");
168         when(impl.getStorageObject("stuff")).thenReturn(serialize(key, existingValue));
169         when(impl.updateCAS(ArgumentMatchers.eq("bar"), ArgumentMatchers.eq("stuff"), ArgumentMatchers.<byte[]>any())).thenReturn(true);
170 
171         impl.updateEntry(key, new HttpCacheCASOperation() {
172 
173             @Override
174             public HttpCacheEntry execute(final HttpCacheEntry existing) throws ResourceIOException {
175                 return updatedValue;
176             }
177 
178         });
179 
180         verify(impl).getForUpdateCAS("bar");
181         verify(impl).getStorageObject("stuff");
182         verify(impl).updateCAS(ArgumentMatchers.eq("bar"), ArgumentMatchers.eq("stuff"), ArgumentMatchers.<byte[]>any());
183     }
184 
185     @Test
186     public void testCacheCASUpdateKeyMismatch() throws Exception {
187         final String key = "foo";
188         final HttpCacheEntry existingValue = HttpTestUtils.makeCacheEntry();
189         final HttpCacheEntry updatedValue = HttpTestUtils.makeCacheEntry();
190 
191         when(impl.digestToStorageKey(key)).thenReturn("bar");
192         when(impl.getForUpdateCAS("bar")).thenReturn("stuff");
193         when(impl.getStorageObject("stuff")).thenReturn(serialize("not-foo", existingValue));
194         when(impl.updateCAS(ArgumentMatchers.eq("bar"), ArgumentMatchers.eq("stuff"), ArgumentMatchers.<byte[]>any())).thenReturn(true);
195 
196         impl.updateEntry(key, new HttpCacheCASOperation() {
197 
198             @Override
199             public HttpCacheEntry execute(final HttpCacheEntry existing) throws ResourceIOException {
200                 Assert.assertThat(existing, CoreMatchers.nullValue());
201                 return updatedValue;
202             }
203 
204         });
205 
206         verify(impl).getForUpdateCAS("bar");
207         verify(impl).getStorageObject("stuff");
208         verify(impl).store(ArgumentMatchers.eq("bar"), ArgumentMatchers.<byte[]>any());
209     }
210 
211     @Test
212     public void testSingleCacheUpdateRetry() throws Exception {
213         final String key = "foo";
214         final HttpCacheEntry existingValue = HttpTestUtils.makeCacheEntry();
215         final HttpCacheEntry updatedValue = HttpTestUtils.makeCacheEntry();
216 
217         when(impl.digestToStorageKey(key)).thenReturn("bar");
218         when(impl.getForUpdateCAS("bar")).thenReturn("stuff");
219         when(impl.getStorageObject("stuff")).thenReturn(serialize(key, existingValue));
220         when(impl.updateCAS(ArgumentMatchers.eq("bar"), ArgumentMatchers.eq("stuff"), ArgumentMatchers.<byte[]>any())).thenReturn(false, true);
221 
222         impl.updateEntry(key, new HttpCacheCASOperation() {
223 
224             @Override
225             public HttpCacheEntry execute(final HttpCacheEntry existing) throws ResourceIOException {
226                 return updatedValue;
227             }
228 
229         });
230 
231         verify(impl, Mockito.times(2)).getForUpdateCAS("bar");
232         verify(impl, Mockito.times(2)).getStorageObject("stuff");
233         verify(impl, Mockito.times(2)).updateCAS(ArgumentMatchers.eq("bar"), ArgumentMatchers.eq("stuff"), ArgumentMatchers.<byte[]>any());
234     }
235 
236     @Test
237     public void testCacheUpdateFail() throws Exception {
238         final String key = "foo";
239         final HttpCacheEntry existingValue = HttpTestUtils.makeCacheEntry();
240         final HttpCacheEntry updatedValue = HttpTestUtils.makeCacheEntry();
241 
242         when(impl.digestToStorageKey(key)).thenReturn("bar");
243         when(impl.getForUpdateCAS("bar")).thenReturn("stuff");
244         when(impl.getStorageObject("stuff")).thenReturn(serialize(key, existingValue));
245         when(impl.updateCAS(ArgumentMatchers.eq("bar"), ArgumentMatchers.eq("stuff"), ArgumentMatchers.<byte[]>any())).thenReturn(false, false, false, true);
246 
247         try {
248             impl.updateEntry(key, new HttpCacheCASOperation() {
249 
250                 @Override
251                 public HttpCacheEntry execute(final HttpCacheEntry existing) throws ResourceIOException {
252                     return updatedValue;
253                 }
254 
255             });
256             Assert.fail("HttpCacheUpdateException expected");
257         } catch (final HttpCacheUpdateException ignore) {
258         }
259 
260         verify(impl, Mockito.times(3)).getForUpdateCAS("bar");
261         verify(impl, Mockito.times(3)).getStorageObject("stuff");
262         verify(impl, Mockito.times(3)).updateCAS(ArgumentMatchers.eq("bar"), ArgumentMatchers.eq("stuff"), ArgumentMatchers.<byte[]>any());
263     }
264 
265     @Test
266     public void testBulkGet() throws Exception {
267         final String key1 = "foo this";
268         final String key2 = "foo that";
269         final String storageKey1 = "bar this";
270         final String storageKey2 = "bar that";
271         final HttpCacheEntry value1 = HttpTestUtils.makeCacheEntry();
272         final HttpCacheEntry value2 = HttpTestUtils.makeCacheEntry();
273 
274         when(impl.digestToStorageKey(key1)).thenReturn(storageKey1);
275         when(impl.digestToStorageKey(key2)).thenReturn(storageKey2);
276 
277         when(impl.bulkRestore(ArgumentMatchers.<String>anyCollection())).thenAnswer(new Answer<Map<String, byte[]>>() {
278 
279             @Override
280             public Map<String, byte[]> answer(final InvocationOnMock invocation) throws Throwable {
281                 final Collection<String> keys = invocation.getArgument(0);
282                 final Map<String, byte[]> resultMap = new HashMap<>();
283                 if (keys.contains(storageKey1)) {
284                     resultMap.put(storageKey1, serialize(key1, value1));
285                 }
286                 if (keys.contains(storageKey2)) {
287                     resultMap.put(storageKey2, serialize(key2, value2));
288                 }
289                 return resultMap;
290             }
291         });
292 
293         final Map<String, HttpCacheEntry> entryMap = impl.getEntries(Arrays.asList(key1, key2));
294         Assert.assertThat(entryMap, CoreMatchers.notNullValue());
295         Assert.assertThat(entryMap.get(key1), HttpCacheEntryMatcher.equivalent(value1));
296         Assert.assertThat(entryMap.get(key2), HttpCacheEntryMatcher.equivalent(value2));
297 
298         verify(impl, Mockito.times(2)).digestToStorageKey(key1);
299         verify(impl, Mockito.times(2)).digestToStorageKey(key2);
300         verify(impl).bulkRestore(Arrays.asList(storageKey1, storageKey2));
301     }
302 
303     @Test
304     public void testBulkGetKeyMismatch() throws Exception {
305         final String key1 = "foo this";
306         final String key2 = "foo that";
307         final String storageKey1 = "bar this";
308         final String storageKey2 = "bar that";
309         final HttpCacheEntry value1 = HttpTestUtils.makeCacheEntry();
310         final HttpCacheEntry value2 = HttpTestUtils.makeCacheEntry();
311 
312         when(impl.digestToStorageKey(key1)).thenReturn(storageKey1);
313         when(impl.digestToStorageKey(key2)).thenReturn(storageKey2);
314 
315         when(impl.bulkRestore(ArgumentMatchers.<String>anyCollection())).thenAnswer(new Answer<Map<String, byte[]>>() {
316 
317             @Override
318             public Map<String, byte[]> answer(final InvocationOnMock invocation) throws Throwable {
319                 final Collection<String> keys = invocation.getArgument(0);
320                 final Map<String, byte[]> resultMap = new HashMap<>();
321                 if (keys.contains(storageKey1)) {
322                     resultMap.put(storageKey1, serialize(key1, value1));
323                 }
324                 if (keys.contains(storageKey2)) {
325                     resultMap.put(storageKey2, serialize("not foo", value2));
326                 }
327                 return resultMap;
328             }
329         });
330 
331         final Map<String, HttpCacheEntry> entryMap = impl.getEntries(Arrays.asList(key1, key2));
332         Assert.assertThat(entryMap, CoreMatchers.notNullValue());
333         Assert.assertThat(entryMap.get(key1), HttpCacheEntryMatcher.equivalent(value1));
334         Assert.assertThat(entryMap.get(key2), CoreMatchers.nullValue());
335 
336         verify(impl, Mockito.times(2)).digestToStorageKey(key1);
337         verify(impl, Mockito.times(2)).digestToStorageKey(key2);
338         verify(impl).bulkRestore(Arrays.asList(storageKey1, storageKey2));
339     }
340 
341 }