Coverage Report - com.jcabi.http.wire.CachingWire
 
Classes in this File Line Coverage Branch Coverage Complexity
CachingWire
75%
21/28
30%
8/26
1.875
CachingWire$1
100%
2/2
N/A
1.875
CachingWire$1$1
100%
2/2
N/A
1.875
CachingWire$Query
91%
11/12
38%
14/36
1.875
 
 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.google.common.cache.CacheBuilder;
 33  
 import com.google.common.cache.CacheLoader;
 34  
 import com.google.common.cache.LoadingCache;
 35  
 import com.jcabi.aspects.Immutable;
 36  
 import com.jcabi.aspects.Tv;
 37  
 import com.jcabi.http.Request;
 38  
 import com.jcabi.http.Response;
 39  
 import com.jcabi.http.Wire;
 40  
 import java.io.IOException;
 41  
 import java.io.InputStream;
 42  
 import java.net.URI;
 43  
 import java.util.Collection;
 44  
 import java.util.Map;
 45  
 import java.util.concurrent.ExecutionException;
 46  
 import lombok.EqualsAndHashCode;
 47  
 import lombok.ToString;
 48  
 
 49  
 /**
 50  
  * Wire that caches GET requests.
 51  
  *
 52  
  * <p>This decorator can be used when you want to avoid duplicate
 53  
  * GET requests to load-sensitive resources, for example:
 54  
  *
 55  
  * <pre> String html = new JdkRequest("http://goggle.com")
 56  
  *   .through(CachingWire.class)
 57  
  *   .header(HttpHeaders.ACCEPT, MediaType.TEXT_PLAIN)
 58  
  *   .fetch()
 59  
  *   .body();</pre>
 60  
  *
 61  
  * <p>Since 1.5, you can also configure it to flush the entire cache
 62  
  * on certain request URI's, for example:
 63  
  *
 64  
  * <pre>new JdkRequest(uri)
 65  
  *   .through(CachingWire.class, "GET /save/.*")
 66  
  *   .uri().path("/save/123").back()
 67  
  *   .fetch();</pre>
 68  
  *
 69  
  * <p>The regular expression provided will be used against a string
 70  
  * constructed as an HTTP method, space, path of the URI together with
 71  
  * query part.
 72  
  *
 73  
  * <p>The class is immutable and thread-safe.
 74  
  *
 75  
  * @author Yegor Bugayenko (yegor@tpc2.com)
 76  
  * @version $Id: 025f47072606e5c8840072ccef9cd097b3f93d42 $
 77  
  * @since 1.0
 78  
  */
 79  
 @Immutable
 80  0
 @ToString
 81  14
 @EqualsAndHashCode(of = { "origin", "regex" })
 82  
 public final class CachingWire implements Wire {
 83  
 
 84  
     /**
 85  
      * Loader.
 86  
      */
 87  
     private static final CacheLoader<Wire,
 88  1
         LoadingCache<CachingWire.Query, Response>> LOADER =
 89  3
         new CacheLoader<Wire, LoadingCache<CachingWire.Query, Response>>() {
 90  
             @Override
 91  
             public LoadingCache<CachingWire.Query, Response> load(
 92  
                 final Wire key) {
 93  2
                 return CacheBuilder.newBuilder().build(
 94  5
                     new CacheLoader<CachingWire.Query, Response>() {
 95  
                         @Override
 96  
                         public Response load(final CachingWire.Query query)
 97  
                             throws IOException {
 98  3
                             return query.fetch();
 99  
                         }
 100  
                     }
 101  
                 );
 102  
             }
 103  
         };
 104  
 
 105  
     /**
 106  
      * Cache.
 107  
      */
 108  
     private static final LoadingCache<Wire,
 109  1
         LoadingCache<CachingWire.Query, Response>> CACHE =
 110  
         CacheBuilder.newBuilder().build(CachingWire.LOADER);
 111  
 
 112  
     /**
 113  
      * Original wire.
 114  
      */
 115  
     private final transient Wire origin;
 116  
 
 117  
     /**
 118  
      * Flushing regular expression.
 119  
      */
 120  
     private final transient String regex;
 121  
 
 122  
     /**
 123  
      * Public ctor.
 124  
      * @param wire Original wire
 125  
      */
 126  
     public CachingWire(final Wire wire) {
 127  2
         this(wire, "$never");
 128  2
     }
 129  
 
 130  
     /**
 131  
      * Public ctor.
 132  
      * @param wire Original wire
 133  
      * @param flsh Flushing regular expression
 134  
      * @since 1.5
 135  
      */
 136  3
     public CachingWire(final Wire wire, final String flsh) {
 137  3
         this.origin = wire;
 138  3
         this.regex = flsh;
 139  3
     }
 140  
 
 141  
     // @checkstyle ParameterNumber (5 lines)
 142  
     @Override
 143  
     public Response send(final Request req, final String home,
 144  
         final String method,
 145  
         final Collection<Map.Entry<String, String>> headers,
 146  
         final InputStream content,
 147  
         final int connect,
 148  
         final int read) throws IOException {
 149  16
         final URI uri = req.uri().get();
 150  16
         final StringBuilder label = new StringBuilder(Tv.HUNDRED)
 151  
             .append(method).append(' ').append(uri.getPath());
 152  16
         if (uri.getQuery() != null) {
 153  1
             label.append('?').append(uri.getQuery());
 154  
         }
 155  16
         if (label.toString().matches(this.regex)) {
 156  
             try {
 157  1
                 CachingWire.CACHE.get(this).invalidateAll();
 158  0
             } catch (final ExecutionException ex) {
 159  0
                 throw new IllegalStateException(ex);
 160  1
             }
 161  
         }
 162  
         final Response rsp;
 163  16
         if (method.equals(Request.GET)) {
 164  
             try {
 165  13
                 rsp = CachingWire.CACHE.get(this).get(
 166  
                     new CachingWire.Query(
 167  
                         this.origin, req, home, headers, content,
 168  
                         connect, read
 169  
                     )
 170  
                 );
 171  0
             } catch (final ExecutionException ex) {
 172  0
                 throw new IOException(ex);
 173  13
             }
 174  
         } else {
 175  3
             rsp = this.origin.send(
 176  
                 req, home, method, headers, content,
 177  
                 connect, read
 178  
             );
 179  
         }
 180  16
         return rsp;
 181  
     }
 182  
 
 183  
     /**
 184  
      * Invalidate the entire cache.
 185  
      * @since 1.15
 186  
      */
 187  
     public static void invalidate() {
 188  0
         CachingWire.CACHE.invalidateAll();
 189  0
     }
 190  
 
 191  
     /**
 192  
      * Query.
 193  
      */
 194  0
     @ToString
 195  23
     @EqualsAndHashCode(of = { "origin", "request", "uri", "headers" })
 196  
     private static final class Query {
 197  
         /**
 198  
          * Origin wire.
 199  
          */
 200  
         private final transient Wire origin;
 201  
         /**
 202  
          * Request.
 203  
          */
 204  
         private final transient Request request;
 205  
         /**
 206  
          * URI.
 207  
          */
 208  
         private final transient String uri;
 209  
         /**
 210  
          * Headers.
 211  
          */
 212  
         private final transient Collection<Map.Entry<String, String>> headers;
 213  
         /**
 214  
          * Body.
 215  
          */
 216  
         private final transient InputStream body;
 217  
         /**
 218  
          * Connect timeout.
 219  
          */
 220  
         private final transient int connect;
 221  
         /**
 222  
          * Read timeout.
 223  
          */
 224  
         private final transient int read;
 225  
 
 226  
         /**
 227  
          * Ctor.
 228  
          * @param wire Original wire
 229  
          * @param req Request
 230  
          * @param home URI to fetch
 231  
          * @param hdrs Headers
 232  
          * @param input Input body
 233  
          * @param cnct Connect timeout
 234  
          * @param rdd Read timeout
 235  
          * @checkstyle ParameterNumberCheck (5 lines)
 236  
          */
 237  
         Query(final Wire wire, final Request req, final String home,
 238  
             final Collection<Map.Entry<String, String>> hdrs,
 239  
             final InputStream input, final int cnct,
 240  13
             final int rdd) {
 241  13
             this.origin = wire;
 242  13
             this.request = req;
 243  13
             this.uri = home;
 244  13
             this.headers = hdrs;
 245  13
             this.body = input;
 246  13
             this.connect = cnct;
 247  13
             this.read = rdd;
 248  13
         }
 249  
         /**
 250  
          * Fetch.
 251  
          * @return Response
 252  
          * @throws IOException If fails
 253  
          */
 254  
         public Response fetch() throws IOException {
 255  3
             return this.origin.send(
 256  
                 this.request, this.uri, Request.GET, this.headers, this.body,
 257  
                 this.connect, this.read
 258  
             );
 259  
         }
 260  
     }
 261  
 
 262  
 }