1
0
mirror of https://gitee.com/mirrors/Spring-Cloud-Alibaba.git synced 2021-06-26 13:25:11 +08:00

Merge pull request #77 from xiaolongzuo/master

Extract acm configuration to bootstrap phase and optimize error message.
This commit is contained in:
xiaojing 2018-10-30 16:21:29 +08:00 committed by GitHub
commit caf9104009
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 127 additions and 125 deletions

View File

@ -16,13 +16,12 @@
package org.springframework.cloud.alicloud.acm; package org.springframework.cloud.alicloud.acm;
import com.taobao.diamond.client.Diamond;
import org.springframework.beans.BeansException; import org.springframework.beans.BeansException;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.alicloud.acm.endpoint.AcmHealthIndicator; import org.springframework.cloud.alicloud.acm.endpoint.AcmHealthIndicator;
import org.springframework.cloud.alicloud.acm.refresh.AcmContextRefresher; import org.springframework.cloud.alicloud.acm.refresh.AcmContextRefresher;
import org.springframework.cloud.alicloud.acm.refresh.AcmRefreshHistory; import org.springframework.cloud.alicloud.acm.refresh.AcmRefreshHistory;
import org.springframework.cloud.alicloud.context.acm.AcmIntegrationProperties;
import org.springframework.cloud.alicloud.context.acm.AcmProperties; import org.springframework.cloud.alicloud.context.acm.AcmProperties;
import org.springframework.cloud.context.refresh.ContextRefresher; import org.springframework.cloud.context.refresh.ContextRefresher;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
@ -30,44 +29,47 @@ import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import com.taobao.diamond.client.Diamond;
/** /**
* Created on 01/10/2017. * Created on 01/10/2017.
* *
* @author juven.xuxb * @author juven.xuxb
*/ */
@Configuration @Configuration
@ConditionalOnClass({Diamond.class}) @ConditionalOnClass({ Diamond.class })
@EnableConfigurationProperties(AcmProperties.class)
public class AcmAutoConfiguration implements ApplicationContextAware { public class AcmAutoConfiguration implements ApplicationContextAware {
private ApplicationContext applicationContext; private ApplicationContext applicationContext;
@Bean @Bean
public AcmPropertySourceRepository acmPropertySourceRepository() { public AcmPropertySourceRepository acmPropertySourceRepository() {
return new AcmPropertySourceRepository(applicationContext); return new AcmPropertySourceRepository(applicationContext);
} }
@Bean @Bean
public AcmHealthIndicator acmHealthIndicator(AcmProperties acmProperties, public AcmHealthIndicator acmHealthIndicator(AcmProperties acmProperties,
AcmPropertySourceRepository acmPropertySourceRepository) { AcmPropertySourceRepository acmPropertySourceRepository) {
return new AcmHealthIndicator(acmProperties, acmPropertySourceRepository); return new AcmHealthIndicator(acmProperties, acmPropertySourceRepository);
} }
@Bean @Bean
public AcmRefreshHistory acmRefreshHistory() { public AcmRefreshHistory acmRefreshHistory() {
return new AcmRefreshHistory(); return new AcmRefreshHistory();
} }
@Bean @Bean
public AcmContextRefresher acmContextRefresher(AcmProperties acmProperties, ContextRefresher contextRefresher, public AcmContextRefresher acmContextRefresher(
AcmRefreshHistory refreshHistory, AcmIntegrationProperties acmIntegrationProperties,
AcmPropertySourceRepository propertySourceRepository) { ContextRefresher contextRefresher, AcmRefreshHistory refreshHistory,
return new AcmContextRefresher(contextRefresher, acmProperties, refreshHistory, propertySourceRepository); AcmPropertySourceRepository propertySourceRepository) {
} return new AcmContextRefresher(contextRefresher, acmIntegrationProperties,
refreshHistory, propertySourceRepository);
}
@Override @Override
public void setApplicationContext(ApplicationContext applicationContext) public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException { throws BeansException {
this.applicationContext = applicationContext; this.applicationContext = applicationContext;
} }
} }

