/*******************************************************************************
* 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.
*
*******************************************************************************/
package org.apache.wink.client;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.net.SocketTimeoutException;
import java.util.HashSet;
import java.util.Properties;
import java.util.Set;
import javax.ws.rs.Consumes;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Application;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.MessageBodyReader;
import javax.ws.rs.ext.MessageBodyWriter;
import javax.ws.rs.ext.Provider;
import org.apache.wink.common.internal.providers.entity.StringProvider;
import org.apache.wink.common.utils.ProviderUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ConfigurationTest extends BaseTest {
public void testConfiguration() {
ClientConfig conf = new ClientConfig();
conf.proxyHost("localhost").proxyPort(8080);
conf.connectTimeout(6000);
conf.followRedirects(true);
assertEquals(conf.getProxyHost(), "localhost");
assertEquals(conf.getProxyPort(), 8080);
assertEquals(conf.getConnectTimeout(), 6000);
assertEquals(conf.isFollowRedirects(), true);
assertEquals(conf.isSupportDTDExpansion(), false);
RestClient rc = new RestClient(conf);
ClientConfig config = rc.getConfig();
// test configuration locking
try {
config.proxyHost("localhost");
fail("Configuration is locked - IllegalStateException must be thrown");
} catch (ClientConfigException e) {
// Success - Configuration is locked
}
}
// exercise the API
public void testAPI() {
String connectTimeoutKey = "wink.client.connectTimeout";
String readTimeoutKey = "wink.client.readTimeout";
String dtdKey = "wink.supportDTDEntityExpansion";
ClientConfig conf1 = new ClientConfig();
System.setProperty(connectTimeoutKey, "50");
System.setProperty(readTimeoutKey, "60");
System.setProperty(dtdKey, "tRUe");
Properties props = conf1.getProperties();
assertEquals("50", props.getProperty(connectTimeoutKey));
assertEquals("60", props.getProperty(readTimeoutKey));
assertEquals("true", props.getProperty(dtdKey));
// changing the system property should not change the props in the conf instance
System.setProperty(connectTimeoutKey, "100");
props = conf1.getProperties();
assertEquals("50", props.getProperty(connectTimeoutKey));
// setting through the api should set the property value:
conf1.supportDTDExpansion(false);
assertEquals("false", props.getProperty(dtdKey));
// setting new properties should change the value
Properties changedProps = new Properties();
changedProps.put(dtdKey, "true");
conf1.setProperties(changedProps);
assertTrue(conf1.isSupportDTDExpansion());
// props are unique per conf instance, however
ClientConfig conf2 = new ClientConfig();
assertEquals("100", conf2.getProperties().getProperty(connectTimeoutKey));
// props are not permitted to be null
conf2.setProperties(null);
assertNotNull(conf2.getProperties());
// should be cleared, however
assertNull(conf2.getProperties().getProperty(connectTimeoutKey));
// bad data should be tolerated (meaning, won't crash the system)
conf2.supportDTDExpansion(true);
changedProps.put(dtdKey, "some bad data");
changedProps.put(connectTimeoutKey, "this is not an int");
changedProps.put(readTimeoutKey, "this is also not an int");
conf2.setProperties(changedProps);
assertFalse(conf2.isSupportDTDExpansion()); // any string not "true" is false
assertEquals(60000, conf2.getReadTimeout()); // reverts to default
assertEquals(60000, conf2.getConnectTimeout()); // reverts to default
}
public void testConnectionThroughProxy() {
// the server will still listen on the default server port.
// if we specify the server port as the proxy port, we in essence test
// that the connection is going through the proxy, because we
// specify a different port for the server in the resource URL
server.getMockHttpServerResponses().get(0).setMockResponseCode(200);
ClientConfig config = new ClientConfig();
config.proxyHost("localhost").proxyPort(serverPort);
RestClient client = new RestClient(config);
String resourceUrl = "http://googoo:" + (serverPort + 1) + "/some/service";
Resource resource = client.resource(resourceUrl);
resource.get(String.class);
assertEquals(resourceUrl, server.getRequestUrl());
}
public void testConnectTimeout() {
int connectTimeout = 2000;
ClientConfig config = new ClientConfig();
// set the connect timeout
config.connectTimeout(connectTimeout);
RestClient client = new RestClient(config);
// shouldn't be able to connect
Resource resource = client.resource("http://localhost:1111/koko");
long before = System.currentTimeMillis();
try {
// the client should "connect timeout"
resource.get(String.class);
fail("Expected Exception to be thrown");
} catch (Exception e) {
// assert that no more than 2 (+tolerance) seconds have passed
long after = System.currentTimeMillis();
// set a tolerance of 1 second
long tolerance = 1000;
long expectedTimeout = connectTimeout + tolerance;
long duration = after - before;
assertTrue("Expected a duration of less than " + expectedTimeout
+ " ms, but was "
+ duration, duration <= expectedTimeout);
}
}
public void testReadTimeout() {
server.getMockHttpServerResponses().get(0).setMockResponseCode(200);
// set the server to delay the response by 5 seconds.
server.setDelayResponse(5000);
ClientConfig config = new ClientConfig();
// set the read timeout to be 2 seconds
config.readTimeout(2000);
RestClient client = new RestClient(config);
Resource resource = client.resource(serviceURL);
long before = System.currentTimeMillis();
try {
// the client should "read timeout" after 2 seconds
resource.get(String.class);
fail("Expected Exception to be thrown");
} catch (Exception e) {
// assert that about 2 seconds have passed
long after = System.currentTimeMillis();
long duration = after - before;
assertTrue("Expected a duration of about 2000 ms, but was " + duration,
duration >= 1500 && duration <= 5000);
// get to the SocketTimeoutException
Throwable throwable = e;
while (throwable != null && !(throwable instanceof SocketTimeoutException)) {
throwable = throwable.getCause();
}
assertNotNull(throwable);
assertEquals("Read timed out", throwable.getMessage());
}
}
public void testApplication() {
server.getMockHttpServerResponses().get(0).setMockResponseCode(200);
server.setDelayResponse(0);
ClientConfig conf = new ClientConfig();
// Create new JAX-RS Application
Application app = new Application() {
@Override
public Set<Class<?>> getClasses() {
HashSet<Class<?>> set = new HashSet<Class<?>>();
set.add(FooProvider.class);
return set;
}
};
conf.applications(app);
RestClient client = new RestClient(conf);
Resource resource = client.resource(serviceURL + "/testResourcePut");
Foo response =
resource.contentType("text/plain").accept("text/plain").post(Foo.class,
new Foo(SENT_MESSAGE));
assertEquals(RECEIVED_MESSAGE, response.foo);
// Negative test - Foo Provider not registered
try {
client = new RestClient();
resource = client.resource(serviceURL + "/testResourcePut");
response =
resource.contentType("text/plain").accept("text/plain").post(Foo.class,
new Foo(SENT_MESSAGE));
fail("ClientRuntimeException must be thrown");
} catch (ClientRuntimeException e) {
// Success
}
}
@Provider
@Consumes
@Produces
public static class FooProvider implements MessageBodyReader<Foo>, MessageBodyWriter<Foo> {
private static final Logger logger = LoggerFactory.getLogger(StringProvider.class);
public boolean isReadable(Class<?> type,
Type genericType,
Annotation[] annotations,
MediaType mediaType) {
return type == Foo.class;
}
public Foo readFrom(Class<Foo> type,
Type genericType,
Annotation[] annotations,
MediaType mediaType,
MultivaluedMap<String, String> httpHeaders,
InputStream entityStream) throws IOException, WebApplicationException {
byte[] bytes = ProviderUtils.readFromStreamAsBytes(entityStream);
String string = new String(bytes, ProviderUtils.getCharset(mediaType));
return new Foo(string);
}
public long getSize(Foo t,
Class<?> type,
Type genericType,
Annotation[] annotations,
MediaType mediaType) {
return t.foo.length();
}
public boolean isWriteable(Class<?> type,
Type genericType,
Annotation[] annotations,
MediaType mediaType) {
return type == Foo.class;
}
public void writeTo(Foo t,
Class<?> type,
Type genericType,
Annotation[] annotations,
MediaType mediaType,
MultivaluedMap<String, Object> httpHeaders,
OutputStream entityStream) throws IOException, WebApplicationException {
logger.trace("Writing {} to stream using {}", t, getClass().getName());
entityStream.write(t.foo.getBytes(ProviderUtils.getCharset(mediaType)));
}
}
public static class Foo {
public String foo;
public Foo(String foo) {
this.foo = foo;
}
}
}