package com.taiwanlife.agw.webservice.config;
import java.util.Iterator;
import java.util.concurrent.TimeUnit;
import javax.net.ssl.SSLContext;
import org.apache.hc.client5.http.ConnectionKeepAliveStrategy;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
import org.apache.hc.client5.http.socket.ConnectionSocketFactory;
import org.apache.hc.client5.http.socket.PlainConnectionSocketFactory;
import org.apache.hc.client5.http.ssl.NoopHostnameVerifier;
import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory;
import org.apache.hc.core5.http.HeaderElement;
import org.apache.hc.core5.http.HeaderElements;
import org.apache.hc.core5.http.HttpResponse;
import org.apache.hc.core5.http.config.Registry;
import org.apache.hc.core5.http.config.RegistryBuilder;
import org.apache.hc.core5.http.message.MessageSupport;
import org.apache.hc.core5.http.protocol.HttpContext;
import org.apache.hc.core5.ssl.SSLContextBuilder;
import org.apache.hc.core5.util.TimeValue;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.web.client.RestTemplate;
import com.taiwanlife.agw.webservice.AgwWebServiceLog;
/**
* - Supports both HTTP and HTTPS - Uses a connection pool to re-use connections
* and save overhead of creating connections. - Has a custom connection
* keep-alive strategy (to apply a default keep-alive if one isn't specified) -
* Starts an idle connection monitor to continuously clean up stale connections.
*/
@Configuration
@EnableScheduling
public class HttpClientConfig implements AgwWebServiceLog {
// private static final Logger LOGGER =
// LoggerFactory.getLogger(HttpClientConfig.class);
// Determines the timeout in milliseconds until a connection is established.
@Value("${api.connectionPool.connectTimeout}")
private int CONNECT_TIMEOUT;
// The timeout when requesting a connection from the connection manager.
@Value("${api.connectionPool.requestTimeout}")
private int REQUEST_TIMEOUT;
// The timeout for waiting for data
@Value("${api.connectionPool.socketTimeout}")
private int SOCKET_TIMEOUT;
@Value("${api.connectionPool.maxTotalConnections}")
private int MAX_TOTAL_CONNECTIONS;
@Value("${api.connectionPool.defaultKeepAliveTimeMillis}")
private int DEFAULT_KEEP_ALIVE_TIME_MILLIS;
@Value("${api.connectionPool.closeIdleConnectionWaitTimeSecs}")
private int CLOSE_IDLE_CONNECTION_WAIT_TIME_SECS;
// === 共用連線池 ===
@Bean
public PoolingHttpClientConnectionManager poolingConnectionManager() {
return new PoolingHttpClientConnectionManager();
}
@Bean
public ConnectionKeepAliveStrategy connectionKeepAliveStrategy() {
return new ConnectionKeepAliveStrategy() {
@Override
public TimeValue getKeepAliveDuration(HttpResponse response, HttpContext context) {
final Iterator<HeaderElement> it = MessageSupport.iterate(response, HeaderElements.KEEP_ALIVE);
if (it.hasNext()) {
final HeaderElement he = it.next();
final String param = he.getName();
final String value = he.getValue();
if (value != null && param.equalsIgnoreCase("timeout")) {
return TimeValue.ofSeconds(Long.parseLong(value));
}
}
return TimeValue.ofSeconds(DEFAULT_KEEP_ALIVE_TIME_MILLIS);
}
};
}
// === 一般的 HttpClient ===
@Bean
public CloseableHttpClient httpClient(PoolingHttpClientConnectionManager connectionManager) {
RequestConfig config = RequestConfig.custom().setConnectTimeout(CONNECT_TIMEOUT, TimeUnit.MILLISECONDS)
.setConnectionRequestTimeout(REQUEST_TIMEOUT, TimeUnit.MILLISECONDS)
.setResponseTimeout(SOCKET_TIMEOUT, TimeUnit.MILLISECONDS).build();
return HttpClients.custom().setConnectionManager(connectionManager).setDefaultRequestConfig(config)
.setKeepAliveStrategy(connectionKeepAliveStrategy()).evictIdleConnections(TimeValue.ofMinutes(5))
.build();
}
// === 一般的 RestTemplate ===
@Bean(name = "secureRestTemplate")
public RestTemplate secureRestTemplate(CloseableHttpClient httpClient) {
return new RestTemplate(new HttpComponentsClientHttpRequestFactory(httpClient));
}
// === 繞過 SSL 驗證的 HttpClient ===
@Bean(name = "insecureHttpClient")
public CloseableHttpClient insecureHttpClient() throws Exception {
SSLContext sslContext = SSLContextBuilder.create().loadTrustMaterial((chain, authType) -> true).build();
SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(sslContext,
NoopHostnameVerifier.INSTANCE);
Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
.register("https", socketFactory).register("http", new PlainConnectionSocketFactory()).build();
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(registry);
connectionManager.setMaxTotal(MAX_TOTAL_CONNECTIONS);
connectionManager.setDefaultMaxPerRoute(20);
RequestConfig config = RequestConfig.custom().setConnectTimeout(CONNECT_TIMEOUT, TimeUnit.MILLISECONDS)
.setConnectionRequestTimeout(REQUEST_TIMEOUT, TimeUnit.MILLISECONDS)
.setResponseTimeout(SOCKET_TIMEOUT, TimeUnit.MILLISECONDS).build();
return HttpClients.custom().setConnectionManager(connectionManager).setDefaultRequestConfig(config)
.setKeepAliveStrategy(connectionKeepAliveStrategy()).evictIdleConnections(TimeValue.ofMinutes(5))
.build();
}
// === 繞過 SSL 驗證的 RestTemplate ===
@Bean(name = "insecureRestTemplate")
public RestTemplate insecureRestTemplate(@Qualifier("insecureHttpClient") CloseableHttpClient httpClient) {
return new RestTemplate(new HttpComponentsClientHttpRequestFactory(httpClient));
}
// === 閒置連線清理排程 ===
@Bean
public Runnable idleConnectionMonitor(PoolingHttpClientConnectionManager connectionManager) {
return new Runnable() {
@Override
@Scheduled(fixedDelay = 10000)
public void run() {
try {
LOGGER.debug("IdleConnectionMonitor - Closing expired and idle connections...");
connectionManager.closeExpired();
connectionManager.closeIdle(TimeValue.ofSeconds(CLOSE_IDLE_CONNECTION_WAIT_TIME_SECS));
} catch (Exception e) {
LOGGER.error("IdleConnectionMonitor - Exception occurred: {}", e.getMessage(), e);
}
}
};
}
}
cGFja2FnZSBjb20udGFpd2FubGlmZS5hZ3cud2Vic2VydmljZS5jb25maWc7CgppbXBvcnQgamF2YS51dGlsLkl0ZXJhdG9yOwppbXBvcnQgamF2YS51dGlsLmNvbmN1cnJlbnQuVGltZVVuaXQ7CgppbXBvcnQgamF2YXgubmV0LnNzbC5TU0xDb250ZXh0OwoKaW1wb3J0IG9yZy5hcGFjaGUuaGMuY2xpZW50NS5odHRwLkNvbm5lY3Rpb25LZWVwQWxpdmVTdHJhdGVneTsKaW1wb3J0IG9yZy5hcGFjaGUuaGMuY2xpZW50NS5odHRwLmNvbmZpZy5SZXF1ZXN0Q29uZmlnOwppbXBvcnQgb3JnLmFwYWNoZS5oYy5jbGllbnQ1Lmh0dHAuaW1wbC5jbGFzc2ljLkNsb3NlYWJsZUh0dHBDbGllbnQ7CmltcG9ydCBvcmcuYXBhY2hlLmhjLmNsaWVudDUuaHR0cC5pbXBsLmNsYXNzaWMuSHR0cENsaWVudHM7CmltcG9ydCBvcmcuYXBhY2hlLmhjLmNsaWVudDUuaHR0cC5pbXBsLmlvLlBvb2xpbmdIdHRwQ2xpZW50Q29ubmVjdGlvbk1hbmFnZXI7CmltcG9ydCBvcmcuYXBhY2hlLmhjLmNsaWVudDUuaHR0cC5zb2NrZXQuQ29ubmVjdGlvblNvY2tldEZhY3Rvcnk7CmltcG9ydCBvcmcuYXBhY2hlLmhjLmNsaWVudDUuaHR0cC5zb2NrZXQuUGxhaW5Db25uZWN0aW9uU29ja2V0RmFjdG9yeTsKaW1wb3J0IG9yZy5hcGFjaGUuaGMuY2xpZW50NS5odHRwLnNzbC5Ob29wSG9zdG5hbWVWZXJpZmllcjsKaW1wb3J0IG9yZy5hcGFjaGUuaGMuY2xpZW50NS5odHRwLnNzbC5TU0xDb25uZWN0aW9uU29ja2V0RmFjdG9yeTsKaW1wb3J0IG9yZy5hcGFjaGUuaGMuY29yZTUuaHR0cC5IZWFkZXJFbGVtZW50OwppbXBvcnQgb3JnLmFwYWNoZS5oYy5jb3JlNS5odHRwLkhlYWRlckVsZW1lbnRzOwppbXBvcnQgb3JnLmFwYWNoZS5oYy5jb3JlNS5odHRwLkh0dHBSZXNwb25zZTsKaW1wb3J0IG9yZy5hcGFjaGUuaGMuY29yZTUuaHR0cC5jb25maWcuUmVnaXN0cnk7CmltcG9ydCBvcmcuYXBhY2hlLmhjLmNvcmU1Lmh0dHAuY29uZmlnLlJlZ2lzdHJ5QnVpbGRlcjsKaW1wb3J0IG9yZy5hcGFjaGUuaGMuY29yZTUuaHR0cC5tZXNzYWdlLk1lc3NhZ2VTdXBwb3J0OwppbXBvcnQgb3JnLmFwYWNoZS5oYy5jb3JlNS5odHRwLnByb3RvY29sLkh0dHBDb250ZXh0OwppbXBvcnQgb3JnLmFwYWNoZS5oYy5jb3JlNS5zc2wuU1NMQ29udGV4dEJ1aWxkZXI7CmltcG9ydCBvcmcuYXBhY2hlLmhjLmNvcmU1LnV0aWwuVGltZVZhbHVlOwppbXBvcnQgb3JnLnNwcmluZ2ZyYW1ld29yay5iZWFucy5mYWN0b3J5LmFubm90YXRpb24uUXVhbGlmaWVyOwppbXBvcnQgb3JnLnNwcmluZ2ZyYW1ld29yay5iZWFucy5mYWN0b3J5LmFubm90YXRpb24uVmFsdWU7CmltcG9ydCBvcmcuc3ByaW5nZnJhbWV3b3JrLmNvbnRleHQuYW5ub3RhdGlvbi5CZWFuOwppbXBvcnQgb3JnLnNwcmluZ2ZyYW1ld29yay5jb250ZXh0LmFubm90YXRpb24uQ29uZmlndXJhdGlvbjsKaW1wb3J0IG9yZy5zcHJpbmdmcmFtZXdvcmsuaHR0cC5jbGllbnQuSHR0cENvbXBvbmVudHNDbGllbnRIdHRwUmVxdWVzdEZhY3Rvcnk7CmltcG9ydCBvcmcuc3ByaW5nZnJhbWV3b3JrLnNjaGVkdWxpbmcuYW5ub3RhdGlvbi5FbmFibGVTY2hlZHVsaW5nOwppbXBvcnQgb3JnLnNwcmluZ2ZyYW1ld29yay5zY2hlZHVsaW5nLmFubm90YXRpb24uU2NoZWR1bGVkOwppbXBvcnQgb3JnLnNwcmluZ2ZyYW1ld29yay53ZWIuY2xpZW50LlJlc3RUZW1wbGF0ZTsKCmltcG9ydCBjb20udGFpd2FubGlmZS5hZ3cud2Vic2VydmljZS5BZ3dXZWJTZXJ2aWNlTG9nOwoKLyoqCiAqIC0gU3VwcG9ydHMgYm90aCBIVFRQIGFuZCBIVFRQUyAtIFVzZXMgYSBjb25uZWN0aW9uIHBvb2wgdG8gcmUtdXNlIGNvbm5lY3Rpb25zCiAqIGFuZCBzYXZlIG92ZXJoZWFkIG9mIGNyZWF0aW5nIGNvbm5lY3Rpb25zLiAtIEhhcyBhIGN1c3RvbSBjb25uZWN0aW9uCiAqIGtlZXAtYWxpdmUgc3RyYXRlZ3kgKHRvIGFwcGx5IGEgZGVmYXVsdCBrZWVwLWFsaXZlIGlmIG9uZSBpc24ndCBzcGVjaWZpZWQpIC0KICogU3RhcnRzIGFuIGlkbGUgY29ubmVjdGlvbiBtb25pdG9yIHRvIGNvbnRpbnVvdXNseSBjbGVhbiB1cCBzdGFsZSBjb25uZWN0aW9ucy4KICovCkBDb25maWd1cmF0aW9uCkBFbmFibGVTY2hlZHVsaW5nCnB1YmxpYyBjbGFzcyBIdHRwQ2xpZW50Q29uZmlnIGltcGxlbWVudHMgQWd3V2ViU2VydmljZUxvZyB7CgkvLyBwcml2YXRlIHN0YXRpYyBmaW5hbCBMb2dnZXIgTE9HR0VSID0KCS8vIExvZ2dlckZhY3RvcnkuZ2V0TG9nZ2VyKEh0dHBDbGllbnRDb25maWcuY2xhc3MpOwoJLy8gRGV0ZXJtaW5lcyB0aGUgdGltZW91dCBpbiBtaWxsaXNlY29uZHMgdW50aWwgYSBjb25uZWN0aW9uIGlzIGVzdGFibGlzaGVkLgoJQFZhbHVlKCIke2FwaS5jb25uZWN0aW9uUG9vbC5jb25uZWN0VGltZW91dH0iKQoJcHJpdmF0ZSBpbnQgQ09OTkVDVF9USU1FT1VUOwoJLy8gVGhlIHRpbWVvdXQgd2hlbiByZXF1ZXN0aW5nIGEgY29ubmVjdGlvbiBmcm9tIHRoZSBjb25uZWN0aW9uIG1hbmFnZXIuCglAVmFsdWUoIiR7YXBpLmNvbm5lY3Rpb25Qb29sLnJlcXVlc3RUaW1lb3V0fSIpCglwcml2YXRlIGludCBSRVFVRVNUX1RJTUVPVVQ7CgkvLyBUaGUgdGltZW91dCBmb3Igd2FpdGluZyBmb3IgZGF0YQoJQFZhbHVlKCIke2FwaS5jb25uZWN0aW9uUG9vbC5zb2NrZXRUaW1lb3V0fSIpCglwcml2YXRlIGludCBTT0NLRVRfVElNRU9VVDsKCUBWYWx1ZSgiJHthcGkuY29ubmVjdGlvblBvb2wubWF4VG90YWxDb25uZWN0aW9uc30iKQoJcHJpdmF0ZSBpbnQgTUFYX1RPVEFMX0NPTk5FQ1RJT05TOwoJQFZhbHVlKCIke2FwaS5jb25uZWN0aW9uUG9vbC5kZWZhdWx0S2VlcEFsaXZlVGltZU1pbGxpc30iKQoJcHJpdmF0ZSBpbnQgREVGQVVMVF9LRUVQX0FMSVZFX1RJTUVfTUlMTElTOwoJQFZhbHVlKCIke2FwaS5jb25uZWN0aW9uUG9vbC5jbG9zZUlkbGVDb25uZWN0aW9uV2FpdFRpbWVTZWNzfSIpCglwcml2YXRlIGludCBDTE9TRV9JRExFX0NPTk5FQ1RJT05fV0FJVF9USU1FX1NFQ1M7CgoJLy8gPT09IOWFseeUqOmAo+e3muaxoCA9PT0KCUBCZWFuCglwdWJsaWMgUG9vbGluZ0h0dHBDbGllbnRDb25uZWN0aW9uTWFuYWdlciBwb29saW5nQ29ubmVjdGlvbk1hbmFnZXIoKSB7CgkJcmV0dXJuIG5ldyBQb29saW5nSHR0cENsaWVudENvbm5lY3Rpb25NYW5hZ2VyKCk7Cgl9CgoJQEJlYW4KCXB1YmxpYyBDb25uZWN0aW9uS2VlcEFsaXZlU3RyYXRlZ3kgY29ubmVjdGlvbktlZXBBbGl2ZVN0cmF0ZWd5KCkgewoJCXJldHVybiBuZXcgQ29ubmVjdGlvbktlZXBBbGl2ZVN0cmF0ZWd5KCkgewoJCQlAT3ZlcnJpZGUKCQkJcHVibGljIFRpbWVWYWx1ZSBnZXRLZWVwQWxpdmVEdXJhdGlvbihIdHRwUmVzcG9uc2UgcmVzcG9uc2UsIEh0dHBDb250ZXh0IGNvbnRleHQpIHsKCQkJCWZpbmFsIEl0ZXJhdG9yPEhlYWRlckVsZW1lbnQ+IGl0ID0gTWVzc2FnZVN1cHBvcnQuaXRlcmF0ZShyZXNwb25zZSwgSGVhZGVyRWxlbWVudHMuS0VFUF9BTElWRSk7CgkJCQlpZiAoaXQuaGFzTmV4dCgpKSB7CgkJCQkJZmluYWwgSGVhZGVyRWxlbWVudCBoZSA9IGl0Lm5leHQoKTsKCQkJCQlmaW5hbCBTdHJpbmcgcGFyYW0gPSBoZS5nZXROYW1lKCk7CgkJCQkJZmluYWwgU3RyaW5nIHZhbHVlID0gaGUuZ2V0VmFsdWUoKTsKCQkJCQlpZiAodmFsdWUgIT0gbnVsbCAmJiBwYXJhbS5lcXVhbHNJZ25vcmVDYXNlKCJ0aW1lb3V0IikpIHsKCQkJCQkJcmV0dXJuIFRpbWVWYWx1ZS5vZlNlY29uZHMoTG9uZy5wYXJzZUxvbmcodmFsdWUpKTsKCQkJCQl9CgkJCQl9CgkJCQlyZXR1cm4gVGltZVZhbHVlLm9mU2Vjb25kcyhERUZBVUxUX0tFRVBfQUxJVkVfVElNRV9NSUxMSVMpOwoJCQl9CgkJfTsKCX0KCgkvLyA9PT0g5LiA6Iis55qEIEh0dHBDbGllbnQgPT09CglAQmVhbgoJcHVibGljIENsb3NlYWJsZUh0dHBDbGllbnQgaHR0cENsaWVudChQb29saW5nSHR0cENsaWVudENvbm5lY3Rpb25NYW5hZ2VyIGNvbm5lY3Rpb25NYW5hZ2VyKSB7CgkJUmVxdWVzdENvbmZpZyBjb25maWcgPSBSZXF1ZXN0Q29uZmlnLmN1c3RvbSgpLnNldENvbm5lY3RUaW1lb3V0KENPTk5FQ1RfVElNRU9VVCwgVGltZVVuaXQuTUlMTElTRUNPTkRTKQoJCQkJLnNldENvbm5lY3Rpb25SZXF1ZXN0VGltZW91dChSRVFVRVNUX1RJTUVPVVQsIFRpbWVVbml0Lk1JTExJU0VDT05EUykKCQkJCS5zZXRSZXNwb25zZVRpbWVvdXQoU09DS0VUX1RJTUVPVVQsIFRpbWVVbml0Lk1JTExJU0VDT05EUykuYnVpbGQoKTsKCgkJcmV0dXJuIEh0dHBDbGllbnRzLmN1c3RvbSgpLnNldENvbm5lY3Rpb25NYW5hZ2VyKGNvbm5lY3Rpb25NYW5hZ2VyKS5zZXREZWZhdWx0UmVxdWVzdENvbmZpZyhjb25maWcpCgkJCQkuc2V0S2VlcEFsaXZlU3RyYXRlZ3koY29ubmVjdGlvbktlZXBBbGl2ZVN0cmF0ZWd5KCkpLmV2aWN0SWRsZUNvbm5lY3Rpb25zKFRpbWVWYWx1ZS5vZk1pbnV0ZXMoNSkpCgkJCQkuYnVpbGQoKTsKCX0KCgkvLyA9PT0g5LiA6Iis55qEIFJlc3RUZW1wbGF0ZSA9PT0KCUBCZWFuKG5hbWUgPSAic2VjdXJlUmVzdFRlbXBsYXRlIikKCXB1YmxpYyBSZXN0VGVtcGxhdGUgc2VjdXJlUmVzdFRlbXBsYXRlKENsb3NlYWJsZUh0dHBDbGllbnQgaHR0cENsaWVudCkgewoJCXJldHVybiBuZXcgUmVzdFRlbXBsYXRlKG5ldyBIdHRwQ29tcG9uZW50c0NsaWVudEh0dHBSZXF1ZXN0RmFjdG9yeShodHRwQ2xpZW50KSk7Cgl9CgoJLy8gPT09IOe5numBjiBTU0wg6amX6K2J55qEIEh0dHBDbGllbnQgPT09CglAQmVhbihuYW1lID0gImluc2VjdXJlSHR0cENsaWVudCIpCglwdWJsaWMgQ2xvc2VhYmxlSHR0cENsaWVudCBpbnNlY3VyZUh0dHBDbGllbnQoKSB0aHJvd3MgRXhjZXB0aW9uIHsKCQlTU0xDb250ZXh0IHNzbENvbnRleHQgPSBTU0xDb250ZXh0QnVpbGRlci5jcmVhdGUoKS5sb2FkVHJ1c3RNYXRlcmlhbCgoY2hhaW4sIGF1dGhUeXBlKSAtPiB0cnVlKS5idWlsZCgpOwoKCQlTU0xDb25uZWN0aW9uU29ja2V0RmFjdG9yeSBzb2NrZXRGYWN0b3J5ID0gbmV3IFNTTENvbm5lY3Rpb25Tb2NrZXRGYWN0b3J5KHNzbENvbnRleHQsCgkJCQlOb29wSG9zdG5hbWVWZXJpZmllci5JTlNUQU5DRSk7CgoJCVJlZ2lzdHJ5PENvbm5lY3Rpb25Tb2NrZXRGYWN0b3J5PiByZWdpc3RyeSA9IFJlZ2lzdHJ5QnVpbGRlci48Q29ubmVjdGlvblNvY2tldEZhY3Rvcnk+Y3JlYXRlKCkKCQkJCS5yZWdpc3RlcigiaHR0cHMiLCBzb2NrZXRGYWN0b3J5KS5yZWdpc3RlcigiaHR0cCIsIG5ldyBQbGFpbkNvbm5lY3Rpb25Tb2NrZXRGYWN0b3J5KCkpLmJ1aWxkKCk7CgoJCVBvb2xpbmdIdHRwQ2xpZW50Q29ubmVjdGlvbk1hbmFnZXIgY29ubmVjdGlvbk1hbmFnZXIgPSBuZXcgUG9vbGluZ0h0dHBDbGllbnRDb25uZWN0aW9uTWFuYWdlcihyZWdpc3RyeSk7CgkJY29ubmVjdGlvbk1hbmFnZXIuc2V0TWF4VG90YWwoTUFYX1RPVEFMX0NPTk5FQ1RJT05TKTsKCQljb25uZWN0aW9uTWFuYWdlci5zZXREZWZhdWx0TWF4UGVyUm91dGUoMjApOwoKCQlSZXF1ZXN0Q29uZmlnIGNvbmZpZyA9IFJlcXVlc3RDb25maWcuY3VzdG9tKCkuc2V0Q29ubmVjdFRpbWVvdXQoQ09OTkVDVF9USU1FT1VULCBUaW1lVW5pdC5NSUxMSVNFQ09ORFMpCgkJCQkuc2V0Q29ubmVjdGlvblJlcXVlc3RUaW1lb3V0KFJFUVVFU1RfVElNRU9VVCwgVGltZVVuaXQuTUlMTElTRUNPTkRTKQoJCQkJLnNldFJlc3BvbnNlVGltZW91dChTT0NLRVRfVElNRU9VVCwgVGltZVVuaXQuTUlMTElTRUNPTkRTKS5idWlsZCgpOwoKCQlyZXR1cm4gSHR0cENsaWVudHMuY3VzdG9tKCkuc2V0Q29ubmVjdGlvbk1hbmFnZXIoY29ubmVjdGlvbk1hbmFnZXIpLnNldERlZmF1bHRSZXF1ZXN0Q29uZmlnKGNvbmZpZykKCQkJCS5zZXRLZWVwQWxpdmVTdHJhdGVneShjb25uZWN0aW9uS2VlcEFsaXZlU3RyYXRlZ3koKSkuZXZpY3RJZGxlQ29ubmVjdGlvbnMoVGltZVZhbHVlLm9mTWludXRlcyg1KSkKCQkJCS5idWlsZCgpOwoJfQoKCS8vID09PSDnuZ7pgY4gU1NMIOmpl+itieeahCBSZXN0VGVtcGxhdGUgPT09CglAQmVhbihuYW1lID0gImluc2VjdXJlUmVzdFRlbXBsYXRlIikKCXB1YmxpYyBSZXN0VGVtcGxhdGUgaW5zZWN1cmVSZXN0VGVtcGxhdGUoQFF1YWxpZmllcigiaW5zZWN1cmVIdHRwQ2xpZW50IikgQ2xvc2VhYmxlSHR0cENsaWVudCBodHRwQ2xpZW50KSB7CgkJcmV0dXJuIG5ldyBSZXN0VGVtcGxhdGUobmV3IEh0dHBDb21wb25lbnRzQ2xpZW50SHR0cFJlcXVlc3RGYWN0b3J5KGh0dHBDbGllbnQpKTsKCX0KCgkvLyA9PT0g6ZaS572u6YCj57ea5riF55CG5o6S56iLID09PQoJQEJlYW4KCXB1YmxpYyBSdW5uYWJsZSBpZGxlQ29ubmVjdGlvbk1vbml0b3IoUG9vbGluZ0h0dHBDbGllbnRDb25uZWN0aW9uTWFuYWdlciBjb25uZWN0aW9uTWFuYWdlcikgewoJCXJldHVybiBuZXcgUnVubmFibGUoKSB7CgkJCUBPdmVycmlkZQoJCQlAU2NoZWR1bGVkKGZpeGVkRGVsYXkgPSAxMDAwMCkKCQkJcHVibGljIHZvaWQgcnVuKCkgewoJCQkJdHJ5IHsKCQkJCQlMT0dHRVIuZGVidWcoIklkbGVDb25uZWN0aW9uTW9uaXRvciAtIENsb3NpbmcgZXhwaXJlZCBhbmQgaWRsZSBjb25uZWN0aW9ucy4uLiIpOwoJCQkJCWNvbm5lY3Rpb25NYW5hZ2VyLmNsb3NlRXhwaXJlZCgpOwoJCQkJCWNvbm5lY3Rpb25NYW5hZ2VyLmNsb3NlSWRsZShUaW1lVmFsdWUub2ZTZWNvbmRzKENMT1NFX0lETEVfQ09OTkVDVElPTl9XQUlUX1RJTUVfU0VDUykpOwoJCQkJfSBjYXRjaCAoRXhjZXB0aW9uIGUpIHsKCQkJCQlMT0dHRVIuZXJyb3IoIklkbGVDb25uZWN0aW9uTW9uaXRvciAtIEV4Y2VwdGlvbiBvY2N1cnJlZDoge30iLCBlLmdldE1lc3NhZ2UoKSwgZSk7CgkJCQl9CgkJCX0KCQl9OwoJfQoKfQo=