View File

@ -16,20 +16,6 @@
package org.springframework.cloud.alicloud.acm.refresh; package org.springframework.cloud.alicloud.acm.refresh;
import com.alibaba.edas.acm.ConfigService;
import com.alibaba.edas.acm.listener.ConfigChangeListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.cloud.alicloud.acm.AcmPropertySourceRepository;
import org.springframework.cloud.alicloud.acm.bootstrap.AcmPropertySource;
import org.springframework.cloud.alicloud.context.acm.AcmProperties;
import org.springframework.cloud.context.refresh.ContextRefresher;
import org.springframework.context.ApplicationListener;
import org.springframework.core.env.Environment;
import org.springframework.util.StringUtils;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.math.BigInteger; import java.math.BigInteger;
import java.security.MessageDigest; import java.security.MessageDigest;
@ -37,6 +23,19 @@ import java.security.NoSuchAlgorithmException;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.cloud.alicloud.acm.AcmPropertySourceRepository;
import org.springframework.cloud.alicloud.acm.bootstrap.AcmPropertySource;
import org.springframework.cloud.alicloud.context.acm.AcmIntegrationProperties;
import org.springframework.cloud.context.refresh.ContextRefresher;
import org.springframework.context.ApplicationListener;
import org.springframework.util.StringUtils;
import com.alibaba.edas.acm.ConfigService;
import com.alibaba.edas.acm.listener.ConfigChangeListener;
/** /**
* On application start up, AcmContextRefresher add diamond listeners to all application * On application start up, AcmContextRefresher add diamond listeners to all application
* level dataIds, when there is a change in the data, listeners will refresh * level dataIds, when there is a change in the data, listeners will refresh
@ -50,7 +49,7 @@ public class AcmContextRefresher implements ApplicationListener<ApplicationReady
private final ContextRefresher contextRefresher; private final ContextRefresher contextRefresher;
private final AcmProperties properties; private final AcmIntegrationProperties acmIntegrationProperties;
private final AcmRefreshHistory refreshHistory; private final AcmRefreshHistory refreshHistory;
@ -58,14 +57,12 @@ public class AcmContextRefresher implements ApplicationListener<ApplicationReady
private Map<String, ConfigChangeListener> listenerMap = new ConcurrentHashMap<>(16); private Map<String, ConfigChangeListener> listenerMap = new ConcurrentHashMap<>(16);
@Autowired
private Environment environment;
public AcmContextRefresher(ContextRefresher contextRefresher, public AcmContextRefresher(ContextRefresher contextRefresher,
AcmProperties properties, AcmRefreshHistory refreshHistory, AcmIntegrationProperties acmIntegrationProperties,
AcmRefreshHistory refreshHistory,
AcmPropertySourceRepository acmPropertySourceRepository) { AcmPropertySourceRepository acmPropertySourceRepository) {
this.contextRefresher = contextRefresher; this.contextRefresher = contextRefresher;
this.properties = properties; this.acmIntegrationProperties = acmIntegrationProperties;
this.refreshHistory = refreshHistory; this.refreshHistory = refreshHistory;
this.acmPropertySourceRepository = acmPropertySourceRepository; this.acmPropertySourceRepository = acmPropertySourceRepository;
} }
@ -76,7 +73,7 @@ public class AcmContextRefresher implements ApplicationListener<ApplicationReady
} }
private void registerDiamondListenersForApplications() { private void registerDiamondListenersForApplications() {
if (properties.isRefreshEnabled()) { if (acmIntegrationProperties.getAcmProperties().isRefreshEnabled()) {
for (AcmPropertySource acmPropertySource : acmPropertySourceRepository for (AcmPropertySource acmPropertySource : acmPropertySourceRepository
.getAll()) { .getAll()) {
if (acmPropertySource.isGroupLevel()) { if (acmPropertySource.isGroupLevel()) {
@ -87,11 +84,8 @@ public class AcmContextRefresher implements ApplicationListener<ApplicationReady
} }
if (acmPropertySourceRepository.getAll().isEmpty()) { if (acmPropertySourceRepository.getAll().isEmpty()) {
String applicationName = environment registerDiamondListener(acmIntegrationProperties
.getProperty("spring.application.name"); .getApplicationConfigurationDataIdWithoutGroup());
String dataId = applicationName + "." + properties.getFileExtension();
registerDiamondListener(dataId);
} }
} }
} }
@ -119,7 +113,8 @@ public class AcmContextRefresher implements ApplicationListener<ApplicationReady
contextRefresher.refresh(); contextRefresher.refresh();
} }
}); });
ConfigService.addListener(dataId, properties.getGroup(), listener); ConfigService.addListener(dataId,
acmIntegrationProperties.getAcmProperties().getGroup(), listener);
} }
} }

View File

@ -16,8 +16,6 @@
package org.springframework.cloud.alicloud.context.acm; package org.springframework.cloud.alicloud.context.acm;
import static org.springframework.core.env.AbstractEnvironment.ACTIVE_PROFILES_PROPERTY_NAME;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -68,7 +66,7 @@ public class AcmContextBootstrapConfiguration {
String applicationName = environment.getProperty("spring.application.name"); String applicationName = environment.getProperty("spring.application.name");
String applicationGroup = environment.getProperty("spring.application.group"); String applicationGroup = environment.getProperty("spring.application.group");
Assert.isTrue(!StringUtils.isEmpty(applicationName), Assert.isTrue(!StringUtils.isEmpty(applicationName),
"'spring.application.name' must be configured.."); "'spring.application.name' must be configured in bootstrap.properties or bootstrap.yml/yaml...");
acmIntegrationProperties.setApplicationName(applicationName); acmIntegrationProperties.setApplicationName(applicationName);
acmIntegrationProperties.setApplicationGroup(applicationGroup); acmIntegrationProperties.setApplicationGroup(applicationGroup);
acmIntegrationProperties.setActiveProfiles(environment.getActiveProfiles()); acmIntegrationProperties.setActiveProfiles(environment.getActiveProfiles());

View File

@ -34,6 +34,10 @@ public class AcmIntegrationProperties {
private AcmProperties acmProperties; private AcmProperties acmProperties;
public String getApplicationConfigurationDataIdWithoutGroup() {
return applicationName + "." + acmProperties.getFileExtension();
}
public List<String> getGroupConfigurationDataIds() { public List<String> getGroupConfigurationDataIds() {
List<String> groupConfigurationDataIds = new ArrayList<>(); List<String> groupConfigurationDataIds = new ArrayList<>();
if (StringUtils.isEmpty(applicationGroup)) { if (StringUtils.isEmpty(applicationGroup)) {
@ -88,4 +92,7 @@ public class AcmIntegrationProperties {
this.acmProperties = acmProperties; this.acmProperties = acmProperties;
} }
public AcmProperties getAcmProperties() {
return acmProperties;
}
} }

View File

@ -34,9 +34,10 @@ public class AliCloudPropertiesTests {
@Test @Test
public void testConfigurationValueDefaultsAreAsExpected() { public void testConfigurationValueDefaultsAreAsExpected() {
this.contextRunner.run(context -> { this.contextRunner.run(context -> {
AliCloudProperties config = context.getBean(AliCloudProperties.class); AliCloudProperties aliCloudProperties = context
assertThat(config.getAccessKey()).isNull(); .getBean(AliCloudProperties.class);
assertThat(config.getSecretKey()).isNull(); assertThat(aliCloudProperties.getAccessKey()).isNull();
assertThat(aliCloudProperties.getSecretKey()).isNull();
}); });
} }
@ -44,9 +45,10 @@ public class AliCloudPropertiesTests {
public void testConfigurationValuesAreCorrectlyLoaded() { public void testConfigurationValuesAreCorrectlyLoaded() {
this.contextRunner.withPropertyValues("spring.cloud.alicloud.access-key=123", this.contextRunner.withPropertyValues("spring.cloud.alicloud.access-key=123",
"spring.cloud.alicloud.secret-key=123456").run(context -> { "spring.cloud.alicloud.secret-key=123456").run(context -> {
AliCloudProperties config = context.getBean(AliCloudProperties.class); AliCloudProperties aliCloudProperties = context
assertThat(config.getAccessKey()).isEqualTo("123"); .getBean(AliCloudProperties.class);
assertThat(config.getSecretKey()).isEqualTo("123456"); assertThat(aliCloudProperties.getAccessKey()).isEqualTo("123");
assertThat(aliCloudProperties.getSecretKey()).isEqualTo("123456");
}); });
} }

View File

@ -29,7 +29,7 @@ import com.alibaba.cloud.context.AliCloudServerMode;
/** /**
* @author xiaolongzuo * @author xiaolongzuo
*/ */
public class AnsPropertiesTests { public class AcmPropertiesTests {
private ApplicationContextRunner contextRunner = new ApplicationContextRunner() private ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration( .withConfiguration(
@ -67,14 +67,15 @@ public class AnsPropertiesTests {
"spring.cloud.alicloud.acm.endpoint=testDomain", "spring.cloud.alicloud.acm.endpoint=testDomain",
"spring.cloud.alicloud.acm.group=testGroup", "spring.cloud.alicloud.acm.group=testGroup",
"spring.cloud.alicloud.acm.file-extension=yaml").run(context -> { "spring.cloud.alicloud.acm.file-extension=yaml").run(context -> {
AcmProperties config = context.getBean(AcmProperties.class); AcmProperties acmProperties = context.getBean(AcmProperties.class);
assertThat(config.getServerMode()).isEqualTo(AliCloudServerMode.EDAS); assertThat(acmProperties.getServerMode())
assertThat(config.getServerList()).isEqualTo("10.10.10.10"); .isEqualTo(AliCloudServerMode.EDAS);
assertThat(config.getServerPort()).isEqualTo("11111"); assertThat(acmProperties.getServerList()).isEqualTo("10.10.10.10");
assertThat(config.getEndpoint()).isEqualTo("testDomain"); assertThat(acmProperties.getServerPort()).isEqualTo("11111");
assertThat(config.getGroup()).isEqualTo("testGroup"); assertThat(acmProperties.getEndpoint()).isEqualTo("testDomain");
assertThat(config.getFileExtension()).isEqualTo("yaml"); assertThat(acmProperties.getGroup()).isEqualTo("testGroup");
assertThat(config.getNamespace()).isEqualTo("testNamespace"); assertThat(acmProperties.getFileExtension()).isEqualTo("yaml");
assertThat(acmProperties.getNamespace()).isEqualTo("testNamespace");
}); });
} }
@ -97,10 +98,6 @@ public class AnsPropertiesTests {
.size()).isEqualTo(2); .size()).isEqualTo(2);
assertThat(acmIntegrationProperties assertThat(acmIntegrationProperties
.getApplicationConfigurationDataIds().size()).isEqualTo(2); .getApplicationConfigurationDataIds().size()).isEqualTo(2);
System.out.println("-----"
+ acmIntegrationProperties.getGroupConfigurationDataIds());
System.out.println(acmIntegrationProperties
.getApplicationConfigurationDataIds());
}); });
} }

