View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
4   //  ------------------------------------------------------------------------
5   //  All rights reserved. This program and the accompanying materials
6   //  are made available under the terms of the Eclipse Public License v1.0
7   //  and Apache License v2.0 which accompanies this distribution.
8   //
9   //      The Eclipse Public License is available at
10  //      http://www.eclipse.org/legal/epl-v10.html
11  //
12  //      The Apache License v2.0 is available at
13  //      http://www.opensource.org/licenses/apache2.0.php
14  //
15  //  You may elect to redistribute this code under either of these licenses.
16  //  ========================================================================
17  //
18  
19  package org.eclipse.jetty.client;
20  
21  import java.util.ArrayList;
22  import java.util.Deque;
23  import java.util.List;
24  import java.util.concurrent.ConcurrentLinkedDeque;
25  import java.util.concurrent.atomic.AtomicLong;
26  
27  import org.eclipse.jetty.client.api.Response;
28  import org.eclipse.jetty.util.AttributesMap;
29  
30  public class HttpConversation extends AttributesMap
31  {
32      private static final AtomicLong ids = new AtomicLong();
33  
34      private final Deque<HttpExchange> exchanges = new ConcurrentLinkedDeque<>();
35      private final long id;
36      private volatile List<Response.ResponseListener> listeners;
37  
38      protected HttpConversation()
39      {
40          this.id = ids.incrementAndGet();
41      }
42  
43      public long getID()
44      {
45          return id;
46      }
47  
48      public Deque<HttpExchange> getExchanges()
49      {
50          return exchanges;
51      }
52  
53      /**
54       * Returns the list of response listeners that needs to be notified of response events.
55       * This list changes as the conversation proceeds, as follows:
56       * <ol>
57       * <li>
58       *     request R1 send => conversation.updateResponseListeners(null)
59       *     <ul>
60       *         <li>exchanges in conversation: E1</li>
61       *         <li>listeners to be notified: E1.listeners</li>
62       *     </ul>
63       * </li>
64       * <li>
65       *     response R1 arrived, 401 => conversation.updateResponseListeners(AuthenticationProtocolHandler.listener)
66       *     <ul>
67       *         <li>exchanges in conversation: E1</li>
68       *         <li>listeners to be notified: AuthenticationProtocolHandler.listener</li>
69       *     </ul>
70       * </li>
71       * <li>
72       *     request R2 send => conversation.updateResponseListeners(null)
73       *     <ul>
74       *         <li>exchanges in conversation: E1 + E2</li>
75       *         <li>listeners to be notified: E2.listeners + E1.listeners</li>
76       *     </ul>
77       * </li>
78       * <li>
79       *     response R2 arrived, 302 => conversation.updateResponseListeners(RedirectProtocolHandler.listener)
80       *     <ul>
81       *         <li>exchanges in conversation: E1 + E2</li>
82       *         <li>listeners to be notified: E2.listeners + RedirectProtocolHandler.listener</li>
83       *     </ul>
84       * </li>
85       * <li>
86       *     request R3 send => conversation.updateResponseListeners(null)
87       *     <ul>
88       *         <li>exchanges in conversation: E1 + E2 + E3</li>
89       *         <li>listeners to be notified: E3.listeners + E1.listeners</li>
90       *     </ul>
91       * </li>
92       * <li>
93       *     response R3 arrived, 200 => conversation.updateResponseListeners(null)
94       *     <ul>
95       *         <li>exchanges in conversation: E1 + E2 + E3</li>
96       *         <li>listeners to be notified: E3.listeners + E1.listeners</li>
97       *     </ul>
98       * </li>
99       * </ol>
100      * Basically the override conversation listener replaces the first exchange response listener,
101      * and we also notify the last exchange response listeners (if it's not also the first).
102      *
103      * This scheme allows for protocol handlers to not worry about other protocol handlers, or to worry
104      * too much about notifying the first exchange response listeners, but still allowing a protocol
105      * handler to perform completion activities while another protocol handler performs new ones (as an
106      * example, the {@link AuthenticationProtocolHandler} stores the successful authentication credentials
107      * while the {@link RedirectProtocolHandler} performs a redirect).
108      *
109      * @return the list of response listeners that needs to be notified of response events
110      */
111     public List<Response.ResponseListener> getResponseListeners()
112     {
113         return listeners;
114     }
115 
116     /**
117      * Requests to update the response listener, eventually using the given override response listener,
118      * that must be notified instead of the first exchange response listeners.
119      * This works in conjunction with {@link #getResponseListeners()}, returning the appropriate response
120      * listeners that needs to be notified of response events.
121      *
122      * @param overrideListener the override response listener
123      */
124     public void updateResponseListeners(Response.ResponseListener overrideListener)
125     {
126         // Create a new instance to avoid that iterating over the listeners
127         // will notify a listener that may send a new request and trigger
128         // another call to this method which will build different listeners
129         // which may be iterated over when the iteration continues.
130         List<Response.ResponseListener> listeners = new ArrayList<>();
131         HttpExchange firstExchange = exchanges.peekFirst();
132         HttpExchange lastExchange = exchanges.peekLast();
133         if (firstExchange == lastExchange)
134         {
135             if (overrideListener != null)
136                 listeners.add(overrideListener);
137             else
138                 listeners.addAll(firstExchange.getResponseListeners());
139         }
140         else
141         {
142             // Order is important, we want to notify the last exchange first
143             listeners.addAll(lastExchange.getResponseListeners());
144             if (overrideListener != null)
145                 listeners.add(overrideListener);
146             else
147                 listeners.addAll(firstExchange.getResponseListeners());
148         }
149         this.listeners = listeners;
150     }
151 
152     public boolean abort(Throwable cause)
153     {
154         HttpExchange exchange = exchanges.peekLast();
155         return exchange != null && exchange.abort(cause);
156     }
157 
158     @Override
159     public String toString()
160     {
161         return String.format("%s[%d]", HttpConversation.class.getSimpleName(), id);
162     }
163 }