Coverage Report - com.jcabi.http.response.WebLinkingResponse
 
Classes in this File Line Coverage Branch Coverage Complexity
WebLinkingResponse
84%
16/19
38%
7/18
1.722
WebLinkingResponse$Link
N/A
N/A
1.722
WebLinkingResponse$SimpleLink
55%
15/27
37%
3/8
1.722
 
 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.response;
 31  
 
 32  
 import com.jcabi.aspects.Immutable;
 33  
 import com.jcabi.http.Request;
 34  
 import com.jcabi.http.Response;
 35  
 import com.jcabi.immutable.ArrayMap;
 36  
 import java.io.IOException;
 37  
 import java.net.URI;
 38  
 import java.util.Collection;
 39  
 import java.util.Locale;
 40  
 import java.util.Map;
 41  
 import java.util.Set;
 42  
 import java.util.concurrent.ConcurrentHashMap;
 43  
 import java.util.concurrent.ConcurrentMap;
 44  
 import java.util.regex.Matcher;
 45  
 import java.util.regex.Pattern;
 46  
 import lombok.EqualsAndHashCode;
 47  
 
 48  
 /**
 49  
  * Web Linking response.
 50  
  *
 51  
  * <p>This response decorator is able to understand and parse {@code Link}
 52  
  * HTTP header according to
 53  
  * <a href="http://tools.ietf.org/html/rfc5988">RFC 5988 "Web Linking"</a>,
 54  
  * for example:
 55  
  *
 56  
  * <pre> String name = new JdkRequest("http://my.example.com")
 57  
  *   .fetch()
 58  
  *   .as(WebLinkingResponse.class)
 59  
  *   .follow("next")
 60  
  *   .fetch();</pre>
 61  
  *
 62  
  * <p>The class is immutable and thread-safe.
 63  
  *
 64  
  * @author Yegor Bugayenko (yegor@tpc2.com)
 65  
  * @version $Id: b4953a8a2bbd3bc5656c93669d3ea8945cb8f70b $
 66  
  * @since 0.9
 67  
  * @see <a href="http://tools.ietf.org/html/rfc5988">RFC 5988 "Web Linking"</a>
 68  
  */
 69  0
 @Immutable
 70  0
 @EqualsAndHashCode(callSuper = true)
 71  
 @SuppressWarnings("PMD.TooManyMethods")
 72  
 public final class WebLinkingResponse extends AbstractResponse {
 73  
 
 74  
     /**
 75  
      * ImmutableHeader name.
 76  
      */
 77  
     private static final String HEADER = "Link";
 78  
 
 79  
     /**
 80  
      * Param name.
 81  
      */
 82  
     private static final String REL = "rel";
 83  
 
 84  
     /**
 85  
      * Public ctor.
 86  
      * @param resp Response
 87  
      */
 88  
     public WebLinkingResponse(final Response resp) {
 89  3
         super(resp);
 90  3
     }
 91  
 
 92  
     /**
 93  
      * Follow link by REL.
 94  
      * @param rel Relation name
 95  
      * @return The same object
 96  
      * @throws IOException If fails
 97  
      */
 98  
     public Request follow(final String rel) throws IOException {
 99  2
         final WebLinkingResponse.Link link = this.links().get(rel);
 100  2
         if (link == null) {
 101  0
             throw new IOException(
 102  
                 String.format(
 103  
                     "Link with rel=\"%s\" doesn't exist, use #hasLink()",
 104  
                     rel
 105  
                 )
 106  
             );
 107  
         }
 108  2
         return new RestResponse(this).jump(link.uri());
 109  
     }
 110  
 
 111  
     /**
 112  
      * Get all links provided.
 113  
      * @return List of all links found
 114  
      * @throws IOException If fails
 115  
      */
 116  
     @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
 117  
     public Map<String, WebLinkingResponse.Link> links() throws IOException {
 118  6
         final ConcurrentMap<String, WebLinkingResponse.Link> links =
 119  
             new ConcurrentHashMap<>(0);
 120  6
         final Collection<String> headers =
 121  
             this.headers().get(WebLinkingResponse.HEADER);
 122  6
         if (headers != null) {
 123  6
             for (final String header : headers) {
 124  14
                 for (final String part : header.split(",")) {
 125  8
                     final WebLinkingResponse.Link link =
 126  
                         new WebLinkingResponse.SimpleLink(part.trim());
 127  8
                     final String rel = link.get(WebLinkingResponse.REL);
 128  8
                     if (rel != null) {
 129  8
                         links.put(rel, link);
 130  
                     }
 131  
                 }
 132  6
             }
 133  
         }
 134  6
         return links;
 135  
     }
 136  
 
 137  
     /**
 138  
      * Single link.
 139  
      */
 140  
     @Immutable
 141  
     public interface Link extends Map<String, String> {
 142  
         /**
 143  
          * Its URI.
 144  
          * @return URI
 145  
          */
 146  
         URI uri();
 147  
     }
 148  
 
 149  
     /**
 150  
      * Implementation of a link.
 151  
      */
 152  8
     @Immutable
 153  0
     @EqualsAndHashCode
 154  
     private static final class SimpleLink implements WebLinkingResponse.Link {
 155  
         /**
 156  
          * Pattern to match link value.
 157  
          */
 158  
         @SuppressWarnings("PMD.UnusedPrivateField")
 159  1
         private static final Pattern PTN = Pattern.compile(
 160  
             "<([^>]+)>\\s*;(.*)"
 161  
         );
 162  
         /**
 163  
          * URI encapsulated.
 164  
          */
 165  
         private final transient String addr;
 166  
         /**
 167  
          * Map of link params.
 168  
          */
 169  
         private final transient ArrayMap<String, String> params;
 170  
         /**
 171  
          * Public ctor (parser).
 172  
          * @param text Text to parse
 173  
          * @throws IOException If fails
 174  
          */
 175  8
         SimpleLink(final String text) throws IOException {
 176  8
             final Matcher matcher = WebLinkingResponse.SimpleLink.PTN
 177  
                 .matcher(text);
 178  8
             if (!matcher.matches()) {
 179  0
                 throw new IOException(
 180  
                     String.format(
 181  
                         "Link header value doesn't comply to RFC-5988: \"%s\"",
 182  
                         text
 183  
                     )
 184  
                 );
 185  
             }
 186  8
             this.addr = matcher.group(1);
 187  8
             final ConcurrentMap<String, String> args =
 188  
                 new ConcurrentHashMap<>(0);
 189  
             for (final String pair
 190  22
                 : matcher.group(2).trim().split("\\s*;\\s*")) {
 191  14
                 final String[] parts = pair.split("=");
 192  14
                 args.put(
 193  
                     parts[0].trim().toLowerCase(Locale.ENGLISH),
 194  
                     parts[1].trim().replaceAll("(^\"|\"$)", "")
 195  
                 );
 196  
             }
 197  8
             this.params = new ArrayMap<>(args);
 198  8
         }
 199  
         @Override
 200  
         public URI uri() {
 201  4
             return URI.create(this.addr);
 202  
         }
 203  
         @Override
 204  
         public int size() {
 205  0
             return this.params.size();
 206  
         }
 207  
         @Override
 208  
         public boolean isEmpty() {
 209  0
             return this.params.isEmpty();
 210  
         }
 211  
         @Override
 212  
         public boolean containsKey(final Object key) {
 213  0
             return this.params.containsKey(key);
 214  
         }
 215  
         @Override
 216  
         public boolean containsValue(final Object value) {
 217  0
             return this.params.containsValue(value);
 218  
         }
 219  
         @Override
 220  
         public String get(final Object key) {
 221  8
             return this.params.get(key);
 222  
         }
 223  
         @Override
 224  
         public String put(final String key, final String value) {
 225  0
             throw new UnsupportedOperationException("#put()");
 226  
         }
 227  
         @Override
 228  
         public String remove(final Object key) {
 229  0
             throw new UnsupportedOperationException("#remove()");
 230  
         }
 231  
         @Override
 232  
         public void putAll(final Map<? extends String, ? extends String> map) {
 233  0
             throw new UnsupportedOperationException("#putAll()");
 234  
         }
 235  
         @Override
 236  
         public void clear() {
 237  0
             throw new UnsupportedOperationException("#clear()");
 238  
         }
 239  
         @Override
 240  
         public Set<String> keySet() {
 241  0
             return this.params.keySet();
 242  
         }
 243  
         @Override
 244  
         public Collection<String> values() {
 245  0
             return this.params.values();
 246  
         }
 247  
         @Override
 248  
         public Set<Map.Entry<String, String>> entrySet() {
 249  2
             return this.params.entrySet();
 250  
         }
 251  
     }
 252  
 
 253  
 }