View File

@ -40,26 +40,26 @@ public class AnsPropertiesTests {
public void testConfigurationValueDefaultsAreAsExpected() public void testConfigurationValueDefaultsAreAsExpected()
throws ClassNotFoundException { throws ClassNotFoundException {
this.contextRunner.withPropertyValues().run(context -> { this.contextRunner.withPropertyValues().run(context -> {
AnsProperties config = context.getBean(AnsProperties.class); AnsProperties ansProperties = context.getBean(AnsProperties.class);
assertThat(config.getServerMode()).isEqualTo(AliCloudServerMode.LOCAL); assertThat(ansProperties.getServerMode()).isEqualTo(AliCloudServerMode.LOCAL);
assertThat(config.getServerList()).isEqualTo("127.0.0.1"); assertThat(ansProperties.getServerList()).isEqualTo("127.0.0.1");
assertThat(config.getServerPort()).isEqualTo("8080"); assertThat(ansProperties.getServerPort()).isEqualTo("8080");
assertThat(config.getClientDomains()).isEqualTo(""); assertThat(ansProperties.getClientDomains()).isEqualTo("");
assertThat(config.getClientWeight()).isEqualTo(1.0F); assertThat(ansProperties.getClientWeight()).isEqualTo(1.0F);
assertThat(config.getClientWeights().size()).isEqualTo(0); assertThat(ansProperties.getClientWeights().size()).isEqualTo(0);
assertThat(config.getClientTokens().size()).isEqualTo(0); assertThat(ansProperties.getClientTokens().size()).isEqualTo(0);
assertThat(config.getClientMetadata().size()).isEqualTo(0); assertThat(ansProperties.getClientMetadata().size()).isEqualTo(0);
assertThat(config.getClientToken()).isNull(); assertThat(ansProperties.getClientToken()).isNull();
assertThat(config.getClientCluster()).isEqualTo("DEFAULT"); assertThat(ansProperties.getClientCluster()).isEqualTo("DEFAULT");
assertThat(config.isRegisterEnabled()).isTrue(); assertThat(ansProperties.isRegisterEnabled()).isTrue();
assertThat(config.getClientInterfaceName()).isNull(); assertThat(ansProperties.getClientInterfaceName()).isNull();
assertThat(config.getClientPort()).isEqualTo(-1); assertThat(ansProperties.getClientPort()).isEqualTo(-1);
assertThat(config.getEnv()).isEqualTo("DEFAULT"); assertThat(ansProperties.getEnv()).isEqualTo("DEFAULT");
assertThat(config.isSecure()).isFalse(); assertThat(ansProperties.isSecure()).isFalse();
assertThat(config.getTags().size()).isEqualTo(1); assertThat(ansProperties.getTags().size()).isEqualTo(1);
assertThat(config.getTags().keySet().iterator().next()) assertThat(ansProperties.getTags().keySet().iterator().next())
.isEqualTo("ANS_SERVICE_TYPE"); .isEqualTo("ANS_SERVICE_TYPE");
assertThat(config.getTags().get("ANS_SERVICE_TYPE")) assertThat(ansProperties.getTags().get("ANS_SERVICE_TYPE"))
.isEqualTo("SPRING_CLOUD"); .isEqualTo("SPRING_CLOUD");
}); });
} }
@ -74,26 +74,27 @@ public class AnsPropertiesTests {
"spring.cloud.alicloud.ans.client-weight=0.9", "spring.cloud.alicloud.ans.client-weight=0.9",
"spring.cloud.alicloud.ans.client-weights.testDomain=0.9") "spring.cloud.alicloud.ans.client-weights.testDomain=0.9")
.run(context -> { .run(context -> {
AnsProperties config = context.getBean(AnsProperties.class); AnsProperties ansProperties = context.getBean(AnsProperties.class);
assertThat(config.getServerMode()).isEqualTo(AliCloudServerMode.EDAS); assertThat(ansProperties.getServerMode())
assertThat(config.getServerList()).isEqualTo("10.10.10.10"); .isEqualTo(AliCloudServerMode.EDAS);
assertThat(config.getServerPort()).isEqualTo("11111"); assertThat(ansProperties.getServerList()).isEqualTo("10.10.10.10");
assertThat(config.getClientDomains()).isEqualTo("testDomain"); assertThat(ansProperties.getServerPort()).isEqualTo("11111");
assertThat(config.getClientWeight()).isEqualTo(0.9F); assertThat(ansProperties.getClientDomains()).isEqualTo("testDomain");
assertThat(config.getClientWeights().size()).isEqualTo(1); assertThat(ansProperties.getClientWeight()).isEqualTo(0.9F);
assertThat(config.getClientTokens().size()).isEqualTo(0); assertThat(ansProperties.getClientWeights().size()).isEqualTo(1);
assertThat(config.getClientMetadata().size()).isEqualTo(0); assertThat(ansProperties.getClientTokens().size()).isEqualTo(0);
assertThat(config.getClientToken()).isNull(); assertThat(ansProperties.getClientMetadata().size()).isEqualTo(0);
assertThat(config.getClientCluster()).isEqualTo("DEFAULT"); assertThat(ansProperties.getClientToken()).isNull();
assertThat(config.isRegisterEnabled()).isTrue(); assertThat(ansProperties.getClientCluster()).isEqualTo("DEFAULT");
assertThat(config.getClientInterfaceName()).isNull(); assertThat(ansProperties.isRegisterEnabled()).isTrue();
assertThat(config.getClientPort()).isEqualTo(-1); assertThat(ansProperties.getClientInterfaceName()).isNull();
assertThat(config.getEnv()).isEqualTo("DEFAULT"); assertThat(ansProperties.getClientPort()).isEqualTo(-1);
assertThat(config.isSecure()).isFalse(); assertThat(ansProperties.getEnv()).isEqualTo("DEFAULT");
assertThat(config.getTags().size()).isEqualTo(1); assertThat(ansProperties.isSecure()).isFalse();
assertThat(config.getTags().keySet().iterator().next()) assertThat(ansProperties.getTags().size()).isEqualTo(1);
assertThat(ansProperties.getTags().keySet().iterator().next())
.isEqualTo("ANS_SERVICE_TYPE"); .isEqualTo("ANS_SERVICE_TYPE");
assertThat(config.getTags().get("ANS_SERVICE_TYPE")) assertThat(ansProperties.getTags().get("ANS_SERVICE_TYPE"))
.isEqualTo("SPRING_CLOUD"); .isEqualTo("SPRING_CLOUD");
}); });
} }

