View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2016 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  
26  import org.eclipse.jetty.client.api.Response;
27  import org.eclipse.jetty.util.AttributesMap;
28  
29  public class HttpConversation extends AttributesMap
30  {
31      private final Deque<HttpExchange> exchanges = new ConcurrentLinkedDeque<>();
32      private volatile List<Response.ResponseListener> listeners;
33  
34      public Deque<HttpExchange> getExchanges()
35      {
36          return exchanges;
37      }
38  
39      /**
40       * Returns the list of response listeners that needs to be notified of response events.
41       * This list changes as the conversation proceeds, as follows:
42       * <ol>
43       * <li>
44       *     request R1 send =&gt; conversation.updateResponseListeners(null)
45       *     <ul>
46       *         <li>exchanges in conversation: E1</li>
47       *         <li>listeners to be notified: E1.listeners</li>
48       *     </ul>
49       * </li>
50       * <li>
51       *     response R1 arrived, 401 =&gt; conversation.updateResponseListeners(AuthenticationProtocolHandler.listener)
52       *     <ul>
53       *         <li>exchanges in conversation: E1</li>
54       *         <li>listeners to be notified: AuthenticationProtocolHandler.listener</li>
55       *     </ul>
56       * </li>
57       * <li>
58       *     request R2 send =&gt; conversation.updateResponseListeners(null)
59       *     <ul>
60       *         <li>exchanges in conversation: E1 + E2</li>
61       *         <li>listeners to be notified: E2.listeners + E1.listeners</li>
62       *     </ul>
63       * </li>
64       * <li>
65       *     response R2 arrived, 302 =&gt; conversation.updateResponseListeners(RedirectProtocolHandler.listener)
66       *     <ul>
67       *         <li>exchanges in conversation: E1 + E2</li>
68       *         <li>listeners to be notified: E2.listeners + RedirectProtocolHandler.listener</li>
69       *     </ul>
70       * </li>
71       * <li>
72       *     request R3 send =&gt; conversation.updateResponseListeners(null)
73       *     <ul>
74       *         <li>exchanges in conversation: E1 + E2 + E3</li>
75       *         <li>listeners to be notified: E3.listeners + E1.listeners</li>
76       *     </ul>
77       * </li>
78       * <li>
79       *     response R3 arrived, 200 =&gt; conversation.updateResponseListeners(null)
80       *     <ul>
81       *         <li>exchanges in conversation: E1 + E2 + E3</li>
82       *         <li>listeners to be notified: E3.listeners + E1.listeners</li>
83       *     </ul>
84       * </li>
85       * </ol>
86       * Basically the override conversation listener replaces the first exchange response listener,
87       * and we also notify the last exchange response listeners (if it's not also the first).
88       *
89       * This scheme allows for protocol handlers to not worry about other protocol handlers, or to worry
90       * too much about notifying the first exchange response listeners, but still allowing a protocol
91       * handler to perform completion activities while another protocol handler performs new ones (as an
92       * example, the {@link AuthenticationProtocolHandler} stores the successful authentication credentials
93       * while the {@link RedirectProtocolHandler} performs a redirect).
94       *
95       * @return the list of response listeners that needs to be notified of response events
96       */
97      public List<Response.ResponseListener> getResponseListeners()
98      {
99          return listeners;
100     }
101 
102     /**
103      * Requests to update the response listener, eventually using the given override response listener,
104      * that must be notified instead of the first exchange response listeners.
105      * This works in conjunction with {@link #getResponseListeners()}, returning the appropriate response
106      * listeners that needs to be notified of response events.
107      *
108      * @param overrideListener the override response listener
109      */
110     public void updateResponseListeners(Response.ResponseListener overrideListener)
111     {
112         // Create a new instance to avoid that iterating over the listeners
113         // will notify a listener that may send a new request and trigger
114         // another call to this method which will build different listeners
115         // which may be iterated over when the iteration continues.
116         List<Response.ResponseListener> listeners = new ArrayList<>();
117         HttpExchange firstExchange = exchanges.peekFirst();
118         HttpExchange lastExchange = exchanges.peekLast();
119         if (firstExchange == lastExchange)
120         {
121             if (overrideListener != null)
122                 listeners.add(overrideListener);
123             else
124                 listeners.addAll(firstExchange.getResponseListeners());
125         }
126         else
127         {
128             // Order is important, we want to notify the last exchange first
129             listeners.addAll(lastExchange.getResponseListeners());
130             if (overrideListener != null)
131                 listeners.add(overrideListener);
132             else
133                 listeners.addAll(firstExchange.getResponseListeners());
134         }
135         this.listeners = listeners;
136     }
137 
138     public boolean abort(Throwable cause)
139     {
140         HttpExchange exchange = exchanges.peekLast();
141         return exchange != null && exchange.abort(cause);
142     }
143 
144     @Override
145     public String toString()
146     {
147         return String.format("%s[%x]", HttpConversation.class.getSimpleName(), hashCode());
148     }
149 }