优化数据源调用

This commit is contained in:
zhouhao
2017-07-02 14:01:32 +08:00
parent 5c9785a63b
commit 08265f788a
17 changed files with 91 additions and 331 deletions

View File

@@ -1,86 +0,0 @@
/*
*
* * Copyright 2016 http://www.hswebframework.org
* *
* * Licensed 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.hswebframework.web.dao.datasource;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
public class DataSourceHolder {
private static DynamicDataSource dynamicDataSource;
private static DataSource defaultDataSource;
private static DatabaseType defaultDatabaseType;
public void init(DataSource dataSource) throws SQLException {
if (null != dataSource) {
try (Connection connection = dataSource.getConnection()) {
install(dataSource, DatabaseType.fromJdbcUrl(connection.getMetaData().getURL()));
}
}
}
public static DataSource getActiveSource() {
if (dynamicDataSource != null) {
return dynamicDataSource.getActiveDataSource();
}
return defaultDataSource;
}
public static String getActiveSourceId() {
if (DynamicDataSource.getActiveDataSourceId() != null) {
return DynamicDataSource.getActiveDataSourceId();
}
return "default";
}
public static DatabaseType getActiveDatabaseType() {
if (dynamicDataSource != null) {
return dynamicDataSource.getActiveDataBaseType();
}
return defaultDatabaseType;
}
public static DataSource getDefaultDataSource() {
return defaultDataSource;
}
public static DatabaseType getDefaultDatabaseType() {
return defaultDatabaseType;
}
public static void install(DynamicDataSource dynamicDataSource) {
if (DataSourceHolder.dynamicDataSource != null) {
throw new UnsupportedOperationException();
}
DataSourceHolder.dynamicDataSource = dynamicDataSource;
}
public static void install(DataSource dataSource, DatabaseType databaseType) {
if (DataSourceHolder.defaultDataSource != null) {
return;
}
DataSourceHolder.defaultDataSource = dataSource;
DataSourceHolder.defaultDatabaseType = databaseType;
}
}

View File

@@ -1,76 +0,0 @@
/*
*
* * Copyright 2016 http://www.hswebframework.org
* *
* * Licensed 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.hswebframework.web.dao.datasource;
import org.hsweb.ezorm.rdb.render.dialect.Dialect;
import org.hswebframework.utils.StringUtils;
public enum DatabaseType {
unknown(null, null, null, null),
mysql("com.mysql.jdbc.Driver", "com.mysql.jdbc.jdbc2.optional.MysqlXADataSource", "select 1", Dialect.MYSQL),
h2("org.h2.Driver", "org.h2.jdbcx.JdbcDataSource", "select 1", Dialect.H2),
oracle("oracle.jdbc.driver.OracleDriver", "oracle.jdbc.xa.client.OracleXADataSource", "select 1 from dual", Dialect.ORACLE);
DatabaseType(String driverClassName, String xaDataSourceClassName, String testQuery, Dialect dialect) {
this.driverClassName = driverClassName;
this.testQuery = testQuery;
this.xaDataSourceClassName = xaDataSourceClassName;
this.dialect = dialect;
}
private final String testQuery;
private final String driverClassName;
private final String xaDataSourceClassName;
private final Dialect dialect;
public String getDriverClassName() {
return driverClassName;
}
public String getXaDataSourceClassName() {
return xaDataSourceClassName;
}
public String getTestQuery() {
return testQuery;
}
public Dialect getDialect() {
return dialect;
}
public static DatabaseType fromJdbcUrl(String url) {
if (!StringUtils.isNullOrEmpty(url)) {
if (!url.trim().startsWith("jdbc")) {
throw new UnsupportedOperationException("URL must start with 'jdbc'");
}
String urlWithoutPrefix = url.substring("jdbc".length()).toLowerCase();
for (DatabaseType driver : values()) {
String prefix = ":" + driver.name().toLowerCase() + ":";
if (driver != unknown && urlWithoutPrefix.startsWith(prefix)) {
return driver;
}
}
}
return unknown;
}
}

View File

@@ -1,94 +0,0 @@
/*
*
* * Copyright 2016 http://www.hswebframework.org
* *
* * Licensed 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.hswebframework.web.dao.datasource;
import org.hswebframework.web.ThreadLocalUtils;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
/**
* 动态数据源接口,此接口实现多数据源的动态切换
*
* @see DataSourceHolder
*/
public interface DynamicDataSource extends DataSource {
String DATA_SOURCE_FLAG = "data-source-id";
String DATA_SOURCE_FLAG_LAST = "data-source-id-last";
/**
* 使用上一次调用的数据源
*/
static void useLast() {
use(ThreadLocalUtils.get(DATA_SOURCE_FLAG_LAST));
}
/**
* 选中参数(数据源ID)对应的数据源,如果数据源不存在,将使用默认数据源
*
* @param dataSourceId 数据源ID
*/
static void use(String dataSourceId) {
ThreadLocalUtils.put(DATA_SOURCE_FLAG, dataSourceId);
}
/**
* 获取当前使用的数据源ID,如果不存在则返回null
*
* @return 数据源ID
*/
static String getActiveDataSourceId() {
return ThreadLocalUtils.get(DATA_SOURCE_FLAG);
}
/**
* 切换为默认数据源,并指定是否记住上一次选中的数据源
*
* @param rememberLast 是否记住上一次选中的数据源
*/
static void useDefault(boolean rememberLast) {
if (rememberLast && null != getActiveDataSourceId())
ThreadLocalUtils.put(DATA_SOURCE_FLAG_LAST, getActiveDataSourceId());
ThreadLocalUtils.remove(DATA_SOURCE_FLAG);
}
/**
* 切换为默认数据源并记住上一次使用的数据源
*/
static void useDefault() {
useDefault(true);
}
/**
* @return 当前激活的数据源
*/
DataSource getActiveDataSource();
/**
* 获取当前激活数据源的数据库类型
*
* @return 数据库类型
* @see DatabaseType
*/
DatabaseType getActiveDataBaseType();
}

