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 => 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 => 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 => 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 => 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 => 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 => 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 }