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