1   /*
2    * Copyright 2017 LINE Corporation
3    *
4    * LINE Corporation licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   https://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  package com.linecorp.centraldogma.client.spring;
17  
18  import java.net.UnknownHostException;
19  import java.util.List;
20  import java.util.Optional;
21  import java.util.concurrent.ExecutionException;
22  import java.util.concurrent.TimeUnit;
23  import java.util.concurrent.TimeoutException;
24  
25  import org.slf4j.Logger;
26  import org.slf4j.LoggerFactory;
27  import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
28  import org.springframework.boot.context.properties.EnableConfigurationProperties;
29  import org.springframework.context.annotation.Bean;
30  import org.springframework.context.annotation.Configuration;
31  import org.springframework.core.env.Environment;
32  
33  import com.google.common.net.HostAndPort;
34  
35  import com.linecorp.armeria.client.ClientFactory;
36  import com.linecorp.armeria.client.ClientFactoryBuilder;
37  import com.linecorp.centraldogma.client.CentralDogma;
38  import com.linecorp.centraldogma.client.armeria.ArmeriaCentralDogmaBuilder;
39  import com.linecorp.centraldogma.client.armeria.ArmeriaClientConfigurator;
40  import com.linecorp.centraldogma.client.armeria.DnsAddressEndpointGroupConfigurator;
41  
42  /**
43   * Spring bean configuration for {@link CentralDogma} client.
44   */
45  @Configuration
46  @ConditionalOnMissingBean(CentralDogma.class)
47  @EnableConfigurationProperties(CentralDogmaSettings.class)
48  public class CentralDogmaClientAutoConfiguration {
49  
50      private static final Logger logger = LoggerFactory.getLogger(CentralDogmaClientAutoConfiguration.class);
51  
52      static final long DEFAULT_INITIALIZATION_TIMEOUT_MILLIS = 15000;
53  
54      /**
55       * Returns a newly created {@link CentralDogma} client.
56       */
57      @Bean
58      public CentralDogma dogmaClient(
59              Environment env,
60              CentralDogmaSettings settings,
61              Optional<List<CentralDogmaClientFactoryConfigurator>> factoryConfigurators,
62              Optional<ArmeriaClientConfigurator> armeriaClientConfigurator,
63              Optional<DnsAddressEndpointGroupConfigurator> dnsAddressEndpointGroupConfigurator)
64              throws UnknownHostException {
65  
66          final ArmeriaCentralDogmaBuilder builder = new ArmeriaCentralDogmaBuilder();
67  
68          if (factoryConfigurators.isPresent()) {
69              final ClientFactoryBuilder clientFactoryBuilder = ClientFactory.builder();
70              factoryConfigurators.get().forEach(configurator -> configurator.configure(clientFactoryBuilder));
71              builder.clientFactory(clientFactoryBuilder.build());
72          }
73  
74          builder.clientConfigurator(cb -> armeriaClientConfigurator.ifPresent(
75                  configurator -> configurator.configure(cb)));
76          dnsAddressEndpointGroupConfigurator.ifPresent(builder::dnsAddressEndpointGroupConfigurator);
77  
78          // Set health check interval.
79          final Long healthCheckIntervalMillis = settings.getHealthCheckIntervalMillis();
80          if (healthCheckIntervalMillis != null) {
81              builder.healthCheckIntervalMillis(healthCheckIntervalMillis);
82          }
83  
84          // Enable or disable TLS.
85          final Boolean useTls = settings.getUseTls();
86          if (useTls != null) {
87              builder.useTls(useTls);
88          }
89  
90          // Set access token.
91          final String accessToken = settings.getAccessToken();
92          if (accessToken != null) {
93              builder.accessToken(accessToken);
94          }
95  
96          // Set profile or hosts.
97          final String profile = settings.getProfile();
98          final List<String> hosts = settings.getHosts();
99          if (profile != null) {
100             if (hosts != null) {
101                 throw new IllegalStateException(
102                         "'hosts' and 'profile' are mutually exclusive. Do not set both of them.");
103             }
104             builder.profile(CentralDogmaClientAutoConfiguration.class.getClassLoader(), profile);
105         } else if (hosts != null) {
106             for (String h : hosts) {
107                 final HostAndPort hostAndPort = HostAndPort.fromString(h);
108                 if (hostAndPort.hasPort()) {
109                     builder.host(hostAndPort.getHost(), hostAndPort.getPort());
110                 } else {
111                     builder.host(hostAndPort.getHost());
112                 }
113             }
114         } else {
115             // Use the currently active Spring Boot profiles if neither profile nor hosts was specified.
116             final String[] springBootProfiles = env.getActiveProfiles();
117             logger.info("Using the Spring Boot profiles as the source of the Central Dogma client profile: {}",
118                         springBootProfiles);
119             builder.profile(springBootProfiles);
120         }
121 
122         // Replication lag tolerance settings.
123         final Integer maxNumRetriesOnReplicationLag = settings.getMaxNumRetriesOnReplicationLag();
124         if (maxNumRetriesOnReplicationLag != null) {
125             builder.maxNumRetriesOnReplicationLag(maxNumRetriesOnReplicationLag);
126         }
127 
128         final Long retryIntervalOnReplicationLagMillis = settings.getRetryIntervalOnReplicationLagMillis();
129         if (retryIntervalOnReplicationLagMillis != null) {
130             builder.retryIntervalOnReplicationLagMillis(retryIntervalOnReplicationLagMillis);
131         }
132 
133         final CentralDogma centralDogma = builder.build();
134         Long initializationTimeoutMillis = settings.initializationTimeoutMillis();
135         if (initializationTimeoutMillis == null) {
136             initializationTimeoutMillis = DEFAULT_INITIALIZATION_TIMEOUT_MILLIS;
137         }
138         if (initializationTimeoutMillis > 0) {
139             try {
140                 centralDogma.whenEndpointReady().get(initializationTimeoutMillis, TimeUnit.MILLISECONDS);
141             } catch (InterruptedException | ExecutionException | TimeoutException e) {
142                 throw new IllegalStateException(
143                         "Failed to initialize the endpoints of " + centralDogma + " in " +
144                         initializationTimeoutMillis + " milliseconds", e);
145             }
146         }
147         return centralDogma;
148     }
149 }