Coverage Report - com.jcabi.http.wire.FcCache
 
Classes in this File Line Coverage Branch Coverage Complexity
FcCache
92%
48/52
63%
14/22
0
 
 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.base.Joiner;
 33  
 import com.jcabi.aspects.Immutable;
 34  
 import com.jcabi.http.Request;
 35  
 import com.jcabi.http.Response;
 36  
 import com.jcabi.http.Wire;
 37  
 import com.jcabi.http.request.DefaultResponse;
 38  
 import com.jcabi.immutable.Array;
 39  
 import com.jcabi.log.Logger;
 40  
 import java.io.ByteArrayInputStream;
 41  
 import java.io.File;
 42  
 import java.io.FileOutputStream;
 43  
 import java.io.IOException;
 44  
 import java.io.InputStream;
 45  
 import java.io.OutputStream;
 46  
 import java.io.UnsupportedEncodingException;
 47  
 import java.net.URLEncoder;
 48  
 import java.util.AbstractMap;
 49  
 import java.util.Collection;
 50  
 import java.util.LinkedList;
 51  
 import java.util.List;
 52  
 import java.util.Map;
 53  
 import javax.json.Json;
 54  
 import javax.json.JsonArrayBuilder;
 55  
 import javax.json.JsonObject;
 56  
 import javax.json.JsonObjectBuilder;
 57  
 import javax.json.JsonString;
 58  
 import lombok.EqualsAndHashCode;
 59  
 import lombok.ToString;
 60  
 import org.apache.commons.io.FileUtils;
 61  
 
 62  
 /**
 63  
  * Cache for FcWire.
 64  
  *
 65  
  * @author Yegor Bugayenko (yegor@tpc2.com)
 66  
  * @version $Id: 6f612473d84a740e4fcb072b8c6467b5c53e028a $
 67  
  * @since 1.16
 68  
  * @checkstyle MultipleStringLiteralsCheck (500 lines)
 69  
  */
 70  
 @Immutable
 71  0
 @ToString
 72  0
 @EqualsAndHashCode
 73  
 final class FcCache {
 74  
 
 75  
     /**
 76  
      * Directory to keep files in.
 77  
      */
 78  
     private final transient String dir;
 79  
 
 80  
     /**
 81  
      * Ctor.
 82  
      */
 83  
     FcCache() {
 84  3
         this(
 85  
             new File(
 86  
                 new File(System.getProperty("java.io.tmpdir")),
 87  
                 String.format(
 88  
                     "%s-%d",
 89  
                     FcCache.class.getCanonicalName(),
 90  
                     System.nanoTime()
 91  
                 )
 92  
             ).getAbsolutePath()
 93  
         );
 94  3
     }
 95  
 
 96  
     /**
 97  
      * Ctor.
 98  
      * @param path Dir with files
 99  
      */
 100  3
     FcCache(final String path) {
 101  3
         this.dir = path;
 102  3
     }
 103  
 
 104  
     /**
 105  
      * Invalidate all.
 106  
      * @throws IOException If fails
 107  
      */
 108  
     public void invalidate() throws IOException {
 109  1
         final File file = this.file("").getParentFile();
 110  1
         if (file.exists()) {
 111  1
             FileUtils.deleteDirectory(file);
 112  1
             Logger.debug(this, "cache invalidated in %s", file);
 113  
         }
 114  1
     }
 115  
 
 116  
     /**
 117  
      * Get and cache.
 118  
      * @param label Label to use
 119  
      * @param wire Original wire
 120  
      * @param request The request
 121  
      * @param home URI to fetch
 122  
      * @param method HTTP method
 123  
      * @param headers Headers
 124  
      * @param input Input body
 125  
      * @param connect Connect timeout
 126  
      * @param read Read timeout
 127  
      * @return Response
 128  
      * @throws IOException If fails
 129  
      * @checkstyle ParameterNumberCheck (10 lines)
 130  
      */
 131  
     public Response get(final String label, final Wire wire,
 132  
         final Request request, final String home, final String method,
 133  
         final Collection<Map.Entry<String, String>> headers,
 134  
         final InputStream input, final int connect, final int read)
 135  
         throws IOException {
 136  13
         final File file = this.file(label);
 137  
         final Response rsp;
 138  13
         if (file.exists()) {
 139  10
             rsp = this.response(request, file);
 140  
         } else {
 141  3
             rsp = this.saved(
 142  
                 wire.send(
 143  
                     request, home, method,
 144  
                     headers, input, connect, read
 145  
                 ),
 146  
                 file
 147  
             );
 148  
         }
 149  13
         return rsp;
 150  
     }
 151  
 
 152  
     /**
 153  
      * Get response from file.
 154  
      * @param req Request
 155  
      * @param file File to read
 156  
      * @return Response
 157  
      * @throws IOException If fails
 158  
      */
 159  
     @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
 160  
     private Response response(final Request req, final File file)
 161  
         throws IOException {
 162  10
         final JsonObject json = Json.createReader(
 163  
             new ByteArrayInputStream(
 164  
                 FileUtils.readFileToByteArray(file)
 165  
             )
 166  
         ).readObject();
 167  10
         final List<Map.Entry<String, String>> map = new LinkedList<>();
 168  10
         final JsonObject headers = json.getJsonObject("headers");
 169  10
         for (final String name : headers.keySet()) {
 170  
             for (final JsonString value
 171  40
                 : headers.getJsonArray(name).getValuesAs(JsonString.class)) {
 172  40
                 map.add(new AbstractMap.SimpleEntry<>(name, value.getString()));
 173  40
             }
 174  40
         }
 175  10
         Logger.debug(this, "cache loaded from %s", file);
 176  10
         return new DefaultResponse(
 177  
             req,
 178  
             json.getInt("status"),
 179  
             json.getString("reason"),
 180  
             new Array<>(map),
 181  
             json.getString("body").getBytes("UTF-8")
 182  
         );
 183  
     }
 184  
 
 185  
     /**
 186  
      * Save response to file.
 187  
      * @param response Response to save
 188  
      * @param file File to read
 189  
      * @return Response
 190  
      * @throws IOException If fails
 191  
      */
 192  
     private Response saved(final Response response, final File file)
 193  
         throws IOException {
 194  3
         final JsonObjectBuilder json = Json.createObjectBuilder();
 195  3
         json.add("status", response.status());
 196  3
         json.add("reason", response.reason());
 197  3
         final JsonObjectBuilder headers = Json.createObjectBuilder();
 198  
         for (final Map.Entry<String, List<String>> pair
 199  3
             : response.headers().entrySet()) {
 200  12
             final JsonArrayBuilder array = Json.createArrayBuilder();
 201  12
             for (final String value : pair.getValue()) {
 202  12
                 array.add(value);
 203  12
             }
 204  12
             headers.add(pair.getKey(), array);
 205  12
         }
 206  3
         json.add("headers", headers);
 207  3
         json.add("body", response.body());
 208  3
         if (file.getParentFile().mkdirs()) {
 209  3
             Logger.debug(this, "directory created for %s", file);
 210  
         }
 211  3
         try (final OutputStream out = new FileOutputStream(file)) {
 212  3
             Json.createWriter(out).write(json.build());
 213  3
         }
 214  3
         Logger.debug(this, "cache saved into %s", file);
 215  3
         return response;
 216  
     }
 217  
 
 218  
     /**
 219  
      * Make file from label.
 220  
      * @param label Label to use
 221  
      * @return File
 222  
      */
 223  
     private File file(final String label) {
 224  
         final String path;
 225  
         try {
 226  14
             path = Joiner.on("/").join(
 227  
                 URLEncoder.encode(label, "UTF-8")
 228  
                     .replaceAll("_", "__")
 229  
                     .replaceAll("\\+", "_")
 230  
                     .replaceAll("%", "_")
 231  
                     .split("(?<=\\G.{4})")
 232  
             );
 233  0
         } catch (final UnsupportedEncodingException ex) {
 234  0
             throw new IllegalStateException(ex);
 235  14
         }
 236  14
         return new File(this.dir, String.format("%s.json", path));
 237  
     }
 238  
 
 239  
 }