View File

@@ -45,6 +45,11 @@
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>org.hswebframework.web</groupId>
<artifactId>hsweb-datasource-api</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>

View File

@@ -23,11 +23,7 @@ import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.TransactionIsolationLevel;
import org.apache.ibatis.transaction.Transaction;
import org.hsweb.ezorm.rdb.executor.AbstractJdbcSqlExecutor;
import org.hsweb.ezorm.rdb.executor.SqlExecutor;
import org.hswebframework.web.commons.entity.factory.EntityFactory;
import org.hswebframework.web.dao.datasource.DataSourceHolder;
import org.hswebframework.web.dao.datasource.DatabaseType;
import org.hswebframework.web.dao.mybatis.dynamic.DynamicDataSourceSqlSessionFactoryBuilder;
import org.hswebframework.web.dao.mybatis.dynamic.DynamicSpringManagedTransaction;
import org.mybatis.spring.SqlSessionFactoryBean;
@@ -36,7 +32,6 @@ import org.mybatis.spring.transaction.SpringManagedTransactionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
@@ -44,13 +39,10 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.ResourceLoader;
import org.springframework.jdbc.datasource.DataSourceUtils;
import org.springframework.util.StringUtils;
import javax.annotation.Resource;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
@Configuration
@EnableConfigurationProperties(MybatisProperties.class)
@@ -116,14 +108,5 @@ public class MyBatisAutoConfiguration {
return factory.getObject();
}
@Bean
@ConditionalOnMissingBean(SqlExecutor.class)
public SqlExecutor sqlExecutor(DataSource dataSource) {
try (Connection connection = dataSource.getConnection()) {
DataSourceHolder.install(dataSource, DatabaseType.fromJdbcUrl(connection.getMetaData().getURL()));
} catch (Exception e) {
throw new RuntimeException(e);
}
return new DefaultJdbcExecutor(dataSource);
}
}

View File

