Coverage Report - com.jcabi.http.mock.MkGrizzlyAdapter
 
Classes in this File Line Coverage Branch Coverage Complexity
MkGrizzlyAdapter
92%
63/68
83%
20/24
2.429
MkGrizzlyAdapter$Conditional
81%
9/11
4%
1/22
2.429
MkGrizzlyAdapter$QueryWithAnswer
87%
7/8
0%
0/20
2.429
 
 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.mock;
 31  
 
 32  
 import com.jcabi.log.Logger;
 33  
 import com.sun.grizzly.tcp.http11.GrizzlyAdapter;
 34  
 import com.sun.grizzly.tcp.http11.GrizzlyRequest;
 35  
 import com.sun.grizzly.tcp.http11.GrizzlyResponse;
 36  
 import java.io.OutputStreamWriter;
 37  
 import java.io.PrintWriter;
 38  
 import java.io.UnsupportedEncodingException;
 39  
 import java.net.HttpURLConnection;
 40  
 import java.util.Collection;
 41  
 import java.util.Iterator;
 42  
 import java.util.LinkedList;
 43  
 import java.util.NoSuchElementException;
 44  
 import java.util.Queue;
 45  
 import java.util.concurrent.ConcurrentLinkedQueue;
 46  
 import java.util.concurrent.atomic.AtomicInteger;
 47  
 import lombok.EqualsAndHashCode;
 48  
 import org.apache.http.HttpHeaders;
 49  
 import org.hamcrest.Matcher;
 50  
 
 51  
 /**
 52  
  * Mocker of Java Servlet container.
 53  
  *
 54  
  * @author Yegor Bugayenko (yegor@tpc2.com)
 55  
  * @version $Id: a95e093eeec53bed7694d6f27dd54de4e28fcc18 $
 56  
  * @since 0.10
 57  
  */
 58  
 @SuppressWarnings("PMD.TooManyMethods")
 59  69
 final class MkGrizzlyAdapter extends GrizzlyAdapter {
 60  
 
 61  
     /**
 62  
      * The encoding to use.
 63  
      */
 64  
     private static final String ENCODING = "UTF-8";
 65  
 
 66  
     /**
 67  
      * Queries received.
 68  
      */
 69  69
     private final transient Queue<QueryWithAnswer> queue =
 70  
         new ConcurrentLinkedQueue<QueryWithAnswer>();
 71  
 
 72  
     /**
 73  
      * Answers to give conditionally.
 74  
      */
 75  69
     private final transient Queue<Conditional> conditionals =
 76  
         new ConcurrentLinkedQueue<Conditional>();
 77  
 
 78  
     // @checkstyle ExecutableStatementCount (55 lines)
 79  
     @Override
 80  
     @SuppressWarnings
 81  
         (
 82  
             {
 83  
                 "PMD.AvoidCatchingThrowable",
 84  
                 "PMD.AvoidInstantiatingObjectsInLoops",
 85  
                 "rawtypes"
 86  
             }
 87  
         )
 88  
     public void service(final GrizzlyRequest request,
 89  
         final GrizzlyResponse response) {
 90  
         try {
 91  107
             final MkQuery query = new GrizzlyQuery(request);
 92  109
             final Iterator<Conditional> iter = this.conditionals.iterator();
 93  109
             boolean matched = false;
 94  112
             while (iter.hasNext()) {
 95  111
                 final Conditional cond = iter.next();
 96  111
                 if (cond.matches(query)) {
 97  110
                     matched = true;
 98  110
                     final MkAnswer answer = cond.answer();
 99  110
                     this.queue.add(new QueryWithAnswer(query, answer));
 100  110
                     for (final String name : answer.headers().keySet()) {
 101  
                         // @checkstyle NestedForDepth (3 lines)
 102  25
                         for (final String value : answer.headers().get(name)) {
 103  29
                             response.addHeader(name, value);
 104  29
                         }
 105  25
                     }
 106  108
                     response.addHeader(
 107  
                         HttpHeaders.SERVER,
 108  
                         String.format(
 109  
                             "%s query #%d, %d answer(s) left",
 110  
                             this.getClass().getName(),
 111  
                             this.queue.size(), this.conditionals.size()
 112  
                         )
 113  
                     );
 114  108
                     response.setStatus(answer.status());
 115  107
                     final byte[] body = answer.bodyBytes();
 116  106
                     response.getStream().write(body);
 117  109
                     response.setContentLength(body.length);
 118  107
                     if (cond.decrement() == 0) {
 119  89
                         iter.remove();
 120  
                     }
 121  
                     break;
 122  
                 }
 123  2
             }
 124  108
             if (!matched) {
 125  1
                 throw new NoSuchElementException("No matching answers found.");
 126  
             }
 127  
             // @checkstyle IllegalCatch (1 line)
 128  1
         } catch (final Throwable ex) {
 129  1
             MkGrizzlyAdapter.fail(response, ex);
 130  107
         }
 131  108
     }
 132  
 
 133  
     /**
 134  
      * Give this answer on the next request(s) if they match the given condition
 135  
      * a certain number of consecutive times.
 136  
      * @param answer Next answer to give
 137  
      * @param query The query that should be satisfied to return this answer
 138  
      * @param count The number of times this answer can be returned for matching
 139  
      *  requests
 140  
      */
 141  
     public void next(final MkAnswer answer, final Matcher<MkQuery> query,
 142  
         final int count) {
 143  98
         this.conditionals.add(new Conditional(answer, query, count));
 144  99
     }
 145  
 
 146  
     /**
 147  
      * Get the oldest request received.
 148  
      * @return Request received
 149  
      */
 150  
     public MkQuery take() {
 151  30
         return this.queue.remove().que;
 152  
     }
 153  
 
 154  
     /**
 155  
      * Get the oldest request received subject to the matching condition.
 156  
      * ({@link java.util.NoSuchElementException} if no elements satisfy the
 157  
      * condition).
 158  
      * @param matcher The matcher specifying the condition
 159  
      * @return Request received satisfying the matcher
 160  
      */
 161  
     public MkQuery take(final Matcher<MkAnswer> matcher) {
 162  1
         MkQuery result = null;
 163  1
         final Iterator<QueryWithAnswer> iter = this.queue.iterator();
 164  1
         while (iter.hasNext()) {
 165  1
             final QueryWithAnswer candidate = iter.next();
 166  1
             if (matcher.matches(candidate.answer())) {
 167  1
                 result = candidate.query();
 168  1
                 iter.remove();
 169  1
                 break;
 170  
             }
 171  0
         }
 172  1
         if (result == null) {
 173  
             // @checkstyle MultipleStringLiterals (1 line)
 174  0
             throw new NoSuchElementException("No matching results found");
 175  
         }
 176  1
         return result;
 177  
     }
 178  
 
 179  
     /**
 180  
      * Get the all requests received satisfying the given matcher.
 181  
      * ({@link java.util.NoSuchElementException} if no elements satisfy the
 182  
      * condition).
 183  
      * @param matcher The matcher specifying the condition
 184  
      * @return Collection of all requests satisfying the matcher, ordered from
 185  
      *  oldest to newest.
 186  
      */
 187  
     public Collection<MkQuery> takeAll(final Matcher<MkAnswer> matcher) {
 188  3
         final Collection<MkQuery> results = new LinkedList<MkQuery>();
 189  3
         final Iterator<QueryWithAnswer> iter = this.queue.iterator();
 190  12
         while (iter.hasNext()) {
 191  9
             final QueryWithAnswer candidate = iter.next();
 192  9
             if (matcher.matches(candidate.answer())) {
 193  8
                 results.add(candidate.query());
 194  8
                 iter.remove();
 195  
             }
 196  9
         }
 197  3
         if (results.isEmpty()) {
 198  0
             throw new NoSuchElementException("No matching results found");
 199  
         }
 200  3
         return results;
 201  
     }
 202  
 
 203  
     /**
 204  
      * Total number of available queue.
 205  
      * @return Number of them
 206  
      */
 207  
     public int queries() {
 208  10
         return this.queue.size();
 209  
     }
 210  
 
 211  
     /**
 212  
      * Notify this response about failure.
 213  
      * @param response The response to notify
 214  
      * @param failure The failure just happened
 215  
      */
 216  
     private static void fail(final GrizzlyResponse<?> response,
 217  
         final Throwable failure) {
 218  1
         response.setStatus(HttpURLConnection.HTTP_INTERNAL_ERROR);
 219  
         final PrintWriter writer;
 220  
         try {
 221  1
             writer = new PrintWriter(
 222  
                 new OutputStreamWriter(
 223  
                     response.getStream(),
 224  
                     MkGrizzlyAdapter.ENCODING
 225  
                 )
 226  
             );
 227  0
         } catch (final UnsupportedEncodingException ex) {
 228  0
             throw new IllegalStateException(ex);
 229  1
         }
 230  
         try {
 231  1
             writer.print(Logger.format("%[exception]s", failure));
 232  
         } finally {
 233  1
             writer.close();
 234  1
         }
 235  1
     }
 236  
 
 237  
     /**
 238  
      * Answer with condition.
 239  
      */
 240  0
     @EqualsAndHashCode(of = { "answr", "condition" })
 241  
     private static final class Conditional {
 242  
         /**
 243  
          * The MkAnswer.
 244  
          */
 245  
         private final transient MkAnswer answr;
 246  
         /**
 247  
          * Condition for this answer.
 248  
          */
 249  
         private final transient Matcher<MkQuery> condition;
 250  
         /**
 251  
          * The number of times the answer is expected to appear.
 252  
          */
 253  
         private final transient AtomicInteger count;
 254  
         /**
 255  
          * Ctor.
 256  
          * @param ans The answer.
 257  
          * @param matcher The matcher.
 258  
          * @param times Number of times the answer should appear.
 259  
          */
 260  
         Conditional(final MkAnswer ans, final Matcher<MkQuery> matcher,
 261  99
             final int times) {
 262  99
             this.answr = ans;
 263  99
             this.condition = matcher;
 264  99
             if (times < 1) {
 265  0
                 throw new IllegalArgumentException(
 266  
                     "Answer must be returned at least once."
 267  
                 );
 268  
             } else {
 269  99
                 this.count = new AtomicInteger(times);
 270  
             }
 271  99
         }
 272  
         /**
 273  
          * Get the answer.
 274  
          * @return The answer
 275  
          */
 276  
         public MkAnswer answer() {
 277  110
             return this.answr;
 278  
         }
 279  
         /**
 280  
          * Does the query match the answer?
 281  
          * @param query The query to match
 282  
          * @return True, if the query matches the condition
 283  
          */
 284  
         public boolean matches(final MkQuery query) {
 285  111
             return this.condition.matches(query);
 286  
         }
 287  
         /**
 288  
          * Decrement the count for this conditional.
 289  
          * @return The updated count
 290  
          */
 291  
         public int decrement() {
 292  106
             return this.count.decrementAndGet();
 293  
         }
 294  
     }
 295  
 
 296  
     /**
 297  
      * Query with answer.
 298  
      */
 299  69
     @EqualsAndHashCode(of = { "answr", "que" })
 300  29
     private static final class QueryWithAnswer {
 301  
         /**
 302  
          * The answer.
 303  
          */
 304  
         private final transient MkAnswer answr;
 305  
         /**
 306  
          * The query.
 307  
          */
 308  
         private final transient MkQuery que;
 309  
         /**
 310  
          * Ctor.
 311  
          * @param qry The query
 312  
          * @param ans The answer
 313  
          */
 314  109
         QueryWithAnswer(final MkQuery qry, final MkAnswer ans) {
 315  109
             this.answr = ans;
 316  109
             this.que = qry;
 317  109
         }
 318  
         /**
 319  
          * Get the query.
 320  
          * @return The query.
 321  
          */
 322  
         public MkQuery query() {
 323  9
             return this.que;
 324  
         }
 325  
         /**
 326  
          * Get the answer.
 327  
          * @return Answer
 328  
          */
 329  
         public MkAnswer answer() {
 330  10
             return this.answr;
 331  
         }
 332  
     }
 333  
 }