View File

@ -35,9 +35,9 @@ public class EdasPropertiesTests {
@Test @Test
public void testConfigurationValueDefaultsAreAsExpected() { public void testConfigurationValueDefaultsAreAsExpected() {
this.contextRunner.withPropertyValues().run(context -> { this.contextRunner.withPropertyValues().run(context -> {
EdasProperties config = context.getBean(EdasProperties.class); EdasProperties edasProperties = context.getBean(EdasProperties.class);
assertThat(config.getNamespace()).isNull(); assertThat(edasProperties.getNamespace()).isNull();
assertThat(config.isApplicationNameValid()).isFalse(); assertThat(edasProperties.isApplicationNameValid()).isFalse();
}); });
} }
@ -47,9 +47,9 @@ public class EdasPropertiesTests {
.withPropertyValues("spring.cloud.alicloud.edas.namespace=testns", .withPropertyValues("spring.cloud.alicloud.edas.namespace=testns",
"spring.application.name=myapps") "spring.application.name=myapps")
.run(context -> { .run(context -> {
EdasProperties config = context.getBean(EdasProperties.class); EdasProperties edasProperties = context.getBean(EdasProperties.class);
assertThat(config.getNamespace()).isEqualTo("testns"); assertThat(edasProperties.getNamespace()).isEqualTo("testns");
assertThat(config.getApplicationName()).isEqualTo("myapps"); assertThat(edasProperties.getApplicationName()).isEqualTo("myapps");
}); });
} }
@ -59,9 +59,9 @@ public class EdasPropertiesTests {
.withPropertyValues("spring.cloud.alicloud.edas.namespace=testns", .withPropertyValues("spring.cloud.alicloud.edas.namespace=testns",
"spring.cloud.alicloud.edas.application.name=myapps") "spring.cloud.alicloud.edas.application.name=myapps")
.run(context -> { .run(context -> {
EdasProperties config = context.getBean(EdasProperties.class); EdasProperties edasProperties = context.getBean(EdasProperties.class);
assertThat(config.getNamespace()).isEqualTo("testns"); assertThat(edasProperties.getNamespace()).isEqualTo("testns");
assertThat(config.getApplicationName()).isEqualTo("myapps"); assertThat(edasProperties.getApplicationName()).isEqualTo("myapps");
}); });
} }