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.ahc;
018    
019    import java.io.ByteArrayInputStream;
020    import java.io.ByteArrayOutputStream;
021    import java.io.File;
022    import java.io.IOException;
023    import java.io.InputStream;
024    import java.io.Serializable;
025    import java.io.UnsupportedEncodingException;
026    import java.util.LinkedHashMap;
027    import java.util.List;
028    import java.util.Map;
029    
030    import com.ning.http.client.BodyGenerator;
031    import com.ning.http.client.HttpResponseHeaders;
032    import com.ning.http.client.HttpResponseStatus;
033    import com.ning.http.client.Request;
034    import com.ning.http.client.RequestBuilder;
035    import com.ning.http.client.generators.ByteArrayBodyGenerator;
036    import com.ning.http.client.generators.FileBodyGenerator;
037    import com.ning.http.client.generators.InputStreamBodyGenerator;
038    import org.apache.camel.CamelExchangeException;
039    import org.apache.camel.Exchange;
040    import org.apache.camel.Message;
041    import org.apache.camel.component.ahc.helper.AhcHelper;
042    import org.apache.camel.component.file.GenericFile;
043    import org.apache.camel.converter.IOConverter;
044    import org.apache.camel.spi.HeaderFilterStrategy;
045    import org.apache.camel.util.ExchangeHelper;
046    import org.apache.camel.util.GZIPHelper;
047    import org.apache.camel.util.IOHelper;
048    import org.slf4j.Logger;
049    import org.slf4j.LoggerFactory;
050    
051    public class DefaultAhcBinding implements AhcBinding {
052    
053        protected final transient Logger log = LoggerFactory.getLogger(this.getClass());
054    
055        @Override
056        public Request prepareRequest(AhcEndpoint endpoint, Exchange exchange) throws CamelExchangeException {
057            if (endpoint.isBridgeEndpoint()) {
058                exchange.setProperty(Exchange.SKIP_GZIP_ENCODING, Boolean.TRUE);
059            }
060    
061            RequestBuilder builder = new RequestBuilder();
062            try {
063                String url = AhcHelper.createURL(exchange, endpoint);
064                log.trace("Setting url {}", url);
065                builder.setUrl(url);
066            } catch (Exception e) {
067                throw new CamelExchangeException("Error creating URL", exchange, e);
068            }
069            String method = extractMethod(exchange);
070            log.trace("Setting method {}", method);
071            builder.setMethod(method);
072    
073            populateHeaders(builder, endpoint, exchange);
074            populateBody(builder, endpoint, exchange);
075    
076            return builder.build();
077        }
078    
079        protected String extractMethod(Exchange exchange) {
080            // prefer method from header
081            String method = exchange.getIn().getHeader(Exchange.HTTP_METHOD, String.class);
082            if (method != null) {
083                return method;
084            }
085    
086            // if there is a body then do a POST otherwise a GET
087            boolean hasBody = exchange.getIn().getBody() != null;
088            return hasBody ? "POST" : "GET";
089        }
090    
091        protected void populateHeaders(RequestBuilder builder, AhcEndpoint endpoint, Exchange exchange) {
092            HeaderFilterStrategy strategy = endpoint.getHeaderFilterStrategy();
093    
094            // propagate headers as HTTP headers
095            for (Map.Entry<String, Object> entry : exchange.getIn().getHeaders().entrySet()) {
096                String headerValue = exchange.getIn().getHeader(entry.getKey(), String.class);
097                if (strategy != null && !strategy.applyFilterToCamelHeaders(entry.getKey(), headerValue, exchange)) {
098                    log.trace("Adding header {} = {}", entry.getKey(), headerValue);
099                    builder.addHeader(entry.getKey(), headerValue);
100                }
101            }
102        }
103    
104        protected void populateBody(RequestBuilder builder, AhcEndpoint endpoint, Exchange exchange) throws CamelExchangeException {
105            Message in = exchange.getIn();
106            if (in.getBody() == null) {
107                return;
108            }
109    
110            String contentType = ExchangeHelper.getContentType(exchange);
111            BodyGenerator body = in.getBody(BodyGenerator.class);
112            String charset = IOConverter.getCharsetName(exchange, false);
113    
114            if (body == null) {
115                try {
116                    Object data = in.getBody();
117                    if (data != null) {
118                        if (contentType != null && AhcConstants.CONTENT_TYPE_JAVA_SERIALIZED_OBJECT.equals(contentType)) {
119                            // serialized java object
120                            Serializable obj = in.getMandatoryBody(Serializable.class);
121                            // write object to output stream
122                            ByteArrayOutputStream bos = new ByteArrayOutputStream();
123                            AhcHelper.writeObjectToStream(bos, obj);
124                            byte[] bytes = bos.toByteArray();
125                            body = new ByteArrayBodyGenerator(bytes);
126                            IOHelper.close(bos);
127                        } else if (data instanceof File || data instanceof GenericFile) {
128                            // file based (could potentially also be a FTP file etc)
129                            File file = in.getBody(File.class);
130                            if (file != null) {
131                                body = new FileBodyGenerator(file);
132                            }
133                        } else if (data instanceof String) {
134                            // be a bit careful with String as any type can most likely be converted to String
135                            // so we only do an instanceof check and accept String if the body is really a String
136                            // do not fallback to use the default charset as it can influence the request
137                            // (for example application/x-www-form-urlencoded forms being sent)
138                            if (charset != null) {
139                                body = new ByteArrayBodyGenerator(((String) data).getBytes(charset));
140                            } else {
141                                body = new ByteArrayBodyGenerator(((String) data).getBytes());
142                            }
143                        }
144                        // fallback as input stream
145                        if (body == null) {
146                            // force the body as an input stream since this is the fallback
147                            InputStream is = in.getMandatoryBody(InputStream.class);
148                            body = new InputStreamBodyGenerator(is);
149                        }
150                    }
151                } catch (UnsupportedEncodingException e) {
152                    throw new CamelExchangeException("Error creating BodyGenerator from message body", exchange, e);
153                } catch (IOException e) {
154                    throw new CamelExchangeException("Error serializing message body", exchange, e);
155                }
156            }
157    
158            if (body != null) {
159                log.trace("Setting body {}", body);
160                builder.setBody(body);
161            }
162            if (charset != null) {
163                log.trace("Setting body charset {}", charset);
164                builder.setBodyEncoding(charset);
165            }
166            // must set content type, even if its null, otherwise it may default to
167            // application/x-www-form-urlencoded which may not be your intention
168            log.trace("Setting Content-Type {}", contentType);
169            builder.setHeader(Exchange.CONTENT_TYPE, contentType);
170        }
171    
172        @Override
173        public void onThrowable(AhcEndpoint endpoint, Exchange exchange, Throwable t) throws Exception {
174            exchange.setException(t);
175        }
176    
177        @Override
178        public void onStatusReceived(AhcEndpoint endpoint, Exchange exchange, HttpResponseStatus responseStatus) throws Exception {
179            exchange.getOut().setHeader(Exchange.HTTP_RESPONSE_CODE, responseStatus.getStatusCode());
180        }
181    
182        @Override
183        public void onHeadersReceived(AhcEndpoint endpoint, Exchange exchange, HttpResponseHeaders headers) throws Exception {
184            for (Map.Entry<String, List<String>> entry : headers.getHeaders().entrySet()) {
185                String key = entry.getKey();
186                List<String> value = entry.getValue();
187                if (value.size() == 1) {
188                    exchange.getOut().getHeaders().put(key, value.get(0));
189                } else {
190                    exchange.getOut().getHeaders().put(key, value);
191                }
192            }
193        }
194    
195        @Override
196        public void onComplete(AhcEndpoint endpoint, Exchange exchange, String url, ByteArrayOutputStream os, int contentLength,
197                               int statusCode, String statusText) throws Exception {
198            // copy from output stream to input stream
199            os.flush();
200            os.close();
201            InputStream is = new ByteArrayInputStream(os.toByteArray());
202    
203            String contentEncoding = exchange.getOut().getHeader(Exchange.CONTENT_ENCODING, String.class);
204            if (!exchange.getProperty(Exchange.SKIP_GZIP_ENCODING, Boolean.FALSE, Boolean.class)) {
205                is = GZIPHelper.uncompressGzip(contentEncoding, is);
206            }
207    
208            // Honor the character encoding
209            String contentType = exchange.getOut().getHeader(Exchange.CONTENT_TYPE, String.class);
210            if (contentType != null) {
211                // find the charset and set it to the Exchange
212                AhcHelper.setCharsetFromContentType(contentType, exchange);
213            }
214    
215            Object body = is;
216            // if content type is a serialized java object then de-serialize it back to a Java object
217            if (contentType != null && contentType.equals(AhcConstants.CONTENT_TYPE_JAVA_SERIALIZED_OBJECT)) {
218                body = AhcHelper.deserializeJavaObjectFromStream(is);
219            }
220    
221            if (!endpoint.isThrowExceptionOnFailure()) {
222                // if we do not use failed exception then populate response for all response codes
223                populateResponse(exchange, body, contentLength, statusCode);
224            } else {
225                if (statusCode >= 100 && statusCode < 300) {
226                    // only populate response for OK response
227                    populateResponse(exchange, body, contentLength, statusCode);
228                } else {
229                    // operation failed so populate exception to throw
230                    throw populateHttpOperationFailedException(endpoint, exchange, url, body, contentLength, statusCode, statusText);
231                }
232            }
233        }
234    
235        private Exception populateHttpOperationFailedException(AhcEndpoint endpoint, Exchange exchange, String url,
236                                                               Object body, int contentLength,
237                                                               int statusCode, String statusText) {
238            Exception answer;
239    
240            if (endpoint.isTransferException() && body != null && body instanceof Exception) {
241                // if the response was a serialized exception then use that
242                return (Exception) body;
243            }
244    
245            // make a defensive copy of the response body in the exception so its detached from the cache
246            String copy = null;
247            if (body != null) {
248                copy = exchange.getContext().getTypeConverter().convertTo(String.class, exchange, body);
249            }
250    
251            Map<String, String> headers = extractResponseHeaders(exchange);
252    
253            if (statusCode >= 300 && statusCode < 400) {
254                String redirectLocation = exchange.getOut().getHeader("Location", String.class);
255                if (redirectLocation != null) {
256                    answer = new AhcOperationFailedException(url, statusCode, statusText, redirectLocation, headers, copy);
257                } else {
258                    // no redirect location
259                    answer = new AhcOperationFailedException(url, statusCode, statusText, null, headers, copy);
260                }
261            } else {
262                // internal server error (error code 500)
263                answer = new AhcOperationFailedException(url, statusCode, statusText, null, headers, copy);
264            }
265    
266            return answer;
267        }
268    
269        private Map<String, String> extractResponseHeaders(Exchange exchange) {
270            Map<String, String> answer = new LinkedHashMap<String, String>();
271            for (Map.Entry<String, Object> entry : exchange.getOut().getHeaders().entrySet()) {
272                String key = entry.getKey();
273                String value = exchange.getContext().getTypeConverter().convertTo(String.class, entry.getValue());
274                if (value != null) {
275                    answer.put(key, value);
276                }
277            }
278            return answer;
279        }
280    
281        private void populateResponse(Exchange exchange, Object body, int contentLength, int responseCode) {
282            exchange.getOut().setBody(body);
283            exchange.getOut().setHeader(Exchange.CONTENT_LENGTH, contentLength);
284        }
285    }