View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2013 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 final HttpClient client;
33      private final long id;
34      private volatile boolean complete;
35      private volatile List<Response.ResponseListener> listeners;
36  
37      public HttpConversation(HttpClient client, long id)
38      {
39          this.client = client;
40          this.id = id;
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         // If we have no override listener, then the
127         // conversation may be completed at a later time
128         complete = overrideListener == null;
129 
130         // Create a new instance to avoid that iterating over the listeners
131         // will notify a listener that may send a new request and trigger
132         // another call to this method which will build different listeners
133         // which may be iterated over when the iteration continues.
134         listeners = new ArrayList<>();
135 
136         HttpExchange firstExchange = exchanges.peekFirst();
137         HttpExchange lastExchange = exchanges.peekLast();
138         if (firstExchange == lastExchange)
139         {
140             if (overrideListener != null)
141                 listeners.add(overrideListener);
142             else
143                 listeners.addAll(firstExchange.getResponseListeners());
144         }
145         else
146         {
147             // Order is important, we want to notify the last exchange first
148             listeners.addAll(lastExchange.getResponseListeners());
149             if (overrideListener != null)
150                 listeners.add(overrideListener);
151             else
152                 listeners.addAll(firstExchange.getResponseListeners());
153         }
154     }
155 
156     public void complete()
157     {
158         if (complete)
159             client.removeConversation(this);
160     }
161 
162     public boolean abort(Throwable cause)
163     {
164         HttpExchange exchange = exchanges.peekLast();
165         return exchange.abort(cause);
166     }
167 
168     @Override
169     public String toString()
170     {
171         return String.format("%s[%d]", HttpConversation.class.getSimpleName(), id);
172     }
173 }