001 /**
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017 package org.apache.camel.component.jetty;
018
019 import java.io.IOException;
020 import java.io.InputStream;
021 import java.io.UnsupportedEncodingException;
022 import java.util.LinkedHashMap;
023 import java.util.Map;
024 import java.util.concurrent.CountDownLatch;
025 import java.util.concurrent.TimeUnit;
026
027 import org.apache.camel.AsyncCallback;
028 import org.apache.camel.CamelExchangeException;
029 import org.apache.camel.Exchange;
030 import org.apache.camel.ExchangeTimedOutException;
031 import org.apache.camel.util.IOHelper;
032 import org.apache.commons.logging.Log;
033 import org.apache.commons.logging.LogFactory;
034 import org.eclipse.jetty.client.ContentExchange;
035 import org.eclipse.jetty.client.HttpClient;
036 import org.eclipse.jetty.client.HttpExchange;
037 import org.eclipse.jetty.http.HttpHeaders;
038 import org.eclipse.jetty.io.Buffer;
039
040 /**
041 * Jetty specific exchange which keeps track of the the request and response.
042 *
043 * @version $Revision: 19854 $
044 */
045 public class JettyContentExchange extends ContentExchange {
046
047 // TODO: Use the AsyncCallback API (CAMEL-2723)
048
049 private static final transient Log LOG = LogFactory.getLog(JettyContentExchange.class);
050
051 private final Map<String, String> headers = new LinkedHashMap<String, String>();
052 private volatile Exchange exchange;
053 private volatile AsyncCallback callback;
054 private volatile JettyHttpBinding jettyBinding;
055 private volatile HttpClient client;
056 private final CountDownLatch done = new CountDownLatch(1);
057
058 public JettyContentExchange(Exchange exchange, JettyHttpBinding jettyBinding, HttpClient client) {
059 super(true); // keep headers by default
060 this.exchange = exchange;
061 this.jettyBinding = jettyBinding;
062 this.client = client;
063 }
064
065 public void setCallback(AsyncCallback callback) {
066 this.callback = callback;
067 }
068
069 @Override
070 protected void onResponseHeader(Buffer name, Buffer value) throws IOException {
071 super.onResponseHeader(name, value);
072 headers.put(name.toString(), value.toString());
073 }
074
075 @Override
076 protected void onRequestComplete() throws IOException {
077 // close the input stream when its not needed anymore
078 InputStream is = getRequestContentSource();
079 if (is != null) {
080 IOHelper.close(is, "RequestContentSource", LOG);
081 }
082 }
083
084 @Override
085 protected void onResponseComplete() throws IOException {
086 try {
087 super.onResponseComplete();
088 } finally {
089 doTaskCompleted();
090 }
091 }
092
093 @Override
094 protected void onExpire() {
095 try {
096 super.onExpire();
097 } finally {
098 doTaskCompleted();
099 }
100 }
101
102 @Override
103 protected void onException(Throwable ex) {
104 try {
105 super.onException(ex);
106 } finally {
107 doTaskCompleted(ex);
108 }
109 }
110
111 @Override
112 protected void onConnectionFailed(Throwable ex) {
113 try {
114 super.onConnectionFailed(ex);
115 } finally {
116 doTaskCompleted(ex);
117 }
118 }
119
120 protected int waitForDoneOrFailure() throws InterruptedException {
121 // just wait a little longer than Jetty itself to be safe
122 // as this timeout is a failsafe in case for some reason Jetty does not callback
123 long timeout = client.getTimeout() + 5000;
124
125 if (LOG.isTraceEnabled()) {
126 LOG.trace("Waiting for done or failure with timeout: " + timeout);
127 }
128 done.await(timeout, TimeUnit.MILLISECONDS);
129
130 return getStatus();
131 }
132
133 public Map<String, String> getHeaders() {
134 return headers;
135 }
136
137 public String getBody() throws UnsupportedEncodingException {
138 return super.getResponseContent();
139 }
140
141 public String getUrl() {
142 String params = getRequestFields().getStringField(HttpHeaders.CONTENT_ENCODING);
143 return getScheme() + "//" + getAddress().toString() + getURI() + (params != null ? "?" + params : "");
144 }
145
146 protected void doTaskCompleted() {
147 // make sure to lower the latch
148 done.countDown();
149
150 if (callback == null) {
151 // this is only for the async callback
152 return;
153 }
154
155 int exchangeState = getStatus();
156
157 if (LOG.isDebugEnabled()) {
158 LOG.debug("TaskComplete with state " + exchangeState + " for url: " + getUrl());
159 }
160
161 try {
162 if (exchangeState == HttpExchange.STATUS_COMPLETED) {
163 // process the response as the state is ok
164 try {
165 jettyBinding.populateResponse(exchange, this);
166 } catch (Exception e) {
167 exchange.setException(e);
168 }
169 } else if (exchangeState == HttpExchange.STATUS_EXPIRED) {
170 // we did timeout
171 exchange.setException(new ExchangeTimedOutException(exchange, client.getTimeout()));
172 } else {
173 // some kind of other error
174 if (exchange.getException() != null) {
175 exchange.setException(new CamelExchangeException("JettyClient failed with state " + exchangeState, exchange));
176 }
177 }
178 } finally {
179 // now invoke callback to indicate we are done async
180 callback.done(false);
181 }
182 }
183
184 protected void doTaskCompleted(Throwable ex) {
185 try {
186 // some kind of other error
187 exchange.setException(new CamelExchangeException("JettyClient failed cause by: " + ex.getMessage(), exchange, ex));
188 } finally {
189 // make sure to lower the latch
190 done.countDown();
191 }
192
193 if (callback != null) {
194 // now invoke callback to indicate we are done async
195 callback.done(false);
196 }
197 }
198
199 }