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