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: 999283 $
044 */
045 public class JettyContentExchange extends ContentExchange {
046
047 private static final transient Log LOG = LogFactory.getLog(JettyContentExchange.class);
048
049 private final Map<String, String> headers = new LinkedHashMap<String, String>();
050 private volatile Exchange exchange;
051 private volatile AsyncCallback callback;
052 private volatile JettyHttpBinding jettyBinding;
053 private volatile HttpClient client;
054 private final CountDownLatch done = new CountDownLatch(1);
055
056 public JettyContentExchange(Exchange exchange, JettyHttpBinding jettyBinding, HttpClient client) {
057 super(true); // keep headers by default
058 this.exchange = exchange;
059 this.jettyBinding = jettyBinding;
060 this.client = client;
061 }
062
063 public void setCallback(AsyncCallback callback) {
064 this.callback = callback;
065 }
066
067 @Override
068 protected void onResponseHeader(Buffer name, Buffer value) throws IOException {
069 super.onResponseHeader(name, value);
070 headers.put(name.toString(), value.toString());
071 }
072
073 @Override
074 protected void onRequestComplete() throws IOException {
075 // close the input stream when its not needed anymore
076 InputStream is = getRequestContentSource();
077 if (is != null) {
078 IOHelper.close(is, "RequestContentSource", LOG);
079 }
080 }
081
082 @Override
083 protected void onResponseComplete() throws IOException {
084 try {
085 super.onResponseComplete();
086 } finally {
087 doTaskCompleted();
088 }
089 }
090
091 @Override
092 protected void onExpire() {
093 try {
094 super.onExpire();
095 } finally {
096 doTaskCompleted();
097 }
098 }
099
100 @Override
101 protected void onException(Throwable ex) {
102 try {
103 super.onException(ex);
104 } finally {
105 doTaskCompleted(ex);
106 }
107 }
108
109 @Override
110 protected void onConnectionFailed(Throwable ex) {
111 try {
112 super.onConnectionFailed(ex);
113 } finally {
114 doTaskCompleted(ex);
115 }
116 }
117
118 protected int waitForDoneOrFailure() throws InterruptedException {
119 // just wait a little longer than Jetty itself to be safe
120 // as this timeout is a failsafe in case for some reason Jetty does not callback
121 long timeout = client.getTimeout() + 5000;
122
123 if (LOG.isTraceEnabled()) {
124 LOG.trace("Waiting for done or failure with timeout: " + timeout);
125 }
126 done.await(timeout, TimeUnit.MILLISECONDS);
127
128 return getStatus();
129 }
130
131 public Map<String, String> getHeaders() {
132 return headers;
133 }
134
135 public String getBody() throws UnsupportedEncodingException {
136 return super.getResponseContent();
137 }
138
139 public String getUrl() {
140 String params = getRequestFields().getStringField(HttpHeaders.CONTENT_ENCODING);
141 return getScheme() + "//" + getAddress().toString() + getURI() + (params != null ? "?" + params : "");
142 }
143
144 protected void doTaskCompleted() {
145 // make sure to lower the latch
146 done.countDown();
147
148 if (callback == null) {
149 // this is only for the async callback
150 return;
151 }
152
153 int exchangeState = getStatus();
154
155 if (LOG.isDebugEnabled()) {
156 LOG.debug("TaskComplete with state " + exchangeState + " for url: " + getUrl());
157 }
158
159 try {
160 if (exchangeState == HttpExchange.STATUS_COMPLETED) {
161 // process the response as the state is ok
162 try {
163 jettyBinding.populateResponse(exchange, this);
164 } catch (Exception e) {
165 exchange.setException(e);
166 }
167 } else if (exchangeState == HttpExchange.STATUS_EXPIRED) {
168 // we did timeout
169 exchange.setException(new ExchangeTimedOutException(exchange, client.getTimeout()));
170 } else {
171 // some kind of other error
172 if (exchange.getException() != null) {
173 exchange.setException(new CamelExchangeException("JettyClient failed with state " + exchangeState, exchange));
174 }
175 }
176 } finally {
177 // now invoke callback to indicate we are done async
178 callback.done(false);
179 }
180 }
181
182 protected void doTaskCompleted(Throwable ex) {
183 try {
184 // some kind of other error
185 exchange.setException(new CamelExchangeException("JettyClient failed cause by: " + ex.getMessage(), exchange, ex));
186 } finally {
187 // make sure to lower the latch
188 done.countDown();
189 }
190
191 if (callback != null) {
192 // now invoke callback to indicate we are done async
193 callback.done(false);
194 }
195 }
196
197 }