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.http;
018    
019    import java.net.URI;
020    import java.util.LinkedHashSet;
021    import java.util.Map;
022    import java.util.Set;
023    
024    import org.apache.camel.Endpoint;
025    import org.apache.camel.ResolveEndpointFailedException;
026    import org.apache.camel.impl.HeaderFilterStrategyComponent;
027    import org.apache.camel.util.CastUtils;
028    import org.apache.camel.util.CollectionHelper;
029    import org.apache.camel.util.IntrospectionSupport;
030    import org.apache.camel.util.ObjectHelper;
031    import org.apache.camel.util.URISupport;
032    import org.apache.commons.httpclient.HttpConnectionManager;
033    import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
034    import org.apache.commons.httpclient.params.HttpClientParams;
035    
036    /**
037     * Defines the <a href="http://camel.apache.org/http.html">HTTP
038     * Component</a>
039     *
040     * @version $Revision: 946128 $
041     */
042    public class HttpComponent extends HeaderFilterStrategyComponent {
043        protected HttpClientConfigurer httpClientConfigurer;
044        protected HttpConnectionManager httpConnectionManager = new MultiThreadedHttpConnectionManager();
045        protected HttpBinding httpBinding;
046        protected HttpConfiguration httpConfiguration;
047    
048        /**
049         * Connects the URL specified on the endpoint to the specified processor.
050         *
051         * @param consumer the consumer
052         * @throws Exception can be thrown
053         */
054        public void connect(HttpConsumer consumer) throws Exception {
055        }
056    
057        /**
058         * Disconnects the URL specified on the endpoint from the specified processor.
059         *
060         * @param consumer the consumer
061         * @throws Exception can be thrown
062         */
063        public void disconnect(HttpConsumer consumer) throws Exception {
064        }
065    
066        /** 
067         * Creates the HttpClientConfigurer based on the given parameters
068         * 
069         * @param parameters the map of parameters 
070         * @return the configurer
071         */
072        protected HttpClientConfigurer createHttpClientConfigurer(Map<String, Object> parameters, Set<AuthMethod> authMethods) {
073            // prefer to use endpoint configured over component configured
074            HttpClientConfigurer configurer = resolveAndRemoveReferenceParameter(parameters, "httpClientConfigurerRef", HttpClientConfigurer.class);
075            if (configurer == null) {
076                // try without ref
077                configurer = resolveAndRemoveReferenceParameter(parameters, "httpClientConfigurer", HttpClientConfigurer.class);
078            }
079            if (configurer == null) {
080                // fallback to component configured
081                configurer = getHttpClientConfigurer();
082            }
083    
084            // authentication can be endpoint configured
085            String authUsername = getAndRemoveParameter(parameters, "authUsername", String.class);
086            AuthMethod authMethod = getAndRemoveParameter(parameters, "authMethod", AuthMethod.class);
087            // validate that if auth username is given then the auth method is also provided
088            if (authUsername != null && authMethod == null) {
089                throw new IllegalArgumentException("Option authMethod must be provided to use authentication");
090            }
091            if (authMethod != null) {
092                String authPassword = getAndRemoveParameter(parameters, "authPassword", String.class);
093                String authDomain = getAndRemoveParameter(parameters, "authDomain", String.class);
094                String authHost = getAndRemoveParameter(parameters, "authHost", String.class);
095                configurer = configureAuth(configurer, authMethod, authUsername, authPassword, authDomain, authHost, authMethods);
096            } else if (httpConfiguration != null) {
097                // or fallback to use component configuration
098                configurer = configureAuth(configurer, httpConfiguration.getAuthMethod(), httpConfiguration.getAuthUsername(),
099                        httpConfiguration.getAuthPassword(), httpConfiguration.getAuthDomain(), httpConfiguration.getAuthHost(), authMethods);
100            }
101    
102            // proxy authentication can be endpoint configured
103            String proxyAuthUsername = getAndRemoveParameter(parameters, "proxyAuthUsername", String.class);
104            AuthMethod proxyAuthMethod = getAndRemoveParameter(parameters, "proxyAuthMethod", AuthMethod.class);
105            // validate that if proxy auth username is given then the proxy auth method is also provided
106            if (proxyAuthUsername != null && proxyAuthMethod == null) {
107                throw new IllegalArgumentException("Option proxyAuthMethod must be provided to use proxy authentication");
108            }
109            if (proxyAuthMethod != null) {
110                String proxyAuthPassword = getAndRemoveParameter(parameters, "proxyAuthPassword", String.class);
111                String proxyAuthDomain = getAndRemoveParameter(parameters, "proxyAuthDomain", String.class);
112                String proxyAuthHost = getAndRemoveParameter(parameters, "proxyAuthHost", String.class);
113                configurer = configureProxyAuth(configurer, proxyAuthMethod, proxyAuthUsername, proxyAuthPassword, proxyAuthDomain, proxyAuthHost, authMethods);
114            } else if (httpConfiguration != null) {
115                // or fallback to use component configuration
116                configurer = configureProxyAuth(configurer, httpConfiguration.getProxyAuthMethod(), httpConfiguration.getProxyAuthUsername(),
117                        httpConfiguration.getProxyAuthPassword(), httpConfiguration.getProxyAuthDomain(), httpConfiguration.getProxyAuthHost(), authMethods);
118            }
119    
120            return configurer;
121        }
122    
123        /**
124         * Configures the authentication method to be used
125         *
126         * @return configurer to used
127         */
128        protected HttpClientConfigurer configureAuth(HttpClientConfigurer configurer, AuthMethod authMethod, String username,
129                                                     String password, String domain, String host, Set<AuthMethod> authMethods) {
130    
131            // no auth is in use
132            if (username == null && authMethod == null) {
133                return configurer;
134            }
135    
136            // validate mandatory options given
137            if (username != null && authMethod == null) {
138                throw new IllegalArgumentException("Option authMethod must be provided to use authentication");
139            }
140            ObjectHelper.notNull(authMethod, "authMethod");
141            ObjectHelper.notNull(username, "authUsername");
142            ObjectHelper.notNull(password, "authPassword");
143    
144            // add it as a auth method used
145            authMethods.add(authMethod);
146    
147            if (authMethod == AuthMethod.Basic || authMethod == AuthMethod.Digest) {
148                return CompositeHttpConfigurer.combineConfigurers(configurer,
149                        new BasicAuthenticationHttpClientConfigurer(false, username, password));
150            } else if (authMethod == AuthMethod.NTLM) {
151                // domain is mandatory for NTLM
152                ObjectHelper.notNull(domain, "authDomain");
153                return CompositeHttpConfigurer.combineConfigurers(configurer,
154                        new NTLMAuthenticationHttpClientConfigurer(false, username, password, domain, host));
155            }
156    
157            throw new IllegalArgumentException("Unknown authMethod " + authMethod);
158        }
159        
160        /**
161         * Configures the proxy authentication method to be used
162         *
163         * @return configurer to used
164         */
165        protected HttpClientConfigurer configureProxyAuth(HttpClientConfigurer configurer, AuthMethod authMethod, String username,
166                                                          String password, String domain, String host, Set<AuthMethod> authMethods) {
167            // no proxy auth is in use
168            if (username == null && authMethod == null) {
169                return configurer;
170            }
171    
172            // validate mandatory options given
173            if (username != null && authMethod == null) {
174                throw new IllegalArgumentException("Option proxyAuthMethod must be provided to use proxy authentication");
175            }
176            ObjectHelper.notNull(authMethod, "proxyAuthMethod");
177            ObjectHelper.notNull(username, "proxyAuthUsername");
178            ObjectHelper.notNull(password, "proxyAuthPassword");
179    
180            // add it as a auth method used
181            authMethods.add(authMethod);
182    
183            if (authMethod == AuthMethod.Basic || authMethod == AuthMethod.Digest) {
184                return CompositeHttpConfigurer.combineConfigurers(configurer,
185                        new BasicAuthenticationHttpClientConfigurer(true, username, password));
186            } else if (authMethod == AuthMethod.NTLM) {
187                // domain is mandatory for NTML
188                ObjectHelper.notNull(domain, "proxyAuthDomain");
189                return CompositeHttpConfigurer.combineConfigurers(configurer,
190                        new NTLMAuthenticationHttpClientConfigurer(true, username, password, domain, host));
191            }
192    
193            throw new IllegalArgumentException("Unknown proxyAuthMethod " + authMethod);
194        }
195    
196        @Override
197        protected Endpoint createEndpoint(String uri, String remaining, Map<String, Object> parameters) throws Exception {
198    
199            // must extract well known parameters before we create the endpoint
200            HttpBinding binding = resolveAndRemoveReferenceParameter(parameters, "httpBindingRef", HttpBinding.class);
201            if (binding == null) {
202                // try without ref
203                binding = resolveAndRemoveReferenceParameter(parameters, "httpBinding", HttpBinding.class);
204            }
205            Boolean throwExceptionOnFailure = getAndRemoveParameter(parameters, "throwExceptionOnFailure", Boolean.class);
206            Boolean bridgeEndpoint = getAndRemoveParameter(parameters, "bridgeEndpoint", Boolean.class);
207            Boolean matchOnUriPrefix = getAndRemoveParameter(parameters, "matchOnUriPrefix", Boolean.class);
208            Boolean disableStreamCache = getAndRemoveParameter(parameters, "disableStreamCache", Boolean.class);
209            String proxyHost = getAndRemoveParameter(parameters, "proxyHost", String.class);
210            Integer proxyPort = getAndRemoveParameter(parameters, "proxyPort", Integer.class);
211            String authMethodPriority = getAndRemoveParameter(parameters, "authMethodPriority", String.class);
212            // http client can be configured from URI options
213            HttpClientParams clientParams = new HttpClientParams();
214            IntrospectionSupport.setProperties(clientParams, parameters, "httpClient.");
215            // validate that we could resolve all httpClient. parameters as this component is lenient
216            validateParameters(uri, parameters, "httpClient.");       
217            
218            // create the configurer to use for this endpoint (authMethods contains the used methods created by the configurer)
219            final Set<AuthMethod> authMethods = new LinkedHashSet<AuthMethod>();
220            HttpClientConfigurer configurer = createHttpClientConfigurer(parameters, authMethods);
221            
222            // restructure uri to be based on the parameters left as we dont want to include the Camel internal options
223            URI httpUri = URISupport.createRemainingURI(new URI(uri), CastUtils.cast(parameters));
224            uri = httpUri.toString();
225    
226            // validate http uri that end-user did not duplicate the http part that can be a common error
227            String part = httpUri.getSchemeSpecificPart();
228            if (part != null) {
229                part = part.toLowerCase();
230                if (part.startsWith("//http//") || part.startsWith("//https//")) {
231                    throw new ResolveEndpointFailedException(uri,
232                            "The uri part is not configured correctly. You have duplicated the http(s) protocol.");
233                }
234            }
235    
236            // create the endpoint
237            HttpEndpoint endpoint = new HttpEndpoint(uri, this, httpUri, clientParams, httpConnectionManager, configurer);
238            setEndpointHeaderFilterStrategy(endpoint);
239    
240            // prefer to use endpoint configured over component configured
241            if (binding == null) {
242                // fallback to component configured
243                binding = getHttpBinding();
244            }
245            if (binding != null) {
246                endpoint.setBinding(binding);
247            }
248            // should we use an exception for failed error codes?
249            if (throwExceptionOnFailure != null) {
250                endpoint.setThrowExceptionOnFailure(throwExceptionOnFailure);
251            }
252            if (bridgeEndpoint != null) {
253                endpoint.setBridgeEndpoint(bridgeEndpoint);
254            }
255            if (matchOnUriPrefix != null) {
256                endpoint.setMatchOnUriPrefix(matchOnUriPrefix);
257            }
258            if (disableStreamCache != null) {
259                endpoint.setDisableStreamCache(disableStreamCache);
260            }
261            if (proxyHost != null) {
262                endpoint.setProxyHost(proxyHost);
263                endpoint.setProxyPort(proxyPort);
264            } else if (httpConfiguration != null) {
265                endpoint.setProxyHost(httpConfiguration.getProxyHost());
266                endpoint.setProxyPort(httpConfiguration.getProxyPort());
267            }
268            if (authMethodPriority != null) {
269                endpoint.setAuthMethodPriority(authMethodPriority);
270            } else if (httpConfiguration != null && httpConfiguration.getAuthMethodPriority() != null) {
271                endpoint.setAuthMethodPriority(httpConfiguration.getAuthMethodPriority());
272            } else {
273                // no explicit auth method priority configured, so use convention over configuration
274                // and set priority based on auth method
275                if (!authMethods.isEmpty()) {
276                    authMethodPriority = CollectionHelper.collectionAsCommaDelimitedString(authMethods);
277                    endpoint.setAuthMethodPriority(authMethodPriority);
278                }
279            }
280    
281            setProperties(endpoint, parameters);
282            return endpoint;
283        }
284        
285        @Override
286        protected boolean useIntrospectionOnEndpoint() {
287            return false;
288        }
289    
290        public HttpClientConfigurer getHttpClientConfigurer() {
291            return httpClientConfigurer;
292        }
293    
294        public void setHttpClientConfigurer(HttpClientConfigurer httpClientConfigurer) {
295            this.httpClientConfigurer = httpClientConfigurer;
296        }
297    
298        public HttpConnectionManager getHttpConnectionManager() {
299            return httpConnectionManager;
300        }
301    
302        public void setHttpConnectionManager(HttpConnectionManager httpConnectionManager) {
303            this.httpConnectionManager = httpConnectionManager;
304        }
305    
306        public HttpBinding getHttpBinding() {
307            return httpBinding;
308        }
309    
310        public void setHttpBinding(HttpBinding httpBinding) {
311            this.httpBinding = httpBinding;
312        }
313    
314        public HttpConfiguration getHttpConfiguration() {
315            return httpConfiguration;
316        }
317    
318        public void setHttpConfiguration(HttpConfiguration httpConfiguration) {
319            this.httpConfiguration = httpConfiguration;
320        }
321    
322    }