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    }