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.jhc;
018    
019    import java.io.IOException;
020    import java.io.InterruptedIOException;
021    import java.net.InetSocketAddress;
022    import java.net.SocketAddress;
023    import java.util.Arrays;
024    import java.util.HashSet;
025    import java.util.Iterator;
026    import java.util.Set;
027    import java.util.concurrent.ThreadFactory;
028    
029    import org.apache.camel.AsyncCallback;
030    import org.apache.camel.AsyncProcessor;
031    import org.apache.camel.Exchange;
032    import org.apache.camel.Message;
033    import org.apache.camel.impl.DefaultProducer;
034    import org.apache.camel.spi.HeaderFilterStrategy;
035    import org.apache.camel.util.AsyncProcessorHelper;
036    import org.apache.commons.logging.Log;
037    import org.apache.commons.logging.LogFactory;
038    import org.apache.http.Header;
039    import org.apache.http.HttpEntity;
040    import org.apache.http.HttpException;
041    import org.apache.http.HttpRequest;
042    import org.apache.http.HttpResponse;
043    import org.apache.http.entity.ByteArrayEntity;
044    import org.apache.http.impl.DefaultConnectionReuseStrategy;
045    import org.apache.http.impl.nio.DefaultClientIOEventDispatch;
046    import org.apache.http.impl.nio.reactor.DefaultConnectingIOReactor;
047    import org.apache.http.message.BasicHttpEntityEnclosingRequest;
048    import org.apache.http.message.BasicHttpRequest;
049    import org.apache.http.nio.NHttpConnection;
050    import org.apache.http.nio.protocol.BufferingHttpClientHandler;
051    import org.apache.http.nio.protocol.EventListener;
052    import org.apache.http.nio.protocol.HttpRequestExecutionHandler;
053    import org.apache.http.nio.reactor.ConnectingIOReactor;
054    import org.apache.http.nio.reactor.IOEventDispatch;
055    import org.apache.http.nio.reactor.SessionRequest;
056    import org.apache.http.nio.reactor.SessionRequestCallback;
057    import org.apache.http.params.HttpParams;
058    import org.apache.http.protocol.BasicHttpProcessor;
059    import org.apache.http.protocol.HttpContext;
060    import org.apache.http.protocol.RequestConnControl;
061    import org.apache.http.protocol.RequestContent;
062    import org.apache.http.protocol.RequestExpectContinue;
063    import org.apache.http.protocol.RequestTargetHost;
064    import org.apache.http.protocol.RequestUserAgent;
065    
066    public class JhcProducer extends DefaultProducer<JhcExchange> implements AsyncProcessor {
067    
068        public static final String HTTP_RESPONSE_CODE = "http.responseCode";
069    
070        @Deprecated
071        public static final Set<String> HEADERS_TO_SKIP = new HashSet<String>(Arrays.asList(
072                "content-length", "content-type", HTTP_RESPONSE_CODE.toLowerCase()));
073    
074        private static final transient Log LOG = LogFactory.getLog(JhcProducer.class);
075    
076        private int nbThreads = 2;
077        private ConnectingIOReactor ioReactor;
078        private ThreadFactory threadFactory;
079        private Thread runner;
080    
081        public JhcProducer(JhcEndpoint endpoint) {
082            super(endpoint);
083        }
084    
085        @Override
086        public JhcEndpoint getEndpoint() {
087            return (JhcEndpoint) super.getEndpoint();
088        }
089    
090        @Override
091        protected void doStart() throws Exception {
092            super.doStart();
093            HttpParams params = getEndpoint().getParams();
094            ioReactor = new DefaultConnectingIOReactor(nbThreads, threadFactory, params);
095            BasicHttpProcessor httpproc = new BasicHttpProcessor();
096            httpproc.addInterceptor(new RequestContent());
097            httpproc.addInterceptor(new RequestTargetHost());
098            httpproc.addInterceptor(new RequestConnControl());
099            httpproc.addInterceptor(new RequestUserAgent());
100            httpproc.addInterceptor(new RequestExpectContinue());
101            BufferingHttpClientHandler handler = new BufferingHttpClientHandler(
102                    httpproc,
103                    new MyHttpRequestExecutionHandler(),
104                    new DefaultConnectionReuseStrategy(),
105                    params);
106            handler.setEventListener(new EventLogger());
107            final IOEventDispatch ioEventDispatch = new DefaultClientIOEventDispatch(handler, params);
108            runner = new Thread(new Runnable() {
109                public void run() {
110                    try {
111                        ioReactor.execute(ioEventDispatch);
112                    } catch (InterruptedIOException ex) {
113                        LOG.info("Interrupted");
114                    } catch (IOException e) {
115                        LOG.warn("I/O error: " + e.getMessage());
116                    }
117                    LOG.debug("Shutdown");
118                }
119    
120            });
121            runner.start();
122        }
123    
124        @Override
125        protected void doStop() throws Exception {
126            ioReactor.shutdown();
127            runner.join();
128            super.doStop();
129        }
130    
131        public void process(Exchange exchange) throws Exception {
132            if (LOG.isDebugEnabled()) {
133                LOG.debug("process: " + exchange);
134            }
135            AsyncProcessorHelper.process(this, exchange);
136        }
137    
138        public boolean process(Exchange exchange, AsyncCallback callback) {
139            if (LOG.isDebugEnabled()) {
140                LOG.debug("processAsync: " + exchange);
141            }
142            SocketAddress addr = new InetSocketAddress(getEndpoint().getHost(), getEndpoint().getPort());
143            exchange.setProperty(AsyncCallback.class.getName(), callback);
144            SessionRequest req = ioReactor.connect(addr, null, exchange, new MySessionRequestCallback());
145            return false;
146        }
147    
148        protected HttpRequest createRequest(Exchange exchange) {
149            String uri = getEndpoint().getEndpointUri();
150            HttpEntity entity = createEntity(exchange);
151            HttpRequest req;
152            if (entity == null) {
153                req = new BasicHttpRequest("GET", getEndpoint().getPath());
154            } else {
155                req = new BasicHttpEntityEnclosingRequest("POST", getEndpoint().getPath());
156                ((BasicHttpEntityEnclosingRequest)req).setEntity(entity);
157            }
158    
159            // propagate headers as HTTP headers
160            HeaderFilterStrategy strategy = ((JhcEndpoint)getEndpoint()).getHeaderFilterStrategy();
161            for (String headerName : exchange.getIn().getHeaders().keySet()) {
162                String headerValue = exchange.getIn().getHeader(headerName, String.class);
163                if (strategy != null && !strategy.applyFilterToCamelHeaders(headerName, headerValue)) {
164                    req.addHeader(headerName, headerValue);
165                }
166            }
167    
168            return req;
169        }
170    
171        protected HttpEntity createEntity(Exchange exchange) {
172            Message in = exchange.getIn();
173            HttpEntity entity = in.getBody(HttpEntity.class);
174            if (entity == null) {
175                byte[] data = in.getBody(byte[].class);
176                if (data == null) {
177                    return null;
178                }
179                entity = new ByteArrayEntity(data);
180                String contentType = in.getHeader("Content-Type", String.class);
181                if (contentType != null) {
182                    ((ByteArrayEntity) entity).setContentType(contentType);
183                }
184                String contentEncoding = in.getHeader("Content-Encoding", String.class);
185                if (contentEncoding != null) {
186                    ((ByteArrayEntity) entity).setContentEncoding(contentEncoding);
187                }
188            }
189            return entity;
190        }
191    
192        static class MySessionRequestCallback implements SessionRequestCallback {
193    
194            public void completed(SessionRequest sessionRequest) {
195                if (LOG.isDebugEnabled()) {
196                    LOG.debug("Completed");
197                }
198            }
199    
200            public void failed(SessionRequest sessionRequest) {
201                if (LOG.isDebugEnabled()) {
202                    LOG.debug("Failed");
203                }
204            }
205    
206            public void timeout(SessionRequest sessionRequest) {
207                if (LOG.isDebugEnabled()) {
208                    LOG.debug("Timeout");
209                }
210            }
211    
212            public void cancelled(SessionRequest sessionRequest) {
213                if (LOG.isDebugEnabled()) {
214                    LOG.debug("Cancelled");
215                }
216            }
217        }
218    
219        class MyHttpRequestExecutionHandler implements HttpRequestExecutionHandler {
220    
221            private static final String REQUEST_SENT       = "request-sent";
222            private static final String RESPONSE_RECEIVED  = "response-received";
223    
224            public void initalizeContext(HttpContext httpContext, Object o) {
225                if (LOG.isDebugEnabled()) {
226                    LOG.debug("Initialize context");
227                }
228                httpContext.setAttribute(Exchange.class.getName(), (Exchange) o);
229            }
230    
231            public HttpRequest submitRequest(HttpContext httpContext) {
232                if (LOG.isDebugEnabled()) {
233                    LOG.debug("Submit request: " + httpContext);
234                }
235                Object flag = httpContext.getAttribute(REQUEST_SENT);
236                if (flag == null) {
237                    // Stick some object into the context
238                    httpContext.setAttribute(REQUEST_SENT, Boolean.TRUE);
239                    Exchange e = (Exchange) httpContext.getAttribute(Exchange.class.getName());
240                    return createRequest(e);
241                } else {
242                    return null;
243                }
244            }
245    
246            public void handleResponse(HttpResponse httpResponse, HttpContext httpContext) throws IOException {
247                if (LOG.isDebugEnabled()) {
248                    LOG.debug("Handle response");
249                }
250                httpContext.setAttribute(RESPONSE_RECEIVED, Boolean.TRUE);
251                Exchange e = (Exchange) httpContext.getAttribute(Exchange.class.getName());
252                e.getOut().setBody(httpResponse.getEntity());
253                
254                HeaderFilterStrategy strategy = getEndpoint().getHeaderFilterStrategy();
255                for (Iterator it = httpResponse.headerIterator(); it.hasNext();) {
256                    Header h = (Header) it.next();
257                    if (strategy != null && !strategy.applyFilterToExternalHeaders(h.getName(), h.getValue())) {
258                        e.getOut().setHeader(h.getName(), h.getValue());
259                    }
260                }
261                
262                e.getOut().setHeader(HTTP_RESPONSE_CODE, httpResponse.getStatusLine().getStatusCode());
263                AsyncCallback callback = (AsyncCallback) e.removeProperty(AsyncCallback.class.getName());
264                callback.done(false);
265            }
266    
267            public void finalizeContext(HttpContext httpContext) {
268            }
269        }
270    
271        static class EventLogger implements EventListener {
272    
273            public void connectionOpen(final NHttpConnection conn) {
274                if (LOG.isDebugEnabled()) {
275                    LOG.debug("Connection open: " + conn);
276                }
277            }
278    
279            public void connectionTimeout(final NHttpConnection conn) {
280                if (LOG.isDebugEnabled()) {
281                    LOG.debug("Connection timed out: " + conn);
282                }
283            }
284    
285            public void connectionClosed(final NHttpConnection conn) {
286                if (LOG.isDebugEnabled()) {
287                    LOG.debug("Connection closed: " + conn);
288                }
289            }
290    
291            public void fatalIOException(final IOException ex, final NHttpConnection conn) {
292                if (LOG.isDebugEnabled()) {
293                    LOG.debug("I/O error: " + ex.getMessage());
294                }
295            }
296    
297            public void fatalProtocolException(final HttpException ex, final NHttpConnection conn) {
298                if (LOG.isDebugEnabled()) {
299                    LOG.debug("HTTP error: " + ex.getMessage());
300                }
301            }
302    
303        }
304    
305    }