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    }