@@ -40,11 +40,11 @@ import org.hsweb.ezorm.rdb.render.dialect.OracleRDBDatabaseMetaData;
import org.hsweb.ezorm.rdb.render.support.simple.CommonSqlRender;
import org.hsweb.ezorm.rdb.render.support.simple.SimpleWhereSqlBuilder;
import org.hswebframework.web.BusinessException;
import org.hswebframework.web.dao.datasource.DataSourceHolder;
import org.hswebframework.web.dao.datasource.DatabaseType;
import org.hswebframework.web.dao.mybatis.plgins.pager.Pager;
import org.hswebframework.web.dao.mybatis.utils.ResultMapsUtils;
import org.hswebframework.utils.StringUtils;
import org.hswebframework.web.datasource.DataSourceHolder;
import org.hswebframework.web.datasource.DatabaseType;
import java.sql.JDBCType;
import java.util.*;
@@ -52,8 +52,9 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* 使用easyorm 动态构建 sql
* @author zhouhao
* @TODO
* @since 2.0
*/
public class EasyOrmSqlBuilder {
@@ -112,7 +113,7 @@ public class EasyOrmSqlBuilder {
};
public RDBDatabaseMetaData getActiveDatabase() {
DatabaseType type = DataSourceHolder.getActiveDatabaseType();
DatabaseType type = DataSourceHolder.currentDatabaseType();
switch (type) {
case h2:
return h2;
@@ -172,8 +173,8 @@ public class EasyOrmSqlBuilder {
SqlAppender appender = new SqlAppender();
columns.forEach(column -> {
RDBColumnMetaData columnMetaData = column.getRDBColumnMetaData();
if (columnMetaData.getName().contains(".")) return;
if (columnMetaData == null) return;
if (columnMetaData.getName().contains(".")) return;
try {
Object tmp = propertyUtils.getProperty(param.getData(), columnMetaData.getAlias());
if (tmp == null) return;
@@ -305,7 +306,7 @@ public class EasyOrmSqlBuilder {
public OracleMeta() {
super();
renderMap.put(SqlRender.TYPE.INSERT, new InsertSqlBuilder());
renderMap.put(SqlRender.TYPE.UPDATE, new UpdateSqlBuilder(Dialect.MYSQL));
renderMap.put(SqlRender.TYPE.UPDATE, new UpdateSqlBuilder(Dialect.ORACLE));
}
}
@@ -313,7 +314,7 @@ public class EasyOrmSqlBuilder {
public H2Meta() {
super();
renderMap.put(SqlRender.TYPE.INSERT, new InsertSqlBuilder());
renderMap.put(SqlRender.TYPE.UPDATE, new UpdateSqlBuilder(Dialect.MYSQL));
renderMap.put(SqlRender.TYPE.UPDATE, new UpdateSqlBuilder(Dialect.H2));
}
}
}

View File

@@ -21,7 +21,7 @@ package org.hswebframework.web.dao.mybatis.dynamic;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory;
import org.apache.ibatis.transaction.Transaction;
import org.hswebframework.web.dao.datasource.DataSourceHolder;
import org.hswebframework.web.datasource.DataSourceHolder;
import org.mybatis.spring.transaction.SpringManagedTransaction;
import org.springframework.jdbc.datasource.ConnectionHolder;
import org.springframework.jdbc.datasource.DataSourceUtils;
@@ -34,6 +34,8 @@ import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import static org.hswebframework.web.datasource.DataSourceHolder.switcher;
/**
* mybatis 同一事务同一个mapper动态数据源切换支持
*
@@ -51,7 +53,7 @@ public class DynamicSpringManagedTransaction implements Transaction {
* @return {@link TransactionProxy}
*/
protected TransactionProxy getProxy() {
return connectionMap.get(DataSourceHolder.getActiveSourceId());
return connectionMap.get(switcher().currentDataSourceId());
}
/**
@@ -60,7 +62,7 @@ public class DynamicSpringManagedTransaction implements Transaction {
* @param proxy
*/
protected void addProxy(TransactionProxy proxy) {
connectionMap.put(DataSourceHolder.getActiveSourceId(), proxy);
connectionMap.put(switcher().currentDataSourceId(), proxy);
}
/**
@@ -79,15 +81,15 @@ public class DynamicSpringManagedTransaction implements Transaction {
return proxy.getConnection();
}
//根据当前激活的数据源 获取jdbc链接
DataSource dataSource = DataSourceHolder.getActiveSource();
String dsId = DataSourceHolder.getActiveSourceId();
DataSource dataSource = DataSourceHolder.currentDataSource().getNative();
String dsId = switcher().currentDataSourceId();
Connection connection = DataSourceUtils.getConnection(dataSource);
proxy = new TransactionProxy(dsId, connection, dataSource);
addProxy(proxy);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(
"DataSource (" + DataSourceHolder.getActiveSourceId() + ") JDBC Connection ["
"DataSource (" + dsId + ") JDBC Connection ["
+ connection
+ "] will"
+ (proxy.isConnectionTransactional ? " " : " not ")

View File

@@ -27,7 +27,7 @@ import org.apache.ibatis.session.defaults.DefaultSqlSession;
import org.apache.ibatis.transaction.Transaction;
import org.apache.ibatis.transaction.TransactionFactory;
import org.apache.ibatis.transaction.managed.ManagedTransactionFactory;
import org.hswebframework.web.dao.datasource.DataSourceHolder;
import org.hswebframework.web.datasource.DataSourceHolder;
import javax.sql.DataSource;
import java.sql.Connection;
@@ -93,7 +93,7 @@ public class DynamicSqlSessionFactory implements SqlSessionFactory {
try {
final Environment environment = getConfiguration().getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
DataSource ds = DataSourceHolder.getActiveSource();
DataSource ds = DataSourceHolder.currentDataSource().getNative();
if (ds == null) ds = environment.getDataSource();
tx = transactionFactory.newTransaction(ds, level, autoCommit);
final Executor executor = getConfiguration().newExecutor(tx, execType);

View File

@@ -28,7 +28,6 @@ import org.hswebframework.web.authorization.oauth2.client.response.OAuth2Respons
import org.hswebframework.web.authorization.shiro.oauth2sso.OAuth2SSOAuthorizingListener;
import org.hswebframework.web.commons.entity.DataStatus;
import org.hswebframework.web.commons.entity.factory.EntityFactory;
import org.hswebframework.web.dao.datasource.DataSourceHolder;
import org.hswebframework.web.dao.datasource.DatabaseType;
import org.hswebframework.web.entity.oauth2.client.OAuth2ServerConfigEntity;
import org.hswebframework.web.service.oauth2.client.OAuth2ServerConfigService;

View File

@@ -27,7 +27,6 @@ import org.hswebframework.web.authorization.oauth2.server.entity.OAuth2ClientEnt
import org.hswebframework.web.authorization.simple.SimpleFieldFilterDataAccessConfig;
import org.hswebframework.web.commons.entity.DataStatus;
import org.hswebframework.web.commons.entity.factory.EntityFactory;
import org.hswebframework.web.dao.datasource.DataSourceHolder;
import org.hswebframework.web.dao.datasource.DatabaseType;
import org.hswebframework.web.dao.oauth2.OAuth2ClientDao;
import org.hswebframework.web.entity.authorization.*;

View File

@@ -61,5 +61,10 @@
<artifactId>hsweb-commons-controller</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.hswebframework.web</groupId>
<artifactId>hsweb-datasource-api</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>

View File

@@ -28,8 +28,8 @@ import org.hsweb.ezorm.rdb.render.dialect.H2RDBDatabaseMetaData;
import org.hsweb.ezorm.rdb.render.dialect.MysqlRDBDatabaseMetaData;
import org.hsweb.ezorm.rdb.render.dialect.OracleRDBDatabaseMetaData;
import org.hsweb.ezorm.rdb.simple.SimpleDatabase;
import org.hswebframework.web.dao.datasource.DataSourceHolder;
import org.hswebframework.web.dao.datasource.DatabaseType;
import org.hswebframework.web.datasource.DataSourceHolder;
import org.hswebframework.web.datasource.DatabaseType;
import org.hswebframework.web.starter.init.SystemInitialize;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
@@ -38,7 +38,6 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import javax.annotation.PostConstruct;
import javax.sql.DataSource;
import java.sql.Connection;
@@ -61,12 +60,12 @@ public class SystemInitializeAutoConfiguration implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
DatabaseType type = DataSourceHolder.getDefaultDatabaseType();
DatabaseType type = DataSourceHolder.currentDatabaseType();
SystemVersion version = appProperties.build();
Connection connection = null;
String jdbcUserName;
try {
connection = DataSourceHolder.getActiveSource().getConnection();
connection = DataSourceHolder.currentDataSource().getConnection();
jdbcUserName = connection.getMetaData().getUserName();
} finally {
if (null != connection) connection.close();

View File

@@ -14,5 +14,5 @@ public interface DatabaseRepository {
RDBDatabase getDatabase(String datasourceId);
RDBDatabase getActiveDatabase();
RDBDatabase getCurrentDatabase();
}

View File

@@ -32,5 +32,10 @@
<artifactId>hsweb-system-dictionary-service-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.hswebframework.web</groupId>
<artifactId>hsweb-datasource-api</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,52 @@
package org.hswebframework.web.service.form.simple;
import org.hsweb.ezorm.core.Database;
import org.hsweb.ezorm.rdb.RDBDatabase;
import org.hswebframework.web.datasource.DataSourceHolder;
import org.hswebframework.web.datasource.DatabaseType;
import org.hswebframework.web.datasource.DynamicDataSource;
import org.hswebframework.web.service.form.DatabaseRepository;
import java.util.HashMap;
import java.util.Map;
/**
* Created by zhouhao on 2017/7/2.
*/
public class SimpleDatabaseRepository implements DatabaseRepository {
private RDBDatabase defaultDatabase;
private Map<String,RDBDatabase> repository = new HashMap<>();
@Override
public RDBDatabase getDefaultDatabase() {
if(defaultDatabase==null){
synchronized (this){
if(defaultDatabase==null){
defaultDatabase=initDatabase(DataSourceHolder.defaultDatabaseType());
}
}
}
return defaultDatabase;
}
@Override
public RDBDatabase getDatabase(String datasourceId) {
DynamicDataSource dynamicDataSource =DataSourceHolder.dataSource(datasourceId);
return repository.computeIfAbsent(datasourceId,id->this.initDatabase(dynamicDataSource.getType()));
}
@Override
public RDBDatabase getCurrentDatabase() {
return repository
.computeIfAbsent(DataSourceHolder.switcher().currentDataSourceId()
,id->this.initDatabase(DataSourceHolder.currentDatabaseType()));
}
private RDBDatabase initDatabase(DatabaseType databaseType){
return null;
}
}

View File

@@ -72,7 +72,6 @@ public class SimpleDynamicFormService extends GenericEntityService<DynamicFormEn
}
@WriteLock("dynamic-form:${#formId}")
public void deploy(String formId) {
DynamicFormEntity formEntity = selectByPk(formId);
assertNotNull(formEntity);

View File

@@ -18,23 +18,15 @@
package org.hswebframework.web.tests;
import org.hsweb.ezorm.rdb.executor.AbstractJdbcSqlExecutor;
import org.hsweb.ezorm.rdb.executor.SqlExecutor;
import org.hswebframework.web.commons.entity.Entity;
import org.hswebframework.web.commons.entity.factory.EntityFactory;
import org.hswebframework.web.commons.entity.factory.MapperEntityFactory;
import org.hswebframework.web.dao.datasource.DataSourceHolder;
import org.hswebframework.web.dao.datasource.DatabaseType;
import org.junit.Before;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.jdbc.datasource.DataSourceUtils;
import org.springframework.mock.web.MockHttpSession;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.context.web.WebAppConfiguration;
@@ -43,12 +35,6 @@ import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilde
import org.springframework.web.context.WebApplicationContext;
import javax.annotation.Resource;
import javax.sql.DataSource;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import java.sql.Connection;
import java.sql.SQLException;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.webAppContextSetup;
@@ -125,25 +111,6 @@ public class SimpleWebApplicationTests {
@WebAppConfiguration
public static class Config {
@Bean
public SqlExecutor sqlExecutor(DataSource dataSource) throws SQLException {
Connection connection = dataSource.getConnection();
try {
DataSourceHolder.install(dataSource, DatabaseType.fromJdbcUrl(connection.getMetaData().getURL()));
} finally {
connection.close();
}
return new AbstractJdbcSqlExecutor() {
@Override
public Connection getConnection() {
return DataSourceUtils.getConnection(dataSource);
}
@Override
public void releaseConnection(Connection connection) throws SQLException {
DataSourceUtils.releaseConnection(connection, dataSource);
}
};
}
}
}