From edeee62f8dcf0d3b515cd5d22e01b2296cc4a894 Mon Sep 17 00:00:00 2001 From: Oleg Kalnichevski Date: Thu, 18 Apr 2013 09:45:19 +0000 Subject: [PATCH] HTTPCLIENT-1238: Contribute Bundle Activator And Central Proxy Configuration Contributed by Simone Tripodi git-svn-id: https://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk@1469247 13f79535-47bb-0310-9956-ffa450edef68 --- RELEASE_NOTES.txt | 3 + httpclient-osgi/pom.xml | 34 ++- .../impl/HttpProxyConfigurationActivator.java | 165 ++++++++++++++ .../osgi/impl/OSGiClientBuilderFactory.java | 62 ++++++ .../osgi/impl/OSGiCredentialsProvider.java | 92 ++++++++ .../http/osgi/impl/OSGiHttpClientBuilder.java | 62 ++++++ .../http/osgi/impl/OSGiHttpRoutePlanner.java | 206 ++++++++++++++++++ .../osgi/impl/OSGiProxyConfiguration.java | 135 ++++++++++++ .../http/osgi/impl/PropertiesUtils.java | 199 +++++++++++++++++ .../apache/http/osgi/impl/package-info.java | 27 +++ .../org/apache/http/osgi/package-info.java | 31 +++ .../services/HttpClientBuilderFactory.java | 38 ++++ .../osgi/services/ProxyConfiguration.java | 46 ++++ .../http/osgi/services/package-info.java | 31 +++ .../OSGI-INF/metatype/metatype.properties | 77 +++++++ .../resources/OSGI-INF/metatype/metatype.xml | 76 +++++++ .../http/osgi/impl/TestPropertiesUtils.java | 129 +++++++++++ 17 files changed, 1407 insertions(+), 6 deletions(-) create mode 100644 httpclient-osgi/src/main/java/org/apache/http/osgi/impl/HttpProxyConfigurationActivator.java create mode 100644 httpclient-osgi/src/main/java/org/apache/http/osgi/impl/OSGiClientBuilderFactory.java create mode 100644 httpclient-osgi/src/main/java/org/apache/http/osgi/impl/OSGiCredentialsProvider.java create mode 100644 httpclient-osgi/src/main/java/org/apache/http/osgi/impl/OSGiHttpClientBuilder.java create mode 100644 httpclient-osgi/src/main/java/org/apache/http/osgi/impl/OSGiHttpRoutePlanner.java create mode 100644 httpclient-osgi/src/main/java/org/apache/http/osgi/impl/OSGiProxyConfiguration.java create mode 100644 httpclient-osgi/src/main/java/org/apache/http/osgi/impl/PropertiesUtils.java create mode 100644 httpclient-osgi/src/main/java/org/apache/http/osgi/impl/package-info.java create mode 100644 httpclient-osgi/src/main/java/org/apache/http/osgi/package-info.java create mode 100644 httpclient-osgi/src/main/java/org/apache/http/osgi/services/HttpClientBuilderFactory.java create mode 100644 httpclient-osgi/src/main/java/org/apache/http/osgi/services/ProxyConfiguration.java create mode 100644 httpclient-osgi/src/main/java/org/apache/http/osgi/services/package-info.java create mode 100644 httpclient-osgi/src/main/resources/OSGI-INF/metatype/metatype.properties create mode 100644 httpclient-osgi/src/main/resources/OSGI-INF/metatype/metatype.xml create mode 100644 httpclient-osgi/src/test/java/org/apache/http/osgi/impl/TestPropertiesUtils.java diff --git a/RELEASE_NOTES.txt b/RELEASE_NOTES.txt index 770d8626b..f1a2e3233 100644 --- a/RELEASE_NOTES.txt +++ b/RELEASE_NOTES.txt @@ -1,6 +1,9 @@ Changes since release 4.3 BETA1 ------------------- +* [HTTPCLIENT-1238] Contribute Bundle Activator And Central Proxy Configuration. + Contributed by Simone Tripodi + * [HTTPCLIENT-1341] DeflateDecompressingEntity does not call Inflater#end. Contributed by Oleg Kalnichevski diff --git a/httpclient-osgi/pom.xml b/httpclient-osgi/pom.xml index 5b26404fb..81e4fc42e 100644 --- a/httpclient-osgi/pom.xml +++ b/httpclient-osgi/pom.xml @@ -48,6 +48,11 @@ ${project.version} compile + + org.apache.httpcomponents + httpcore + compile + commons-codec commons-codec @@ -72,16 +77,30 @@ ${project.version} compile + + org.osgi + org.osgi.core + 4.2.0 + provided + + + org.osgi + org.osgi.compendium + 4.2.0 + provided + + + + junit + junit + test + src/main/resources - true - - **/*.properties - @@ -103,6 +122,7 @@ org.apache.http.impl.cookie.*;version=${project.version}, org.apache.http.impl.conn.*;version=${project.version}, org.apache.http.impl.client.*;version=${project.version} + org.apache.http.osgi.services;version=${project.version} *;scope=compile|runtime;inline=true @@ -111,10 +131,11 @@ javax.net, javax.net.ssl, javax.security.auth.x500, - org.ietf.jgss, + org.ietf.jgss,, + org.osgi.framework, + org.osgi.service.cm, org.apache.commons.logging;version=${commons-logging.version}, org.apache.http;version=${httpcore.version}, - org.apache.http.config.*;version=${httpcore.version}, org.apache.http.concurrent;version=${httpcore.version}, org.apache.http.entity;version=${httpcore.version}, org.apache.http.io;version=${httpcore.version}, @@ -130,6 +151,7 @@ net.sf.ehcache.*;resolution:=optional, net.spy.memcached.*;resolution:=optional + org.apache.http.osgi.impl.HttpProxyConfigurationActivator <_removeheaders>JAVA_1_3_HOME,JAVA_1_4_HOME diff --git a/httpclient-osgi/src/main/java/org/apache/http/osgi/impl/HttpProxyConfigurationActivator.java b/httpclient-osgi/src/main/java/org/apache/http/osgi/impl/HttpProxyConfigurationActivator.java new file mode 100644 index 000000000..b641dd452 --- /dev/null +++ b/httpclient-osgi/src/main/java/org/apache/http/osgi/impl/HttpProxyConfigurationActivator.java @@ -0,0 +1,165 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ +package org.apache.http.osgi.impl; + +import java.io.Closeable; +import java.io.IOException; +import java.util.Dictionary; +import java.util.Hashtable; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.osgi.services.HttpClientBuilderFactory; +import org.apache.http.osgi.services.ProxyConfiguration; +import org.osgi.framework.BundleActivator; +import org.osgi.framework.BundleContext; +import org.osgi.framework.Constants; +import org.osgi.framework.ServiceRegistration; +import org.osgi.service.cm.ConfigurationException; +import org.osgi.service.cm.ManagedServiceFactory; + +/** + * @since 4.3 + */ +public final class HttpProxyConfigurationActivator implements BundleActivator, ManagedServiceFactory { + + private static final String SERVICE_FACTORY_NAME = "Apache HTTP Client Proxy Configuration Factory"; + + private static final String SERVICE_PID = "org.apache.http.proxyconfigurator"; + + private ServiceRegistration configurator; + + private ServiceRegistration clientFactory; + + private BundleContext context; + + private final Map registeredConfigurations = new LinkedHashMap(); + + private final List trackedHttpClients = new LinkedList(); + + /** + * {@inheritDoc} + */ + public void start(final BundleContext context) throws Exception { + this.context = context; + + // ensure we receive configurations for the proxy selector + final Hashtable props = new Hashtable(); + props.put(Constants.SERVICE_PID, getName()); + props.put(Constants.SERVICE_VENDOR, context.getBundle().getHeaders(Constants.BUNDLE_VENDOR)); + props.put(Constants.SERVICE_DESCRIPTION, SERVICE_FACTORY_NAME); + + configurator = context.registerService(ManagedServiceFactory.class.getName(), this, props); + clientFactory = context.registerService(HttpClientBuilderFactory.class.getName(), + new OSGiClientBuilderFactory(context, registeredConfigurations, trackedHttpClients), + props); + } + + /** + * {@inheritDoc} + */ + public void stop(final BundleContext context) throws Exception { + // unregister services + for (final ServiceRegistration registeredConfiguration : registeredConfigurations.values()) { + registeredConfiguration.unregister(); + } + + // unregister service factory + if (configurator != null) { + configurator.unregister(); + } + + if (clientFactory != null) { + clientFactory.unregister(); + } + + // ensure all http clients - generated with the - are terminated + for (final CloseableHttpClient client : trackedHttpClients) { + if (null != client) { + closeQuietly(client); + } + } + + // remove all tracked services + registeredConfigurations.clear(); + // remove all tracked created clients + trackedHttpClients.clear(); + } + + /** + * {@inheritDoc} + */ + public String getName() { + return SERVICE_PID; + } + + /** + * {@inheritDoc} + */ + public void updated(final String pid, @SuppressWarnings("rawtypes") final Dictionary config) throws ConfigurationException { + final ServiceRegistration registration = registeredConfigurations.get(pid); + OSGiProxyConfiguration proxyConfiguration; + + if (registration == null) { + proxyConfiguration = new OSGiProxyConfiguration(); + final ServiceRegistration configurationRegistration = context.registerService(ProxyConfiguration.class.getName(), + proxyConfiguration, + config); + registeredConfigurations.put(pid, configurationRegistration); + } else { + proxyConfiguration = (OSGiProxyConfiguration) context.getService(registration.getReference()); + } + + @SuppressWarnings("unchecked") // data type is known + final + Dictionary properties = config; + proxyConfiguration.update(properties); + } + + /** + * {@inheritDoc} + */ + public void deleted(final String pid) { + final ServiceRegistration registeredConfiguration = registeredConfigurations.get(pid); + if (null != registeredConfiguration) { + registeredConfiguration.unregister(); + registeredConfigurations.remove(pid); + } + } + + private static void closeQuietly(final Closeable closeable) { + try { + closeable.close(); + } catch (final IOException e) { + // do nothing + } + } + +} diff --git a/httpclient-osgi/src/main/java/org/apache/http/osgi/impl/OSGiClientBuilderFactory.java b/httpclient-osgi/src/main/java/org/apache/http/osgi/impl/OSGiClientBuilderFactory.java new file mode 100644 index 000000000..cbd562642 --- /dev/null +++ b/httpclient-osgi/src/main/java/org/apache/http/osgi/impl/OSGiClientBuilderFactory.java @@ -0,0 +1,62 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ +package org.apache.http.osgi.impl; + +import java.util.List; +import java.util.Map; + +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.osgi.services.HttpClientBuilderFactory; +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceRegistration; + +/** + * @since 4.3 + */ +public final class OSGiClientBuilderFactory implements HttpClientBuilderFactory { + + private final BundleContext bundleContext; + + private final Map registeredConfigurations; + + private final List trackedHttpClients; + + public OSGiClientBuilderFactory( + final BundleContext bundleContext, + final Map registeredConfigurations, + final List trackedHttpClients) { + this.bundleContext = bundleContext; + this.registeredConfigurations = registeredConfigurations; + this.trackedHttpClients = trackedHttpClients; + } + + public HttpClientBuilder newBuilder() { + return new OSGiHttpClientBuilder(bundleContext, registeredConfigurations, trackedHttpClients); + } + +} diff --git a/httpclient-osgi/src/main/java/org/apache/http/osgi/impl/OSGiCredentialsProvider.java b/httpclient-osgi/src/main/java/org/apache/http/osgi/impl/OSGiCredentialsProvider.java new file mode 100644 index 000000000..d4f6027a3 --- /dev/null +++ b/httpclient-osgi/src/main/java/org/apache/http/osgi/impl/OSGiCredentialsProvider.java @@ -0,0 +1,92 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ +package org.apache.http.osgi.impl; + +import java.util.Map; + +import org.apache.http.auth.AuthScope; +import org.apache.http.auth.Credentials; +import org.apache.http.auth.UsernamePasswordCredentials; +import org.apache.http.client.CredentialsProvider; +import org.apache.http.osgi.services.ProxyConfiguration; +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceRegistration; + +/** + * @since 4.3 + */ +final class OSGiCredentialsProvider implements CredentialsProvider { + + private final BundleContext bundleContext; + + private final Map registeredConfigurations; + + public OSGiCredentialsProvider( + final BundleContext bundleContext, + final Map registeredConfigurations) { + this.bundleContext = bundleContext; + this.registeredConfigurations = registeredConfigurations; + } + + /** + * {@inheritDoc} + */ + public void setCredentials(final AuthScope authscope, final Credentials credentials) { + // do nothing, not used in this version + } + + /** + * {@inheritDoc} + */ + public Credentials getCredentials(final AuthScope authscope) { + // iterate over all active proxy configurations at the moment of getting the credential + for (final ServiceRegistration registration : registeredConfigurations.values()) { + final Object proxyConfigurationObject = bundleContext.getService(registration.getReference()); + if (proxyConfigurationObject != null) { + final ProxyConfiguration proxyConfiguration = (ProxyConfiguration) proxyConfigurationObject; + if (proxyConfiguration.isEnabled()) { + final AuthScope actual = new AuthScope(proxyConfiguration.getHostname(), proxyConfiguration.getPort()); + if (authscope.equals(actual)) { + return new UsernamePasswordCredentials(proxyConfiguration.getUsername(), + proxyConfiguration.getPassword()); + } + + } + } + } + // credentials no longer available! + return null; + } + + /** + * {@inheritDoc} + */ + public void clear() { + // do nothing, not used in this version + } + +} diff --git a/httpclient-osgi/src/main/java/org/apache/http/osgi/impl/OSGiHttpClientBuilder.java b/httpclient-osgi/src/main/java/org/apache/http/osgi/impl/OSGiHttpClientBuilder.java new file mode 100644 index 000000000..d8d89a55a --- /dev/null +++ b/httpclient-osgi/src/main/java/org/apache/http/osgi/impl/OSGiHttpClientBuilder.java @@ -0,0 +1,62 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ +package org.apache.http.osgi.impl; + +import java.util.List; +import java.util.Map; + +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceRegistration; + +/** + * @since 4.3 + */ +final class OSGiHttpClientBuilder extends HttpClientBuilder { + + private final List trackedHttpClients; + + public OSGiHttpClientBuilder( + final BundleContext bundleContext, + final Map registeredConfigurations, + final List trackedHttpClients) { + this.trackedHttpClients = trackedHttpClients; + setDefaultCredentialsProvider( + new OSGiCredentialsProvider(bundleContext, registeredConfigurations)); + setRoutePlanner( + new OSGiHttpRoutePlanner(bundleContext, registeredConfigurations)); + } + + @Override + public CloseableHttpClient build() { + final CloseableHttpClient httpClient = super.build(); + trackedHttpClients.add(httpClient); + return httpClient; + } + +} diff --git a/httpclient-osgi/src/main/java/org/apache/http/osgi/impl/OSGiHttpRoutePlanner.java b/httpclient-osgi/src/main/java/org/apache/http/osgi/impl/OSGiHttpRoutePlanner.java new file mode 100644 index 000000000..01c22022b --- /dev/null +++ b/httpclient-osgi/src/main/java/org/apache/http/osgi/impl/OSGiHttpRoutePlanner.java @@ -0,0 +1,206 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ +package org.apache.http.osgi.impl; + +import java.util.Locale; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.http.HttpException; +import org.apache.http.HttpHost; +import org.apache.http.HttpRequest; +import org.apache.http.impl.conn.DefaultRoutePlanner; +import org.apache.http.osgi.services.ProxyConfiguration; +import org.apache.http.protocol.HttpContext; +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceRegistration; + +/** + * @since 4.3 + */ +final class OSGiHttpRoutePlanner extends DefaultRoutePlanner { + + private static final String DOT = "."; + + /** + * The IP mask pattern against which hosts are matched. + */ + public static final Pattern IP_MASK_PATTERN = Pattern.compile("^([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\." + + "([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\." + + "([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\." + + "([01]?\\d\\d?|2[0-4]\\d|25[0-5])$"); + + private final BundleContext bundleContext; + + private final Map registeredConfigurations; + + public OSGiHttpRoutePlanner( + final BundleContext bundleContext, + final Map registeredConfigurations) { + super(null); + this.bundleContext = bundleContext; + this.registeredConfigurations = registeredConfigurations; + } + + /** + * {@inheritDoc} + */ + @Override + protected HttpHost determineProxy(final HttpHost target, final HttpRequest request, final HttpContext context) throws HttpException { + ProxyConfiguration proxyConfiguration = null; + + for (final ServiceRegistration registration : registeredConfigurations.values()) { + final Object proxyConfigurationObject = bundleContext.getService(registration.getReference()); + if (proxyConfigurationObject != null) { + proxyConfiguration = (ProxyConfiguration) proxyConfigurationObject; + if (proxyConfiguration.isEnabled()) { + for (final String exception : proxyConfiguration.getProxyExceptions()) { + if (createMatcher(exception).matches(target.getHostName())) { + return null; + } else { + return new HttpHost(proxyConfiguration.getHostname(), proxyConfiguration.getPort()); + } + } + } + } + } + + return null; + } + + private static HostMatcher createMatcher(final String name) { + final NetworkAddress na = NetworkAddress.parse(name); + if (na != null) { + return new IPAddressMatcher(na); + } + + if (name.startsWith(DOT)) { + return new DomainNameMatcher(name); + } + + return new HostNameMatcher(name); + } + + private static interface HostMatcher { + + boolean matches(String host); + + } + + private static class HostNameMatcher implements HostMatcher { + + private final String hostName; + + HostNameMatcher(final String hostName) { + this.hostName = hostName; + } + + public boolean matches(final String host) { + return hostName.equalsIgnoreCase(host); + } + } + + private static class DomainNameMatcher implements HostMatcher { + + private final String domainName; + + DomainNameMatcher(final String domainName) { + this.domainName = domainName.toLowerCase(Locale.ENGLISH); + } + + public boolean matches(final String host) { + return host.toLowerCase(Locale.ENGLISH).endsWith(domainName); + } + } + + private static class IPAddressMatcher implements HostMatcher { + + private final NetworkAddress address; + + IPAddressMatcher(final NetworkAddress address) { + this.address = address; + } + + public boolean matches(final String host) { + final NetworkAddress hostAddress = NetworkAddress.parse(host); + return hostAddress != null && address.address == (hostAddress.address & address.mask); + } + + } + + private static class NetworkAddress { + + final int address; + + final int mask; + + static NetworkAddress parse(final String adrSpec) { + + if (null != adrSpec) { + final Matcher nameMatcher = IP_MASK_PATTERN.matcher(adrSpec); + if (nameMatcher.matches()) { + try { + final int i1 = toInt(nameMatcher.group(1), 255); + final int i2 = toInt(nameMatcher.group(2), 255); + final int i3 = toInt(nameMatcher.group(3), 255); + final int i4 = toInt(nameMatcher.group(4), 255); + final int ip = i1 << 24 | i2 << 16 | i3 << 8 | i4; + + int mask = toInt(nameMatcher.group(6), 32); + mask = (mask == 32) ? -1 : -1 - (-1 >>> mask); + + return new NetworkAddress(ip, mask); + } catch (final NumberFormatException nfe) { + // not expected after the pattern match ! + } + } + } + + return null; + } + + private static int toInt(final String value, final int max) { + if (value == null || value.length() == 0) { + return max; + } + + int number = Integer.parseInt(value); + if (number > max) { + number = max; + } + return number; + } + + NetworkAddress(final int address, final int mask) { + this.address = address; + this.mask = mask; + } + + } + +} diff --git a/httpclient-osgi/src/main/java/org/apache/http/osgi/impl/OSGiProxyConfiguration.java b/httpclient-osgi/src/main/java/org/apache/http/osgi/impl/OSGiProxyConfiguration.java new file mode 100644 index 000000000..df6dbc97c --- /dev/null +++ b/httpclient-osgi/src/main/java/org/apache/http/osgi/impl/OSGiProxyConfiguration.java @@ -0,0 +1,135 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ +package org.apache.http.osgi.impl; + +import static java.lang.String.format; +import static org.apache.http.osgi.impl.PropertiesUtils.to; + +import java.util.Dictionary; + +import org.apache.http.osgi.services.ProxyConfiguration; + +/** + * @since 4.3 + */ +public final class OSGiProxyConfiguration implements ProxyConfiguration { + + /** + * Property indicating whether this particular proxy is enabled (shall be used or not). Defaults to true. + */ + private static final String PROPERTYNAME_PROXY_ENABLED = "proxy.enabled"; + + private static final boolean PROPERTYDEFAULT_PROXY_ENABLED = true; + + /** + * Property representing the hostname of the proxy. Defaults to empty. + */ + private static final String PROPERTYNAME_PROXY_HOSTNAME = "proxy.host"; + + private static final String PROPERTYDEFAULT_PROXY_HOSTNAME = ""; + + /** + * Property representing the port of the proxy. Defaults to 0. + */ + private static final String PROPERTYNAME_PROXY_PORT = "proxy.port"; + + private static final int PROPERTYDEFAULT_PROXY_PORT = 0; + + /** + * Property representing the username to authenticate with towards the proxy. Defaults to empty. + */ + private static final String PROPERTYNAME_PROXY_USERNAME = "proxy.username"; + + private static final String PROPERTYDEFAULT_PROXY_USERNAME = ""; + + /** + * Property representing the password to authenticate with towards the proxy. Defaults to empty. + */ + private static final String PROPERTYNAME_PROXY_PASSWORD = "proxy.password"; + + private static final String PROPERTYDEFAULT_PROXY_PASSWORD = ""; + + /** + * A multivalue property representing host patterns for which no proxy shall be used. By default localhost is + * excluded. + */ + private static final String PROPERTYNAME_PROXY_EXCEPTIONS = "proxy.exceptions"; + + private static final String[] PROPERTYDEFAULT_PROXY_EXCEPTIONS = new String[]{"localhost", "127.0.0.1"}; + + private boolean enabled; + + private String hostname; + + private int port; + + private String username; + + private String password; + + private String[] proxyExceptions; + + public boolean isEnabled() { + return enabled; + } + + public String getHostname() { + return hostname; + } + + public int getPort() { + return port; + } + + public String getUsername() { + return username; + } + + public String getPassword() { + return password; + } + + public String[] getProxyExceptions() { + return proxyExceptions; + } + + public void update(final Dictionary config) { + enabled = to(config.get(PROPERTYNAME_PROXY_ENABLED), boolean.class, PROPERTYDEFAULT_PROXY_ENABLED); + hostname = to(config.get(PROPERTYNAME_PROXY_HOSTNAME), String.class, PROPERTYDEFAULT_PROXY_HOSTNAME); + port = to(config.get(PROPERTYNAME_PROXY_PORT), int.class, PROPERTYDEFAULT_PROXY_PORT); + username = to(config.get(PROPERTYNAME_PROXY_USERNAME), String.class, PROPERTYDEFAULT_PROXY_USERNAME); + password = to(config.get(PROPERTYNAME_PROXY_PASSWORD), String.class, PROPERTYDEFAULT_PROXY_PASSWORD); + proxyExceptions = to(config.get(PROPERTYNAME_PROXY_EXCEPTIONS), String[].class, PROPERTYDEFAULT_PROXY_EXCEPTIONS); + } + + @Override + public String toString() { + return format("ProxyConfiguration [enabled=%s, hostname=%s, port=%s, username=%s, password=%s, proxyExceptions=%s]", + proxyExceptions, enabled, hostname, port, username, password, proxyExceptions); + } + +} diff --git a/httpclient-osgi/src/main/java/org/apache/http/osgi/impl/PropertiesUtils.java b/httpclient-osgi/src/main/java/org/apache/http/osgi/impl/PropertiesUtils.java new file mode 100644 index 000000000..7d69b01b3 --- /dev/null +++ b/httpclient-osgi/src/main/java/org/apache/http/osgi/impl/PropertiesUtils.java @@ -0,0 +1,199 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ +package org.apache.http.osgi.impl; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @since 4.3 + */ +final class PropertiesUtils { + + private static final Map, PropertyConverter> CONVERTERS_REGISTRY = + new HashMap, PropertiesUtils.PropertyConverter>(); + + static { + register(new BooleanPropertyConverter(), boolean.class, Boolean.class); + register(new StringPropertyConverter(), String.class); + register(new StringArrayPropertyConverter(), String[].class); + register(new IntegerPropertyConverter(), int.class, Integer.class); + register(new LongPropertyConverter(), long.class, Long.class); + register(new DoublePropertyConverter(), double.class, Double.class); + } + + private static void register(PropertyConverter converter, Class...targetTypes) { + for (Class targetType : targetTypes) { + CONVERTERS_REGISTRY.put(targetType, converter); + } + } + + public static T to(Object propValue, Class targetType, T defaultValue) { + if (propValue == null) { + return defaultValue; + } + + if (!targetType.isArray()) { + propValue = toObject(propValue); + } + + if (targetType.isInstance(propValue)) { + return targetType.cast(propValue); + } + + if (CONVERTERS_REGISTRY.containsKey(targetType)) { + @SuppressWarnings("unchecked") // type driven by targetType + PropertyConverter converter = (PropertyConverter) CONVERTERS_REGISTRY.get(targetType); + try { + return converter.to(propValue); + } catch (Throwable t) { + // don't care, fall through to default value + } + } + + return defaultValue; + } + + /** + * Returns the parameter as a single value. If the + * parameter is neither an array nor a java.util.Collection the + * parameter is returned unmodified. If the parameter is a non-empty array, + * the first array element is returned. If the property is a non-empty + * java.util.Collection, the first collection element is returned. + * + * @param propValue the parameter to convert. + */ + private static Object toObject(Object propValue) { + if (propValue.getClass().isArray()) { + Object[] prop = (Object[]) propValue; + return prop.length > 0 ? prop[0] : null; + } + + if (propValue instanceof Collection) { + Collection prop = (Collection) propValue; + return prop.isEmpty() ? null : prop.iterator().next(); + } + + return propValue; + } + + /** + * Hidden constructor, this class must not be instantiated. + */ + private PropertiesUtils() { + // do nothing + } + + private static interface PropertyConverter { + + T to(Object propValue); + + } + + private static class BooleanPropertyConverter implements PropertyConverter { + + public Boolean to(Object propValue) { + return Boolean.valueOf(String.valueOf(propValue)); + } + + } + + private static class StringPropertyConverter implements PropertyConverter { + + public String to(Object propValue) { + return String.valueOf(propValue); + } + + } + + private static class StringArrayPropertyConverter implements PropertyConverter { + + public String[] to(Object propValue) { + if (propValue instanceof String) { + // single string + return new String[] { (String) propValue }; + } + + if (propValue.getClass().isArray()) { + // other array + Object[] valueArray = (Object[]) propValue; + List values = new ArrayList(valueArray.length); + for (Object value : valueArray) { + if (value != null) { + values.add(value.toString()); + } + } + return values.toArray(new String[values.size()]); + + } + + if (propValue instanceof Collection) { + // collection + Collection valueCollection = (Collection) propValue; + List valueList = new ArrayList(valueCollection.size()); + for (Object value : valueCollection) { + if (value != null) { + valueList.add(value.toString()); + } + } + return valueList.toArray(new String[valueList.size()]); + } + + // don't care, fall through to default value + throw new IllegalArgumentException(); + } + + } + + private static class IntegerPropertyConverter implements PropertyConverter { + + public Integer to(Object propValue) { + return Integer.valueOf(String.valueOf(propValue)); + } + + } + + private static class LongPropertyConverter implements PropertyConverter { + + public Long to(Object propValue) { + return Long.valueOf(String.valueOf(propValue)); + } + + } + + private static class DoublePropertyConverter implements PropertyConverter { + + public Double to(Object propValue) { + return Double.valueOf(String.valueOf(propValue)); + } + + } + +} diff --git a/httpclient-osgi/src/main/java/org/apache/http/osgi/impl/package-info.java b/httpclient-osgi/src/main/java/org/apache/http/osgi/impl/package-info.java new file mode 100644 index 000000000..47a2ede29 --- /dev/null +++ b/httpclient-osgi/src/main/java/org/apache/http/osgi/impl/package-info.java @@ -0,0 +1,27 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ +package org.apache.http.osgi.impl; diff --git a/httpclient-osgi/src/main/java/org/apache/http/osgi/package-info.java b/httpclient-osgi/src/main/java/org/apache/http/osgi/package-info.java new file mode 100644 index 000000000..58cb6d201 --- /dev/null +++ b/httpclient-osgi/src/main/java/org/apache/http/osgi/package-info.java @@ -0,0 +1,31 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ + +/** + * @since 4.3 + */ +package org.apache.http.osgi; diff --git a/httpclient-osgi/src/main/java/org/apache/http/osgi/services/HttpClientBuilderFactory.java b/httpclient-osgi/src/main/java/org/apache/http/osgi/services/HttpClientBuilderFactory.java new file mode 100644 index 000000000..f9a4464b2 --- /dev/null +++ b/httpclient-osgi/src/main/java/org/apache/http/osgi/services/HttpClientBuilderFactory.java @@ -0,0 +1,38 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ +package org.apache.http.osgi.services; + +import org.apache.http.impl.client.HttpClientBuilder; + +/** + * @since 4.3 + */ +public interface HttpClientBuilderFactory { + + HttpClientBuilder newBuilder(); + +} diff --git a/httpclient-osgi/src/main/java/org/apache/http/osgi/services/ProxyConfiguration.java b/httpclient-osgi/src/main/java/org/apache/http/osgi/services/ProxyConfiguration.java new file mode 100644 index 000000000..d3d92e462 --- /dev/null +++ b/httpclient-osgi/src/main/java/org/apache/http/osgi/services/ProxyConfiguration.java @@ -0,0 +1,46 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ +package org.apache.http.osgi.services; + +/** + * @since 4.3 + */ +public interface ProxyConfiguration { + + boolean isEnabled(); + + String getHostname(); + + int getPort(); + + String getUsername(); + + String getPassword(); + + String[] getProxyExceptions(); + +} diff --git a/httpclient-osgi/src/main/java/org/apache/http/osgi/services/package-info.java b/httpclient-osgi/src/main/java/org/apache/http/osgi/services/package-info.java new file mode 100644 index 000000000..bac867d29 --- /dev/null +++ b/httpclient-osgi/src/main/java/org/apache/http/osgi/services/package-info.java @@ -0,0 +1,31 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ + +/** + * @since 4.3 + */ +package org.apache.http.osgi.services; diff --git a/httpclient-osgi/src/main/resources/OSGI-INF/metatype/metatype.properties b/httpclient-osgi/src/main/resources/OSGI-INF/metatype/metatype.properties new file mode 100644 index 000000000..30e35f260 --- /dev/null +++ b/httpclient-osgi/src/main/resources/OSGI-INF/metatype/metatype.properties @@ -0,0 +1,77 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +proxyconfigurator.name = Apache HTTP Components Proxy Configurator +proxyconfigurator.description = Factory configuration for transparent proxies used by every HTTP Client. + +proxyconfig.name = Apache HTTP Components Proxy Configuration +proxyconfig.description = Proxy configuration for central and transparent proxying of http client connections. + +proxy.enabled.name = Enable HTTP Proxy +proxy.enabled.description = Whether to enable or disable this particular proxy configuration. \ +The default value is false. + +proxy.host.name = HTTP Proxy Host +proxy.host.description = Host name (or IP Address) of the HTTP Proxy. This property is ignored if \ + this proxy configuration is disabled. This property does not have a default value. Enabling \ + this proxy but not setting the HTTP Proxy Host effectively disables this configuration. + +proxy.port.name = HTTP Proxy Port +proxy.port.description = TCP port of the HTTP Proxy. This property is ignored if \ + this proxy configuration is disabled. This property does not have a default value. Enabling \ + this proxy but not setting the HTTP Proxy Port effectively disables this configuration. + +proxy.user.name = HTTP Proxy User +proxy.user.description = The name of the user to authenticate as with the HTTP \ + Proxy Host. If this field is empty, the proxy is considered to not be \ + authenticated. The default is empty. This property is ignored if proxying is \ + disabled or the proxy host is not properly configured. + +proxy.password.name = HTTP Proxy Password +proxy.password.description = The password of the HTTP Proxy user to authenticate \ + with. The default is empty. This property is ignored if proxying is \ + disabled or the proxy host is not properly configured. + +proxy.ntlm.host.name = HTTP Proxy NTLM Host +proxy.ntlm.host.description = The host the authentication request is \ + originating from. Essentially, the computer name for this machine. By default \ + the credentials assume simple username password authentication. If the proxy \ + happens to be a Microsoft IIS Server using NTLM authentication this property \ + must be set to the NT Domain name of the user to authenticate as. This is \ + not set by default. + +proxy.ntlm.domain.name = HTTP Proxy NTLM Domain +proxy.ntlm.domain.description = The NTLM domain to authenticate within. By \ + default the credentials assume simple username password authentication. If \ + the proxy happens to be a Microsoft IIS Server using NTLM authentication this \ + property must be set to the NT Domain name of the user to authenticate as. \ + This is not set by default. + +proxy.exceptions.name = No Proxy For +proxy.exceptions.description = Lists domain names, host names, IP Addresses or \ + or network addresses for which this proxy configuration should not be used. A domain \ + name indicating all hosts of a domain is indicated by a leading dot, e.g. \ + ".day.com". A network address is indicated with subnet mask notation indicating \ + the number of bits make up the network address, e.g 192.168.1.0/24 means the \ + class C network "192.168.1". Note that for proxy selection, the host name of \ + URL is not resolved but directly compared to the list of exceptions. For this \ + reason you might want to indicate both the network address and the domain for \ + targets which should not be passed through the proxy. This property has no \ + effect if this proxy configuration is disabled. The default value is [ localhost, \ + 127.0.0.1 ]. \ No newline at end of file diff --git a/httpclient-osgi/src/main/resources/OSGI-INF/metatype/metatype.xml b/httpclient-osgi/src/main/resources/OSGI-INF/metatype/metatype.xml new file mode 100644 index 000000000..2bddab498 --- /dev/null +++ b/httpclient-osgi/src/main/resources/OSGI-INF/metatype/metatype.xml @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/httpclient-osgi/src/test/java/org/apache/http/osgi/impl/TestPropertiesUtils.java b/httpclient-osgi/src/test/java/org/apache/http/osgi/impl/TestPropertiesUtils.java new file mode 100644 index 000000000..7ae7b152b --- /dev/null +++ b/httpclient-osgi/src/test/java/org/apache/http/osgi/impl/TestPropertiesUtils.java @@ -0,0 +1,129 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ +package org.apache.http.osgi.impl; + +import static org.apache.http.osgi.impl.PropertiesUtils.to; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +/** + * @since 4.3 + */ +public final class TestPropertiesUtils { + + @Test + public void toBoolean() { + assertConverted(true, null, boolean.class, true); + assertConverted(true, null, Boolean.class, true); + assertConverted(false, "false", boolean.class, null); + assertConverted(false, "false", Boolean.class, null); + // whatever value is interpreted as `false` by Boolean.valueOf + assertConverted(false, "not a boolean", boolean.class, true); + assertConverted(false, "not a boolean", Boolean.class, true); + } + + @Test + public void toSingleString() { + assertConverted("fallback to default value", null, String.class, "fallback to default value"); + // use an object which represents the string + assertConverted("use the passed value", new StringBuilder("use the passed value"), String.class, null); + // use the "identity" converter + assertConverted("use the passed value", "use the passed value", String.class, null); + // convert another object + assertConverted("456789", 456789, String.class, null); + } + + @Test + public void toStringArray() { + assertConvertedArray(new String[]{"fallback to default value"}, + null, + String[].class, + new String[]{"fallback to default value"}); + // a string is converted to an array with 1 element + assertConvertedArray(new String[]{"a single string"}, + "a single string", + String[].class, + null); + // use an object which represents the string + assertConvertedArray(new String[]{"null objects", "will be ignored"}, + new Object[]{new StringBuilder("null objects"), null, new StringBuilder("will be ignored")}, + String[].class, + null); + // use the "identity" converter + assertConvertedArray(new String[]{"null objects", "will be ignored"}, + new Object[]{"null objects", null, "will be ignored"}, + String[].class, + null); + assertConvertedArray(new String[]{"fallback to default value"}, + 456789, + String[].class, + new String[]{"fallback to default value"}); + } + + @Test + public void toInt() { + assertConverted(123, null, int.class, 123); + assertConverted(123, null, Integer.class, 123); + assertConverted(456, "456", int.class, null); + assertConverted(456, "456", Integer.class, null); + assertConverted(789, "not an integer", int.class, 789); + assertConverted(789, "not an integer", Integer.class, 789); + } + + @Test + public void toLong() { + assertConverted(123l, null, long.class, 123l); + assertConverted(123l, null, Long.class, 123l); + assertConverted(456l, "456", long.class, null); + assertConverted(456l, "456", Long.class, null); + assertConverted(789l, "not a long", long.class, 789l); + assertConverted(789l, "not a long", Long.class, 789l); + } + + @Test + public void toDouble() { + assertConverted(123d, null, double.class, 123d); + assertConverted(123d, null, Double.class, 123d); + assertConverted(456d, "456", double.class, null); + assertConverted(456d, "456", Double.class, null); + assertConverted(789d, "not a double", double.class, 789d); + assertConverted(789d, "not a double", Double.class, 789d); + } + + private static void assertConverted(T expected, Object propValue, Class targetType, T defaultValue) { + T actual = to(propValue, targetType, defaultValue); + assertEquals(expected, actual); + } + + private static void assertConvertedArray(T[] expected, Object propValue, Class targetType, T[] defaultValue) { + T[] actual = to(propValue, targetType, defaultValue); + assertArrayEquals(expected, actual); + } + +}