Coverage Report - com.jcabi.http.wire.AbstractHeaderBasedCachingWire
 
Classes in this File Line Coverage Branch Coverage Complexity
AbstractHeaderBasedCachingWire
92%
38/41
83%
15/18
2.286
 
 1  
 /**
 2  
  * Copyright (c) 2011-2017, jcabi.com
 3  
  * All rights reserved.
 4  
  *
 5  
  * Redistribution and use in source and binary forms, with or without
 6  
  * modification, are permitted provided that the following conditions
 7  
  * are met: 1) Redistributions of source code must retain the above
 8  
  * copyright notice, this list of conditions and the following
 9  
  * disclaimer. 2) Redistributions in binary form must reproduce the above
 10  
  * copyright notice, this list of conditions and the following
 11  
  * disclaimer in the documentation and/or other materials provided
 12  
  * with the distribution. 3) Neither the name of the jcabi.com nor
 13  
  * the names of its contributors may be used to endorse or promote
 14  
  * products derived from this software without specific prior written
 15  
  * permission.
 16  
  *
 17  
  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 18  
  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
 19  
  * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 20  
  * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
 21  
  * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 22  
  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 23  
  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 24  
  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 25  
  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 26  
  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 27  
  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 28  
  * OF THE POSSIBILITY OF SUCH DAMAGE.
 29  
  */
 30  
 package com.jcabi.http.wire;
 31  
 
 32  
 import com.jcabi.http.Request;
 33  
 import com.jcabi.http.Response;
 34  
 import com.jcabi.http.Wire;
 35  
 import java.io.IOException;
 36  
 import java.io.InputStream;
 37  
 import java.net.HttpURLConnection;
 38  
 import java.util.Collection;
 39  
 import java.util.Map;
 40  
 import java.util.concurrent.ConcurrentHashMap;
 41  
 
 42  
 /**
 43  
  * This is the base class to handle http responses with 304 state.
 44  
  *
 45  
  * @author Ievgen Degtiarenko (ievgen.degtiarenko@gmail.com)
 46  
  * @version $Id: f9f1d1a719d819fc3f8d25a2091a25f265e1cd3b $
 47  
  * @since 2.0
 48  
  */
 49  
 public abstract class AbstractHeaderBasedCachingWire implements Wire {
 50  
 
 51  
     /**
 52  
      * Cache.
 53  
      */
 54  
     private final transient Map<Request, Response> cache;
 55  
 
 56  
     /**
 57  
      * Original wire.
 58  
      */
 59  
     private final transient Wire origin;
 60  
 
 61  
     /**
 62  
      * This header will be return by server to identify content version.
 63  
      */
 64  
     private final transient String scvh;
 65  
 
 66  
     /**
 67  
      * This header will be sent by client to check if server content has
 68  
      * been changed.
 69  
      */
 70  
     private final transient String cmch;
 71  
 
 72  
     /**
 73  
      * Ctor.
 74  
      * @param scvh Server Response Version Header name
 75  
      * @param cmch Client Modification Check Header name
 76  
      * @param wire Original wire
 77  
      */
 78  
     AbstractHeaderBasedCachingWire(
 79  
         final String scvh, final String cmch, final Wire wire
 80  8
     ) {
 81  8
         this.scvh = scvh;
 82  8
         this.cmch = cmch;
 83  8
         this.origin = wire;
 84  8
         this.cache = new ConcurrentHashMap<>();
 85  8
     }
 86  
 
 87  
     // @checkstyle ParameterNumber (3 lines)
 88  
     @Override
 89  
     public final Response send(
 90  
         final Request req, final String home, final String method,
 91  
         final Collection<Map.Entry<String, String>> headers,
 92  
         final InputStream content, final int connect, final int read
 93  
     ) throws IOException {
 94  
         final Response rsp;
 95  27
         if (method.equals(Request.GET) && !this.requestHasCmcHeader(headers)) {
 96  25
             rsp = this.consultCache(
 97  
                 req, home, method, headers, content, connect, read
 98  
             );
 99  
         } else {
 100  2
             rsp = this.origin.send(
 101  
                 req, home, method, headers, content, connect, read
 102  
             );
 103  
         }
 104  27
         return rsp;
 105  
     }
 106  
 
 107  
     /**
 108  
      * Check cache and update if needed.
 109  
      *
 110  
      * @param req Request
 111  
      * @param home URI to fetch
 112  
      * @param method HTTP method
 113  
      * @param headers Headers
 114  
      * @param content HTTP body
 115  
      * @param connect The connect timeout
 116  
      * @param read The read timeout
 117  
      * @return Response obtained
 118  
      * @throws IOException if fails
 119  
      * @checkstyle ParameterNumber (6 lines)
 120  
      */
 121  
     private Response consultCache(
 122  
         final Request req, final String home, final String method,
 123  
         final Collection<Map.Entry<String, String>> headers,
 124  
         final InputStream content, final int connect, final int read
 125  
     ) throws IOException {
 126  
         final Response rsp;
 127  25
         if (this.cache.containsKey(req)) {
 128  17
             rsp = this.validateCacheWithServer(
 129  
                 req, home, method, headers, content, connect, read
 130  
             );
 131  
         } else {
 132  8
             rsp = this.origin.send(
 133  
                 req, home, method, headers, content, connect, read
 134  
             );
 135  8
             this.updateCache(req, rsp);
 136  
         }
 137  25
         return rsp;
 138  
     }
 139  
 
 140  
     /**
 141  
      * Check response and update cache or evict from cache if needed.
 142  
      * @param req Request
 143  
      * @param home URI to fetch
 144  
      * @param method HTTP method
 145  
      * @param headers Headers
 146  
      * @param content HTTP body
 147  
      * @param connect The connect timeout
 148  
      * @param read The read timeout
 149  
      * @return Response obtained
 150  
      * @throws IOException if fails
 151  
      * @checkstyle ParameterNumber (8 lines)
 152  
      */
 153  
     private Response validateCacheWithServer(
 154  
         final Request req, final String home, final String method,
 155  
         final Collection<Map.Entry<String, String>> headers,
 156  
         final InputStream content, final int connect, final int read
 157  
     ) throws IOException {
 158  17
         final Response cached = this.cache.get(req);
 159  17
         final Collection<Map.Entry<String, String>> hdrs = this.enrich(
 160  
             headers, cached
 161  
         );
 162  17
         Response result = this.origin.send(
 163  
             req, home, method, hdrs, content, connect, read
 164  
         );
 165  17
         if (result.status() == HttpURLConnection.HTTP_NOT_MODIFIED) {
 166  13
             result = cached;
 167  
         } else {
 168  4
             this.updateCache(req, result);
 169  
         }
 170  17
         return result;
 171  
     }
 172  
 
 173  
     /**
 174  
      * Add, update or evict response in cache.
 175  
      * @param req The request to be used as key
 176  
      * @param rsp The response to add/update
 177  
      */
 178  
     private void updateCache(final Request req, final Response rsp) {
 179  12
         if (rsp.headers().containsKey(this.scvh)) {
 180  8
             this.cache.put(req, rsp);
 181  4
         } else if (rsp.status() == HttpURLConnection.HTTP_OK) {
 182  3
             this.cache.remove(req);
 183  
         }
 184  12
     }
 185  
 
 186  
     /**
 187  
      * Add identify content version header.
 188  
      *
 189  
      * @param headers Original headers
 190  
      * @param rsp Cached response
 191  
      * @return Map with extra header
 192  
      */
 193  
     private Collection<Map.Entry<String, String>> enrich(
 194  
         final Collection<Map.Entry<String, String>> headers, final Response rsp
 195  
     ) {
 196  17
         final Collection<String> list = rsp.headers().get(
 197  
             this.scvh
 198  
         );
 199  17
         final Map<String, String> map =
 200  
             new ConcurrentHashMap<>(headers.size() + 1);
 201  17
         for (final Map.Entry<String, String> entry : headers) {
 202  0
             map.put(entry.getKey(), entry.getValue());
 203  0
         }
 204  17
         map.put(
 205  
             this.cmch, list.iterator().next()
 206  
         );
 207  17
         return map.entrySet();
 208  
     }
 209  
     /**
 210  
      * Check if the request send through this Wire has the cmch header.
 211  
      * @param headers The headers of the request.
 212  
      * @return True if the request contains the cmch header, false otherwise.
 213  
      */
 214  
     private boolean requestHasCmcHeader(
 215  
         final Collection<Map.Entry<String, String>> headers
 216  
     ) {
 217  27
         boolean requestHasCmch = false;
 218  27
         for (final Map.Entry<String, String> header : headers) {
 219  2
             if (header.getKey().equals(this.cmch)) {
 220  2
                 requestHasCmch = true;
 221  2
                 break;
 222  
             }
 223  0
         }
 224  27
         return requestHasCmch;
 225  
     }
 226  
 }