Coverage Report - com.jcabi.http.request.JdkRequest
 
Classes in this File Line Coverage Branch Coverage Complexity
JdkRequest
50%
10/20
0%
0/12
1.941
JdkRequest$1
94%
49/52
91%
22/24
1.941
 
 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.request;
 31  
 
 32  
 import com.jcabi.aspects.Immutable;
 33  
 import com.jcabi.aspects.Loggable;
 34  
 import com.jcabi.http.ImmutableHeader;
 35  
 import com.jcabi.http.Request;
 36  
 import com.jcabi.http.RequestBody;
 37  
 import com.jcabi.http.RequestURI;
 38  
 import com.jcabi.http.Response;
 39  
 import com.jcabi.http.Wire;
 40  
 import com.jcabi.immutable.Array;
 41  
 import java.io.ByteArrayOutputStream;
 42  
 import java.io.IOException;
 43  
 import java.io.InputStream;
 44  
 import java.io.OutputStream;
 45  
 import java.net.HttpURLConnection;
 46  
 import java.net.URI;
 47  
 import java.net.URL;
 48  
 import java.net.URLConnection;
 49  
 import java.util.Collection;
 50  
 import java.util.LinkedList;
 51  
 import java.util.List;
 52  
 import java.util.Map;
 53  
 import lombok.EqualsAndHashCode;
 54  
 import lombok.ToString;
 55  
 
 56  
 /**
 57  
  * Implementation of {@link Request}, based on JDK.
 58  
  *
 59  
  * <p>This implementation will be enough in most situations. However,
 60  
  * sometimes you may need better tuning or an ability to fetch custom
 61  
  * HTTP methods (JDK doesn't support PATCH, for example). In this case,
 62  
  * use {@link ApacheRequest}.
 63  
  *
 64  
  * <p>The class is immutable and thread-safe.
 65  
  *
 66  
  * @author Yegor Bugayenko (yegor@tpc2.com)
 67  
  * @version $Id: 4f121de7e53e852ab14924e03a5be11cdd718fd5 $
 68  
  * @since 0.8
 69  
  * @checkstyle ClassDataAbstractionCoupling (500 lines)
 70  
  */
 71  
 @Immutable
 72  0
 @EqualsAndHashCode(of = "base")
 73  0
 @ToString(of = "base")
 74  
 @Loggable(Loggable.DEBUG)
 75  
 @SuppressWarnings("PMD.TooManyMethods")
 76  
 public final class JdkRequest implements Request {
 77  
 
 78  
     /**
 79  
      * The wire to use.
 80  
      * @checkstyle AnonInnerLength (200 lines)
 81  
      */
 82  1
     private static final Wire WIRE = new Wire() {
 83  
         // @checkstyle ParameterNumber (6 lines)
 84  
         @Override
 85  
         public Response send(final Request req, final String home,
 86  
             final String method,
 87  
             final Collection<Map.Entry<String, String>> headers,
 88  
             final InputStream content,
 89  
             final int connect,
 90  
             final int read) throws IOException {
 91  90
             final URLConnection raw = new URL(home).openConnection();
 92  90
             if (!(raw instanceof HttpURLConnection)) {
 93  0
                 throw new IOException(
 94  
                     String.format(
 95  
                         "'%s' opens %s instead of expected HttpURLConnection",
 96  
                         home, raw.getClass().getName()
 97  
                     )
 98  
                 );
 99  
             }
 100  90
             final HttpURLConnection conn = HttpURLConnection.class.cast(raw);
 101  
             try {
 102  90
                 conn.setConnectTimeout(connect);
 103  90
                 conn.setReadTimeout(read);
 104  90
                 conn.setRequestMethod(method);
 105  90
                 conn.setUseCaches(false);
 106  90
                 conn.setInstanceFollowRedirects(false);
 107  90
                 for (final Map.Entry<String, String> header : headers) {
 108  38
                     conn.addRequestProperty(header.getKey(), header.getValue());
 109  38
                 }
 110  90
                 if (method.equals(Request.POST) || method.equals(Request.PUT)
 111  
                     || method.equals(Request.PATCH)) {
 112  17
                     conn.setDoOutput(true);
 113  17
                     final OutputStream output = conn.getOutputStream();
 114  
                     try {
 115  17
                         this.writeFully(content, output);
 116  
                     } finally {
 117  17
                         output.close();
 118  17
                         content.close();
 119  17
                     }
 120  
                 }
 121  90
                 return new DefaultResponse(
 122  
                     req,
 123  
                     conn.getResponseCode(),
 124  
                     conn.getResponseMessage(),
 125  
                     this.headers(conn.getHeaderFields()),
 126  
                     this.body(conn)
 127  
                 );
 128  0
             } catch (final IOException exp) {
 129  0
                 throw new IOException(
 130  
                     String.format("Failed %s request to %s", method, home),
 131  
                     exp
 132  
                 );
 133  
             } finally {
 134  90
                 conn.disconnect();
 135  
             }
 136  
         }
 137  
         /**
 138  
          * Fully write the input stream contents to the output stream.
 139  
          * @param content The content to write
 140  
          * @param output The output stream to write to
 141  
          * @throws IOException If an IO Exception occurs
 142  
          */
 143  
         private void writeFully(final InputStream content,
 144  
             final OutputStream output) throws IOException {
 145  
             // @checkstyle MagicNumber (1 line)
 146  17
             final byte[] buffer = new byte[8192];
 147  17
             for (int bytes = content.read(buffer); bytes != -1;
 148  10
                 bytes = content.read(buffer)) {
 149  10
                 output.write(buffer, 0, bytes);
 150  
             }
 151  17
         }
 152  
         /**
 153  
          * Get headers from response.
 154  
          * @param fields ImmutableHeader fields
 155  
          * @return Headers
 156  
          */
 157  
         private Array<Map.Entry<String, String>> headers(
 158  
             final Map<String, List<String>> fields) {
 159  90
             final Collection<Map.Entry<String, String>> headers =
 160  
                 new LinkedList<Map.Entry<String, String>>();
 161  
             for (final Map.Entry<String, List<String>> field
 162  90
                 : fields.entrySet()) {
 163  444
                 if (field.getKey() == null) {
 164  90
                     continue;
 165  
                 }
 166  354
                 for (final String value : field.getValue()) {
 167  358
                     headers.add(new ImmutableHeader(field.getKey(), value));
 168  358
                 }
 169  354
             }
 170  90
             return new Array<>(headers);
 171  
         }
 172  
         /**
 173  
          * Get response body of connection.
 174  
          * @param conn Connection
 175  
          * @return Body
 176  
          * @throws IOException
 177  
          */
 178  
         private byte[] body(final HttpURLConnection conn) throws IOException {
 179  
             final InputStream input;
 180  90
             if (conn.getResponseCode() >= HttpURLConnection.HTTP_BAD_REQUEST) {
 181  5
                 input = conn.getErrorStream();
 182  
             } else {
 183  85
                 input = conn.getInputStream();
 184  
             }
 185  
             final byte[] body;
 186  90
             if (input == null) {
 187  4
                 body = new byte[0];
 188  
             } else {
 189  
                 try {
 190  
                     // @checkstyle MagicNumber (1 line)
 191  86
                     final byte[] buffer = new byte[8192];
 192  86
                     final ByteArrayOutputStream output =
 193  
                         new ByteArrayOutputStream();
 194  86
                     for (int bytes = input.read(buffer); bytes != -1;
 195  40
                         bytes = input.read(buffer)) {
 196  40
                         output.write(buffer, 0, bytes);
 197  
                     }
 198  86
                     body = output.toByteArray();
 199  
                 } finally {
 200  86
                     input.close();
 201  86
                 }
 202  
             }
 203  90
             return body;
 204  
         }
 205  
     };
 206  
 
 207  
     /**
 208  
      * Base request.
 209  
      */
 210  
     private final transient Request base;
 211  
 
 212  
     /**
 213  
      * Public ctor.
 214  
      * @param url The resource to work with
 215  
      */
 216  
     public JdkRequest(final URL url) {
 217  0
         this(url.toString());
 218  0
     }
 219  
 
 220  
     /**
 221  
      * Public ctor.
 222  
      * @param uri The resource to work with
 223  
      */
 224  
     public JdkRequest(final URI uri) {
 225  51
         this(uri.toString());
 226  51
     }
 227  
 
 228  
     /**
 229  
      * Public ctor.
 230  
      * @param uri The resource to work with
 231  
      */
 232  51
     public JdkRequest(final String uri) {
 233  51
         this.base = new BaseRequest(JdkRequest.WIRE, uri);
 234  51
     }
 235  
 
 236  
     @Override
 237  
     public RequestURI uri() {
 238  3
         return this.base.uri();
 239  
     }
 240  
 
 241  
     @Override
 242  
     public Request header(final String name, final Object value) {
 243  0
         return this.base.header(name, value);
 244  
     }
 245  
 
 246  
     @Override
 247  
     public Request reset(final String name) {
 248  0
         return this.base.reset(name);
 249  
     }
 250  
 
 251  
     @Override
 252  
     public RequestBody body() {
 253  0
         return this.base.body();
 254  
     }
 255  
 
 256  
     @Override
 257  
     public RequestBody multipartBody() {
 258  0
         return this.base.multipartBody();
 259  
     }
 260  
 
 261  
     @Override
 262  
     public Request method(final String method) {
 263  11
         return this.base.method(method);
 264  
     }
 265  
 
 266  
     @Override
 267  
     public Request timeout(final int connect, final int read) {
 268  0
         return this.base.timeout(connect, read);
 269  
     }
 270  
 
 271  
     @Override
 272  
     public Response fetch() throws IOException {
 273  2
         return this.base.fetch();
 274  
     }
 275  
 
 276  
     @Override
 277  
     public Response fetch(final InputStream stream) throws IOException {
 278  0
         return this.base.fetch(stream);
 279  
     }
 280  
 
 281  
     @Override
 282  
     public <T extends Wire> Request through(final Class<T> type,
 283  
         final Object... args) {
 284  35
         return this.base.through(type, args);
 285  
     }
 286  
 
 287  
 }