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 }