You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@kylin.apache.org by xx...@apache.org on 2022/12/05 10:20:58 UTC

[kylin] 06/22: KYLIN-5314 check name conflict before export tds file

This is an automated email from the ASF dual-hosted git repository.

xxyu pushed a commit to branch kylin5
in repository https://gitbox.apache.org/repos/asf/kylin.git

commit f4f2b21ee8127424f13f3739b4324a7bcfcc8f53
Author: Pengfei Zhan <pe...@kyligence.io>
AuthorDate: Wed Oct 12 20:50:19 2022 +0800

    KYLIN-5314 check name conflict before export tds file
---
 .../kylin/rest/controller/NBasicController.java    |  39 +-
 .../org/apache/kylin/common/KylinConfigBase.java   |  40 +-
 .../common/exception/code/ErrorCodeServer.java     |   2 +
 .../org/apache/kylin/common/msg/CnMessage.java     |  16 -
 .../java/org/apache/kylin/common/msg/Message.java  |  14 +-
 .../resources/kylin_error_msg_conf_cn.properties   |   2 +
 .../resources/kylin_error_msg_conf_en.properties   |   2 +
 .../main/resources/kylin_errorcode_conf.properties |   2 +
 .../kylin/rest/service/ModelBuildService.java      |  34 +-
 .../rest/service/FusionModelServiceBuildTest.java  |   8 +-
 .../kylin/rest/service/ModelServiceBuildTest.java  |  68 +--
 .../rest/controller/open/OpenModelController.java  |  59 +-
 .../kylin/rest/controller/NModelController.java    |  95 +---
 .../kylin/rest/controller/NTableController.java    |  21 +-
 .../rest/controller/v2/NModelControllerV2.java     |   5 +-
 .../rest/controller/v2/NProjectControllerV2.java   |   6 +-
 .../controller/open/OpenModelControllerTest.java   |  69 +++
 .../rest/controller/NModelControllerTest.java      |  74 +--
 .../kylin/rest/service/AbstractModelService.java   | 140 +++++
 .../kylin/rest/service/FusionModelService.java     |   6 +-
 .../apache/kylin/rest/service/ModelService.java    | 407 +-------------
 .../apache/kylin/rest/service/ModelTdsService.java | 336 ++++++++++++
 .../kylin/rest/service/ModelServiceTest.java       | 479 +---------------
 .../kylin/rest/service/ModelTdsServiceTest.java    | 607 +++++++++++++++++++++
 .../org/apache/kylin/tool/bisync/BISyncTool.java   |  20 +-
 .../org/apache/kylin/tool/bisync/SyncContext.java  |   2 +
 .../apache/kylin/tool/bisync/SyncModelBuilder.java | 308 +++++------
 .../apache/kylin/tool/bisync/model/ColumnDef.java  |  80 +--
 .../apache/kylin/tool/bisync/model/SyncModel.java  |  72 +--
 .../bisync/tableau/TableauDataSourceConverter.java |   2 +-
 .../kap/secondstorage/SecondStorageLockTest.java   |   6 +-
 .../kylin/tool/bisync/SyncModelBuilderTest.java    |  78 ++-
 .../kylin/tool/bisync/SyncModelTestUtil.java       |   1 +
 33 files changed, 1597 insertions(+), 1503 deletions(-)

diff --git a/src/common-service/src/main/java/org/apache/kylin/rest/controller/NBasicController.java b/src/common-service/src/main/java/org/apache/kylin/rest/controller/NBasicController.java
index 24bce526ed..4ffe05c013 100644
--- a/src/common-service/src/main/java/org/apache/kylin/rest/controller/NBasicController.java
+++ b/src/common-service/src/main/java/org/apache/kylin/rest/controller/NBasicController.java
@@ -58,7 +58,9 @@ import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Locale;
+import java.util.Map;
 import java.util.Objects;
+import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
@@ -78,28 +80,28 @@ import org.apache.kylin.common.exception.KylinException;
 import org.apache.kylin.common.exception.ServerErrorCode;
 import org.apache.kylin.common.msg.Message;
 import org.apache.kylin.common.msg.MsgPicker;
+import org.apache.kylin.common.persistence.transaction.TransactionException;
 import org.apache.kylin.common.util.DateFormat;
 import org.apache.kylin.common.util.JsonUtil;
+import org.apache.kylin.common.util.Unsafe;
 import org.apache.kylin.job.constant.JobStatusEnum;
 import org.apache.kylin.job.dao.ExecutablePO;
 import org.apache.kylin.job.execution.JobTypeEnum;
+import org.apache.kylin.metadata.model.NDataModel;
+import org.apache.kylin.metadata.model.NDataModelManager;
+import org.apache.kylin.metadata.project.NProjectManager;
 import org.apache.kylin.metadata.project.ProjectInstance;
+import org.apache.kylin.metadata.streaming.KafkaConfigManager;
 import org.apache.kylin.rest.constant.Constant;
 import org.apache.kylin.rest.exception.ForbiddenException;
 import org.apache.kylin.rest.exception.NotFoundException;
 import org.apache.kylin.rest.exception.UnauthorizedException;
+import org.apache.kylin.rest.request.Validation;
 import org.apache.kylin.rest.response.EnvelopeResponse;
 import org.apache.kylin.rest.response.ErrorResponse;
+import org.apache.kylin.rest.service.ProjectService;
 import org.apache.kylin.rest.service.UserService;
 import org.apache.kylin.rest.util.PagingUtil;
-import org.apache.kylin.common.persistence.transaction.TransactionException;
-import org.apache.kylin.common.util.Unsafe;
-import org.apache.kylin.metadata.model.NDataModel;
-import org.apache.kylin.metadata.model.NDataModelManager;
-import org.apache.kylin.metadata.project.NProjectManager;
-import org.apache.kylin.metadata.streaming.KafkaConfigManager;
-import org.apache.kylin.rest.request.Validation;
-import org.apache.kylin.rest.service.ProjectService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -370,8 +372,8 @@ public class NBasicController {
         return isAdmin;
     }
 
-    public HashMap<String, Object> getDataResponse(String name, List<?> result, int offset, int limit) {
-        HashMap<String, Object> data = new HashMap<>();
+    public Map<String, Object> getDataResponse(String name, List<?> result, int offset, int limit) {
+        Map<String, Object> data = new HashMap<>();
         data.put(name, PagingUtil.cutPage(result, offset, limit));
         data.put("size", result.size());
         return data;
@@ -381,6 +383,19 @@ public class NBasicController {
         return PagingUtil.cutPage(result, offset, limit);
     }
 
+    public String getHost(String serverHost, String serverName) {
+        String host = KylinConfig.getInstanceFromEnv().getModelExportHost();
+        host = Optional.ofNullable(Optional.ofNullable(host).orElse(serverHost)).orElse(serverName);
+        return host;
+    }
+
+    public int getPort(Integer serverPort, Integer requestServerPort) {
+        Integer port = KylinConfig.getInstanceFromEnv().getModelExportPort() == -1 ? null
+                : KylinConfig.getInstanceFromEnv().getModelExportPort();
+        port = Optional.ofNullable(Optional.ofNullable(port).orElse(serverPort)).orElse(requestServerPort);
+        return port;
+    }
+
     public String checkProjectName(String project) {
         if (StringUtils.isEmpty(project)) {
             throw new KylinException(EMPTY_PROJECT_NAME, MsgPicker.getMsg().getEmptyProjectName());
@@ -632,8 +647,8 @@ public class NBasicController {
         if (CollectionUtils.isEmpty(statuses)) {
             return;
         }
-        List<String> streamingJobsStatus = Arrays.asList(JobStatusEnum.STARTING.name(),
-                JobStatusEnum.RUNNING.name(), JobStatusEnum.STOPPING.name(), JobStatusEnum.ERROR.name(), JobStatusEnum.STOPPED.name());
+        List<String> streamingJobsStatus = Arrays.asList(JobStatusEnum.STARTING.name(), JobStatusEnum.RUNNING.name(),
+                JobStatusEnum.STOPPING.name(), JobStatusEnum.ERROR.name(), JobStatusEnum.STOPPED.name());
         for (String status : statuses) {
             if (!streamingJobsStatus.contains(status)) {
                 throw new KylinException(PARAMETER_INVALID_SUPPORT_LIST, "statuses",
diff --git a/src/core-common/src/main/java/org/apache/kylin/common/KylinConfigBase.java b/src/core-common/src/main/java/org/apache/kylin/common/KylinConfigBase.java
index d0b4fda104..acfe61c924 100644
--- a/src/core-common/src/main/java/org/apache/kylin/common/KylinConfigBase.java
+++ b/src/core-common/src/main/java/org/apache/kylin/common/KylinConfigBase.java
@@ -553,6 +553,7 @@ public abstract class KylinConfigBase implements Serializable {
     public boolean getSecondStorageUseLowCardinality() {
         return Boolean.parseBoolean(getOptional("kylin.second-storage.use-low-cardinality", TRUE));
     }
+
     public long getSecondStorageLowCardinalityNumber() {
         return Long.parseLong(getOptional("kylin.second-storage.low-cardinality-number", "10000"));
     }
@@ -966,8 +967,9 @@ public abstract class KylinConfigBase implements Serializable {
     public String getWritingClusterWorkingDir(String withSuffix) {
         // This step will remove the '/' symbol from the end of the writingClusterWorkingDir
         Path writingClusterPath = new Path(getWritingClusterWorkingDir());
-        if (!writingClusterPath.isAbsolute()){
-            throw new IllegalArgumentException("kylin.env.hdfs-write-working-dir must be absolute, but got " + writingClusterPath);
+        if (!writingClusterPath.isAbsolute()) {
+            throw new IllegalArgumentException(
+                    "kylin.env.hdfs-write-working-dir must be absolute, but got " + writingClusterPath);
         }
 
         // make sure path is qualified
@@ -2032,16 +2034,16 @@ public abstract class KylinConfigBase implements Serializable {
 
     public String[] getTableDetectorTransformers() {
         String value = getOptional("kylin.query.table-detect-transformers");
-        return value == null ? new String[] { POWER_BI_CONVERTER,
-                "org.apache.kylin.query.util.DefaultQueryTransformer", "org.apache.kylin.query.util.EscapeTransformer" }
+        return value == null
+                ? new String[] { POWER_BI_CONVERTER, "org.apache.kylin.query.util.DefaultQueryTransformer",
+                        "org.apache.kylin.query.util.EscapeTransformer" }
                 : getOptionalStringArray("kylin.query.table-detect-transformers", new String[0]);
     }
 
     public String[] getQueryTransformers() {
         String value = getOptional("kylin.query.transformers");
-        return value == null ? new String[] { POWER_BI_CONVERTER,
-                "org.apache.kylin.query.util.DefaultQueryTransformer", "org.apache.kylin.query.util.EscapeTransformer",
-                "org.apache.kylin.query.util.ConvertToComputedColumn",
+        return value == null ? new String[] { POWER_BI_CONVERTER, "org.apache.kylin.query.util.DefaultQueryTransformer",
+                "org.apache.kylin.query.util.EscapeTransformer", "org.apache.kylin.query.util.ConvertToComputedColumn",
                 "org.apache.kylin.query.util.KeywordDefaultDirtyHack", "org.apache.kylin.query.security.RowFilter" }
                 : getOptionalStringArray("kylin.query.transformers", new String[0]);
     }
@@ -2211,12 +2213,13 @@ public abstract class KylinConfigBase implements Serializable {
     }
 
     public String[] getPushDownConverterClassNames() {
-        return getOptionalStringArray("kylin.query.pushdown.converter-class-names", new String[] {
-                "org.apache.kylin.source.adhocquery.DoubleQuotePushDownConverter",
-                POWER_BI_CONVERTER, "org.apache.kylin.query.util.KeywordDefaultDirtyHack",
-                "org.apache.kylin.query.util.RestoreFromComputedColumn", "org.apache.kylin.query.security.RowFilter",
-                "org.apache.kylin.query.security.HackSelectStarWithColumnACL",
-                "org.apache.kylin.query.util.SparkSQLFunctionConverter" });
+        return getOptionalStringArray("kylin.query.pushdown.converter-class-names",
+                new String[] { "org.apache.kylin.source.adhocquery.DoubleQuotePushDownConverter", POWER_BI_CONVERTER,
+                        "org.apache.kylin.query.util.KeywordDefaultDirtyHack",
+                        "org.apache.kylin.query.util.RestoreFromComputedColumn",
+                        "org.apache.kylin.query.security.RowFilter",
+                        "org.apache.kylin.query.security.HackSelectStarWithColumnACL",
+                        "org.apache.kylin.query.util.SparkSQLFunctionConverter" });
     }
 
     @ThirdPartyDependencies({
@@ -2821,11 +2824,13 @@ public abstract class KylinConfigBase implements Serializable {
     }
 
     public int getQueryHistoryAccelerateMaxSize() {
-        return Integer.parseInt(this.getOptional("kylin.favorite.query-history-accelerate-max-size", ONE_HUNDRED_THOUSAND));
+        return Integer
+                .parseInt(this.getOptional("kylin.favorite.query-history-accelerate-max-size", ONE_HUNDRED_THOUSAND));
     }
 
     public int getQueryHistoryStatMetaUpdateMaxSize() {
-        return Integer.parseInt(this.getOptional("kylin.query.query-history-stat-update-max-size", ONE_HUNDRED_THOUSAND));
+        return Integer
+                .parseInt(this.getOptional("kylin.query.query-history-stat-update-max-size", ONE_HUNDRED_THOUSAND));
     }
 
     public long getQueryHistoryAccelerateInterval() {
@@ -3606,6 +3611,10 @@ public abstract class KylinConfigBase implements Serializable {
         return Boolean.parseBoolean(getOptional("kylin.model.tds-expose-model-join-key", TRUE));
     }
 
+    public boolean skipCheckTds() {
+        return Boolean.parseBoolean(getOptional("kylin.model.skip-check-tds", FALSE));
+    }
+
     public boolean isHdfsMetricsPeriodicCalculationEnabled() {
         return Boolean.parseBoolean(getOptional("kylin.metrics.hdfs-periodic-calculation-enabled", FALSE));
     }
@@ -3622,6 +3631,7 @@ public abstract class KylinConfigBase implements Serializable {
     public boolean isSkipResourceCheck() {
         return Boolean.parseBoolean(getOptional("kylin.build.resource.skip-resource-check", FALSE));
     }
+
     public int getSecondStorageSkippingIndexGranularity() {
         int granularity = Integer.parseInt(getOptional("kylin.second-storage.skipping-index.granularity", "3"));
         return granularity <= 0 ? 3 : granularity;
diff --git a/src/core-common/src/main/java/org/apache/kylin/common/exception/code/ErrorCodeServer.java b/src/core-common/src/main/java/org/apache/kylin/common/exception/code/ErrorCodeServer.java
index 3a0ca0195d..c8abaa1142 100644
--- a/src/core-common/src/main/java/org/apache/kylin/common/exception/code/ErrorCodeServer.java
+++ b/src/core-common/src/main/java/org/apache/kylin/common/exception/code/ErrorCodeServer.java
@@ -32,6 +32,8 @@ public enum ErrorCodeServer implements ErrorCodeProducer {
     MODEL_NAME_DUPLICATE("KE-010002206"),
     SIMPLIFIED_MEASURES_MISSING_ID("KE-010002207"),
     MODEL_NOT_EXIST_SEGMENTS("KE-010002208"),
+    MODEL_TDS_EXPORT_DIM_COL_AND_MEASURE_NAME_CONFLICT("KE-010002301"),
+    MODEL_TDS_EXPORT_COLUMN_AND_MEASURE_NAME_CONFLICT("KE-010002302"),
 
     // 100252XX Cube
     CUBE_NOT_EXIST("KE-010025201"),
diff --git a/src/core-common/src/main/java/org/apache/kylin/common/msg/CnMessage.java b/src/core-common/src/main/java/org/apache/kylin/common/msg/CnMessage.java
index 482d58fef5..3688c2db26 100644
--- a/src/core-common/src/main/java/org/apache/kylin/common/msg/CnMessage.java
+++ b/src/core-common/src/main/java/org/apache/kylin/common/msg/CnMessage.java
@@ -1700,21 +1700,6 @@ public class CnMessage extends Message {
         return "当前 Segments 所包含的索引未加载至 HDFS 存储/对象存储,请确保索引加载至 HDFS 存储后再合并。";
     }
 
-    @Override
-    public String getDuplicateModelColumnAndMeasureName() {
-        return "模型中的列名 %s 与度量名 %s 重复,无法导出 TDS。请去除重名后再重试。";
-    }
-
-    @Override
-    public String getDuplicateDimensionNameAndMeasureName() {
-        return "维度名 %s 与度量名 %s 重复,无法导出 TDS。请去除重名后再重试。";
-    }
-
-    @Override
-    public String getDuplicateDimensionColAndMeasureName() {
-        return "维度的列名 %s 与度量名 %s 重复,无法导出 TDS。请去除重名后再重试。";
-    }
-
     @Override
     public String getProfilingNotEnabled() {
         return "构建火焰图" + PARAMETER_NOT_ENABLED;
@@ -1750,7 +1735,6 @@ public class CnMessage extends Message {
         return "构建火焰图任务" + TASK_TIMEOUT;
     }
 
-
     @Override
     public String getSecondStorageIndexNotSupport() {
         return "Order by列和Skipping Index列不支持使用分区列";
diff --git a/src/core-common/src/main/java/org/apache/kylin/common/msg/Message.java b/src/core-common/src/main/java/org/apache/kylin/common/msg/Message.java
index 84a274ca50..cad28ace11 100644
--- a/src/core-common/src/main/java/org/apache/kylin/common/msg/Message.java
+++ b/src/core-common/src/main/java/org/apache/kylin/common/msg/Message.java
@@ -39,7 +39,6 @@ public class Message {
     private static final String LACK_PROJECT = "Please fill in the project parameters.";
     private static final String NON_EXIST_PROJECT = "Project %s doesn't exist. Please confirm and try again later.";
     private static final String DUP_MODCOL_MEASURE_NAME = "There are duplicated names among model column %s and measure name %s. Cannot export a valid TDS file. Please correct the duplicated names and try again.";
-    private static final String DUP_DIM_MEASURE_NAME = "There are duplicated names among dimension name %s and measure name %s. Cannot export a valid TDS file. Please correct the duplicated names and try again.";
     private static final String DUP_DIMCOL_MEASURE_NAME = "There are duplicated names among dimension column %s and measure name %s. Cannot export a valid TDS file. Please correct the duplicated names and try again.";
     private static final String MODIFY_PERMISSION_OF_SUPER_ADMIN = "Super Admin’s permission can’t be modified.";
     private static final String ILLEGAL_AUTHORIZING_USER = "Unable to modify. Only Super Admin or System Admin with query permission can modify query permission.";
@@ -1303,6 +1302,7 @@ public class Message {
     public String getInvalidLowCardinalityDataType() {
         return SECOND_STORAGE_CARDINALITY_DATATYPE_INVALID;
     }
+
     public String getJobRestartFailed() {
         return "Tiered storage task doesn't support restart.\n";
     }
@@ -1525,18 +1525,6 @@ public class Message {
         return "The indexes included in the selected segments are not loaded to HDFS storage/object storage. Please ensure the indexes are loaded into HDFS storage and try merging again.";
     }
 
-    public String getDuplicateModelColumnAndMeasureName() {
-        return DUP_MODCOL_MEASURE_NAME;
-    }
-
-    public String getDuplicateDimensionNameAndMeasureName() {
-        return DUP_DIM_MEASURE_NAME;
-    }
-
-    public String getDuplicateDimensionColAndMeasureName() {
-        return DUP_DIMCOL_MEASURE_NAME;
-    }
-
     public String getProfilingNotEnabled() {
         return PROFILING_NOT_ENABLED;
     }
diff --git a/src/core-common/src/main/resources/kylin_error_msg_conf_cn.properties b/src/core-common/src/main/resources/kylin_error_msg_conf_cn.properties
index 27641ca5b2..327c28d18d 100644
--- a/src/core-common/src/main/resources/kylin_error_msg_conf_cn.properties
+++ b/src/core-common/src/main/resources/kylin_error_msg_conf_cn.properties
@@ -32,6 +32,8 @@ KE-010002205=无效的模型名称 “%s”。请使用字母、数字或下划
 KE-010002206=模型 “%s” 已存在。请重新命名。
 KE-010002207=修改模型时,simplified_measures 参数中的每个度量必须传入 id 值。请为以下度量传入 id 之后重试:%s。
 KE-010002208=模型上线必须存在 Segment。请重新输入。
+KE-010002301=维度的列名 %s 与度量名 %s 重复,无法导出 TDS。请去除重名后再重试。
+KE-010002302=模型中的列名 %s 与度量名 %s 重复,无法导出 TDS。请去除重名后再重试。
 
 ## 100252XX Cube
 KE-010025201=无法找到相关 Cube。
diff --git a/src/core-common/src/main/resources/kylin_error_msg_conf_en.properties b/src/core-common/src/main/resources/kylin_error_msg_conf_en.properties
index 3f87894a80..5a5f9ea5fb 100644
--- a/src/core-common/src/main/resources/kylin_error_msg_conf_en.properties
+++ b/src/core-common/src/main/resources/kylin_error_msg_conf_en.properties
@@ -32,6 +32,8 @@ KE-010002205=The model name "%s" is invalid. Please use letters, numbers and und
 KE-010002206=Model "%s" already exists. Please rename it.
 KE-010002207=When modifying model, each measure id is required in simplified_measures parameter. Please pass ids for following measures and try again: %s.
 KE-010002208=The online model must have a segment. Please re-enter.
+KE-010002301=There are duplicated names among dimension column %s and measure name %s. Cannot export a valid TDS file. Please correct the duplicated names and try again.
+KE-010002302=There are duplicated names among model column %s and measure name %s. Cannot export a valid TDS file. Please correct the duplicated names and try again.
 
 ## 100252XX Cube
 KE-010025201=Can't find the cube.
diff --git a/src/core-common/src/main/resources/kylin_errorcode_conf.properties b/src/core-common/src/main/resources/kylin_errorcode_conf.properties
index 5b506d68fb..7f07828a6a 100644
--- a/src/core-common/src/main/resources/kylin_errorcode_conf.properties
+++ b/src/core-common/src/main/resources/kylin_errorcode_conf.properties
@@ -33,6 +33,8 @@ KE-010002205
 KE-010002206
 KE-010002207
 KE-010002208
+KE-010002301
+KE-010002302
 
 ## 100252XX Cube
 KE-010025201
diff --git a/src/data-loading-service/src/main/java/org/apache/kylin/rest/service/ModelBuildService.java b/src/data-loading-service/src/main/java/org/apache/kylin/rest/service/ModelBuildService.java
index 8b08ee171d..5548fd30d5 100644
--- a/src/data-loading-service/src/main/java/org/apache/kylin/rest/service/ModelBuildService.java
+++ b/src/data-loading-service/src/main/java/org/apache/kylin/rest/service/ModelBuildService.java
@@ -48,14 +48,6 @@ import org.apache.kylin.job.exception.JobSubmissionException;
 import org.apache.kylin.job.execution.JobTypeEnum;
 import org.apache.kylin.job.manager.JobManager;
 import org.apache.kylin.job.model.JobParam;
-import org.apache.kylin.metadata.model.PartitionDesc;
-import org.apache.kylin.metadata.model.SegmentRange;
-import org.apache.kylin.metadata.model.SegmentStatusEnum;
-import org.apache.kylin.metadata.model.SegmentStatusEnumToDisplay;
-import org.apache.kylin.metadata.model.TableDesc;
-import org.apache.kylin.query.util.PushDownUtil;
-import org.apache.kylin.rest.util.AclEvaluate;
-import org.apache.kylin.source.SourceFactory;
 import org.apache.kylin.metadata.cube.model.IndexPlan;
 import org.apache.kylin.metadata.cube.model.NBatchConstants;
 import org.apache.kylin.metadata.cube.model.NDataSegment;
@@ -67,9 +59,15 @@ import org.apache.kylin.metadata.model.MultiPartitionDesc;
 import org.apache.kylin.metadata.model.NDataModel;
 import org.apache.kylin.metadata.model.NDataModelManager;
 import org.apache.kylin.metadata.model.NTableMetadataManager;
+import org.apache.kylin.metadata.model.PartitionDesc;
+import org.apache.kylin.metadata.model.SegmentRange;
+import org.apache.kylin.metadata.model.SegmentStatusEnum;
+import org.apache.kylin.metadata.model.SegmentStatusEnumToDisplay;
+import org.apache.kylin.metadata.model.TableDesc;
 import org.apache.kylin.metadata.model.util.MultiPartitionUtil;
 import org.apache.kylin.metadata.project.EnhancedUnitOfWork;
 import org.apache.kylin.metadata.sourceusage.SourceUsageManager;
+import org.apache.kylin.query.util.PushDownUtil;
 import org.apache.kylin.rest.aspect.Transaction;
 import org.apache.kylin.rest.request.PartitionsRefreshRequest;
 import org.apache.kylin.rest.request.SegmentTimeRequest;
@@ -82,6 +80,7 @@ import org.apache.kylin.rest.service.params.FullBuildSegmentParams;
 import org.apache.kylin.rest.service.params.IncrementBuildSegmentParams;
 import org.apache.kylin.rest.service.params.MergeSegmentParams;
 import org.apache.kylin.rest.service.params.RefreshSegmentParams;
+import org.apache.kylin.source.SourceFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
@@ -93,14 +92,11 @@ import lombok.val;
 import lombok.var;
 
 @Component("modelBuildService")
-public class ModelBuildService extends BasicService implements ModelBuildSupporter {
+public class ModelBuildService extends AbstractModelService implements ModelBuildSupporter {
 
     @Autowired
     private ModelService modelService;
 
-    @Autowired
-    private AclEvaluate aclEvaluate;
-
     @Autowired
     private SegmentHelper segmentHelper;
 
@@ -150,7 +146,7 @@ public class ModelBuildService extends BasicService implements ModelBuildSupport
 
     public JobInfoResponse fullBuildSegmentsManually(FullBuildSegmentParams params) {
         aclEvaluate.checkProjectOperationPermission(params.getProject());
-        modelService.checkModelPermission(params.getProject(), params.getModelId());
+        checkModelPermission(params.getProject(), params.getModelId());
         List<JobInfoResponse.JobInfo> jobIds = EnhancedUnitOfWork
                 .doInTransactionWithCheckAndRetry(() -> constructFullBuild(params), params.getProject());
         JobInfoResponse jobInfoResponse = new JobInfoResponse();
@@ -228,7 +224,7 @@ public class ModelBuildService extends BasicService implements ModelBuildSupport
         List<JobInfoResponse.JobInfo> jobIds = new ArrayList<>();
         NDataflowManager dfMgr = getManager(NDataflowManager.class, params.getProject());
         val jobManager = getManager(JobManager.class, params.getProject());
-        IndexPlan indexPlan = modelService.getIndexPlan(params.getModelId(), params.getProject());
+        IndexPlan indexPlan = getIndexPlan(params.getModelId(), params.getProject());
         NDataflow df = dfMgr.getDataflow(indexPlan.getUuid());
 
         for (String id : params.getSegmentIds()) {
@@ -263,7 +259,7 @@ public class ModelBuildService extends BasicService implements ModelBuildSupport
     public JobInfoResponse incrementBuildSegmentsManually(IncrementBuildSegmentParams params) throws Exception {
         String project = params.getProject();
         aclEvaluate.checkProjectOperationPermission(project);
-        modelService.checkModelPermission(project, params.getModelId());
+        checkModelPermission(project, params.getModelId());
         val modelManager = getManager(NDataModelManager.class, project);
         if (PartitionDesc.isEmptyPartitionDesc(params.getPartitionDesc())) {
             throw new KylinException(EMPTY_PARTITION_COLUMN, "Partition column is null.'");
@@ -441,7 +437,7 @@ public class ModelBuildService extends BasicService implements ModelBuildSupport
             List<String[]> partitionValues, boolean parallelBuild, boolean buildAllPartitions, int priority,
             String yarnQueue, Object tag) {
         aclEvaluate.checkProjectOperationPermission(project);
-        modelService.checkModelPermission(project, modelId);
+        checkModelPermission(project, modelId);
         modelService.checkSegmentsExistById(modelId, project, new String[] { segmentId });
         modelService.checkModelIsMLP(modelId, project);
         val dfm = getManager(NDataflowManager.class, project);
@@ -527,7 +523,7 @@ public class ModelBuildService extends BasicService implements ModelBuildSupport
         val segment = df.getSegment(param.getSegmentId());
         var partitions = param.getPartitionIds();
         aclEvaluate.checkProjectOperationPermission(project);
-        modelService.checkModelPermission(project, modelId);
+        checkModelPermission(project, modelId);
 
         if (CollectionUtils.isEmpty(param.getPartitionIds())) {
             partitions = modelService.getModelById(modelId, project).getMultiPartitionDesc()
@@ -561,7 +557,7 @@ public class ModelBuildService extends BasicService implements ModelBuildSupport
 
         val dfManager = getManager(NDataflowManager.class, project);
         val jobManager = getManager(JobManager.class, project);
-        val indexPlan = modelService.getIndexPlan(modelId, project);
+        val indexPlan = getIndexPlan(modelId, project);
         val df = dfManager.getDataflow(indexPlan.getUuid());
 
         NDataSegment mergeSeg = NDataflowManager.getInstance(KylinConfig.getInstanceFromEnv(), project).mergeSegments(
@@ -588,7 +584,7 @@ public class ModelBuildService extends BasicService implements ModelBuildSupport
             List<Long> indexIds, boolean parallelBuildBySegment, int priority, boolean partialBuild, String yarnQueue,
             Object tag) {
         aclEvaluate.checkProjectOperationPermission(project);
-        modelService.checkModelPermission(project, modelId);
+        checkModelPermission(project, modelId);
         val dfManger = getManager(NDataflowManager.class, project);
         NDataflow dataflow = dfManger.getDataflow(modelId);
         modelService.checkSegmentsExistById(modelId, project, segmentIds.toArray(new String[0]));
diff --git a/src/data-loading-service/src/test/java/org/apache/kylin/rest/service/FusionModelServiceBuildTest.java b/src/data-loading-service/src/test/java/org/apache/kylin/rest/service/FusionModelServiceBuildTest.java
index 9b9e5f814b..cad5a66e73 100644
--- a/src/data-loading-service/src/test/java/org/apache/kylin/rest/service/FusionModelServiceBuildTest.java
+++ b/src/data-loading-service/src/test/java/org/apache/kylin/rest/service/FusionModelServiceBuildTest.java
@@ -22,17 +22,17 @@ import java.util.Arrays;
 
 import org.apache.kylin.common.KylinConfig;
 import org.apache.kylin.common.exception.KylinException;
-import org.apache.kylin.rest.constant.Constant;
-import org.apache.kylin.rest.util.AclEvaluate;
-import org.apache.kylin.rest.util.AclUtil;
 import org.apache.kylin.common.scheduler.EventBusFactory;
 import org.apache.kylin.metadata.cube.model.NDataflowManager;
 import org.apache.kylin.metadata.model.ManagementType;
 import org.apache.kylin.metadata.model.NDataModel;
 import org.apache.kylin.metadata.model.NDataModelManager;
 import org.apache.kylin.rest.config.initialize.ModelUpdateListener;
+import org.apache.kylin.rest.constant.Constant;
 import org.apache.kylin.rest.request.IndexesToSegmentsRequest;
 import org.apache.kylin.rest.service.params.IncrementBuildSegmentParams;
+import org.apache.kylin.rest.util.AclEvaluate;
+import org.apache.kylin.rest.util.AclUtil;
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
@@ -98,7 +98,9 @@ public class FusionModelServiceBuildTest extends SourceTestCase {
         ReflectionTestUtils.setField(modelService, "aclEvaluate", aclEvaluate);
         ReflectionTestUtils.setField(modelBuildService, "aclEvaluate", aclEvaluate);
         ReflectionTestUtils.setField(modelBuildService, "modelService", modelService);
+        ReflectionTestUtils.setField(modelBuildService, "userGroupService", userGroupService);
         ReflectionTestUtils.setField(fusionModelService, "modelBuildService", modelBuildService);
+        ReflectionTestUtils.setField(fusionModelService, "userGroupService", userGroupService);
         ReflectionTestUtils.setField(modelService, "userGroupService", userGroupService);
         ReflectionTestUtils.setField(semanticService, "userGroupService", userGroupService);
         ReflectionTestUtils.setField(indexPlanService, "aclEvaluate", aclEvaluate);
diff --git a/src/data-loading-service/src/test/java/org/apache/kylin/rest/service/ModelServiceBuildTest.java b/src/data-loading-service/src/test/java/org/apache/kylin/rest/service/ModelServiceBuildTest.java
index 03dad20631..3dad56193f 100644
--- a/src/data-loading-service/src/test/java/org/apache/kylin/rest/service/ModelServiceBuildTest.java
+++ b/src/data-loading-service/src/test/java/org/apache/kylin/rest/service/ModelServiceBuildTest.java
@@ -18,14 +18,31 @@
 
 package org.apache.kylin.rest.service;
 
-import com.google.common.collect.Lists;
-import com.google.common.collect.Sets;
-import org.apache.kylin.engine.spark.job.ExecutableAddCuboidHandler;
-import org.apache.kylin.engine.spark.job.ExecutableAddSegmentHandler;
-import org.apache.kylin.engine.spark.job.ExecutableMergeOrRefreshHandler;
-import org.apache.kylin.engine.spark.job.NSparkCubingJob;
-import lombok.val;
-import lombok.var;
+import static org.apache.kylin.common.exception.code.ErrorCodeServer.JOB_CONCURRENT_SUBMIT_LIMIT;
+import static org.apache.kylin.common.exception.code.ErrorCodeServer.JOB_CREATE_CHECK_FAIL;
+import static org.apache.kylin.common.exception.code.ErrorCodeServer.JOB_CREATE_CHECK_MULTI_PARTITION_ABANDON;
+import static org.apache.kylin.common.exception.code.ErrorCodeServer.JOB_CREATE_CHECK_MULTI_PARTITION_DUPLICATE;
+import static org.apache.kylin.common.exception.code.ErrorCodeServer.SEGMENT_LOCKED;
+import static org.apache.kylin.common.exception.code.ErrorCodeServer.SEGMENT_MERGE_CHECK_INDEX_ILLEGAL;
+import static org.apache.kylin.common.exception.code.ErrorCodeServer.SEGMENT_MERGE_CHECK_PARTITION_ILLEGAL;
+import static org.apache.kylin.common.exception.code.ErrorCodeServer.SEGMENT_NOT_EXIST_ID;
+import static org.apache.kylin.common.exception.code.ErrorCodeServer.SEGMENT_REFRESH_SELECT_RANGE_EMPTY;
+import static org.apache.kylin.common.exception.code.ErrorCodeServer.SEGMENT_STATUS;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+import java.util.TimeZone;
+import java.util.stream.Collectors;
+
 import org.apache.commons.collections.CollectionUtils;
 import org.apache.kylin.common.KylinConfig;
 import org.apache.kylin.common.exception.KylinException;
@@ -35,6 +52,10 @@ import org.apache.kylin.common.persistence.transaction.UnitOfWork;
 import org.apache.kylin.common.scheduler.EventBusFactory;
 import org.apache.kylin.common.util.DateFormat;
 import org.apache.kylin.common.util.JsonUtil;
+import org.apache.kylin.engine.spark.job.ExecutableAddCuboidHandler;
+import org.apache.kylin.engine.spark.job.ExecutableAddSegmentHandler;
+import org.apache.kylin.engine.spark.job.ExecutableMergeOrRefreshHandler;
+import org.apache.kylin.engine.spark.job.NSparkCubingJob;
 import org.apache.kylin.engine.spark.utils.ComputedColumnEvalUtil;
 import org.apache.kylin.job.dao.ExecutablePO;
 import org.apache.kylin.job.execution.AbstractExecutable;
@@ -103,30 +124,11 @@ import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.springframework.test.util.ReflectionTestUtils;
 
-import java.io.IOException;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Comparator;
-import java.util.List;
-import java.util.Locale;
-import java.util.Set;
-import java.util.TimeZone;
-import java.util.stream.Collectors;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
 
-import static org.apache.kylin.common.exception.code.ErrorCodeServer.JOB_CONCURRENT_SUBMIT_LIMIT;
-import static org.apache.kylin.common.exception.code.ErrorCodeServer.JOB_CREATE_CHECK_FAIL;
-import static org.apache.kylin.common.exception.code.ErrorCodeServer.JOB_CREATE_CHECK_MULTI_PARTITION_ABANDON;
-import static org.apache.kylin.common.exception.code.ErrorCodeServer.JOB_CREATE_CHECK_MULTI_PARTITION_DUPLICATE;
-import static org.apache.kylin.common.exception.code.ErrorCodeServer.SEGMENT_LOCKED;
-import static org.apache.kylin.common.exception.code.ErrorCodeServer.SEGMENT_MERGE_CHECK_INDEX_ILLEGAL;
-import static org.apache.kylin.common.exception.code.ErrorCodeServer.SEGMENT_MERGE_CHECK_PARTITION_ILLEGAL;
-import static org.apache.kylin.common.exception.code.ErrorCodeServer.SEGMENT_NOT_EXIST_ID;
-import static org.apache.kylin.common.exception.code.ErrorCodeServer.SEGMENT_REFRESH_SELECT_RANGE_EMPTY;
-import static org.apache.kylin.common.exception.code.ErrorCodeServer.SEGMENT_STATUS;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
+import lombok.val;
+import lombok.var;
 
 public class ModelServiceBuildTest extends SourceTestCase {
     @InjectMocks
@@ -147,9 +149,6 @@ public class ModelServiceBuildTest extends SourceTestCase {
     @InjectMocks
     private final TableService tableService = Mockito.spy(new TableService());
 
-    @InjectMocks
-    private final TableExtService tableExtService = Mockito.spy(new TableExtService());
-
     @InjectMocks
     private final IndexPlanService indexPlanService = Mockito.spy(new IndexPlanService());
 
@@ -196,6 +195,7 @@ public class ModelServiceBuildTest extends SourceTestCase {
         ReflectionTestUtils.setField(modelService, "accessService", accessService);
         ReflectionTestUtils.setField(modelService, "userGroupService", userGroupService);
         ReflectionTestUtils.setField(semanticService, "userGroupService", userGroupService);
+        ReflectionTestUtils.setField(modelBuildService, "userGroupService", userGroupService);
         ReflectionTestUtils.setField(semanticService, "expandableMeasureUtil",
                 new ExpandableMeasureUtil((model, ccDesc) -> {
                     String ccExpression = KapQueryUtil.massageComputedColumn(model, model.getProject(), ccDesc,
diff --git a/src/metadata-server/src/main/java/io/kyligence/kap/rest/controller/open/OpenModelController.java b/src/metadata-server/src/main/java/io/kyligence/kap/rest/controller/open/OpenModelController.java
index 8f2b405c4e..d9642ade0c 100644
--- a/src/metadata-server/src/main/java/io/kyligence/kap/rest/controller/open/OpenModelController.java
+++ b/src/metadata-server/src/main/java/io/kyligence/kap/rest/controller/open/OpenModelController.java
@@ -28,6 +28,7 @@ import static org.apache.kylin.common.exception.code.ErrorCodeServer.PROJECT_MUL
 
 import java.io.IOException;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 import java.util.Locale;
 import java.util.Set;
@@ -70,8 +71,12 @@ import org.apache.kylin.rest.response.OpenGetIndexResponse.IndexDetail;
 import org.apache.kylin.rest.service.FusionIndexService;
 import org.apache.kylin.rest.service.FusionModelService;
 import org.apache.kylin.rest.service.ModelService;
+import org.apache.kylin.rest.service.ModelTdsService;
+import org.apache.kylin.rest.util.AclPermissionUtil;
 import org.apache.kylin.tool.bisync.SyncContext;
+import org.apache.kylin.tool.bisync.model.SyncModel;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.stereotype.Controller;
 import org.springframework.web.bind.annotation.DeleteMapping;
 import org.springframework.web.bind.annotation.GetMapping;
@@ -84,6 +89,7 @@ import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.ResponseBody;
 
 import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Lists;
 
@@ -108,6 +114,10 @@ public class OpenModelController extends NBasicController {
     @Autowired
     private NModelController modelController;
 
+    @Autowired
+    @Qualifier("modelTdsService")
+    private ModelTdsService tdsService;
+
     @Autowired
     private FusionIndexService fusionIndexService;
 
@@ -150,8 +160,8 @@ public class OpenModelController extends NBasicController {
             @RequestParam(value = "only_normal_dim", required = false, defaultValue = "true") boolean onlyNormalDim) {
         String projectName = checkProjectName(project);
         return modelController.getModels(modelId, modelAlias, exactMatch, projectName, owner, status, table, offset,
-                limit, sortBy, reverse, modelAliasOrOwner, Arrays.asList(ModelAttributeEnum.BATCH), lastModifyFrom,
-                lastModifyTo, onlyNormalDim);
+                limit, sortBy, reverse, modelAliasOrOwner, Collections.singletonList(ModelAttributeEnum.BATCH),
+                lastModifyFrom, lastModifyTo, onlyNormalDim);
     }
 
     @ApiOperation(value = "getIndexes", tags = { "AI" })
@@ -354,28 +364,24 @@ public class OpenModelController extends NBasicController {
         return modelController.updatePartitionSemantic(modelId, param);
     }
 
-    @ApiOperation(value = "validate tds export", tags = { "QE" })
-    @GetMapping(value = "/validate_export")
-    @ResponseBody
-    public EnvelopeResponse<Boolean> validateExport(@RequestParam(value = "model_name") String modelAlias,
-            @RequestParam(value = "project") String project) {
-        String projectName = checkProjectName(project);
-        String modelId = getModel(modelAlias, projectName).getId();
-        return modelController.validateExport(modelId, projectName);
-    }
-
     @ApiOperation(value = "export model", tags = { "QE" }, notes = "Add URL: {model}")
     @GetMapping(value = "/{model_name:.+}/export")
     @ResponseBody
     public void exportModel(@PathVariable("model_name") String modelAlias,
             @RequestParam(value = "project") String project, @RequestParam(value = "export_as") SyncContext.BI exportAs,
             @RequestParam(value = "element", required = false, defaultValue = "AGG_INDEX_COL") SyncContext.ModelElement element,
-            @RequestParam(value = "server_host", required = false) String host,
-            @RequestParam(value = "server_port", required = false) Integer port, HttpServletRequest request,
+            @RequestParam(value = "server_host", required = false) String serverHost,
+            @RequestParam(value = "server_port", required = false) Integer serverPort, HttpServletRequest request,
             HttpServletResponse response) throws IOException {
         String projectName = checkProjectName(project);
         String modelId = getModel(modelAlias, projectName).getId();
-        modelController.exportModel(modelId, projectName, exportAs, element, host, port, request, response);
+        String host = getHost(serverHost, request.getServerName());
+        int port = getPort(serverPort, request.getServerPort());
+
+        SyncContext syncContext = tdsService.prepareSyncContext(projectName, modelId, exportAs, element, host, port);
+        SyncModel syncModel = tdsService.exportModel(syncContext);
+        tdsService.preCheckNameConflict(syncModel);
+        tdsService.dumpSyncModel(syncContext, syncModel, response);
     }
 
     @ApiOperation(value = "bi export", tags = { "QE" })
@@ -384,15 +390,30 @@ public class OpenModelController extends NBasicController {
     public void biExport(@RequestParam("model_name") String modelAlias, @RequestParam(value = "project") String project,
             @RequestParam(value = "export_as") SyncContext.BI exportAs,
             @RequestParam(value = "element", required = false, defaultValue = "AGG_INDEX_COL") SyncContext.ModelElement element,
-            @RequestParam(value = "server_host", required = false) String host,
-            @RequestParam(value = "server_port", required = false) Integer port,
+            @RequestParam(value = "server_host", required = false) String serverHost,
+            @RequestParam(value = "server_port", required = false) Integer serverPort,
             @RequestParam(value = "dimensions", required = false) List<String> dimensions,
             @RequestParam(value = "measures", required = false) List<String> measures, HttpServletRequest request,
             HttpServletResponse response) throws IOException {
         String projectName = checkProjectName(project);
         String modelId = getModel(modelAlias, projectName).getId();
-        modelController.biExport(modelId, projectName, exportAs, element, host, port, dimensions, measures, request,
-                response);
+        String host = getHost(serverHost, request.getServerName());
+        int port = getPort(serverPort, request.getServerPort());
+        if (dimensions == null) {
+            // no need filter of given dimensions
+            dimensions = ImmutableList.of();
+        }
+        if (measures == null) {
+            // no need filter of given measures
+            measures = ImmutableList.of();
+        }
+
+        SyncContext syncContext = tdsService.prepareSyncContext(projectName, modelId, exportAs, element, host, port);
+        SyncModel syncModel = AclPermissionUtil.isAdmin()
+                ? tdsService.exportTDSDimensionsAndMeasuresByAdmin(syncContext, dimensions, measures)
+                : tdsService.exportTDSDimensionsAndMeasuresByNormalUser(syncContext, dimensions, measures);
+        tdsService.preCheckNameConflict(syncModel);
+        tdsService.dumpSyncModel(syncContext, syncModel, response);
     }
 
     @ApiOperation(value = "updateModelName", tags = { "AI" })
diff --git a/src/metadata-server/src/main/java/org/apache/kylin/rest/controller/NModelController.java b/src/metadata-server/src/main/java/org/apache/kylin/rest/controller/NModelController.java
index 8b3ffd04b1..6efa43ec6e 100644
--- a/src/metadata-server/src/main/java/org/apache/kylin/rest/controller/NModelController.java
+++ b/src/metadata-server/src/main/java/org/apache/kylin/rest/controller/NModelController.java
@@ -30,18 +30,14 @@ import java.util.Collections;
 import java.util.Date;
 import java.util.List;
 import java.util.Locale;
-import java.util.Optional;
 
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
 import org.apache.commons.lang.StringUtils;
 import org.apache.commons.lang.exception.ExceptionUtils;
-import org.apache.kylin.common.KylinConfig;
-import org.apache.kylin.common.exception.CommonErrorCode;
 import org.apache.kylin.common.exception.KylinException;
 import org.apache.kylin.metadata.model.NDataModel;
-import org.apache.kylin.metadata.model.NDataModelManager;
 import org.apache.kylin.metadata.model.PartitionDesc;
 import org.apache.kylin.metadata.model.exception.LookupTableException;
 import org.apache.kylin.rest.constant.ModelAttributeEnum;
@@ -73,13 +69,14 @@ import org.apache.kylin.rest.response.ModelConfigResponse;
 import org.apache.kylin.rest.response.ModelSaveCheckResponse;
 import org.apache.kylin.rest.response.MultiPartitionValueResponse;
 import org.apache.kylin.rest.response.PurgeModelAffectedResponse;
+import org.apache.kylin.rest.service.AbstractModelService;
 import org.apache.kylin.rest.service.FusionIndexService;
 import org.apache.kylin.rest.service.FusionModelService;
 import org.apache.kylin.rest.service.IndexPlanService;
 import org.apache.kylin.rest.service.ModelService;
-import org.apache.kylin.rest.util.AclPermissionUtil;
-import org.apache.kylin.tool.bisync.BISyncModel;
+import org.apache.kylin.rest.service.ModelTdsService;
 import org.apache.kylin.tool.bisync.SyncContext;
+import org.apache.kylin.tool.bisync.model.SyncModel;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.stereotype.Controller;
@@ -112,6 +109,10 @@ public class NModelController extends NBasicController {
     @Qualifier("modelService")
     private ModelService modelService;
 
+    @Autowired
+    @Qualifier("modelTdsService")
+    private ModelTdsService tdsService;
+
     @Autowired
     private FusionModelService fusionModelService;
 
@@ -484,7 +485,7 @@ public class NModelController extends NBasicController {
         checkRequiredArg(MODEL_ID, modelId);
         String newAlias = modelRenameRequest.getNewModelName();
         String description = modelRenameRequest.getDescription();
-        if (!StringUtils.containsOnly(newAlias, ModelService.VALID_NAME_FOR_MODEL)) {
+        if (!StringUtils.containsOnly(newAlias, AbstractModelService.VALID_NAME_FOR_MODEL)) {
             throw new KylinException(MODEL_NAME_INVALID, newAlias);
         }
 
@@ -545,7 +546,7 @@ public class NModelController extends NBasicController {
         String newModelName = request.getNewModelName();
         checkRequiredArg(MODEL_ID, modelId);
         checkRequiredArg(NEW_MODEL_NAME, newModelName);
-        if (!StringUtils.containsOnly(newModelName, ModelService.VALID_NAME_FOR_MODEL)) {
+        if (!StringUtils.containsOnly(newModelName, AbstractModelService.VALID_NAME_FOR_MODEL)) {
             throw new KylinException(MODEL_NAME_INVALID, newModelName);
         }
         modelService.cloneModel(modelId, request.getNewModelName(), request.getProject());
@@ -636,9 +637,12 @@ public class NModelController extends NBasicController {
     @GetMapping(value = "/validate_export")
     @ResponseBody
     public EnvelopeResponse<Boolean> validateExport(@RequestParam(value = "model") String modelId,
-            @RequestParam(value = "project") String project) {
+            @RequestParam(value = "project") String project,
+            @RequestParam(value = "element", required = false, defaultValue = "AGG_INDEX_COL") SyncContext.ModelElement element) {
         String projectName = checkProjectName(project);
-        Boolean result = modelService.validateExport(projectName, modelId);
+        SyncContext virtualContext = tdsService.prepareSyncContext(projectName, modelId, null, element, "", -1);
+        SyncModel syncModel = tdsService.exportModel(virtualContext);
+        Boolean result = tdsService.preCheckNameConflict(syncModel);
         return new EnvelopeResponse<>(KylinException.CODE_SUCCESS, result, "");
     }
 
@@ -652,75 +656,12 @@ public class NModelController extends NBasicController {
             @RequestParam(value = "server_port", required = false) Integer serverPort, HttpServletRequest request,
             HttpServletResponse response) throws IOException {
         String projectName = checkProjectName(project);
-
         String host = getHost(serverHost, request.getServerName());
-        Integer port = getPort(serverPort, request.getServerPort());
-
-        modelService.validateExport(projectName, modelId);
-        BISyncModel syncModel = modelService.exportModel(projectName, modelId, exportAs, element, host, port);
-
-        dumpSyncModel(modelId, exportAs, projectName, syncModel, response);
-    }
-
-    @ApiOperation(value = "biExport", tags = { "QE" })
-    @GetMapping(value = "/bi_export")
-    @ResponseBody
-    public void biExport(@RequestParam("model") String modelId, @RequestParam(value = "project") String project,
-            @RequestParam(value = "export_as") SyncContext.BI exportAs,
-            @RequestParam(value = "element", required = false, defaultValue = "AGG_INDEX_COL") SyncContext.ModelElement element,
-            @RequestParam(value = "server_host", required = false) String serverHost,
-            @RequestParam(value = "server_port", required = false) Integer serverPort,
-            @RequestParam(value = "dimensions", required = false) List<String> dimensions,
-            @RequestParam(value = "measures", required = false) List<String> measures, HttpServletRequest request,
-            HttpServletResponse response) throws IOException {
-        String projectName = checkProjectName(project);
-
-        String host = getHost(serverHost, request.getServerName());
-        Integer port = getPort(serverPort, request.getServerPort());
-
-        modelService.validateExport(projectName, modelId);
-        SyncContext syncContext = modelService.getSyncContext(projectName, modelId, exportAs, element, host, port);
-
-        BISyncModel syncModel = AclPermissionUtil.isAdmin()
-                ? modelService.exportTDSDimensionsAndMeasuresByAdmin(syncContext, dimensions, measures)
-                : modelService.exportTDSDimensionsAndMeasuresByNormalUser(syncContext, dimensions, measures);
-
-        dumpSyncModel(modelId, exportAs, projectName, syncModel, response);
-    }
-
-    private void dumpSyncModel(String modelId, SyncContext.BI exportAs, String projectName, BISyncModel syncModel,
-            HttpServletResponse response) throws IOException {
-        NDataModelManager manager = NDataModelManager.getInstance(KylinConfig.getInstanceFromEnv(), projectName);
-        NDataModel dataModel = manager.getDataModelDesc(modelId);
-        String alias = dataModel.getAlias();
-        String fileName = String.format(Locale.ROOT, "%s_%s_%s", projectName, alias,
-                new SimpleDateFormat("yyyyMMddHHmmss", Locale.getDefault(Locale.Category.FORMAT)).format(new Date()));
-        switch (exportAs) {
-        case TABLEAU_CONNECTOR_TDS:
-        case TABLEAU_ODBC_TDS:
-            response.setContentType("application/xml");
-            response.setHeader("Content-Disposition",
-                    String.format(Locale.ROOT, "attachment; filename=\"%s.tds\"", fileName));
-            break;
-        default:
-            throw new KylinException(CommonErrorCode.UNKNOWN_ERROR_CODE, "unrecognized export target");
-        }
-        syncModel.dump(response.getOutputStream());
-        response.getOutputStream().flush();
-        response.getOutputStream().close();
-    }
-
-    private String getHost(String serverHost, String serverName) {
-        String host = KylinConfig.getInstanceFromEnv().getModelExportHost();
-        host = Optional.ofNullable(Optional.ofNullable(host).orElse(serverHost)).orElse(serverName);
-        return host;
-    }
+        int port = getPort(serverPort, request.getServerPort());
 
-    private Integer getPort(Integer serverPort, Integer requestServerPort) {
-        Integer port = KylinConfig.getInstanceFromEnv().getModelExportPort() == -1 ? null
-                : KylinConfig.getInstanceFromEnv().getModelExportPort();
-        port = Optional.ofNullable(Optional.ofNullable(port).orElse(serverPort)).orElse(requestServerPort);
-        return port;
+        SyncContext syncContext = tdsService.prepareSyncContext(projectName, modelId, exportAs, element, host, port);
+        SyncModel syncModel = tdsService.exportModel(syncContext);
+        tdsService.dumpSyncModel(syncContext, syncModel, response);
     }
 
     @ApiOperation(value = "updateMultiPartitionMapping", tags = { "QE" }, notes = "Add URL: {model}")
diff --git a/src/metadata-server/src/main/java/org/apache/kylin/rest/controller/NTableController.java b/src/metadata-server/src/main/java/org/apache/kylin/rest/controller/NTableController.java
index 8b6181ef8f..5e43fc3ea3 100644
--- a/src/metadata-server/src/main/java/org/apache/kylin/rest/controller/NTableController.java
+++ b/src/metadata-server/src/main/java/org/apache/kylin/rest/controller/NTableController.java
@@ -246,13 +246,12 @@ public class NTableController extends NBasicController {
         return new EnvelopeResponse<>(KylinException.CODE_SUCCESS, loadTableResponse, "");
     }
 
-
-    @ApiOperation(value = "loadAWSTablesCompatibleCrossAccount", tags = {"KC"},
-            notes = "Update Body: data_source_type, need_sampling, sampling_rows, data_source_properties")
+    @ApiOperation(value = "loadAWSTablesCompatibleCrossAccount", tags = {
+            "KC" }, notes = "Update Body: data_source_type, need_sampling, sampling_rows, data_source_properties")
     @PostMapping(value = "/compatibility/aws")
     @ResponseBody
-    public EnvelopeResponse<LoadTableResponse> loadAWSTablesCompatibleCrossAccount(@RequestBody AWSTableLoadRequest tableLoadRequest)
-            throws Exception {
+    public EnvelopeResponse<LoadTableResponse> loadAWSTablesCompatibleCrossAccount(
+            @RequestBody AWSTableLoadRequest tableLoadRequest) throws Exception {
         checkProjectName(tableLoadRequest.getProject());
         if (NProjectManager.getInstance(KylinConfig.getInstanceFromEnv())
                 .getProject(tableLoadRequest.getProject()) == null) {
@@ -263,8 +262,8 @@ public class NTableController extends NBasicController {
         }
 
         LoadTableResponse loadTableResponse = new LoadTableResponse();
-        LoadTableResponse loadByTable = tableExtService.loadAWSTablesCompatibleCrossAccount(tableLoadRequest.getTables(),
-                tableLoadRequest.getProject());
+        LoadTableResponse loadByTable = tableExtService
+                .loadAWSTablesCompatibleCrossAccount(tableLoadRequest.getTables(), tableLoadRequest.getProject());
         loadTableResponse.getFailed().addAll(loadByTable.getFailed());
         loadTableResponse.getLoaded().addAll(loadByTable.getLoaded());
 
@@ -277,13 +276,13 @@ public class NTableController extends NBasicController {
         return new EnvelopeResponse<>(KylinException.CODE_SUCCESS, loadTableResponse, "");
     }
 
-    @ApiOperation(value = "updateLoadedAWSTableExtProp", tags = {"KC" }, notes = "Update Body: data_source_properties")
+    @ApiOperation(value = "updateLoadedAWSTableExtProp", tags = { "KC" }, notes = "Update Body: data_source_properties")
     @PutMapping(value = "/ext/prop/aws")
     @ResponseBody
-    public EnvelopeResponse<UpdateAWSTableExtDescResponse> updateLoadedAWSTableExtProp(@RequestBody UpdateAWSTableExtDescRequest request) {
+    public EnvelopeResponse<UpdateAWSTableExtDescResponse> updateLoadedAWSTableExtProp(
+            @RequestBody UpdateAWSTableExtDescRequest request) {
         checkProjectName(request.getProject());
-        if (NProjectManager.getInstance(KylinConfig.getInstanceFromEnv())
-                .getProject(request.getProject()) == null) {
+        if (NProjectManager.getInstance(KylinConfig.getInstanceFromEnv()).getProject(request.getProject()) == null) {
             throw new KylinException(PROJECT_NOT_EXIST, request.getProject());
         }
         if (CollectionUtils.isEmpty(request.getTables())) {
diff --git a/src/metadata-server/src/main/java/org/apache/kylin/rest/controller/v2/NModelControllerV2.java b/src/metadata-server/src/main/java/org/apache/kylin/rest/controller/v2/NModelControllerV2.java
index 5de11e8846..7552b476d5 100644
--- a/src/metadata-server/src/main/java/org/apache/kylin/rest/controller/v2/NModelControllerV2.java
+++ b/src/metadata-server/src/main/java/org/apache/kylin/rest/controller/v2/NModelControllerV2.java
@@ -20,14 +20,13 @@ package org.apache.kylin.rest.controller.v2;
 import static org.apache.kylin.common.constant.HttpConstant.HTTP_VND_APACHE_KYLIN_V2_JSON;
 
 import java.util.ArrayList;
-import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
 import org.apache.kylin.common.exception.KylinException;
-import org.apache.kylin.rest.response.EnvelopeResponse;
 import org.apache.kylin.metadata.model.NDataModel;
 import org.apache.kylin.rest.controller.NBasicController;
+import org.apache.kylin.rest.response.EnvelopeResponse;
 import org.apache.kylin.rest.service.ModelService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Qualifier;
@@ -65,7 +64,7 @@ public class NModelControllerV2 extends NBasicController {
                 modelService.getModels(modelAlias, project, exactMatch, null, Lists.newArrayList(), sortBy, reverse));
         models = modelService.addOldParams(project, models);
 
-        HashMap<String, Object> modelResponse = getDataResponse("models", models, offset, limit);
+        Map<String, Object> modelResponse = getDataResponse("models", models, offset, limit);
         return new EnvelopeResponse<>(KylinException.CODE_SUCCESS, modelResponse, "");
     }
 
diff --git a/src/metadata-server/src/main/java/org/apache/kylin/rest/controller/v2/NProjectControllerV2.java b/src/metadata-server/src/main/java/org/apache/kylin/rest/controller/v2/NProjectControllerV2.java
index e9f864080d..060dc323a0 100644
--- a/src/metadata-server/src/main/java/org/apache/kylin/rest/controller/v2/NProjectControllerV2.java
+++ b/src/metadata-server/src/main/java/org/apache/kylin/rest/controller/v2/NProjectControllerV2.java
@@ -19,13 +19,13 @@ package org.apache.kylin.rest.controller.v2;
 
 import static org.apache.kylin.common.constant.HttpConstant.HTTP_VND_APACHE_KYLIN_V2_JSON;
 
-import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 import org.apache.kylin.common.exception.KylinException;
 import org.apache.kylin.metadata.project.ProjectInstance;
-import org.apache.kylin.rest.response.EnvelopeResponse;
 import org.apache.kylin.rest.controller.NBasicController;
+import org.apache.kylin.rest.response.EnvelopeResponse;
 import org.apache.kylin.rest.service.ProjectService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Qualifier;
@@ -54,7 +54,7 @@ public class NProjectControllerV2 extends NBasicController {
             @RequestParam(value = "exact", required = false, defaultValue = "true") boolean exactMatch) {
 
         List<ProjectInstance> readableProjects = projectService.getReadableProjects(project, exactMatch);
-        HashMap<String, Object> projects = getDataResponse("projects", readableProjects, offset, size);
+        Map<String, Object> projects = getDataResponse("projects", readableProjects, offset, size);
         return new EnvelopeResponse<>(KylinException.CODE_SUCCESS, projects, "");
     }
 
diff --git a/src/metadata-server/src/test/java/io/kyligence/kap/rest/controller/open/OpenModelControllerTest.java b/src/metadata-server/src/test/java/io/kyligence/kap/rest/controller/open/OpenModelControllerTest.java
index 0d22ea3cef..e36ccf48de 100644
--- a/src/metadata-server/src/test/java/io/kyligence/kap/rest/controller/open/OpenModelControllerTest.java
+++ b/src/metadata-server/src/test/java/io/kyligence/kap/rest/controller/open/OpenModelControllerTest.java
@@ -37,6 +37,7 @@ import org.apache.kylin.common.util.NLocalFileMetadataTestCase;
 import org.apache.kylin.common.util.Pair;
 import org.apache.kylin.common.util.RandomUtil;
 import org.apache.kylin.metadata.cube.model.IndexEntity;
+import org.apache.kylin.metadata.cube.model.NDataflowManager;
 import org.apache.kylin.metadata.model.MultiPartitionDesc;
 import org.apache.kylin.metadata.model.NDataModel;
 import org.apache.kylin.metadata.model.NDataModelManager;
@@ -61,7 +62,10 @@ import org.apache.kylin.rest.response.OpenGetIndexResponse;
 import org.apache.kylin.rest.service.FusionIndexService;
 import org.apache.kylin.rest.service.FusionModelService;
 import org.apache.kylin.rest.service.ModelService;
+import org.apache.kylin.rest.service.ModelTdsService;
 import org.apache.kylin.rest.util.AclEvaluate;
+import org.apache.kylin.tool.bisync.SyncContext;
+import org.apache.kylin.tool.bisync.model.SyncModel;
 import org.hamcrest.Matchers;
 import org.junit.After;
 import org.junit.Assert;
@@ -102,6 +106,9 @@ public class OpenModelControllerTest extends NLocalFileMetadataTestCase {
     @Mock
     private FusionModelService fusionModelService;
 
+    @Mock
+    private ModelTdsService tdsService;
+
     @Mock
     private AclEvaluate aclEvaluate;
 
@@ -502,6 +509,66 @@ public class OpenModelControllerTest extends NLocalFileMetadataTestCase {
                 .andExpect(MockMvcResultMatchers.status().isOk());
     }
 
+    @Test
+    public void testBIExportByADMIN() throws Exception {
+        String project = "default";
+        String modelName = "741ca86a-1f13-46da-a59f-95fb68615e3a";
+        SyncContext syncContext = new SyncContext();
+        syncContext.setProjectName(project);
+        syncContext.setModelId(modelName);
+        syncContext.setTargetBI(SyncContext.BI.TABLEAU_CONNECTOR_TDS);
+        syncContext.setModelElement(SyncContext.ModelElement.AGG_INDEX_AND_TABLE_INDEX_COL);
+        syncContext.setHost("localhost");
+        syncContext.setPort(8080);
+        syncContext.setDataflow(NDataflowManager.getInstance(getTestConfig(), project).getDataflow(modelName));
+        syncContext.setKylinConfig(getTestConfig());
+        SyncModel syncModel = Mockito.mock(SyncModel.class);
+        NDataModel model = new NDataModel();
+        model.setUuid("aaa");
+        Mockito.doReturn(model).when(openModelController).getModel(Mockito.anyString(), Mockito.anyString());
+        Mockito.doReturn(syncContext).when(tdsService).prepareSyncContext(project, modelName,
+                SyncContext.BI.TABLEAU_CONNECTOR_TDS, SyncContext.ModelElement.CUSTOM_COLS, "localhost", 8080);
+        Mockito.doReturn(syncModel).when(tdsService).exportTDSDimensionsAndMeasuresByAdmin(syncContext,
+                Lists.newArrayList(), Lists.newArrayList());
+        mockMvc.perform(MockMvcRequestBuilders.get("/api/models/bi_export").param("model_name", modelName)
+                .param("project", project).param("export_as", "TABLEAU_CONNECTOR_TDS").param("element", "CUSTOM_COLS")
+                .param("server_host", "localhost").param("server_port", "8080").param("dimensions", "")
+                .param("measures", "").contentType(MediaType.APPLICATION_JSON)
+                .accept(MediaType.parseMediaType(HTTP_VND_APACHE_KYLIN_V4_PUBLIC_JSON)))
+                .andExpect(MockMvcResultMatchers.status().isOk());
+    }
+
+    @Test
+    public void testBIExportByNormalUser() throws Exception {
+        String project = "default";
+        String modelName = "741ca86a-1f13-46da-a59f-95fb68615e3a";
+        SyncContext syncContext = new SyncContext();
+        syncContext.setProjectName(project);
+        syncContext.setModelId(modelName);
+        syncContext.setTargetBI(SyncContext.BI.TABLEAU_CONNECTOR_TDS);
+        syncContext.setModelElement(SyncContext.ModelElement.AGG_INDEX_AND_TABLE_INDEX_COL);
+        syncContext.setHost("localhost");
+        syncContext.setPort(8080);
+        syncContext.setDataflow(NDataflowManager.getInstance(getTestConfig(), project).getDataflow(modelName));
+        syncContext.setKylinConfig(getTestConfig());
+        SyncModel syncModel = Mockito.mock(SyncModel.class);
+        SecurityContextHolder.getContext()
+                .setAuthentication(new TestingAuthenticationToken("u1", "ANALYST", Constant.ROLE_ANALYST));
+        NDataModel model = new NDataModel();
+        model.setUuid("aaa");
+        Mockito.doReturn(model).when(openModelController).getModel(Mockito.anyString(), Mockito.anyString());
+        Mockito.doReturn(syncContext).when(tdsService).prepareSyncContext(project, modelName,
+                SyncContext.BI.TABLEAU_CONNECTOR_TDS, SyncContext.ModelElement.CUSTOM_COLS, "localhost", 8080);
+        Mockito.doReturn(syncModel).when(tdsService).exportTDSDimensionsAndMeasuresByNormalUser(syncContext,
+                Lists.newArrayList(), Lists.newArrayList());
+        mockMvc.perform(MockMvcRequestBuilders.get("/api/models/bi_export").param("model_name", modelName)
+                .param("project", project).param("export_as", "TABLEAU_CONNECTOR_TDS").param("element", "CUSTOM_COLS")
+                .param("server_host", "localhost").param("server_port", "8080").param("dimensions", "")
+                .param("measures", "").contentType(MediaType.APPLICATION_JSON)
+                .accept(MediaType.parseMediaType(HTTP_VND_APACHE_KYLIN_V4_PUBLIC_JSON)))
+                .andExpect(MockMvcResultMatchers.status().isOk());
+    }
+
     @Test
     public void testUpdateModelStatus() throws Exception {
         String project = "default";
@@ -566,6 +633,8 @@ public class OpenModelControllerTest extends NLocalFileMetadataTestCase {
         OpenModelRequest request = new OpenModelRequest();
         request.setProject(project);
         request.setModelName(modelAlias);
+        Mockito.doReturn(Mockito.mock(BuildBaseIndexResponse.class)).when(fusionModelService)
+                .updateDataModelSemantic(request.getProject(), request);
         mockMvc.perform(MockMvcRequestBuilders.put("/api/models/modification").contentType(MediaType.APPLICATION_JSON)
                 .content(JsonUtil.writeValueAsString(request))
                 .accept(MediaType.parseMediaType(HTTP_VND_APACHE_KYLIN_V4_PUBLIC_JSON)))
diff --git a/src/metadata-server/src/test/java/org/apache/kylin/rest/controller/NModelControllerTest.java b/src/metadata-server/src/test/java/org/apache/kylin/rest/controller/NModelControllerTest.java
index 7fccbc44aa..47520fa814 100644
--- a/src/metadata-server/src/test/java/org/apache/kylin/rest/controller/NModelControllerTest.java
+++ b/src/metadata-server/src/test/java/org/apache/kylin/rest/controller/NModelControllerTest.java
@@ -62,9 +62,9 @@ import org.apache.kylin.rest.response.NDataSegmentResponse;
 import org.apache.kylin.rest.response.RelatedModelResponse;
 import org.apache.kylin.rest.service.FusionModelService;
 import org.apache.kylin.rest.service.ModelService;
-import org.apache.kylin.tool.bisync.BISyncModel;
-import org.apache.kylin.tool.bisync.BISyncTool;
+import org.apache.kylin.rest.service.ModelTdsService;
 import org.apache.kylin.tool.bisync.SyncContext;
+import org.apache.kylin.tool.bisync.model.SyncModel;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
@@ -96,6 +96,9 @@ public class NModelControllerTest extends NLocalFileMetadataTestCase {
     @Mock
     private ModelService modelService;
 
+    @Mock
+    private ModelTdsService tdsService;
+
     @Mock
     private FusionModelService fusionModelService;
 
@@ -673,7 +676,7 @@ public class NModelControllerTest extends NLocalFileMetadataTestCase {
         UpdateMultiPartitionValueRequest request = new UpdateMultiPartitionValueRequest();
         request.setProject("default");
         List<String[]> partition_values = Lists.newArrayList();
-        String[] value = new String[]{"5"};
+        String[] value = new String[] { "5" };
         partition_values.add(value);
         request.setSubPartitionValues(partition_values);
         Mockito.doNothing().when(modelService).addMultiPartitionValues(request.getProject(),
@@ -713,64 +716,18 @@ public class NModelControllerTest extends NLocalFileMetadataTestCase {
     }
 
     @Test
-    public void testBIExportByADMIN() throws Exception {
-        String project = "default";
-        String modelName = "741ca86a-1f13-46da-a59f-95fb68615e3a";
-        SyncContext syncContext = new SyncContext();
-        syncContext.setProjectName(project);
-        syncContext.setModelId(modelName);
-        syncContext.setTargetBI(SyncContext.BI.TABLEAU_CONNECTOR_TDS);
-        syncContext.setModelElement(SyncContext.ModelElement.AGG_INDEX_AND_TABLE_INDEX_COL);
-        syncContext.setHost("localhost");
-        syncContext.setPort(8080);
-        syncContext.setDataflow(NDataflowManager.getInstance(getTestConfig(), project).getDataflow(modelName));
-        syncContext.setKylinConfig(getTestConfig());
-        BISyncModel syncModel = BISyncTool.dumpToBISyncModel(syncContext);
-        Mockito.doReturn(syncContext).when(modelService).getSyncContext(project, modelName,
-                SyncContext.BI.TABLEAU_CONNECTOR_TDS, SyncContext.ModelElement.CUSTOM_COLS, "localhost", 8080);
-        Mockito.doReturn(syncModel).when(modelService).exportTDSDimensionsAndMeasuresByAdmin(syncContext,
-                Lists.newArrayList(), Lists.newArrayList());
-        mockMvc.perform(MockMvcRequestBuilders.get("/api/models/bi_export").param("model", modelName)
-                .param("project", project).param("export_as", "TABLEAU_CONNECTOR_TDS").param("element", "CUSTOM_COLS")
-                .param("server_host", "localhost").param("server_port", "8080").param("dimensions", "")
-                .param("measures", "").contentType(MediaType.APPLICATION_JSON)
-                .accept(MediaType.parseMediaType(HTTP_VND_APACHE_KYLIN_JSON)))
-                .andExpect(MockMvcResultMatchers.status().isOk());
-    }
-
-    @Test
-    public void testBIExportByNormalUser() throws Exception {
+    public void testValidateExport() throws Exception {
         String project = "default";
         String modelName = "741ca86a-1f13-46da-a59f-95fb68615e3a";
         SyncContext syncContext = new SyncContext();
         syncContext.setProjectName(project);
         syncContext.setModelId(modelName);
-        syncContext.setTargetBI(SyncContext.BI.TABLEAU_CONNECTOR_TDS);
-        syncContext.setModelElement(SyncContext.ModelElement.AGG_INDEX_AND_TABLE_INDEX_COL);
-        syncContext.setHost("localhost");
-        syncContext.setPort(8080);
-        syncContext.setDataflow(NDataflowManager.getInstance(getTestConfig(), project).getDataflow(modelName));
-        syncContext.setKylinConfig(getTestConfig());
-        BISyncModel syncModel = BISyncTool.dumpToBISyncModel(syncContext);
-        SecurityContextHolder.getContext()
-                .setAuthentication(new TestingAuthenticationToken("u1", "ANALYST", Constant.ROLE_ANALYST));
-        Mockito.doReturn(syncContext).when(modelService).getSyncContext(project, modelName,
+        syncContext.setModelElement(SyncContext.ModelElement.ALL_COLS);
+        SyncModel syncModel = Mockito.mock(SyncModel.class);
+        Mockito.doReturn(syncContext).when(tdsService).prepareSyncContext(project, modelName,
                 SyncContext.BI.TABLEAU_CONNECTOR_TDS, SyncContext.ModelElement.CUSTOM_COLS, "localhost", 8080);
-        Mockito.doReturn(syncModel).when(modelService).exportTDSDimensionsAndMeasuresByNormalUser(syncContext,
-                Lists.newArrayList(), Lists.newArrayList());
-        mockMvc.perform(MockMvcRequestBuilders.get("/api/models/bi_export").param("model", modelName)
-                .param("project", project).param("export_as", "TABLEAU_CONNECTOR_TDS").param("element", "CUSTOM_COLS")
-                .param("server_host", "localhost").param("server_port", "8080").param("dimensions", "")
-                .param("measures", "").contentType(MediaType.APPLICATION_JSON)
-                .accept(MediaType.parseMediaType(HTTP_VND_APACHE_KYLIN_JSON)))
-                .andExpect(MockMvcResultMatchers.status().isOk());
-    }
-
-    @Test
-    public void testValidateExport() throws Exception {
-        String project = "default";
-        String modelName = "741ca86a-1f13-46da-a59f-95fb68615e3a";
-        when(modelService.validateExport(project, modelName)).thenReturn(Boolean.TRUE);
+        Mockito.doReturn(syncModel).when(tdsService).exportModel(syncContext);
+        Mockito.doReturn(Boolean.TRUE).when(tdsService).preCheckNameConflict(syncModel);
         mockMvc.perform(MockMvcRequestBuilders.get("/api/models/validate_export").param("model", modelName)
                 .param("project", project).contentType(MediaType.APPLICATION_JSON))
                 .andExpect(MockMvcResultMatchers.status().isOk());
@@ -789,10 +746,9 @@ public class NModelControllerTest extends NLocalFileMetadataTestCase {
         syncContext.setPort(8080);
         syncContext.setDataflow(NDataflowManager.getInstance(getTestConfig(), project).getDataflow(modelName));
         syncContext.setKylinConfig(getTestConfig());
-        BISyncModel syncModel = BISyncTool.dumpToBISyncModel(syncContext);
-        Mockito.doReturn(syncModel).when(modelService).exportModel(project, modelName,
-                SyncContext.BI.TABLEAU_CONNECTOR_TDS, SyncContext.ModelElement.AGG_INDEX_AND_TABLE_INDEX_COL,
-                "localhost", 8080);
+        syncContext.setAdmin(true);
+        SyncModel syncModel = Mockito.mock(SyncModel.class);
+        Mockito.doReturn(syncModel).when(tdsService).exportModel(syncContext);
         mockMvc.perform(MockMvcRequestBuilders.get("/api/models/{model}/export", modelName).param("project", project)
                 .param("export_as", "TABLEAU_CONNECTOR_TDS").param("element", "AGG_INDEX_AND_TABLE_INDEX_COL")
                 .param("server_host", "localhost").param("server_port", "8080").contentType(MediaType.APPLICATION_JSON)
diff --git a/src/modeling-service/src/main/java/org/apache/kylin/rest/service/AbstractModelService.java b/src/modeling-service/src/main/java/org/apache/kylin/rest/service/AbstractModelService.java
new file mode 100644
index 0000000000..b46314b2e1
--- /dev/null
+++ b/src/modeling-service/src/main/java/org/apache/kylin/rest/service/AbstractModelService.java
@@ -0,0 +1,140 @@
+/*
+ * 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.kylin.rest.service;
+
+import static org.apache.kylin.common.exception.ServerErrorCode.FAILED_UPDATE_MODEL;
+import static org.apache.kylin.common.exception.code.ErrorCodeServer.MODEL_ID_NOT_EXIST;
+import static org.apache.kylin.common.exception.code.ErrorCodeServer.MODEL_NAME_EMPTY;
+import static org.apache.kylin.common.exception.code.ErrorCodeServer.MODEL_NAME_INVALID;
+import static org.apache.kylin.common.exception.code.ErrorCodeServer.MODEL_NAME_NOT_EXIST;
+import static org.apache.kylin.common.exception.code.ErrorCodeServer.MODEL_NOT_EXIST;
+
+import java.util.Arrays;
+import java.util.Set;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.kylin.common.exception.KylinException;
+import org.apache.kylin.common.msg.MsgPicker;
+import org.apache.kylin.metadata.acl.AclTCRManager;
+import org.apache.kylin.metadata.cube.model.IndexPlan;
+import org.apache.kylin.metadata.cube.model.NIndexPlanManager;
+import org.apache.kylin.metadata.model.ColumnDesc;
+import org.apache.kylin.metadata.model.NDataModel;
+import org.apache.kylin.metadata.model.NDataModelManager;
+import org.apache.kylin.metadata.model.NTableMetadataManager;
+import org.apache.kylin.rest.util.AclEvaluate;
+import org.apache.kylin.rest.util.AclPermissionUtil;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import com.google.common.collect.Sets;
+
+import lombok.val;
+import lombok.var;
+
+public class AbstractModelService extends BasicService {
+
+    public static final String VALID_NAME_FOR_MODEL = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890_";
+
+    @Autowired
+    public AclEvaluate aclEvaluate;
+
+    @Autowired
+    public AccessService accessService;
+
+    public void checkModelPermission(String project, String modelId) {
+        String userName = aclEvaluate.getCurrentUserName();
+        Set<String> groups = getCurrentUserGroups();
+        if (AclPermissionUtil.isAdmin() || AclPermissionUtil.isAdminInProject(project, groups)) {
+            return;
+        }
+        Set<String> allAuthTables = Sets.newHashSet();
+        Set<String> allAuthColumns = Sets.newHashSet();
+        var auths = getManager(AclTCRManager.class, project).getAuthTablesAndColumns(project, userName, true);
+        allAuthTables.addAll(auths.getTables());
+        allAuthColumns.addAll(auths.getColumns());
+        for (val group : groups) {
+            auths = getManager(AclTCRManager.class, project).getAuthTablesAndColumns(project, group, false);
+            allAuthTables.addAll(auths.getTables());
+            allAuthColumns.addAll(auths.getColumns());
+        }
+
+        NDataModel model = getModelById(modelId, project);
+        Set<String> tablesInModel = Sets.newHashSet();
+        model.getJoinTables().forEach(table -> tablesInModel.add(table.getTable()));
+        tablesInModel.add(model.getRootFactTableName());
+        tablesInModel.forEach(table -> {
+            if (!allAuthTables.contains(table)) {
+                throw new KylinException(FAILED_UPDATE_MODEL, MsgPicker.getMsg().getModelModifyAbandon(table));
+            }
+        });
+        tablesInModel.stream().filter(allAuthTables::contains).forEach(table -> {
+            ColumnDesc[] columnDescs = NTableMetadataManager.getInstance(getConfig(), project).getTableDesc(table)
+                    .getColumns();
+            Arrays.stream(columnDescs).map(column -> table + "." + column.getName()).forEach(column -> {
+                if (!allAuthColumns.contains(column)) {
+                    throw new KylinException(FAILED_UPDATE_MODEL, MsgPicker.getMsg().getModelModifyAbandon(column));
+                }
+            });
+        });
+    }
+
+    public NDataModel getModelById(String modelId, String project) {
+        NDataModelManager modelManager = getManager(NDataModelManager.class, project);
+        NDataModel nDataModel = modelManager.getDataModelDesc(modelId);
+        if (null == nDataModel) {
+            throw new KylinException(MODEL_ID_NOT_EXIST, modelId);
+        }
+        return nDataModel;
+    }
+
+    public NDataModel getModelByAlias(String modelAlias, String project) {
+        NDataModelManager modelManager = getManager(NDataModelManager.class, project);
+        NDataModel nDataModel = modelManager.getDataModelDescByAlias(modelAlias);
+        if (null == nDataModel) {
+            throw new KylinException(MODEL_NAME_NOT_EXIST, modelAlias);
+        }
+        return nDataModel;
+    }
+
+    public Set<String> listAllModelIdsInProject(String project) {
+        NDataModelManager dataModelManager = getManager(NDataModelManager.class, project);
+        return dataModelManager.listAllModelIds();
+    }
+
+    public IndexPlan getIndexPlan(String modelId, String project) {
+        NIndexPlanManager indexPlanManager = getManager(NIndexPlanManager.class, project);
+        return indexPlanManager.getIndexPlan(modelId);
+    }
+
+    public void primaryCheck(NDataModel modelDesc) {
+        if (modelDesc == null) {
+            throw new KylinException(MODEL_NOT_EXIST);
+        }
+
+        String modelAlias = modelDesc.getAlias();
+
+        if (StringUtils.isEmpty(modelAlias)) {
+            throw new KylinException(MODEL_NAME_EMPTY);
+        }
+        if (!StringUtils.containsOnly(modelAlias, VALID_NAME_FOR_MODEL)) {
+            throw new KylinException(MODEL_NAME_INVALID, modelAlias);
+        }
+    }
+
+}
diff --git a/src/modeling-service/src/main/java/org/apache/kylin/rest/service/FusionModelService.java b/src/modeling-service/src/main/java/org/apache/kylin/rest/service/FusionModelService.java
index 23f2e11328..84bf26d971 100644
--- a/src/modeling-service/src/main/java/org/apache/kylin/rest/service/FusionModelService.java
+++ b/src/modeling-service/src/main/java/org/apache/kylin/rest/service/FusionModelService.java
@@ -26,10 +26,9 @@ import org.apache.kylin.common.KylinConfig;
 import org.apache.kylin.common.exception.KylinException;
 import org.apache.kylin.common.exception.ServerErrorCode;
 import org.apache.kylin.common.msg.MsgPicker;
+import org.apache.kylin.common.scheduler.EventBusFactory;
 import org.apache.kylin.common.util.JsonUtil;
 import org.apache.kylin.common.util.Pair;
-import org.apache.kylin.rest.response.DataResult;
-import org.apache.kylin.common.scheduler.EventBusFactory;
 import org.apache.kylin.metadata.model.FusionModel;
 import org.apache.kylin.metadata.model.FusionModelManager;
 import org.apache.kylin.metadata.model.NDataModel;
@@ -39,6 +38,7 @@ import org.apache.kylin.rest.request.IndexesToSegmentsRequest;
 import org.apache.kylin.rest.request.ModelRequest;
 import org.apache.kylin.rest.request.OwnerChangeRequest;
 import org.apache.kylin.rest.response.BuildBaseIndexResponse;
+import org.apache.kylin.rest.response.DataResult;
 import org.apache.kylin.rest.response.JobInfoResponse;
 import org.apache.kylin.rest.response.JobInfoResponseWithFailure;
 import org.apache.kylin.rest.response.NDataModelResponse;
@@ -53,7 +53,7 @@ import lombok.extern.slf4j.Slf4j;
 
 @Slf4j
 @Service("fusionModelService")
-public class FusionModelService extends BasicService implements TableFusionModelSupporter {
+public class FusionModelService extends AbstractModelService implements TableFusionModelSupporter {
 
     @Autowired
     private ModelService modelService;
diff --git a/src/modeling-service/src/main/java/org/apache/kylin/rest/service/ModelService.java b/src/modeling-service/src/main/java/org/apache/kylin/rest/service/ModelService.java
index 474b10fe82..7082c4890b 100644
--- a/src/modeling-service/src/main/java/org/apache/kylin/rest/service/ModelService.java
+++ b/src/modeling-service/src/main/java/org/apache/kylin/rest/service/ModelService.java
@@ -42,13 +42,11 @@ import static org.apache.kylin.common.exception.ServerErrorCode.INVALID_PARTITIO
 import static org.apache.kylin.common.exception.ServerErrorCode.INVALID_RANGE;
 import static org.apache.kylin.common.exception.ServerErrorCode.INVALID_SEGMENT_PARAMETER;
 import static org.apache.kylin.common.exception.ServerErrorCode.MODEL_BROKEN;
-import static org.apache.kylin.common.exception.ServerErrorCode.MODEL_EXPORT_ERROR;
 import static org.apache.kylin.common.exception.ServerErrorCode.MODEL_ONLINE_ABANDON;
 import static org.apache.kylin.common.exception.ServerErrorCode.PERMISSION_DENIED;
 import static org.apache.kylin.common.exception.ServerErrorCode.STREAMING_INDEX_UPDATE_DISABLE;
 import static org.apache.kylin.common.exception.ServerErrorCode.TABLE_NOT_EXIST;
 import static org.apache.kylin.common.exception.ServerErrorCode.TIMESTAMP_COLUMN_NOT_EXIST;
-import static org.apache.kylin.common.exception.ServerErrorCode.UNAUTHORIZED_ENTITY;
 import static org.apache.kylin.common.exception.ServerErrorCode.VIEW_PARTITION_DATE_FORMAT_DETECTION_FORBIDDEN;
 import static org.apache.kylin.common.exception.code.ErrorCodeServer.COMPUTED_COLUMN_CONFLICT;
 import static org.apache.kylin.common.exception.code.ErrorCodeServer.COMPUTED_COLUMN_NAME_OR_EXPR_EMPTY;
@@ -56,10 +54,7 @@ import static org.apache.kylin.common.exception.code.ErrorCodeServer.DATETIME_FO
 import static org.apache.kylin.common.exception.code.ErrorCodeServer.DATETIME_FORMAT_PARSE_ERROR;
 import static org.apache.kylin.common.exception.code.ErrorCodeServer.MODEL_ID_NOT_EXIST;
 import static org.apache.kylin.common.exception.code.ErrorCodeServer.MODEL_NAME_DUPLICATE;
-import static org.apache.kylin.common.exception.code.ErrorCodeServer.MODEL_NAME_EMPTY;
-import static org.apache.kylin.common.exception.code.ErrorCodeServer.MODEL_NAME_INVALID;
 import static org.apache.kylin.common.exception.code.ErrorCodeServer.MODEL_NAME_NOT_EXIST;
-import static org.apache.kylin.common.exception.code.ErrorCodeServer.MODEL_NOT_EXIST;
 import static org.apache.kylin.common.exception.code.ErrorCodeServer.PARAMETER_INVALID_SUPPORT_LIST;
 import static org.apache.kylin.common.exception.code.ErrorCodeServer.PROJECT_NOT_EXIST;
 import static org.apache.kylin.common.exception.code.ErrorCodeServer.SEGMENT_BUILD_RANGE_OVERLAP;
@@ -133,7 +128,6 @@ import org.apache.kylin.common.util.JsonUtil;
 import org.apache.kylin.common.util.Pair;
 import org.apache.kylin.common.util.RandomUtil;
 import org.apache.kylin.common.util.StringUtil;
-import org.apache.kylin.engine.spark.smarter.IndexDependencyParser;
 import org.apache.kylin.engine.spark.utils.ComputedColumnEvalUtil;
 import org.apache.kylin.job.SecondStorageJobParamUtil;
 import org.apache.kylin.job.common.SegmentUtil;
@@ -146,7 +140,6 @@ import org.apache.kylin.job.handler.SecondStorageSegmentCleanJobHandler;
 import org.apache.kylin.job.handler.SecondStorageSegmentLoadJobHandler;
 import org.apache.kylin.job.manager.JobManager;
 import org.apache.kylin.job.model.JobParam;
-import org.apache.kylin.metadata.acl.AclTCRDigest;
 import org.apache.kylin.metadata.acl.AclTCRManager;
 import org.apache.kylin.metadata.acl.NDataModelAclParams;
 import org.apache.kylin.metadata.cube.cuboid.NAggregationGroup;
@@ -163,6 +156,7 @@ import org.apache.kylin.metadata.cube.model.NDataflowManager;
 import org.apache.kylin.metadata.cube.model.NDataflowUpdate;
 import org.apache.kylin.metadata.cube.model.NIndexPlanManager;
 import org.apache.kylin.metadata.cube.model.RuleBasedIndex;
+import org.apache.kylin.metadata.favorite.FavoriteRuleManager;
 import org.apache.kylin.metadata.model.AutoMergeTimeEnum;
 import org.apache.kylin.metadata.model.ColumnDesc;
 import org.apache.kylin.metadata.model.ComputedColumnDesc;
@@ -196,7 +190,6 @@ import org.apache.kylin.metadata.model.UpdateImpact;
 import org.apache.kylin.metadata.model.VolatileRange;
 import org.apache.kylin.metadata.model.schema.AffectedModelContext;
 import org.apache.kylin.metadata.model.tool.CalciteParser;
-import org.apache.kylin.metadata.model.util.ComputedColumnUtil;
 import org.apache.kylin.metadata.model.util.MultiPartitionUtil;
 import org.apache.kylin.metadata.model.util.scd2.SCD2CondChecker;
 import org.apache.kylin.metadata.project.EnhancedUnitOfWork;
@@ -204,6 +197,7 @@ import org.apache.kylin.metadata.project.NProjectManager;
 import org.apache.kylin.metadata.project.ProjectInstance;
 import org.apache.kylin.metadata.realization.RealizationStatusEnum;
 import org.apache.kylin.metadata.streaming.KafkaConfig;
+import org.apache.kylin.query.util.KapQueryUtil;
 import org.apache.kylin.query.util.PushDownUtil;
 import org.apache.kylin.query.util.QueryParams;
 import org.apache.kylin.rest.aspect.Transaction;
@@ -244,12 +238,10 @@ import org.apache.kylin.rest.response.SegmentCheckResponse;
 import org.apache.kylin.rest.response.SegmentPartitionResponse;
 import org.apache.kylin.rest.response.SegmentRangeResponse;
 import org.apache.kylin.rest.response.SimplifiedMeasure;
-import org.apache.kylin.rest.security.MutableAclRecord;
 import org.apache.kylin.rest.service.params.FullBuildSegmentParams;
 import org.apache.kylin.rest.service.params.IncrementBuildSegmentParams;
 import org.apache.kylin.rest.service.params.MergeSegmentParams;
 import org.apache.kylin.rest.service.params.ModelQueryParams;
-import org.apache.kylin.rest.util.AclEvaluate;
 import org.apache.kylin.rest.util.AclPermissionUtil;
 import org.apache.kylin.rest.util.ModelTriple;
 import org.apache.kylin.rest.util.ModelUtils;
@@ -260,10 +252,6 @@ import org.apache.kylin.source.adhocquery.PushDownConverterKeyWords;
 import org.apache.kylin.streaming.event.StreamingJobDropEvent;
 import org.apache.kylin.streaming.event.StreamingJobKillEvent;
 import org.apache.kylin.streaming.manager.StreamingJobManager;
-import org.apache.kylin.tool.bisync.BISyncModel;
-import org.apache.kylin.tool.bisync.BISyncTool;
-import org.apache.kylin.tool.bisync.SyncContext;
-import org.apache.kylin.tool.bisync.model.MeasureDef;
 import org.apache.spark.sql.SparderEnv;
 import org.apache.spark.sql.SparkSession;
 import org.slf4j.Logger;
@@ -286,8 +274,6 @@ import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
 
 import io.kyligence.kap.guava20.shaded.common.base.Supplier;
-import org.apache.kylin.metadata.favorite.FavoriteRuleManager;
-import org.apache.kylin.query.util.KapQueryUtil;
 import io.kyligence.kap.secondstorage.SecondStorage;
 import io.kyligence.kap.secondstorage.SecondStorageNodeHelper;
 import io.kyligence.kap.secondstorage.SecondStorageUpdater;
@@ -304,15 +290,13 @@ import lombok.extern.slf4j.Slf4j;
 
 @Slf4j
 @Component("modelService")
-public class ModelService extends BasicService implements TableModelSupporter, ProjectModelSupporter {
+public class ModelService extends AbstractModelService implements TableModelSupporter, ProjectModelSupporter {
 
     private static final Logger logger = LoggerFactory.getLogger(ModelService.class);
 
     private static final String LAST_MODIFY = "last_modify";
     public static final String REC_COUNT = "recommendations_count";
 
-    public static final String VALID_NAME_FOR_MODEL = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890_";
-
     public static final String VALID_NAME_FOR_DIMENSION = "^[\\u4E00-\\u9FA5a-zA-Z0-9 _\\-()%?()]+$";
 
     public static final String VALID_NAME_FOR_MEASURE = "^[\\u4E00-\\u9FA5a-zA-Z0-9 _\\-()%?().]+$";
@@ -334,15 +318,9 @@ public class ModelService extends BasicService implements TableModelSupporter, P
     @Qualifier("segmentHelper")
     private SegmentHelperSupporter segmentHelper;
 
-    @Autowired
-    public AclEvaluate aclEvaluate;
-
     @Autowired
     private ProjectService projectService;
 
-    @Autowired
-    private AccessService accessService;
-
     @Setter
     @Autowired(required = false)
     private ModelQuerySupporter modelQuerySupporter;
@@ -359,24 +337,6 @@ public class ModelService extends BasicService implements TableModelSupporter, P
     @Autowired(required = false)
     private List<ModelChangeSupporter> modelChangeSupporters = Lists.newArrayList();
 
-    public NDataModel getModelById(String modelId, String project) {
-        NDataModelManager modelManager = getManager(NDataModelManager.class, project);
-        NDataModel nDataModel = modelManager.getDataModelDesc(modelId);
-        if (null == nDataModel) {
-            throw new KylinException(MODEL_ID_NOT_EXIST, modelId);
-        }
-        return nDataModel;
-    }
-
-    public NDataModel getModelByAlias(String modelAlias, String project) {
-        NDataModelManager modelManager = getManager(NDataModelManager.class, project);
-        NDataModel nDataModel = modelManager.getDataModelDescByAlias(modelAlias);
-        if (null == nDataModel) {
-            throw new KylinException(MODEL_NAME_NOT_EXIST, modelAlias);
-        }
-        return nDataModel;
-    }
-
     /**
      * for 3x rest api
      *
@@ -875,10 +835,10 @@ public class ModelService extends BasicService implements TableModelSupporter, P
         return modelResponseStatus;
     }
 
-    private long getEmptyIndexesCount(String project, String id) {
+    private long getEmptyIndexesCount(String project, String modelId) {
         val indexPlanManager = getManager(NIndexPlanManager.class, project);
-        val indexPlan = indexPlanManager.getIndexPlan(id);
-        return indexPlan.getAllLayoutsReadOnly().size() - indexPlanManager.getAvailableIndexesCount(project, id);
+        val indexPlan = indexPlanManager.getIndexPlan(modelId);
+        return indexPlan.getAllLayoutsReadOnly().size() - indexPlanManager.getAvailableIndexesCount(project, modelId);
     }
 
     private List<NDataModelResponse> sortExpansionRate(boolean reverse, List<NDataModelResponse> filterModels) {
@@ -934,7 +894,7 @@ public class ModelService extends BasicService implements TableModelSupporter, P
                 && isSCD2;
     }
 
-    protected RealizationStatusEnum getModelStatus(String modelId, String projectName) {
+    public RealizationStatusEnum getModelStatus(String modelId, String projectName) {
         val indexPlan = getIndexPlan(modelId, projectName);
         if (indexPlan != null) {
             return getManager(NDataflowManager.class, projectName).getDataflow(indexPlan.getUuid()).getStatus();
@@ -1366,12 +1326,6 @@ public class ModelService extends BasicService implements TableModelSupporter, P
         return relatedModel;
     }
 
-    @VisibleForTesting
-    public IndexPlan getIndexPlan(String modelId, String project) {
-        NIndexPlanManager indexPlanManager = getManager(NIndexPlanManager.class, project);
-        return indexPlanManager.getIndexPlan(modelId);
-    }
-
     private void checkAliasExist(String modelId, String newAlias, String project) {
         if (!checkModelAliasUniqueness(modelId, newAlias, project)) {
             throw new KylinException(MODEL_NAME_DUPLICATE, newAlias);
@@ -1539,11 +1493,6 @@ public class ModelService extends BasicService implements TableModelSupporter, P
         }
     }
 
-    public Set<String> listAllModelIdsInProject(String project) {
-        NDataModelManager dataModelManager = getManager(NDataModelManager.class, project);
-        return dataModelManager.listAllModelIds();
-    }
-
     @Transaction(project = 0)
     public void offlineAllModelsInProject(String project) {
         aclEvaluate.checkProjectWritePermission(project);
@@ -1848,7 +1797,7 @@ public class ModelService extends BasicService implements TableModelSupporter, P
                         .anyMatch(column -> column.getName().equalsIgnoreCase(columnName));
                 if (!hasPartitionColumn && !modelRequest.getDimensionNameIdMap().containsKey(fullColumnName)) {
                     throw new KylinException(TIMESTAMP_COLUMN_NOT_EXIST,
-                            String.format(Locale.ROOT, MsgPicker.getMsg().getTimestampPartitionColumnNotExist()));
+                            MsgPicker.getMsg().getTimestampPartitionColumnNotExist());
                 }
             }
         }
@@ -2624,21 +2573,6 @@ public class ModelService extends BasicService implements TableModelSupporter, P
         }
     }
 
-    public void primaryCheck(NDataModel modelDesc) {
-        if (modelDesc == null) {
-            throw new KylinException(MODEL_NOT_EXIST);
-        }
-
-        String modelAlias = modelDesc.getAlias();
-
-        if (StringUtils.isEmpty(modelAlias)) {
-            throw new KylinException(MODEL_NAME_EMPTY);
-        }
-        if (!StringUtils.containsOnly(modelAlias, VALID_NAME_FOR_MODEL)) {
-            throw new KylinException(MODEL_NAME_INVALID, modelAlias);
-        }
-    }
-
     public ComputedColumnUsageResponse getComputedColumnUsages(String project) {
         aclEvaluate.checkProjectWritePermission(project);
         ComputedColumnUsageResponse ret = new ComputedColumnUsageResponse();
@@ -4007,241 +3941,6 @@ public class ModelService extends BasicService implements TableModelSupporter, P
                 : dataModelDesc.getPartitionDesc().getPartitionDateFormat();
     }
 
-    public BISyncModel exportModel(String projectName, String modelId, SyncContext.BI targetBI,
-            SyncContext.ModelElement modelElement, String host, int port) {
-        SyncContext syncContext = getADMINSyncContext(projectName, modelId, targetBI, modelElement, host, port);
-
-        return BISyncTool.dumpToBISyncModel(syncContext);
-    }
-
-    public BISyncModel exportTDSDimensionsAndMeasuresByNormalUser(SyncContext syncContext, List<String> dimensions,
-            List<String> measures) {
-        Set<String> groups = getCurrentUserGroups();
-        String currentUserName = aclEvaluate.getCurrentUserName();
-        String projectName = syncContext.getProjectName();
-        String modelId = syncContext.getModelId();
-        NDataflow dataflow = getManager(NDataflowManager.class, projectName).getDataflow(modelId);
-        if (dataflow.getStatus() == RealizationStatusEnum.BROKEN) {
-            throw new KylinException(ServerErrorCode.MODEL_BROKEN,
-                    "The model is broken and cannot be exported TDS file");
-        }
-
-        Set<String> authTables = getAllAuthTables(projectName, groups, currentUserName);
-        Set<String> authColumns = getAllAuthColumns(projectName, groups, currentUserName);
-
-        Set<String> newAuthColumns = Sets.newHashSet();
-        dataflow.getModel().getAllTables().forEach(tableRef -> {
-            List<TblColRef> collect = tableRef.getColumns().stream()
-                    .filter(column -> authColumns.contains(column.getCanonicalName())).collect(Collectors.toList());
-            collect.forEach(x -> newAuthColumns.add(x.getAliasDotName()));
-        });
-
-        checkTableHasColumnPermission(syncContext.getModelElement(), projectName, modelId, newAuthColumns, dimensions,
-                measures);
-
-        return BISyncTool.dumpHasPermissionToBISyncModel(syncContext, authTables, newAuthColumns, dimensions, measures);
-    }
-
-    public BISyncModel exportTDSDimensionsAndMeasuresByAdmin(SyncContext syncContext, List<String> dimensions,
-            List<String> measures) {
-        String projectName = syncContext.getProjectName();
-        String modelId = syncContext.getModelId();
-        NDataflow dataflow = getManager(NDataflowManager.class, projectName).getDataflow(modelId);
-        if (dataflow.getStatus() == RealizationStatusEnum.BROKEN) {
-            throw new KylinException(MODEL_BROKEN, "The model is broken and cannot be exported TDS file");
-        }
-        checkModelExportPermission(projectName, modelId);
-        checkModelPermission(projectName, modelId);
-        return BISyncTool.dumpBISyncModel(syncContext, dimensions, measures);
-    }
-
-    public SyncContext getADMINSyncContext(String projectName, String modelId, SyncContext.BI targetBI,
-            SyncContext.ModelElement element, String host, int port) {
-        NDataflow dataflow = getManager(NDataflowManager.class, projectName).getDataflow(modelId);
-        if (dataflow.getStatus() == RealizationStatusEnum.BROKEN) {
-            throw new KylinException(MODEL_BROKEN, "The model is broken and cannot be exported TDS file");
-        }
-        checkModelExportPermission(projectName, modelId);
-        checkModelPermission(projectName, modelId);
-
-        return getSyncContext(projectName, modelId, targetBI, element, host, port);
-    }
-
-    public SyncContext getSyncContext(String projectName, String modelId, SyncContext.BI targetBI,
-            SyncContext.ModelElement modelElement, String host, int port) {
-        SyncContext syncContext = new SyncContext();
-        syncContext.setProjectName(projectName);
-        syncContext.setModelId(modelId);
-        syncContext.setTargetBI(targetBI);
-        syncContext.setModelElement(modelElement);
-        syncContext.setHost(host);
-        syncContext.setPort(port);
-        syncContext.setDataflow(getManager(NDataflowManager.class, projectName).getDataflow(modelId));
-        syncContext.setKylinConfig(getManager(NProjectManager.class).getProject(projectName).getConfig());
-        return syncContext;
-    }
-
-    public void checkTableHasColumnPermission(SyncContext.ModelElement modelElement, String project, String modeId,
-            Set<String> authColumns, List<String> dimensions, List<String> measures) {
-        if (AclPermissionUtil.isAdmin()) {
-            return;
-        }
-        aclEvaluate.checkProjectReadPermission(project);
-
-        NDataModel model = getManager(NDataModelManager.class, project).getDataModelDesc(modeId);
-        long jointCount = model.getJoinTables().stream()
-                .filter(table -> authColumns
-                        .containsAll(Arrays.stream(table.getJoin().getPrimaryKeyColumns())
-                                .map(TblColRef::getAliasDotName).collect(Collectors.toSet()))
-                        && authColumns.containsAll(Arrays.stream(table.getJoin().getForeignKeyColumns())
-                                .map(TblColRef::getAliasDotName).collect(Collectors.toSet())))
-                .count();
-        long singleTableCount = model.getAllTables().stream().filter(ref -> ref.getColumns().stream()
-                .map(TblColRef::getAliasDotName).collect(Collectors.toSet()).stream().anyMatch(authColumns::contains))
-                .count();
-
-        if (jointCount != model.getJoinTables().size() || singleTableCount == 0
-                || (modelElement.equals(SyncContext.ModelElement.CUSTOM_COLS)
-                        && !checkColumnPermission(model, authColumns, dimensions, measures))) {
-            throw new KylinException(ServerErrorCode.INVALID_TABLE_AUTH,
-                    MsgPicker.getMsg().getTableNoColumnsPermission());
-        }
-    }
-
-    public boolean checkColumnPermission(NDataModel model, Set<String> authColumns, List<String> dimensions,
-            List<String> measures) {
-
-        if (!checkDimensionPermission(model, authColumns, dimensions)) {
-            return false;
-        }
-        if (CollectionUtils.isEmpty(measures)) {
-            return true;
-        }
-        List<MeasureDef> authMeasures = model.getEffectiveMeasures().values().stream()
-                .filter(measure -> measures.contains(measure.getName()))
-                .filter(measure -> checkMeasurePermission(authColumns, measure, model)).map(MeasureDef::new)
-                .collect(Collectors.toList());
-        return authMeasures.size() == measures.size();
-
-    }
-
-    private boolean checkDimensionPermission(NDataModel model, Set<String> authColumns, List<String> dimensions) {
-        if (CollectionUtils.isEmpty(dimensions)) {
-            return true;
-        }
-        List<ComputedColumnDesc> computedColumnDescs = model.getComputedColumnDescs().stream()
-                .filter(cc -> dimensions.contains(cc.getFullName())).collect(Collectors.toList());
-
-        long authComputedCount = computedColumnDescs.stream()
-                .filter(cc -> authColumns.containsAll(convertCCToNormalCols(model, cc))).count();
-
-        if (computedColumnDescs.size() != authComputedCount) {
-            return false;
-        }
-
-        List<String> normalColumns = dimensions.stream().filter(column -> !computedColumnDescs.stream()
-                .map(ComputedColumnDesc::getFullName).collect(Collectors.toList()).contains(column))
-                .collect(Collectors.toList());
-        return authColumns.containsAll(normalColumns);
-    }
-
-    public Set<String> convertCCToNormalCols(NDataModel model, ComputedColumnDesc computedColumnDesc) {
-        IndexDependencyParser parser = new IndexDependencyParser(model);
-        try {
-            Set<TblColRef> tblColRefList = parser.unwrapComputeColumn(computedColumnDesc.getInnerExpression());
-            return tblColRefList.stream().map(TblColRef::getAliasDotName).collect(Collectors.toSet());
-        } catch (Exception e) {
-            log.warn("UnWrap computed column {} in project {} model {} exception",
-                    computedColumnDesc.getInnerExpression(), model.getProject(), model.getAlias(), e);
-        }
-        return Collections.emptySet();
-    }
-
-    private boolean checkMeasurePermission(Set<String> authColumns, NDataModel.Measure measure, NDataModel model) {
-        Set<String> measureColumns = measure.getFunction().getParameters().stream()
-                .filter(parameterDesc -> parameterDesc.getColRef() != null)
-                .map(parameterDesc -> parameterDesc.getColRef().getAliasDotName()).collect(Collectors.toSet());
-
-        List<ComputedColumnDesc> computedColumnDescs = model.getComputedColumnDescs().stream()
-                .filter(cc -> measureColumns.contains(cc.getFullName())).collect(Collectors.toList());
-
-        long authComputedCount = computedColumnDescs.stream()
-                .filter(cc -> authColumns.containsAll(convertCCToNormalCols(model, cc))).count();
-
-        if (computedColumnDescs.size() != authComputedCount) {
-            return false;
-        }
-
-        List<String> normalColumns = measureColumns.stream().filter(column -> !computedColumnDescs.stream()
-                .map(ComputedColumnDesc::getFullName).collect(Collectors.toList()).contains(column))
-                .collect(Collectors.toList());
-
-        return authColumns.containsAll(normalColumns);
-    }
-
-    private Set<String> getAllAuthTables(String project, Set<String> groups, String user) {
-        Set<String> allAuthTables = Sets.newHashSet();
-        AclTCRDigest auths = getManager(AclTCRManager.class, project).getAuthTablesAndColumns(project, user, true);
-        allAuthTables.addAll(auths.getTables());
-        for (String group : groups) {
-            auths = getManager(AclTCRManager.class, project).getAuthTablesAndColumns(project, group, false);
-            allAuthTables.addAll(auths.getTables());
-        }
-        return allAuthTables;
-    }
-
-    private Set<String> getAllAuthColumns(String project, Set<String> groups, String user) {
-        Set<String> allAuthColumns = Sets.newHashSet();
-        AclTCRDigest auths = getManager(AclTCRManager.class, project).getAuthTablesAndColumns(project, user, true);
-        allAuthColumns.addAll(auths.getColumns());
-        for (String group : groups) {
-            auths = getManager(AclTCRManager.class, project).getAuthTablesAndColumns(project, group, false);
-            allAuthColumns.addAll(auths.getColumns());
-        }
-        return allAuthColumns;
-    }
-
-    private void checkModelExportPermission(String project, String modeId) {
-        if (AclPermissionUtil.isAdmin()) {
-            return;
-        }
-        aclEvaluate.checkProjectReadPermission(project);
-
-        NDataModel model = getManager(NDataModelManager.class, project).getDataModelDesc(modeId);
-        Map<String, Set<String>> modelTableColumns = new HashMap<>();
-        for (TableRef tableRef : model.getAllTables()) {
-            modelTableColumns.putIfAbsent(tableRef.getTableIdentity(), new HashSet<>());
-            modelTableColumns.get(tableRef.getTableIdentity())
-                    .addAll(tableRef.getColumns().stream().map(TblColRef::getName).collect(Collectors.toSet()));
-        }
-        AclTCRManager aclManager = AclTCRManager.getInstance(KylinConfig.getInstanceFromEnv(), project);
-
-        String currentUserName = AclPermissionUtil.getCurrentUsername();
-        Set<String> groupsOfExecuteUser = accessService.getGroupsOfExecuteUser(currentUserName);
-        MutableAclRecord acl = AclPermissionUtil.getProjectAcl(project);
-        Set<String> groupsInProject = AclPermissionUtil.filterGroupsInProject(groupsOfExecuteUser, acl);
-        if (AclPermissionUtil.isAdminInProject(project, groupsOfExecuteUser)) {
-            return;
-        }
-        AclTCRDigest digest = aclManager.getAllUnauthorizedTableColumn(currentUserName, groupsInProject,
-                modelTableColumns);
-        Set<String> authorizedCC = ComputedColumnUtil
-                .getAuthorizedCC(Arrays.asList(model),
-                        ccSourceCols -> aclManager.isColumnsAuthorized(currentUserName, groupsOfExecuteUser,
-                                ccSourceCols))
-                .stream().map(ccDesc -> ccDesc.getTableIdentity() + "." + ccDesc.getColumnName())
-                .collect(Collectors.toSet());
-        if (digest.getColumns() != null && !digest.getColumns().isEmpty()
-                && digest.getColumns().stream().anyMatch(column -> !authorizedCC.contains(column))) {
-            throw new KylinException(UNAUTHORIZED_ENTITY,
-                    "current user does not have full permission on requesting model");
-        }
-        if (digest.getTables() != null && !digest.getTables().isEmpty()) {
-            throw new KylinException(UNAUTHORIZED_ENTITY,
-                    "current user does not have full permission on requesting model");
-        }
-    }
-
     public List<SegmentPartitionResponse> getSegmentPartitions(String project, String modelId, String segmentId,
             List<String> status, String sortBy, boolean reverse) {
         aclEvaluate.checkProjectReadPermission(project);
@@ -4300,20 +3999,17 @@ public class ModelService extends BasicService implements TableModelSupporter, P
         if (CollectionUtils.isEmpty(partitions)) {
             return;
         }
+        NDataflowManager dataflowManager = getManager(NDataflowManager.class, project);
         if (StringUtils.isNotEmpty(segmentId)) {
             // remove partition in target segment
-            getManager(NDataflowManager.class, project).removeLayoutPartition(modelId, partitions,
-                    Sets.newHashSet(segmentId));
+            dataflowManager.removeLayoutPartition(modelId, partitions, Sets.newHashSet(segmentId));
             // remove partition in target segment
-            getManager(NDataflowManager.class, project).removeSegmentPartition(modelId, partitions,
-                    Sets.newHashSet(segmentId));
+            dataflowManager.removeSegmentPartition(modelId, partitions, Sets.newHashSet(segmentId));
         } else {
             // remove partition in all layouts
-            getManager(NDataflowManager.class, project).removeLayoutPartition(modelId, Sets.newHashSet(partitions),
-                    null);
+            dataflowManager.removeLayoutPartition(modelId, Sets.newHashSet(partitions), null);
             // remove partition in all  segments
-            getManager(NDataflowManager.class, project).removeSegmentPartition(modelId, Sets.newHashSet(partitions),
-                    null);
+            dataflowManager.removeSegmentPartition(modelId, Sets.newHashSet(partitions), null);
             // remove partition in model
             getManager(NDataModelManager.class, project).updateDataModel(modelId, copyForWrite -> {
                 val multiPartitionDesc = copyForWrite.getMultiPartitionDesc();
@@ -4331,43 +4027,6 @@ public class ModelService extends BasicService implements TableModelSupporter, P
         return model;
     }
 
-    public void checkModelPermission(String project, String modelId) {
-        String userName = aclEvaluate.getCurrentUserName();
-        Set<String> groups = getCurrentUserGroups();
-        if (AclPermissionUtil.isAdmin() || AclPermissionUtil.isAdminInProject(project, groups)) {
-            return;
-        }
-        Set<String> allAuthTables = Sets.newHashSet();
-        Set<String> allAuthColumns = Sets.newHashSet();
-        var auths = getManager(AclTCRManager.class, project).getAuthTablesAndColumns(project, userName, true);
-        allAuthTables.addAll(auths.getTables());
-        allAuthColumns.addAll(auths.getColumns());
-        for (val group : groups) {
-            auths = getManager(AclTCRManager.class, project).getAuthTablesAndColumns(project, group, false);
-            allAuthTables.addAll(auths.getTables());
-            allAuthColumns.addAll(auths.getColumns());
-        }
-
-        NDataModel model = getModelById(modelId, project);
-        Set<String> tablesInModel = Sets.newHashSet();
-        model.getJoinTables().forEach(table -> tablesInModel.add(table.getTable()));
-        tablesInModel.add(model.getRootFactTableName());
-        tablesInModel.forEach(table -> {
-            if (!allAuthTables.contains(table)) {
-                throw new KylinException(FAILED_UPDATE_MODEL, MsgPicker.getMsg().getModelModifyAbandon(table));
-            }
-        });
-        tablesInModel.stream().filter(allAuthTables::contains).forEach(table -> {
-            ColumnDesc[] columnDescs = NTableMetadataManager.getInstance(getConfig(), project).getTableDesc(table)
-                    .getColumns();
-            Arrays.stream(columnDescs).map(column -> table + "." + column.getName()).forEach(column -> {
-                if (!allAuthColumns.contains(column)) {
-                    throw new KylinException(FAILED_UPDATE_MODEL, MsgPicker.getMsg().getModelModifyAbandon(column));
-                }
-            });
-        });
-    }
-
     public InvalidIndexesResponse detectInvalidIndexes(ModelRequest request) {
         String project = request.getProject();
         aclEvaluate.checkProjectReadPermission(project);
@@ -4540,46 +4199,6 @@ public class ModelService extends BasicService implements TableModelSupporter, P
         request.setProject(projectName);
     }
 
-    public Boolean validateExport(String projectName, String modelId) {
-        val dataModelDesc = getModelById(modelId, projectName);
-        List<MeasureDef> measureDefs = dataModelDesc.getEffectiveMeasures().values().stream().map(MeasureDef::new)
-                .collect(Collectors.toList());
-        val measures = measureDefs.stream().map(measureDef -> measureDef.getMeasure().getName())
-                .collect(Collectors.toSet());
-        val columns = dataModelDesc.getAllNamedColumns().stream().filter(column -> !column.isDimension())
-                .map(NDataModel.NamedColumn::getName).collect(Collectors.toSet());
-
-        // check duplicate name in measures and other model columns
-        val duplicateMeasureAndModelColumn = Sets.intersection(measures, columns);
-        if (CollectionUtils.isNotEmpty(duplicateMeasureAndModelColumn)) {
-            val duplicateName = duplicateMeasureAndModelColumn.stream().findFirst().orElse(null);
-            throw new KylinException(MODEL_EXPORT_ERROR, String.format(Locale.ROOT,
-                    MsgPicker.getMsg().getDuplicateModelColumnAndMeasureName(), duplicateName, duplicateName));
-        }
-
-        // check duplicate name in dimension columns and measures
-        val dimensionCols = dataModelDesc.getEffectiveDimensions().values().stream().map(TblColRef::getColumnDesc)
-                .map(ColumnDesc::getName).collect(Collectors.toSet());
-        val duplicateDimColAndMeasureNames = Sets.intersection(dimensionCols, measures);
-        if (CollectionUtils.isNotEmpty(duplicateDimColAndMeasureNames)) {
-            val duplicateName = duplicateDimColAndMeasureNames.stream().findFirst().orElse(null);
-            throw new KylinException(MODEL_EXPORT_ERROR, String.format(Locale.ROOT,
-                    MsgPicker.getMsg().getDuplicateDimensionColAndMeasureName(), duplicateName, duplicateName));
-        }
-
-        // check duplicate name in dimensions and measures
-        val dimNames = dataModelDesc.getAllNamedColumns().stream().filter(NDataModel.NamedColumn::isDimension)
-                .map(NDataModel.NamedColumn::getName).collect(Collectors.toSet());
-        val duplicateDimMeasureNames = Sets.intersection(dimNames, measures);
-        if (CollectionUtils.isNotEmpty(duplicateDimMeasureNames)) {
-            val duplicateName = duplicateDimMeasureNames.stream().findFirst().orElse(null);
-            throw new KylinException(MODEL_EXPORT_ERROR, String.format(Locale.ROOT,
-                    MsgPicker.getMsg().getDuplicateDimensionNameAndMeasureName(), duplicateName, duplicateName));
-        }
-
-        return true;
-    }
-
     public void checkCCEmpty(ModelRequest modelRequest) {
         List<ComputedColumnDesc> ccList = modelRequest.getComputedColumnDescs();
         if (CollectionUtils.isEmpty(ccList)) {
diff --git a/src/modeling-service/src/main/java/org/apache/kylin/rest/service/ModelTdsService.java b/src/modeling-service/src/main/java/org/apache/kylin/rest/service/ModelTdsService.java
new file mode 100644
index 0000000000..df277831e5
--- /dev/null
+++ b/src/modeling-service/src/main/java/org/apache/kylin/rest/service/ModelTdsService.java
@@ -0,0 +1,336 @@
+/*
+ * 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.kylin.rest.service;
+
+import static org.apache.kylin.common.exception.ServerErrorCode.MODEL_BROKEN;
+import static org.apache.kylin.common.exception.ServerErrorCode.UNAUTHORIZED_ENTITY;
+import static org.apache.kylin.common.exception.code.ErrorCodeServer.MODEL_TDS_EXPORT_COLUMN_AND_MEASURE_NAME_CONFLICT;
+import static org.apache.kylin.common.exception.code.ErrorCodeServer.MODEL_TDS_EXPORT_DIM_COL_AND_MEASURE_NAME_CONFLICT;
+
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.kylin.common.KylinConfig;
+import org.apache.kylin.common.exception.CommonErrorCode;
+import org.apache.kylin.common.exception.KylinException;
+import org.apache.kylin.common.exception.ServerErrorCode;
+import org.apache.kylin.common.msg.MsgPicker;
+import org.apache.kylin.engine.spark.smarter.IndexDependencyParser;
+import org.apache.kylin.metadata.acl.AclTCRDigest;
+import org.apache.kylin.metadata.acl.AclTCRManager;
+import org.apache.kylin.metadata.cube.model.NDataflow;
+import org.apache.kylin.metadata.cube.model.NDataflowManager;
+import org.apache.kylin.metadata.model.ComputedColumnDesc;
+import org.apache.kylin.metadata.model.NDataModel;
+import org.apache.kylin.metadata.model.NDataModelManager;
+import org.apache.kylin.metadata.model.TableRef;
+import org.apache.kylin.metadata.model.TblColRef;
+import org.apache.kylin.metadata.model.util.ComputedColumnUtil;
+import org.apache.kylin.metadata.project.NProjectManager;
+import org.apache.kylin.metadata.project.ProjectInstance;
+import org.apache.kylin.metadata.realization.RealizationStatusEnum;
+import org.apache.kylin.rest.security.MutableAclRecord;
+import org.apache.kylin.rest.util.AclPermissionUtil;
+import org.apache.kylin.tool.bisync.BISyncModel;
+import org.apache.kylin.tool.bisync.BISyncTool;
+import org.apache.kylin.tool.bisync.SyncContext;
+import org.apache.kylin.tool.bisync.SyncModelBuilder;
+import org.apache.kylin.tool.bisync.model.ColumnDef;
+import org.apache.kylin.tool.bisync.model.MeasureDef;
+import org.apache.kylin.tool.bisync.model.SyncModel;
+import org.springframework.stereotype.Component;
+
+import com.google.common.collect.Sets;
+
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+@Component("modelTdsService")
+public class ModelTdsService extends AbstractModelService {
+
+    public void dumpSyncModel(SyncContext syncContext, SyncModel syncModel, HttpServletResponse response)
+            throws IOException {
+        String projectName = syncContext.getProjectName();
+        String modelId = syncContext.getModelId();
+        SyncContext.BI exportAs = syncContext.getTargetBI();
+        BISyncModel biSyncModel = BISyncTool.getBISyncModel(syncContext, syncModel);
+
+        NDataModelManager manager = NDataModelManager.getInstance(KylinConfig.getInstanceFromEnv(), projectName);
+        NDataModel dataModel = manager.getDataModelDesc(modelId);
+        String alias = dataModel.getAlias();
+        String fileName = String.format(Locale.ROOT, "%s_%s_%s", projectName, alias,
+                new SimpleDateFormat("yyyyMMddHHmmss", Locale.getDefault(Locale.Category.FORMAT)).format(new Date()));
+        switch (exportAs) {
+        case TABLEAU_CONNECTOR_TDS:
+        case TABLEAU_ODBC_TDS:
+            response.setContentType("application/xml");
+            response.setHeader("Content-Disposition",
+                    String.format(Locale.ROOT, "attachment; filename=\"%s.tds\"", fileName));
+            break;
+        default:
+            throw new KylinException(CommonErrorCode.UNKNOWN_ERROR_CODE, "unrecognized export target");
+        }
+        biSyncModel.dump(response.getOutputStream());
+        response.getOutputStream().flush();
+        response.getOutputStream().close();
+    }
+
+    public boolean preCheckNameConflict(SyncModel syncModel) {
+        ProjectInstance prjInstance = getManager(NProjectManager.class).getProject(syncModel.getProject());
+        boolean skipCheckTds = prjInstance.getConfig().skipCheckTds();
+        Set<String> measureNames = syncModel.getMetrics().stream().filter(measureDef -> !measureDef.isHidden())
+                .map(measureDef -> measureDef.getMeasure().getName()).collect(Collectors.toSet());
+        Map<String, ColumnDef> nameOfColDefMap = syncModel.getColumnDefMap().values().stream()
+                .collect(Collectors.toMap(ColumnDef::getColumnName, Function.identity()));
+        Sets.SetView<String> intersection = Sets.intersection(nameOfColDefMap.keySet(), measureNames);
+        if (skipCheckTds || CollectionUtils.isEmpty(intersection)) {
+            return true;
+        }
+
+        String name = intersection.iterator().next();
+        ColumnDef columnDef = nameOfColDefMap.get(name);
+        if (columnDef.isDimension()) {
+            throw new KylinException(MODEL_TDS_EXPORT_DIM_COL_AND_MEASURE_NAME_CONFLICT, name, name);
+        } else {
+            throw new KylinException(MODEL_TDS_EXPORT_COLUMN_AND_MEASURE_NAME_CONFLICT, name, name);
+        }
+    }
+
+    public SyncModel exportModel(SyncContext syncContext) {
+        checkModelExportPermission(syncContext.getProjectName(), syncContext.getModelId());
+        checkModelPermission(syncContext.getProjectName(), syncContext.getModelId());
+        return new SyncModelBuilder(syncContext).buildSourceSyncModel();
+    }
+
+    public SyncModel exportTDSDimensionsAndMeasuresByNormalUser(SyncContext syncContext, List<String> dimensions,
+            List<String> measures) {
+        String project = syncContext.getProjectName();
+        String modelId = syncContext.getModelId();
+
+        Set<String> authTables = Sets.newHashSet();
+        Set<String> authColumns = Sets.newHashSet();
+        AclTCRManager aclMgr = getManager(AclTCRManager.class, project);
+        AclTCRDigest uDigest = aclMgr.getAuthTablesAndColumns(project, aclEvaluate.getCurrentUserName(), true);
+        authTables.addAll(uDigest.getTables());
+        authColumns.addAll(uDigest.getColumns());
+        getCurrentUserGroups().forEach(group -> {
+            AclTCRDigest gDigest = aclMgr.getAuthTablesAndColumns(project, group, false);
+            authTables.addAll(gDigest.getTables());
+            authColumns.addAll(gDigest.getColumns());
+        });
+
+        Set<String> authorizedCols = Sets.newHashSet();
+        getModelById(modelId, project).getAllTables().forEach(tableRef -> {
+            List<String> colIdentityList = tableRef.getColumns().stream()
+                    .filter(colRef -> authColumns.contains(colRef.getCanonicalName())) //
+                    .map(TblColRef::getAliasDotName) //
+                    .collect(Collectors.toList());
+            authorizedCols.addAll(colIdentityList);
+        });
+
+        checkTableHasColumnPermission(syncContext.getModelElement(), project, modelId, authorizedCols, dimensions,
+                measures);
+        return new SyncModelBuilder(syncContext).buildHasPermissionSourceSyncModel(authTables, authColumns, dimensions,
+                measures);
+    }
+
+    public SyncModel exportTDSDimensionsAndMeasuresByAdmin(SyncContext syncContext, List<String> dimensions,
+            List<String> measures) {
+        String projectName = syncContext.getProjectName();
+        String modelId = syncContext.getModelId();
+        checkModelExportPermission(projectName, modelId);
+        checkModelPermission(projectName, modelId);
+        return new SyncModelBuilder(syncContext).buildSourceSyncModel(dimensions, measures);
+    }
+
+    private void checkBrokenModel(String projectName, String modelId) {
+        NDataflow dataflow = getManager(NDataflowManager.class, projectName).getDataflow(modelId);
+        if (dataflow.getStatus() == RealizationStatusEnum.BROKEN) {
+            throw new KylinException(MODEL_BROKEN, "The model is broken and cannot be exported TDS file");
+        }
+    }
+
+    public SyncContext prepareSyncContext(String projectName, String modelId, SyncContext.BI targetBI,
+            SyncContext.ModelElement modelElement, String host, int port) {
+        checkBrokenModel(projectName, modelId);
+        SyncContext syncContext = new SyncContext();
+        syncContext.setProjectName(projectName);
+        syncContext.setModelId(modelId);
+        syncContext.setTargetBI(targetBI);
+        syncContext.setModelElement(modelElement);
+        syncContext.setHost(host);
+        syncContext.setPort(port);
+        syncContext.setAdmin(AclPermissionUtil.isAdmin());
+        syncContext.setDataflow(getManager(NDataflowManager.class, projectName).getDataflow(modelId));
+        syncContext.setKylinConfig(getManager(NProjectManager.class).getProject(projectName).getConfig());
+        return syncContext;
+    }
+
+    public void checkTableHasColumnPermission(SyncContext.ModelElement modelElement, String project, String modelId,
+            Set<String> authColumns, List<String> dimensions, List<String> measures) {
+        if (AclPermissionUtil.isAdmin()) {
+            return;
+        }
+        aclEvaluate.checkProjectReadPermission(project);
+
+        NDataModel model = getModelById(modelId, project);
+        long jointCount = model.getJoinTables().stream()
+                .filter(table -> authColumns
+                        .containsAll(Arrays.stream(table.getJoin().getPrimaryKeyColumns())
+                                .map(TblColRef::getAliasDotName).collect(Collectors.toSet()))
+                        && authColumns.containsAll(Arrays.stream(table.getJoin().getForeignKeyColumns())
+                                .map(TblColRef::getAliasDotName).collect(Collectors.toSet())))
+                .count();
+        long singleTableCount = model.getAllTables().stream().filter(ref -> ref.getColumns().stream()
+                .map(TblColRef::getAliasDotName).collect(Collectors.toSet()).stream().anyMatch(authColumns::contains))
+                .count();
+
+        if (jointCount != model.getJoinTables().size() || singleTableCount == 0
+                || (modelElement.equals(SyncContext.ModelElement.CUSTOM_COLS)
+                        && !checkColumnPermission(model, authColumns, dimensions, measures))) {
+            throw new KylinException(ServerErrorCode.INVALID_TABLE_AUTH,
+                    MsgPicker.getMsg().getTableNoColumnsPermission());
+        }
+    }
+
+    public boolean checkColumnPermission(NDataModel model, Set<String> authColumns, List<String> dimensions,
+            List<String> measures) {
+
+        if (!checkDimensionPermission(model, authColumns, dimensions)) {
+            return false;
+        }
+        if (CollectionUtils.isEmpty(measures)) {
+            return true;
+        }
+        List<MeasureDef> authMeasures = model.getEffectiveMeasures().values().stream()
+                .filter(measure -> measures.contains(measure.getName()))
+                .filter(measure -> checkMeasurePermission(authColumns, measure, model)).map(MeasureDef::new)
+                .collect(Collectors.toList());
+        return authMeasures.size() == measures.size();
+
+    }
+
+    private boolean checkDimensionPermission(NDataModel model, Set<String> authColumns, List<String> dimensions) {
+        if (CollectionUtils.isEmpty(dimensions)) {
+            return true;
+        }
+        List<ComputedColumnDesc> computedColumnDescs = model.getComputedColumnDescs().stream()
+                .filter(cc -> dimensions.contains(cc.getFullName())).collect(Collectors.toList());
+
+        long authComputedCount = computedColumnDescs.stream()
+                .filter(cc -> authColumns.containsAll(convertCCToNormalCols(model, cc))).count();
+
+        if (computedColumnDescs.size() != authComputedCount) {
+            return false;
+        }
+
+        List<String> normalColumns = dimensions.stream().filter(column -> !computedColumnDescs.stream()
+                .map(ComputedColumnDesc::getFullName).collect(Collectors.toList()).contains(column))
+                .collect(Collectors.toList());
+        return authColumns.containsAll(normalColumns);
+    }
+
+    public Set<String> convertCCToNormalCols(NDataModel model, ComputedColumnDesc computedColumnDesc) {
+        IndexDependencyParser parser = new IndexDependencyParser(model);
+        try {
+            Set<TblColRef> tblColRefList = parser.unwrapComputeColumn(computedColumnDesc.getInnerExpression());
+            return tblColRefList.stream().map(TblColRef::getAliasDotName).collect(Collectors.toSet());
+        } catch (Exception e) {
+            log.warn("UnWrap computed column {} in project {} model {} exception",
+                    computedColumnDesc.getInnerExpression(), model.getProject(), model.getAlias(), e);
+        }
+        return Collections.emptySet();
+    }
+
+    private boolean checkMeasurePermission(Set<String> authColumns, NDataModel.Measure measure, NDataModel model) {
+        Set<String> measureColumns = measure.getFunction().getParameters().stream()
+                .filter(parameterDesc -> parameterDesc.getColRef() != null)
+                .map(parameterDesc -> parameterDesc.getColRef().getAliasDotName()).collect(Collectors.toSet());
+
+        List<ComputedColumnDesc> computedColumnDescs = model.getComputedColumnDescs().stream()
+                .filter(cc -> measureColumns.contains(cc.getFullName())).collect(Collectors.toList());
+
+        long authComputedCount = computedColumnDescs.stream()
+                .filter(cc -> authColumns.containsAll(convertCCToNormalCols(model, cc))).count();
+
+        if (computedColumnDescs.size() != authComputedCount) {
+            return false;
+        }
+
+        List<String> normalColumns = measureColumns.stream().filter(column -> !computedColumnDescs.stream()
+                .map(ComputedColumnDesc::getFullName).collect(Collectors.toList()).contains(column))
+                .collect(Collectors.toList());
+
+        return authColumns.containsAll(normalColumns);
+    }
+
+    private void checkModelExportPermission(String project, String modeId) {
+        if (AclPermissionUtil.isAdmin()) {
+            return;
+        }
+        aclEvaluate.checkProjectReadPermission(project);
+
+        NDataModel model = getManager(NDataModelManager.class, project).getDataModelDesc(modeId);
+        Map<String, Set<String>> modelTableColumns = new HashMap<>();
+        for (TableRef tableRef : model.getAllTables()) {
+            modelTableColumns.putIfAbsent(tableRef.getTableIdentity(), new HashSet<>());
+            modelTableColumns.get(tableRef.getTableIdentity())
+                    .addAll(tableRef.getColumns().stream().map(TblColRef::getName).collect(Collectors.toSet()));
+        }
+        AclTCRManager aclManager = AclTCRManager.getInstance(KylinConfig.getInstanceFromEnv(), project);
+
+        String currentUserName = AclPermissionUtil.getCurrentUsername();
+        Set<String> groupsOfExecuteUser = accessService.getGroupsOfExecuteUser(currentUserName);
+        MutableAclRecord acl = AclPermissionUtil.getProjectAcl(project);
+        Set<String> groupsInProject = AclPermissionUtil.filterGroupsInProject(groupsOfExecuteUser, acl);
+        if (AclPermissionUtil.isAdminInProject(project, groupsOfExecuteUser)) {
+            return;
+        }
+        AclTCRDigest digest = aclManager.getAllUnauthorizedTableColumn(currentUserName, groupsInProject,
+                modelTableColumns);
+        Set<String> authorizedCC = ComputedColumnUtil
+                .getAuthorizedCC(Collections.singletonList(model),
+                        ccSourceCols -> aclManager.isColumnsAuthorized(currentUserName, groupsOfExecuteUser,
+                                ccSourceCols))
+                .stream().map(ComputedColumnDesc::getIdentName).collect(Collectors.toSet());
+        if (digest.getColumns() != null && !digest.getColumns().isEmpty()
+                && digest.getColumns().stream().anyMatch(column -> !authorizedCC.contains(column))) {
+            throw new KylinException(UNAUTHORIZED_ENTITY,
+                    "current user does not have full permission on requesting model");
+        }
+        if (digest.getTables() != null && !digest.getTables().isEmpty()) {
+            throw new KylinException(UNAUTHORIZED_ENTITY,
+                    "current user does not have full permission on requesting model");
+        }
+    }
+}
diff --git a/src/modeling-service/src/test/java/org/apache/kylin/rest/service/ModelServiceTest.java b/src/modeling-service/src/test/java/org/apache/kylin/rest/service/ModelServiceTest.java
index 6ed591f405..adad3571f6 100644
--- a/src/modeling-service/src/test/java/org/apache/kylin/rest/service/ModelServiceTest.java
+++ b/src/modeling-service/src/test/java/org/apache/kylin/rest/service/ModelServiceTest.java
@@ -51,7 +51,6 @@ import java.io.DataOutputStream;
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
-import java.io.InputStreamReader;
 import java.lang.reflect.Field;
 import java.nio.charset.Charset;
 import java.nio.file.Files;
@@ -123,6 +122,8 @@ import org.apache.kylin.metadata.cube.model.PartitionStatusEnum;
 import org.apache.kylin.metadata.cube.model.PartitionStatusEnumToDisplay;
 import org.apache.kylin.metadata.cube.model.RuleBasedIndex;
 import org.apache.kylin.metadata.cube.optimization.FrequencyMap;
+import org.apache.kylin.metadata.favorite.FavoriteRule;
+import org.apache.kylin.metadata.favorite.FavoriteRuleManager;
 import org.apache.kylin.metadata.model.AutoMergeTimeEnum;
 import org.apache.kylin.metadata.model.BadModelException;
 import org.apache.kylin.metadata.model.BadModelException.CauseType;
@@ -154,6 +155,9 @@ import org.apache.kylin.metadata.model.util.scd2.SimplifiedJoinTableDesc;
 import org.apache.kylin.metadata.project.EnhancedUnitOfWork;
 import org.apache.kylin.metadata.query.QueryTimesResponse;
 import org.apache.kylin.metadata.realization.RealizationStatusEnum;
+import org.apache.kylin.metadata.recommendation.candidate.JdbcRawRecStore;
+import org.apache.kylin.metadata.user.ManagedUser;
+import org.apache.kylin.query.util.KapQueryUtil;
 import org.apache.kylin.rest.config.initialize.ModelBrokenListener;
 import org.apache.kylin.rest.constant.Constant;
 import org.apache.kylin.rest.constant.ModelStatusToDisplayEnum;
@@ -187,8 +191,6 @@ import org.apache.kylin.rest.util.AclUtil;
 import org.apache.kylin.rest.util.SCD2SimplificationConvertUtil;
 import org.apache.kylin.streaming.jobs.StreamingJobListener;
 import org.apache.kylin.streaming.manager.StreamingJobManager;
-import org.apache.kylin.tool.bisync.SyncContext;
-import org.apache.kylin.tool.bisync.tableau.TableauDatasourceModel;
 import org.apache.kylin.util.BrokenEntityProxy;
 import org.apache.kylin.util.PasswordEncodeFactory;
 import org.hamcrest.BaseMatcher;
@@ -209,19 +211,12 @@ import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.security.crypto.password.PasswordEncoder;
 import org.springframework.test.util.ReflectionTestUtils;
 
-import com.google.common.base.Charsets;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
-import com.google.common.io.CharStreams;
 import com.google.common.primitives.Longs;
 
 import io.kyligence.kap.clickhouse.MockSecondStorage;
-import org.apache.kylin.metadata.favorite.FavoriteRule;
-import org.apache.kylin.metadata.favorite.FavoriteRuleManager;
-import org.apache.kylin.metadata.recommendation.candidate.JdbcRawRecStore;
-import org.apache.kylin.metadata.user.ManagedUser;
-import org.apache.kylin.query.util.KapQueryUtil;
 import io.kyligence.kap.secondstorage.SecondStorageNodeHelper;
 import io.kyligence.kap.secondstorage.SecondStorageUtil;
 import io.kyligence.kap.secondstorage.config.Node;
@@ -433,7 +428,6 @@ public class ModelServiceTest extends SourceTestCase {
         Assert.assertEquals("nmodel_basic_inner", models.get(0).getAlias());
     }
 
-//    @Ignore("TODO: re-run to check.")
     @Test
     public void testGetFusionModels() {
         List<NDataModelResponse> models = modelService.getModels("", "streaming_test", false, "", null, "usage", true);
@@ -528,8 +522,7 @@ public class ModelServiceTest extends SourceTestCase {
         Assert.assertEquals(1, models.size());
         NDataModelResponse model = models.get(0);
         Assert.assertTrue(model.getSimpleTables().stream().map(SimplifiedTableResponse::getColumns)
-                .flatMap(List::stream)
-                .anyMatch(SimplifiedColumnResponse::isComputedColumn));
+                .flatMap(List::stream).anyMatch(SimplifiedColumnResponse::isComputedColumn));
     }
 
     @Test
@@ -570,8 +563,7 @@ public class ModelServiceTest extends SourceTestCase {
         NIndexPlanManager indexPlanManager = NIndexPlanManager.getInstance(getTestConfig(), getProject());
         val indexPlan = indexPlanManager.getIndexPlan(modelId);
         indexPlanManager.updateIndexPlan(modelId, copyForWrite -> {
-            copyForWrite.markIndexesToBeDeleted(modelId,
-                    new HashSet<>(indexPlan.getAllLayouts()));
+            copyForWrite.markIndexesToBeDeleted(modelId, new HashSet<>(indexPlan.getAllLayouts()));
             copyForWrite.getIndexes().clear();
         });
         NDataflowManager dataflowManager = NDataflowManager.getInstance(KylinConfig.getInstanceFromEnv(), "default");
@@ -1392,7 +1384,6 @@ public class ModelServiceTest extends SourceTestCase {
                 && models.get(0).getStatus() == ModelStatusToDisplayEnum.OFFLINE);
     }
 
-//    @Ignore("TODO: re-run to check.")
     @Test
     public void testUpdateFusionDataModelStatus() {
         val project = "streaming_test";
@@ -1417,7 +1408,6 @@ public class ModelServiceTest extends SourceTestCase {
         Assert.assertEquals(ModelStatusToDisplayEnum.OFFLINE, models.get(0).getStatus());
     }
 
-//    @Ignore("TODO: re-run to check.")
     @Test
     public void testUpdateFusionDataModelStatus1() {
         val project = "streaming_test";
@@ -1447,7 +1437,6 @@ public class ModelServiceTest extends SourceTestCase {
 
     }
 
-//    @Ignore("TODO: re-run to check.")
     @Test
     public void testUpdateFusionDataModelStatus2() {
         val project = "streaming_test";
@@ -4770,7 +4759,6 @@ public class ModelServiceTest extends SourceTestCase {
         Assert.assertTrue(model.isBroken());
     }
 
-//    @Ignore("TODO: re-run to check.")
     @Test
     public void testGetBrokenFusionModel() {
         String project = "streaming_test";
@@ -5093,394 +5081,6 @@ public class ModelServiceTest extends SourceTestCase {
         }
     }
 
-    @Test
-    public void testExportTDSByAdmin() throws Exception {
-        val project = "default";
-        val modelId = "cb596712-3a09-46f8-aea1-988b43fe9b6c";
-        prepareBasic(project);
-        List<String> dimensions = Lists.newArrayList();
-        dimensions.add("DEFAULT.TEST_MEASURE.FLAG");
-        dimensions.add("DEFAULT.TEST_MEASURE.PRICE1");
-        dimensions.add("DEFAULT.TEST_MEASURE.ID1");
-        List<String> measurs = Lists.newArrayList();
-        measurs.add("COUNT_STAR");
-        measurs.add("SUM_1");
-        SyncContext syncContext = modelService.getADMINSyncContext(project, modelId,
-                SyncContext.BI.TABLEAU_CONNECTOR_TDS, SyncContext.ModelElement.CUSTOM_COLS, "localhost", 8080);
-        TableauDatasourceModel datasource1 = (TableauDatasourceModel) modelService
-                .exportTDSDimensionsAndMeasuresByAdmin(syncContext, dimensions, measurs);
-        ByteArrayOutputStream outStream4 = new ByteArrayOutputStream();
-        datasource1.dump(outStream4);
-        Assert.assertEquals(getExpectedTds("/bisync_tableau/nmodel_full_measure_test.connector_admin.tds"),
-                outStream4.toString(Charset.defaultCharset().name()));
-    }
-
-    @Test
-    public void testExportTDSByUser() throws Exception {
-        val project = "default";
-        val modelId = "cb596712-3a09-46f8-aea1-988b43fe9b6c";
-        prepareBasic(project);
-        List<String> dimensions = Lists.newArrayList();
-        dimensions.add("TEST_MEASURE.ID1");
-        dimensions.add("TEST_MEASURE.ID2");
-        dimensions.add("TEST_MEASURE.ID3");
-        dimensions.add("TEST_MEASURE1.ID1");
-        dimensions.add("TEST_MEASURE1.NAME1");
-        dimensions.add("TEST_MEASURE1.NAME2");
-        dimensions.add("TEST_MEASURE1.NAME3");
-        List<String> measurs = Lists.newArrayList();
-        measurs.add("COUNT_STAR");
-        measurs.add("SUM_1");
-        SecurityContextHolder.getContext()
-                .setAuthentication(new TestingAuthenticationToken("u1", "ANALYST", Constant.ROLE_ANALYST));
-        SyncContext syncContext = modelService.getSyncContext(project, modelId, SyncContext.BI.TABLEAU_CONNECTOR_TDS,
-                SyncContext.ModelElement.CUSTOM_COLS, "localhost", 8080);
-        TableauDatasourceModel datasource1 = (TableauDatasourceModel) modelService
-                .exportTDSDimensionsAndMeasuresByNormalUser(syncContext, dimensions, measurs);
-        ByteArrayOutputStream outStream4 = new ByteArrayOutputStream();
-        datasource1.dump(outStream4);
-        Assert.assertEquals(getExpectedTds("/bisync_tableau/nmodel_full_measure_test.connector_user.tds"),
-                outStream4.toString(Charset.defaultCharset().name()));
-    }
-
-    @Test
-    public void testExportTDSByUserAndElement() throws Exception {
-        val project = "default";
-        val modelId = "cb596712-3a09-46f8-aea1-988b43fe9b6c";
-        prepareBasic(project);
-        try {
-            SecurityContextHolder.getContext()
-                    .setAuthentication(new TestingAuthenticationToken("u1", "ANALYST", Constant.ROLE_ANALYST));
-            SyncContext syncContext = modelService.getSyncContext(project, modelId,
-                    SyncContext.BI.TABLEAU_CONNECTOR_TDS, SyncContext.ModelElement.AGG_INDEX_COL, "localhost", 8080);
-            TableauDatasourceModel datasource1 = (TableauDatasourceModel) modelService
-                    .exportTDSDimensionsAndMeasuresByNormalUser(syncContext, null, null);
-            ByteArrayOutputStream outStream4 = new ByteArrayOutputStream();
-            datasource1.dump(outStream4);
-            Assert.assertEquals(
-                    getExpectedTds("/bisync_tableau/nmodel_full_measure_test.connector_user_agg_index_col.tds"),
-                    outStream4.toString(Charset.defaultCharset().name()));
-
-            TableauDatasourceModel datasource = (TableauDatasourceModel) modelService
-                    .exportTDSDimensionsAndMeasuresByNormalUser(syncContext, new ArrayList<>(), new ArrayList<>());
-        } finally {
-            SecurityContextHolder.getContext()
-                    .setAuthentication(new TestingAuthenticationToken("ADMIN", "ADMIN", Constant.ROLE_ADMIN));
-        }
-    }
-
-    @Test
-    public void testCheckModelExportPermissionException() {
-        val project = "default";
-        val modelId = "cb596712-3a09-46f8-aea1-988b43fe9b6c";
-        prepareBasic(project);
-        modelService.getADMINSyncContext(project, modelId, SyncContext.BI.TABLEAU_CONNECTOR_TDS,
-                SyncContext.ModelElement.AGG_INDEX_COL, "localhost", 8080);
-        try {
-            Mockito.when(accessService.getGroupsOfExecuteUser(Mockito.any(String.class)))
-                    .thenReturn(Sets.newHashSet("ROLE_ANALYST"));
-            SecurityContextHolder.getContext()
-                    .setAuthentication(new TestingAuthenticationToken("u1", "ANALYST", Constant.ROLE_ANALYST));
-            thrown.expect(KylinException.class);
-            thrown.expectMessage("current user does not have full permission on requesting model");
-            modelService.getADMINSyncContext(project, modelId, SyncContext.BI.TABLEAU_CONNECTOR_TDS,
-                    SyncContext.ModelElement.AGG_INDEX_COL, "localhost", 8080);
-        } finally {
-            SecurityContextHolder.getContext()
-                    .setAuthentication(new TestingAuthenticationToken("ADMIN", "ADMIN", Constant.ROLE_ADMIN));
-        }
-    }
-
-    @Test
-    public void testCheckModelExportPermission() {
-        val project = "default";
-        val modelId = "cb596712-3a09-46f8-aea1-988b43fe9b6c";
-        prepareBasic(project);
-        modelService.getADMINSyncContext(project, modelId, SyncContext.BI.TABLEAU_CONNECTOR_TDS,
-                SyncContext.ModelElement.AGG_INDEX_COL, "localhost", 8080);
-        modelService.getADMINSyncContext(project, modelId, SyncContext.BI.TABLEAU_CONNECTOR_TDS,
-                SyncContext.ModelElement.AGG_INDEX_COL, "localhost", 8080);
-    }
-
-    @Test
-    public void testCheckModelExportPermissionWithCC() {
-        val project = "cc_test";
-        val modelId = "0d146f1a-bdd3-4548-87ac-21c2c6f9a0da";
-        AclTCRManager manager = AclTCRManager.getInstance(getTestConfig(), project);
-        {
-            AclTCR u1a1 = new AclTCR();
-            manager.updateAclTCR(u1a1, "u1", true);
-            SecurityContextHolder.getContext()
-                    .setAuthentication(new TestingAuthenticationToken("u1", "ANALYST", Constant.ROLE_ANALYST));
-            Mockito.when(accessService.getGroupsOfExecuteUser(Mockito.any(String.class)))
-                    .thenReturn(Sets.newHashSet("ROLE_ANALYST"));
-            modelService.getADMINSyncContext(project, modelId, SyncContext.BI.TABLEAU_CONNECTOR_TDS,
-                    SyncContext.ModelElement.AGG_INDEX_COL, "localhost", 8080);
-        }
-        {
-            try {
-                AclTCR u1a1 = new AclTCR();
-                AclTCR.Table u1t1 = new AclTCR.Table();
-                AclTCR.ColumnRow u1cr1 = new AclTCR.ColumnRow();
-                AclTCR.Column u1c1 = new AclTCR.Column();
-                u1c1.add("ORDER_ID");
-                u1cr1.setColumn(u1c1);
-                u1t1.put("SSB.LINEORDER", u1cr1);
-                u1a1.setTable(u1t1);
-                manager.updateAclTCR(u1a1, "u1", true);
-                thrown.expect(KylinException.class);
-                thrown.expectMessage("current user does not have full permission on requesting model");
-                modelService.getADMINSyncContext(project, modelId, SyncContext.BI.TABLEAU_CONNECTOR_TDS,
-                        SyncContext.ModelElement.AGG_INDEX_COL, "localhost", 8080);
-            } finally {
-                SecurityContextHolder.getContext()
-                        .setAuthentication(new TestingAuthenticationToken("ADMIN", "ADMIN", Constant.ROLE_ADMIN));
-            }
-        }
-
-    }
-
-    @Test
-    public void testExportTDSByBroken() {
-        val project = "test_broken_project";
-        val modelId = "4b93b131-824e-6966-c4dd-5a4268d27095";
-        List<String> dimensions = Lists.newArrayList();
-        List<String> measurs = Lists.newArrayList();
-        SyncContext syncContext = modelService.getSyncContext(project, modelId, SyncContext.BI.TABLEAU_CONNECTOR_TDS,
-                SyncContext.ModelElement.CUSTOM_COLS, "localhost", 8080);
-        Assert.assertThrows(KylinException.class,
-                () -> modelService.exportTDSDimensionsAndMeasuresByNormalUser(syncContext, dimensions, measurs));
-
-        Assert.assertThrows(KylinException.class,
-                () -> modelService.exportTDSDimensionsAndMeasuresByAdmin(syncContext, dimensions, measurs));
-    }
-
-    @Test
-    public void testExportTDSMeasurePermission() {
-        val project = "default";
-        val modelId = "82fa7671-a935-45f5-8779-85703601f49a";
-        prepareBasicByMeasure(project);
-        List<String> dimensions = Lists.newArrayList();
-        //"ORDER_ID", "PRICE", "CAL_DT", "PRICE", "ITEM_COUNT", "LEAF_CATEG_ID"
-        dimensions.add("TEST_KYLIN_FACT.ORDER_ID");
-        dimensions.add("TEST_KYLIN_FACT.PRICE");
-        dimensions.add("TEST_KYLIN_FACT.CAL_DT");
-        dimensions.add("TEST_KYLIN_FACT.PRICE");
-        dimensions.add("TEST_KYLIN_FACT.ITEM_COUNT");
-        dimensions.add("TEST_KYLIN_FACT.LEAF_CATEG_ID");
-        //"ORDER_ID", "TEST_TIME_ENC", "TEST_DATE_ENC"
-        dimensions.add("TEST_ORDER.ORDER_ID");
-        dimensions.add("TEST_ORDER.TEST_TIME_ENC");
-        dimensions.add("TEST_ORDER.TEST_DATE_ENC");
-        //"ORDER_ID", "PRICE", "CAL_DT", "TRANS_ID"
-        dimensions.add("TEST_MEASURE.ORDER_ID");
-        dimensions.add("TEST_MEASURE.PRICE");
-        dimensions.add("TEST_MEASURE.CAL_DT");
-        dimensions.add("TEST_MEASURE.TRANS_ID");
-
-        List<String> measures = Lists.newArrayList();
-        measures.add("TRANS_CNT");
-        measures.add("GMV_SUM");
-        measures.add("GMV_MIN");
-        measures.add("GMV_MAX");
-        measures.add("ITEM_COUNT_SUM");
-        measures.add("ITEM_COUNT_MAX");
-        measures.add("ITEM_COUNT_MIN");
-        measures.add("SELLER_HLL");
-        measures.add("COUNT_DISTINCT");
-        measures.add("TOP_SELLER");
-        measures.add("TEST_COUNT_DISTINCT_BITMAP");
-        measures.add("GVM_PERCENTILE");
-        SecurityContextHolder.getContext()
-                .setAuthentication(new TestingAuthenticationToken("u1", "ANALYST", Constant.ROLE_ANALYST));
-        SyncContext syncContext = modelService.getSyncContext(project, modelId, SyncContext.BI.TABLEAU_CONNECTOR_TDS,
-                SyncContext.ModelElement.CUSTOM_COLS, "localhost", 8080);
-        Assert.assertThrows(KylinException.class,
-                () -> modelService.exportTDSDimensionsAndMeasuresByNormalUser(syncContext, dimensions, measures));
-    }
-
-    private void prepareBasicByMeasure(String project) {
-        AclTCRManager manager = AclTCRManager.getInstance(getTestConfig(), project);
-
-        AclTCR u1a1 = new AclTCR();
-        AclTCR.Table u1t1 = new AclTCR.Table();
-        AclTCR.ColumnRow u1cr1 = new AclTCR.ColumnRow();
-        AclTCR.Column u1c1 = new AclTCR.Column();
-        u1c1.addAll(Arrays.asList("ORDER_ID", "PRICE", "CAL_DT", "PRICE", "ITEM_COUNT", "LEAF_CATEG_ID"));
-        u1cr1.setColumn(u1c1);
-
-        AclTCR.ColumnRow u1cr2 = new AclTCR.ColumnRow();
-        AclTCR.Column u1c2 = new AclTCR.Column();
-        u1c2.addAll(Arrays.asList("ORDER_ID", "TEST_TIME_ENC", "TEST_DATE_ENC"));
-        u1cr2.setColumn(u1c2);
-        u1t1.put("DEFAULT.TEST_KYLIN_FACT", u1cr1);
-        u1t1.put("DEFAULT.TEST_ORDER", u1cr2);
-        u1a1.setTable(u1t1);
-        manager.updateAclTCR(u1a1, "u1", true);
-    }
-
-    @Test
-    public void testExportModel() throws Exception {
-        val project = "default";
-        val modelId = "cb596712-3a09-46f8-aea1-988b43fe9b6c";
-        prepareBasic(project);
-        TableauDatasourceModel datasource1 = (TableauDatasourceModel) modelService.exportModel(project, modelId,
-                SyncContext.BI.TABLEAU_CONNECTOR_TDS, SyncContext.ModelElement.AGG_INDEX_AND_TABLE_INDEX_COL,
-                "localhost", 8080);
-        ByteArrayOutputStream outStream4 = new ByteArrayOutputStream();
-        datasource1.dump(outStream4);
-        Assert.assertEquals(getExpectedTds("/bisync_tableau/nmodel_full_measure_test.connector.tds"),
-                outStream4.toString(Charset.defaultCharset().name()));
-    }
-
-    private String getExpectedTds(String path) throws IOException {
-        return CharStreams.toString(new InputStreamReader(getClass().getResourceAsStream(path), Charsets.UTF_8));
-    }
-
-    private void prepareBasic(String project) {
-        AclTCRManager manager = AclTCRManager.getInstance(getTestConfig(), project);
-
-        AclTCR u1a1 = new AclTCR();
-        AclTCR.Table u1t1 = new AclTCR.Table();
-        AclTCR.ColumnRow u1cr1 = new AclTCR.ColumnRow();
-        AclTCR.Column u1c1 = new AclTCR.Column();
-        u1c1.addAll(Arrays.asList("ID1", "ID2", "ID3"));
-        u1cr1.setColumn(u1c1);
-
-        AclTCR.ColumnRow u1cr2 = new AclTCR.ColumnRow();
-        AclTCR.Column u1c2 = new AclTCR.Column();
-        u1c2.addAll(Arrays.asList("ID1", "NAME1", "NAME2", "NAME3"));
-        u1cr2.setColumn(u1c2);
-        u1t1.put("DEFAULT.TEST_MEASURE", u1cr1);
-        u1t1.put("DEFAULT.TEST_MEASURE1", u1cr2);
-        u1a1.setTable(u1t1);
-        manager.updateAclTCR(u1a1, "u1", true);
-
-        AclTCR g1a1 = new AclTCR();
-        AclTCR.Table g1t1 = new AclTCR.Table();
-        AclTCR.ColumnRow g1cr1 = new AclTCR.ColumnRow();
-        AclTCR.Column g1c1 = new AclTCR.Column();
-        g1c1.addAll(Arrays.asList("ID1", "ID2", "ID3", "ID4"));
-        g1cr1.setColumn(g1c1);
-        g1t1.put("DEFAULT.TEST_MEASURE", g1cr1);
-        g1a1.setTable(g1t1);
-        manager.updateAclTCR(g1a1, "g1", false);
-    }
-
-    @Test
-    public void testCheckTablePermission() {
-        val project = "default";
-        val modelId = "cb596712-3a09-46f8-aea1-988b43fe9b6c";
-        thrown.expect(KylinException.class);
-        thrown.expectMessage(MsgPicker.getMsg().getTableNoColumnsPermission());
-
-        AclTCRManager manager = AclTCRManager.getInstance(getTestConfig(), project);
-        Set<String> columns = new HashSet<>();
-        columns.add("DEFAULT.TEST_MEASURE1.NAME1");
-        columns.add("DEFAULT.TEST_MEASURE1.NAME2");
-        columns.add("DEFAULT.TEST_MEASURE1.NAME3");
-
-        AclTCR u1a1 = new AclTCR();
-        AclTCR.Table u1t1 = new AclTCR.Table();
-        AclTCR.ColumnRow u1cr1 = new AclTCR.ColumnRow();
-        AclTCR.Column u1c1 = new AclTCR.Column();
-        u1cr1.setColumn(u1c1);
-
-        AclTCR.ColumnRow u1cr2 = new AclTCR.ColumnRow();
-        AclTCR.Column u1c2 = new AclTCR.Column();
-        u1c2.addAll(Arrays.asList("NAME1", "NAME2", "NAME3"));
-        u1cr2.setColumn(u1c2);
-        u1t1.put("DEFAULT.TEST_MEASURE", u1cr1);
-        u1t1.put("DEFAULT.TEST_MEASURE1", u1cr2);
-        u1a1.setTable(u1t1);
-        manager.updateAclTCR(u1a1, "u1", true);
-        SecurityContextHolder.getContext()
-                .setAuthentication(new TestingAuthenticationToken("u1", "ANALYST", Constant.ROLE_ANALYST));
-        List<String> dimensions = Lists.newArrayList();
-        dimensions.add("TEST_MEASURE.FLAG");
-        dimensions.add("TEST_MEASURE.PRICE1");
-        dimensions.add("TEST_MEASURE.ID1");
-        List<String> measurs = Lists.newArrayList();
-        measurs.add("COUNT_STAR");
-        measurs.add("SUM_1");
-        modelService.checkTableHasColumnPermission(SyncContext.ModelElement.CUSTOM_COLS, project, modelId, columns,
-                dimensions, measurs);
-
-        dimensions.add("TEST_MEASURE.ID4");
-        Assert.assertThrows(KylinException.class,
-                () -> modelService.checkTableHasColumnPermission(SyncContext.ModelElement.CUSTOM_COLS, project, modelId,
-                        columns, dimensions, measurs));
-    }
-
-    @Test
-    public void testExportTDSCheckColumnPermission() {
-        val project = "default";
-        val modelId = "89af4ee2-2cdb-4b07-b39e-4c29856309aa";
-
-        NDataModelManager modelManager = NDataModelManager.getInstance(KylinConfig.getInstanceFromEnv(), project);
-        NDataModel dataModel = modelManager.getDataModelDesc(modelId);
-
-        Set<String> authColumns = Sets.newHashSet();
-        List<String> dimensions = Lists.newArrayList();
-        List<String> measurs = Lists.newArrayList();
-
-        Assert.assertTrue(modelService.checkColumnPermission(dataModel, authColumns, null, measurs));
-        Assert.assertTrue(modelService.checkColumnPermission(dataModel, authColumns, null, null));
-        Assert.assertTrue(modelService.checkColumnPermission(dataModel, authColumns, dimensions, null));
-        Assert.assertTrue(modelService.checkColumnPermission(dataModel, authColumns, dimensions, measurs));
-
-        authColumns.add("DEFAULT.TEST_KYLIN_FACT.PRICE");
-        authColumns.add("DEFAULT.TEST_KYLIN_FACT.ITEM_COUNT");
-        authColumns.add("EDW.TEST_CAL_DT.CAL_DT");
-        authColumns.add("DEFAULT.TEST_ACCOUNT.ACCOUNT_ID");
-
-        Set<String> newAuthColumns = Sets.newHashSet();
-        dataModel.getAllTables().forEach(tableRef -> {
-            List<TblColRef> collect = tableRef.getColumns().stream()
-                    .filter(column -> authColumns.contains(column.getCanonicalName())).collect(Collectors.toList());
-            collect.forEach(x -> newAuthColumns.add(x.getAliasDotName()));
-        });
-
-        dimensions.add("TEST_KYLIN_FACT.DEAL_AMOUNT");
-        dimensions.add("TEST_KYLIN_FACT.TRANS_ID");
-
-        Assert.assertFalse(modelService.checkColumnPermission(dataModel, newAuthColumns, dimensions, measurs));
-
-        newAuthColumns.add("TEST_KYLIN_FACT.TRANS_ID");
-
-        measurs.add("SUM_NEST4");
-        measurs.add("COUNT_CAL_DT");
-        Assert.assertTrue(modelService.checkColumnPermission(dataModel, newAuthColumns, dimensions, measurs));
-
-        Assert.assertTrue(modelService.checkColumnPermission(dataModel, newAuthColumns, dimensions, measurs));
-
-    }
-
-    @Test
-    public void testConvertCCToNormalCols() {
-        val project = "default";
-        val modelId = "89af4ee2-2cdb-4b07-b39e-4c29856309aa";
-        NDataModelManager modelManager = NDataModelManager.getInstance(KylinConfig.getInstanceFromEnv(), project);
-        NDataModel dataModel = modelManager.getDataModelDesc(modelId);
-        NDataModel.Measure measure = dataModel.getEffectiveMeasures().values().stream()
-                .filter(x -> x.getName().equals("SUM_NEST4")).findFirst().get();
-        Set<String> measureColumns = measure.getFunction().getParameters().stream()
-                .filter(parameterDesc -> parameterDesc.getColRef() != null)
-                .map(parameterDesc -> parameterDesc.getColRef().getCanonicalName()).collect(Collectors.toSet());
-        ComputedColumnDesc sumNest4 = dataModel.getComputedColumnDescs().stream()
-                .filter(x -> measureColumns.contains(x.getIdentName())).findFirst().get();
-        Set<String> strings = modelService.convertCCToNormalCols(dataModel, sumNest4);
-        Assert.assertEquals("TEST_KYLIN_FACT.PRICE, TEST_KYLIN_FACT.ITEM_COUNT", String.join(", ", strings));
-
-        sumNest4.setInnerExpression("1 + 2");
-        Set<String> set = modelService.convertCCToNormalCols(dataModel, sumNest4);
-        Assert.assertEquals(Collections.emptySet(), set);
-
-        HashSet<Object> authColumns = Sets.newHashSet();
-        authColumns.add("DEFAULT.TEST_KYLIN_FACT.PRICE");
-        Assert.assertTrue(authColumns.containsAll(set));
-    }
-
     @Test
     public void testBuildExceptionMessage() {
         NDataModelManager modelManager = NDataModelManager.getInstance(KylinConfig.getInstanceFromEnv(), "default");
@@ -5517,8 +5117,8 @@ public class ModelServiceTest extends SourceTestCase {
     public void testBuildDuplicateCCException() {
         Set<String> set = Sets.newHashSet("test");
         Assert.assertThrows("The computed column name \"test\" has been used in the current model. Please rename it.\n",
-                KylinException.class, () -> ReflectionTestUtils.invokeMethod(modelService, "buildDuplicateCCException",
-                        set));
+                KylinException.class,
+                () -> ReflectionTestUtils.invokeMethod(modelService, "buildDuplicateCCException", set));
     }
 
     @Test
@@ -5612,63 +5212,6 @@ public class ModelServiceTest extends SourceTestCase {
         }
     }
 
-    @Test
-    public void testExportTDSWithDupMeasureDimColumnNames() throws IOException {
-        String projectName = "default";
-        String modelId = "199ee99e-8419-3e7a-7cad-97059999ec0a";
-        val modelRequest = JsonUtil.readValue(
-                new File("src/test/resources/ut_meta/dup_name_test/model_desc/model_dup_mea_dimcol.json"),
-                ModelRequest.class);
-        modelRequest.setProject(projectName);
-        List<NamedColumn> simplifiedDims = modelRequest.getAllNamedColumns().stream().filter(NamedColumn::isDimension)
-                .collect(Collectors.toList());
-        modelRequest.setSimplifiedDimensions(simplifiedDims);
-        modelService.createModel(projectName, modelRequest);
-        NDataModelManager projectInstance = NDataModelManager.getInstance(getTestConfig(), projectName);
-        Assert.assertNotNull(projectInstance.getDataModelDescByAlias("model_dup_mea_dimcol"));
-        Assert.assertThrows(
-                "There are duplicated names among dimension column LO_LINENUMBER and measure name LO_LINENUMBER. Cannot export a valid TDS file. Please correct the duplicated names and try again.",
-                KylinException.class, () -> modelService.validateExport(projectName, modelId));
-    }
-
-    @Test
-    public void testExportTDSWithDupMeasureDimensionNames() throws IOException {
-        String projectName = "default";
-        String modelId = "6f8cd656-9beb-47f6-87f5-89a8c548d17c";
-        val modelRequest = JsonUtil.readValue(
-                new File("src/test/resources/ut_meta/dup_name_test/model_desc/model_dup_mea_dim.json"),
-                ModelRequest.class);
-        modelRequest.setProject(projectName);
-        List<NamedColumn> simplifiedDims = modelRequest.getAllNamedColumns().stream().filter(NamedColumn::isDimension)
-                .collect(Collectors.toList());
-        modelRequest.setSimplifiedDimensions(simplifiedDims);
-        modelService.createModel(projectName, modelRequest);
-        NDataModelManager projectInstance = NDataModelManager.getInstance(getTestConfig(), projectName);
-        Assert.assertNotNull(projectInstance.getDataModelDescByAlias("model_dup_mea_dim"));
-        Assert.assertThrows(
-                "There are duplicated names among dimension name LO_TEST and measure name LO_TEST. Cannot export a valid TDS file. Please correct the duplicated names and try again.",
-                KylinException.class, () -> modelService.validateExport(projectName, modelId));
-    }
-
-    @Test
-    public void testExportTDSWithDupMeasureColumnNames() throws IOException {
-        String projectName = "default";
-        String modelId = "2ed3bf12-ad40-e8a0-73da-8dc3b4c798bb";
-        val modelRequest = JsonUtil.readValue(
-                new File("src/test/resources/ut_meta/dup_name_test/model_desc/model_dup_mea_col.json"),
-                ModelRequest.class);
-        modelRequest.setProject(projectName);
-        List<NamedColumn> simplifiedDims = modelRequest.getAllNamedColumns().stream().filter(NamedColumn::isDimension)
-                .collect(Collectors.toList());
-        modelRequest.setSimplifiedDimensions(simplifiedDims);
-        modelService.createModel(projectName, modelRequest);
-        NDataModelManager projectInstance = NDataModelManager.getInstance(getTestConfig(), projectName);
-        Assert.assertNotNull(projectInstance.getDataModelDescByAlias("model_dup_mea_col"));
-        Assert.assertThrows(
-                "There are duplicated names among model column LO_LINENUMBER and measure name LO_LINENUMBER. Cannot export a valid TDS file. Please correct the duplicated names and try again.",
-                KylinException.class, () -> modelService.validateExport(projectName, modelId));
-    }
-
     @Test
     public void testCheckComputedColumnExprWithSqlKeyword() throws IOException {
         String projectName = "keyword";
@@ -5836,8 +5379,8 @@ public class ModelServiceTest extends SourceTestCase {
         Assert.assertEquals(1, details.size());
         Assert.assertEquals(COMPUTED_COLUMN_CONFLICT_ADJUST_INFO.getErrorCode().getCode(),
                 details.get(0).getDetailCode());
-        Assert.assertEquals(COMPUTED_COLUMN_CONFLICT_ADJUST_INFO.getMsg("CC_1", "CUSTOMER.C_NAME +'USA'",
-                "CC_CNAME", "CUSTOMER.C_NAME +'USA'", "CC_CNAME"), details.get(0).getDetailMsg());
+        Assert.assertEquals(COMPUTED_COLUMN_CONFLICT_ADJUST_INFO.getMsg("CC_1", "CUSTOMER.C_NAME +'USA'", "CC_CNAME",
+                "CUSTOMER.C_NAME +'USA'", "CC_CNAME"), details.get(0).getDetailMsg());
     }
 
     private void testNoCCConflict(ModelRequest originRequest) {
diff --git a/src/modeling-service/src/test/java/org/apache/kylin/rest/service/ModelTdsServiceTest.java b/src/modeling-service/src/test/java/org/apache/kylin/rest/service/ModelTdsServiceTest.java
new file mode 100644
index 0000000000..f4194a0be4
--- /dev/null
+++ b/src/modeling-service/src/test/java/org/apache/kylin/rest/service/ModelTdsServiceTest.java
@@ -0,0 +1,607 @@
+/*
+ * 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.kylin.rest.service;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.nio.charset.Charset;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.apache.kylin.common.KylinConfig;
+import org.apache.kylin.common.exception.KylinException;
+import org.apache.kylin.common.msg.MsgPicker;
+import org.apache.kylin.common.scheduler.EventBusFactory;
+import org.apache.kylin.common.util.JsonUtil;
+import org.apache.kylin.junit.rule.TransactionExceptedException;
+import org.apache.kylin.metadata.acl.AclTCR;
+import org.apache.kylin.metadata.acl.AclTCRManager;
+import org.apache.kylin.metadata.cube.model.NDataflowManager;
+import org.apache.kylin.metadata.model.ComputedColumnDesc;
+import org.apache.kylin.metadata.model.NDataModel;
+import org.apache.kylin.metadata.model.NDataModelManager;
+import org.apache.kylin.metadata.model.TblColRef;
+import org.apache.kylin.metadata.recommendation.candidate.JdbcRawRecStore;
+import org.apache.kylin.rest.constant.Constant;
+import org.apache.kylin.rest.request.ModelRequest;
+import org.apache.kylin.rest.util.AclEvaluate;
+import org.apache.kylin.rest.util.AclUtil;
+import org.apache.kylin.tool.bisync.BISyncTool;
+import org.apache.kylin.tool.bisync.SyncContext;
+import org.apache.kylin.tool.bisync.model.SyncModel;
+import org.apache.kylin.tool.bisync.tableau.TableauDatasourceModel;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.springframework.security.authentication.TestingAuthenticationToken;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.test.util.ReflectionTestUtils;
+
+import com.google.common.base.Charsets;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import com.google.common.io.CharStreams;
+
+import lombok.val;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public class ModelTdsServiceTest extends SourceTestCase {
+
+    @InjectMocks
+    private final ModelService modelService = Mockito.spy(new ModelService());
+
+    @InjectMocks
+    private final ModelTdsService tdsService = Mockito.spy(new ModelTdsService());
+
+    @InjectMocks
+    private final ModelSemanticHelper semanticService = Mockito.spy(new ModelSemanticHelper());
+
+    @InjectMocks
+    private final IndexPlanService indexPlanService = Mockito.spy(new IndexPlanService());
+
+    @Mock
+    private final AclUtil aclUtil = Mockito.spy(AclUtil.class);
+
+    @Mock
+    private final AclEvaluate aclEvaluate = Mockito.spy(AclEvaluate.class);
+
+    @Mock
+    protected IUserGroupService userGroupService = Mockito.spy(NUserGroupService.class);
+
+    @Mock
+    private final AccessService accessService = Mockito.spy(AccessService.class);
+
+    @Rule
+    public TransactionExceptedException thrown = TransactionExceptedException.none();
+
+    protected String getProject() {
+        return "default";
+    }
+
+    @Before
+    public void setup() {
+        super.setup();
+        overwriteSystemProp("HADOOP_USER_NAME", "root");
+        ReflectionTestUtils.setField(aclEvaluate, "aclUtil", aclUtil);
+        ReflectionTestUtils.setField(modelService, "aclEvaluate", aclEvaluate);
+        ReflectionTestUtils.setField(modelService, "accessService", accessService);
+        ReflectionTestUtils.setField(modelService, "userGroupService", userGroupService);
+        ReflectionTestUtils.setField(modelService, "userGroupService", userGroupService);
+
+        ReflectionTestUtils.setField(tdsService, "accessService", accessService);
+        ReflectionTestUtils.setField(tdsService, "userGroupService", userGroupService);
+        ReflectionTestUtils.setField(tdsService, "aclEvaluate", aclEvaluate);
+
+        modelService.setSemanticUpdater(semanticService);
+        modelService.setIndexPlanService(indexPlanService);
+
+        try {
+            new JdbcRawRecStore(getTestConfig());
+        } catch (Exception e) {
+            //
+        }
+    }
+
+    @After
+    public void tearDown() {
+        getTestConfig().setProperty("kylin.metadata.semi-automatic-mode", "false");
+        EventBusFactory.getInstance().restart();
+        cleanupTestMetadata();
+    }
+
+    @Test
+    public void testExportTDSWithDupMeasureDimColumnNames() throws IOException {
+        String projectName = "default";
+        String modelId = "199ee99e-8419-3e7a-7cad-97059999ec0a";
+        val modelRequest = JsonUtil.readValue(
+                new File("src/test/resources/ut_meta/dup_name_test/model_desc/model_dup_mea_dimcol.json"),
+                ModelRequest.class);
+        modelRequest.setProject(projectName);
+        List<NDataModel.NamedColumn> simplifiedDims = modelRequest.getAllNamedColumns().stream()
+                .filter(NDataModel.NamedColumn::isDimension).collect(Collectors.toList());
+        modelRequest.setSimplifiedDimensions(simplifiedDims);
+        modelService.createModel(projectName, modelRequest);
+        NDataModelManager projectInstance = NDataModelManager.getInstance(getTestConfig(), projectName);
+        Assert.assertNotNull(projectInstance.getDataModelDescByAlias("model_dup_mea_dimcol"));
+        SyncContext syncContext = new SyncContext();
+        syncContext.setProjectName(projectName);
+        syncContext.setModelId(modelId);
+        syncContext.setModelElement(SyncContext.ModelElement.ALL_COLS);
+        syncContext.setAdmin(true);
+        syncContext.setDataflow(NDataflowManager.getInstance(getTestConfig(), projectName).getDataflow(modelId));
+        syncContext.setKylinConfig(getTestConfig());
+        SyncModel syncModel = tdsService.exportModel(syncContext);
+        Assert.assertThrows(
+                "There are duplicated names among dimension column LO_LINENUMBER and measure name LO_LINENUMBER. Cannot export a valid TDS file. Please correct the duplicated names and try again.",
+                KylinException.class, () -> tdsService.preCheckNameConflict(syncModel));
+    }
+
+    @Test
+    public void testExportTdsWithDupMeasureDimensionNamesNoConflict() throws IOException {
+        String projectName = "default";
+        String modelId = "6f8cd656-9beb-47f6-87f5-89a8c548d17c";
+        val modelRequest = JsonUtil.readValue(
+                new File("src/test/resources/ut_meta/dup_name_test/model_desc/model_dup_mea_dim.json"),
+                ModelRequest.class);
+        modelRequest.setProject(projectName);
+        List<NDataModel.NamedColumn> simplifiedDims = modelRequest.getAllNamedColumns().stream()
+                .filter(NDataModel.NamedColumn::isDimension).collect(Collectors.toList());
+        modelRequest.setSimplifiedDimensions(simplifiedDims);
+        modelService.createModel(projectName, modelRequest);
+        NDataModelManager projectInstance = NDataModelManager.getInstance(getTestConfig(), projectName);
+        Assert.assertNotNull(projectInstance.getDataModelDescByAlias("model_dup_mea_dim"));
+        SyncContext syncContext = new SyncContext();
+        syncContext.setProjectName(projectName);
+        syncContext.setModelId(modelId);
+        syncContext.setModelElement(SyncContext.ModelElement.ALL_COLS);
+        syncContext.setAdmin(true);
+        syncContext.setDataflow(NDataflowManager.getInstance(getTestConfig(), projectName).getDataflow(modelId));
+        syncContext.setKylinConfig(getTestConfig());
+        SyncModel syncModel = tdsService.exportModel(syncContext);
+        Assert.assertTrue(tdsService.preCheckNameConflict(syncModel));
+    }
+
+    @Test
+    public void testExportTDSWithDupMeasureColumnNames() throws IOException {
+        String projectName = "default";
+        String modelId = "2ed3bf12-ad40-e8a0-73da-8dc3b4c798bb";
+        val modelRequest = JsonUtil.readValue(
+                new File("src/test/resources/ut_meta/dup_name_test/model_desc/model_dup_mea_col.json"),
+                ModelRequest.class);
+        modelRequest.setProject(projectName);
+        List<NDataModel.NamedColumn> simplifiedDims = modelRequest.getAllNamedColumns().stream()
+                .filter(NDataModel.NamedColumn::isDimension).collect(Collectors.toList());
+        modelRequest.setSimplifiedDimensions(simplifiedDims);
+        modelService.createModel(projectName, modelRequest);
+        NDataModelManager projectInstance = NDataModelManager.getInstance(getTestConfig(), projectName);
+        Assert.assertNotNull(projectInstance.getDataModelDescByAlias("model_dup_mea_col"));
+        SyncContext syncContext = new SyncContext();
+        syncContext.setProjectName(projectName);
+        syncContext.setModelId(modelId);
+        syncContext.setModelElement(SyncContext.ModelElement.ALL_COLS);
+        syncContext.setDataflow(NDataflowManager.getInstance(getTestConfig(), projectName).getDataflow(modelId));
+        syncContext.setKylinConfig(getTestConfig());
+        syncContext.setAdmin(true);
+        SyncModel syncModel = tdsService.exportModel(syncContext);
+        Assert.assertThrows(
+                "There are duplicated names among model column LO_LINENUMBER and measure name LO_LINENUMBER. Cannot export a valid TDS file. Please correct the duplicated names and try again.",
+                KylinException.class, () -> tdsService.preCheckNameConflict(syncModel));
+    }
+
+    @Test
+    public void testExportTDSByAdmin() throws Exception {
+        val project = "default";
+        val modelId = "cb596712-3a09-46f8-aea1-988b43fe9b6c";
+        prepareBasic(project);
+        List<String> dimensions = Lists.newArrayList();
+        dimensions.add("DEFAULT.TEST_MEASURE.FLAG");
+        dimensions.add("DEFAULT.TEST_MEASURE.PRICE1");
+        dimensions.add("DEFAULT.TEST_MEASURE.ID1");
+        List<String> measurs = Lists.newArrayList();
+        measurs.add("COUNT_STAR");
+        measurs.add("SUM_1");
+        SyncContext syncContext = tdsService.prepareSyncContext(project, modelId, SyncContext.BI.TABLEAU_CONNECTOR_TDS,
+                SyncContext.ModelElement.CUSTOM_COLS, "localhost", 8080);
+        SyncModel syncModel = tdsService.exportTDSDimensionsAndMeasuresByAdmin(syncContext, dimensions, measurs);
+        TableauDatasourceModel datasource1 = (TableauDatasourceModel) BISyncTool.getBISyncModel(syncContext, syncModel);
+        ByteArrayOutputStream outStream4 = new ByteArrayOutputStream();
+        datasource1.dump(outStream4);
+        Assert.assertEquals(getExpectedTds("/bisync_tableau/nmodel_full_measure_test.connector_admin.tds"),
+                outStream4.toString(Charset.defaultCharset().name()));
+    }
+
+    @Test
+    public void testExportTDSByUser() throws Exception {
+        val project = "default";
+        val modelId = "cb596712-3a09-46f8-aea1-988b43fe9b6c";
+        prepareBasic(project);
+        List<String> dimensions = Lists.newArrayList();
+        dimensions.add("TEST_MEASURE.ID1");
+        dimensions.add("TEST_MEASURE.ID2");
+        dimensions.add("TEST_MEASURE.ID3");
+        dimensions.add("TEST_MEASURE1.ID1");
+        dimensions.add("TEST_MEASURE1.NAME1");
+        dimensions.add("TEST_MEASURE1.NAME2");
+        dimensions.add("TEST_MEASURE1.NAME3");
+        List<String> measurs = Lists.newArrayList();
+        measurs.add("COUNT_STAR");
+        measurs.add("SUM_1");
+        SecurityContextHolder.getContext()
+                .setAuthentication(new TestingAuthenticationToken("u1", "ANALYST", Constant.ROLE_ANALYST));
+        SyncContext syncContext = tdsService.prepareSyncContext(project, modelId, SyncContext.BI.TABLEAU_CONNECTOR_TDS,
+                SyncContext.ModelElement.CUSTOM_COLS, "localhost", 8080);
+        syncContext.setAdmin(false);
+        SyncModel syncModel = tdsService.exportTDSDimensionsAndMeasuresByNormalUser(syncContext, dimensions, measurs);
+        TableauDatasourceModel datasource1 = (TableauDatasourceModel) BISyncTool.getBISyncModel(syncContext, syncModel);
+        ByteArrayOutputStream outStream4 = new ByteArrayOutputStream();
+        datasource1.dump(outStream4);
+        Assert.assertEquals(getExpectedTds("/bisync_tableau/nmodel_full_measure_test.connector_user.tds"),
+                outStream4.toString(Charset.defaultCharset().name()));
+    }
+
+    @Test
+    public void testExportTDSByUserAndElement() throws Exception {
+        val project = "default";
+        val modelId = "cb596712-3a09-46f8-aea1-988b43fe9b6c";
+        prepareBasic(project);
+        List<String> dimensions = Lists.newArrayList();
+        dimensions.add("TEST_MEASURE.ID1");
+        try {
+            SecurityContextHolder.getContext()
+                    .setAuthentication(new TestingAuthenticationToken("u1", "ANALYST", Constant.ROLE_ANALYST));
+            SyncContext syncContext = tdsService.prepareSyncContext(project, modelId,
+                    SyncContext.BI.TABLEAU_CONNECTOR_TDS, SyncContext.ModelElement.AGG_INDEX_COL, "localhost", 8080);
+            SyncModel syncModel = tdsService.exportTDSDimensionsAndMeasuresByNormalUser(syncContext, dimensions,
+                    ImmutableList.of());
+            TableauDatasourceModel datasource1 = (TableauDatasourceModel) BISyncTool.getBISyncModel(syncContext,
+                    syncModel);
+            ByteArrayOutputStream outStream4 = new ByteArrayOutputStream();
+            datasource1.dump(outStream4);
+            Assert.assertEquals(
+                    getExpectedTds("/bisync_tableau/nmodel_full_measure_test.connector_user_agg_index_col.tds"),
+                    outStream4.toString(Charset.defaultCharset().name()));
+        } finally {
+            SecurityContextHolder.getContext()
+                    .setAuthentication(new TestingAuthenticationToken("ADMIN", "ADMIN", Constant.ROLE_ADMIN));
+        }
+    }
+
+    @Test
+    public void testCheckModelExportPermissionException() {
+        val project = "default";
+        val modelId = "cb596712-3a09-46f8-aea1-988b43fe9b6c";
+        prepareBasic(project);
+        tdsService.prepareSyncContext(project, modelId, SyncContext.BI.TABLEAU_CONNECTOR_TDS,
+                SyncContext.ModelElement.AGG_INDEX_COL, "localhost", 8080);
+        try {
+            Mockito.when(accessService.getGroupsOfExecuteUser(Mockito.any(String.class)))
+                    .thenReturn(Sets.newHashSet("ROLE_ANALYST"));
+            SecurityContextHolder.getContext()
+                    .setAuthentication(new TestingAuthenticationToken("u1", "ANALYST", Constant.ROLE_ANALYST));
+            thrown.expect(KylinException.class);
+            thrown.expectMessage("current user does not have full permission on requesting model");
+            SyncContext syncContext = tdsService.prepareSyncContext(project, modelId,
+                    SyncContext.BI.TABLEAU_CONNECTOR_TDS, SyncContext.ModelElement.AGG_INDEX_COL, "localhost", 8080);
+            tdsService.exportModel(syncContext);
+        } finally {
+            SecurityContextHolder.getContext()
+                    .setAuthentication(new TestingAuthenticationToken("ADMIN", "ADMIN", Constant.ROLE_ADMIN));
+        }
+    }
+
+    @Test
+    public void testCheckModelExportPermission() {
+        val project = "default";
+        val modelId = "cb596712-3a09-46f8-aea1-988b43fe9b6c";
+        prepareBasic(project);
+        tdsService.prepareSyncContext(project, modelId, SyncContext.BI.TABLEAU_CONNECTOR_TDS,
+                SyncContext.ModelElement.AGG_INDEX_COL, "localhost", 8080);
+        tdsService.prepareSyncContext(project, modelId, SyncContext.BI.TABLEAU_CONNECTOR_TDS,
+                SyncContext.ModelElement.AGG_INDEX_COL, "localhost", 8080);
+    }
+
+    @Test
+    public void testCheckModelExportPermissionWithCC() {
+        val project = "cc_test";
+        val modelId = "0d146f1a-bdd3-4548-87ac-21c2c6f9a0da";
+        AclTCRManager manager = AclTCRManager.getInstance(getTestConfig(), project);
+        {
+            AclTCR u1a1 = new AclTCR();
+            manager.updateAclTCR(u1a1, "u1", true);
+            SecurityContextHolder.getContext()
+                    .setAuthentication(new TestingAuthenticationToken("u1", "ANALYST", Constant.ROLE_ANALYST));
+            Mockito.when(accessService.getGroupsOfExecuteUser(Mockito.any(String.class)))
+                    .thenReturn(Sets.newHashSet("ROLE_ANALYST"));
+            tdsService.prepareSyncContext(project, modelId, SyncContext.BI.TABLEAU_CONNECTOR_TDS,
+                    SyncContext.ModelElement.AGG_INDEX_COL, "localhost", 8080);
+        }
+        {
+            try {
+                AclTCR u1a1 = new AclTCR();
+                AclTCR.Table u1t1 = new AclTCR.Table();
+                AclTCR.ColumnRow u1cr1 = new AclTCR.ColumnRow();
+                AclTCR.Column u1c1 = new AclTCR.Column();
+                u1c1.add("ORDER_ID");
+                u1cr1.setColumn(u1c1);
+                u1t1.put("SSB.LINEORDER", u1cr1);
+                u1a1.setTable(u1t1);
+                manager.updateAclTCR(u1a1, "u1", true);
+                thrown.expect(KylinException.class);
+                thrown.expectMessage("current user does not have full permission on requesting model");
+                SyncContext syncContext = tdsService.prepareSyncContext(project, modelId,
+                        SyncContext.BI.TABLEAU_CONNECTOR_TDS, SyncContext.ModelElement.AGG_INDEX_COL, "localhost",
+                        8080);
+                tdsService.exportModel(syncContext);
+            } finally {
+                SecurityContextHolder.getContext()
+                        .setAuthentication(new TestingAuthenticationToken("ADMIN", "ADMIN", Constant.ROLE_ADMIN));
+            }
+        }
+
+    }
+
+    @Test
+    public void testExportTDSByBroken() {
+        val project = "test_broken_project";
+        val modelId = "4b93b131-824e-6966-c4dd-5a4268d27095";
+        List<String> dimensions = Lists.newArrayList();
+        List<String> measures = Lists.newArrayList();
+        Assert.assertThrows(KylinException.class, () -> tdsService.prepareSyncContext(project, modelId,
+                SyncContext.BI.TABLEAU_CONNECTOR_TDS, SyncContext.ModelElement.CUSTOM_COLS, "localhost", 8080));
+    }
+
+    @Test
+    public void testExportTDSMeasurePermission() {
+        val project = "default";
+        val modelId = "82fa7671-a935-45f5-8779-85703601f49a";
+        prepareBasicByMeasure(project);
+        List<String> dimensions = Lists.newArrayList();
+        //"ORDER_ID", "PRICE", "CAL_DT", "PRICE", "ITEM_COUNT", "LEAF_CATEG_ID"
+        dimensions.add("TEST_KYLIN_FACT.ORDER_ID");
+        dimensions.add("TEST_KYLIN_FACT.PRICE");
+        dimensions.add("TEST_KYLIN_FACT.CAL_DT");
+        dimensions.add("TEST_KYLIN_FACT.PRICE");
+        dimensions.add("TEST_KYLIN_FACT.ITEM_COUNT");
+        dimensions.add("TEST_KYLIN_FACT.LEAF_CATEG_ID");
+        //"ORDER_ID", "TEST_TIME_ENC", "TEST_DATE_ENC"
+        dimensions.add("TEST_ORDER.ORDER_ID");
+        dimensions.add("TEST_ORDER.TEST_TIME_ENC");
+        dimensions.add("TEST_ORDER.TEST_DATE_ENC");
+        //"ORDER_ID", "PRICE", "CAL_DT", "TRANS_ID"
+        dimensions.add("TEST_MEASURE.ORDER_ID");
+        dimensions.add("TEST_MEASURE.PRICE");
+        dimensions.add("TEST_MEASURE.CAL_DT");
+        dimensions.add("TEST_MEASURE.TRANS_ID");
+
+        List<String> measures = Lists.newArrayList();
+        measures.add("TRANS_CNT");
+        measures.add("GMV_SUM");
+        measures.add("GMV_MIN");
+        measures.add("GMV_MAX");
+        measures.add("ITEM_COUNT_SUM");
+        measures.add("ITEM_COUNT_MAX");
+        measures.add("ITEM_COUNT_MIN");
+        measures.add("SELLER_HLL");
+        measures.add("COUNT_DISTINCT");
+        measures.add("TOP_SELLER");
+        measures.add("TEST_COUNT_DISTINCT_BITMAP");
+        measures.add("GVM_PERCENTILE");
+        SecurityContextHolder.getContext()
+                .setAuthentication(new TestingAuthenticationToken("u1", "ANALYST", Constant.ROLE_ANALYST));
+        SyncContext syncContext = tdsService.prepareSyncContext(project, modelId, SyncContext.BI.TABLEAU_CONNECTOR_TDS,
+                SyncContext.ModelElement.CUSTOM_COLS, "localhost", 8080);
+        Assert.assertThrows(KylinException.class,
+                () -> tdsService.exportTDSDimensionsAndMeasuresByNormalUser(syncContext, dimensions, measures));
+    }
+
+    private void prepareBasicByMeasure(String project) {
+        AclTCRManager manager = AclTCRManager.getInstance(getTestConfig(), project);
+
+        AclTCR u1a1 = new AclTCR();
+        AclTCR.Table u1t1 = new AclTCR.Table();
+        AclTCR.ColumnRow u1cr1 = new AclTCR.ColumnRow();
+        AclTCR.Column u1c1 = new AclTCR.Column();
+        u1c1.addAll(Arrays.asList("ORDER_ID", "PRICE", "CAL_DT", "PRICE", "ITEM_COUNT", "LEAF_CATEG_ID"));
+        u1cr1.setColumn(u1c1);
+
+        AclTCR.ColumnRow u1cr2 = new AclTCR.ColumnRow();
+        AclTCR.Column u1c2 = new AclTCR.Column();
+        u1c2.addAll(Arrays.asList("ORDER_ID", "TEST_TIME_ENC", "TEST_DATE_ENC"));
+        u1cr2.setColumn(u1c2);
+        u1t1.put("DEFAULT.TEST_KYLIN_FACT", u1cr1);
+        u1t1.put("DEFAULT.TEST_ORDER", u1cr2);
+        u1a1.setTable(u1t1);
+        manager.updateAclTCR(u1a1, "u1", true);
+    }
+
+    @Test
+    public void testExportModel() throws Exception {
+        val project = "default";
+        val modelId = "cb596712-3a09-46f8-aea1-988b43fe9b6c";
+        prepareBasic(project);
+        SyncContext syncContext = tdsService.prepareSyncContext(project, modelId, SyncContext.BI.TABLEAU_CONNECTOR_TDS,
+                SyncContext.ModelElement.AGG_INDEX_AND_TABLE_INDEX_COL, "localhost", 8080);
+        SyncModel syncModel = tdsService.exportModel(syncContext);
+        TableauDatasourceModel datasource1 = (TableauDatasourceModel) BISyncTool.getBISyncModel(syncContext, syncModel);
+        ByteArrayOutputStream outStream4 = new ByteArrayOutputStream();
+        datasource1.dump(outStream4);
+        Assert.assertEquals(getExpectedTds("/bisync_tableau/nmodel_full_measure_test.connector.tds"),
+                outStream4.toString(Charset.defaultCharset().name()));
+    }
+
+    private String getExpectedTds(String path) throws IOException {
+        return CharStreams.toString(new InputStreamReader(getClass().getResourceAsStream(path), Charsets.UTF_8));
+    }
+
+    private void prepareBasic(String project) {
+        AclTCRManager manager = AclTCRManager.getInstance(getTestConfig(), project);
+
+        AclTCR u1a1 = new AclTCR();
+        AclTCR.Table u1t1 = new AclTCR.Table();
+        AclTCR.ColumnRow u1cr1 = new AclTCR.ColumnRow();
+        AclTCR.Column u1c1 = new AclTCR.Column();
+        u1c1.addAll(Arrays.asList("ID1", "ID2", "ID3"));
+        u1cr1.setColumn(u1c1);
+
+        AclTCR.ColumnRow u1cr2 = new AclTCR.ColumnRow();
+        AclTCR.Column u1c2 = new AclTCR.Column();
+        u1c2.addAll(Arrays.asList("ID1", "NAME1", "NAME2", "NAME3"));
+        u1cr2.setColumn(u1c2);
+        u1t1.put("DEFAULT.TEST_MEASURE", u1cr1);
+        u1t1.put("DEFAULT.TEST_MEASURE1", u1cr2);
+        u1a1.setTable(u1t1);
+        manager.updateAclTCR(u1a1, "u1", true);
+
+        AclTCR g1a1 = new AclTCR();
+        AclTCR.Table g1t1 = new AclTCR.Table();
+        AclTCR.ColumnRow g1cr1 = new AclTCR.ColumnRow();
+        AclTCR.Column g1c1 = new AclTCR.Column();
+        g1c1.addAll(Arrays.asList("ID1", "ID2", "ID3", "ID4"));
+        g1cr1.setColumn(g1c1);
+        g1t1.put("DEFAULT.TEST_MEASURE", g1cr1);
+        g1a1.setTable(g1t1);
+        manager.updateAclTCR(g1a1, "g1", false);
+    }
+
+    @Test
+    public void testCheckTablePermission() {
+        val project = "default";
+        val modelId = "cb596712-3a09-46f8-aea1-988b43fe9b6c";
+        thrown.expect(KylinException.class);
+        thrown.expectMessage(MsgPicker.getMsg().getTableNoColumnsPermission());
+
+        AclTCRManager manager = AclTCRManager.getInstance(getTestConfig(), project);
+        Set<String> columns = new HashSet<>();
+        columns.add("DEFAULT.TEST_MEASURE1.NAME1");
+        columns.add("DEFAULT.TEST_MEASURE1.NAME2");
+        columns.add("DEFAULT.TEST_MEASURE1.NAME3");
+
+        AclTCR u1a1 = new AclTCR();
+        AclTCR.Table u1t1 = new AclTCR.Table();
+        AclTCR.ColumnRow u1cr1 = new AclTCR.ColumnRow();
+        AclTCR.Column u1c1 = new AclTCR.Column();
+        u1cr1.setColumn(u1c1);
+
+        AclTCR.ColumnRow u1cr2 = new AclTCR.ColumnRow();
+        AclTCR.Column u1c2 = new AclTCR.Column();
+        u1c2.addAll(Arrays.asList("NAME1", "NAME2", "NAME3"));
+        u1cr2.setColumn(u1c2);
+        u1t1.put("DEFAULT.TEST_MEASURE", u1cr1);
+        u1t1.put("DEFAULT.TEST_MEASURE1", u1cr2);
+        u1a1.setTable(u1t1);
+        manager.updateAclTCR(u1a1, "u1", true);
+        SecurityContextHolder.getContext()
+                .setAuthentication(new TestingAuthenticationToken("u1", "ANALYST", Constant.ROLE_ANALYST));
+        List<String> dimensions = Lists.newArrayList();
+        dimensions.add("TEST_MEASURE.FLAG");
+        dimensions.add("TEST_MEASURE.PRICE1");
+        dimensions.add("TEST_MEASURE.ID1");
+        List<String> measurs = Lists.newArrayList();
+        measurs.add("COUNT_STAR");
+        measurs.add("SUM_1");
+        tdsService.checkTableHasColumnPermission(SyncContext.ModelElement.CUSTOM_COLS, project, modelId, columns,
+                dimensions, measurs);
+
+        dimensions.add("TEST_MEASURE.ID4");
+        Assert.assertThrows(KylinException.class,
+                () -> tdsService.checkTableHasColumnPermission(SyncContext.ModelElement.CUSTOM_COLS, project, modelId,
+                        columns, dimensions, measurs));
+    }
+
+    @Test
+    public void testExportTDSCheckColumnPermission() {
+        val project = "default";
+        val modelId = "89af4ee2-2cdb-4b07-b39e-4c29856309aa";
+
+        NDataModelManager modelManager = NDataModelManager.getInstance(KylinConfig.getInstanceFromEnv(), project);
+        NDataModel dataModel = modelManager.getDataModelDesc(modelId);
+
+        Set<String> authColumns = Sets.newHashSet();
+        List<String> dimensions = Lists.newArrayList();
+        List<String> measurs = Lists.newArrayList();
+
+        Assert.assertTrue(tdsService.checkColumnPermission(dataModel, authColumns, null, measurs));
+        Assert.assertTrue(tdsService.checkColumnPermission(dataModel, authColumns, null, null));
+        Assert.assertTrue(tdsService.checkColumnPermission(dataModel, authColumns, dimensions, null));
+        Assert.assertTrue(tdsService.checkColumnPermission(dataModel, authColumns, dimensions, measurs));
+
+        authColumns.add("DEFAULT.TEST_KYLIN_FACT.PRICE");
+        authColumns.add("DEFAULT.TEST_KYLIN_FACT.ITEM_COUNT");
+        authColumns.add("EDW.TEST_CAL_DT.CAL_DT");
+        authColumns.add("DEFAULT.TEST_ACCOUNT.ACCOUNT_ID");
+
+        Set<String> newAuthColumns = Sets.newHashSet();
+        dataModel.getAllTables().forEach(tableRef -> {
+            List<TblColRef> collect = tableRef.getColumns().stream()
+                    .filter(column -> authColumns.contains(column.getCanonicalName())).collect(Collectors.toList());
+            collect.forEach(x -> newAuthColumns.add(x.getAliasDotName()));
+        });
+
+        dimensions.add("TEST_KYLIN_FACT.DEAL_AMOUNT");
+        dimensions.add("TEST_KYLIN_FACT.TRANS_ID");
+
+        Assert.assertFalse(tdsService.checkColumnPermission(dataModel, newAuthColumns, dimensions, measurs));
+
+        newAuthColumns.add("TEST_KYLIN_FACT.TRANS_ID");
+
+        measurs.add("SUM_NEST4");
+        measurs.add("COUNT_CAL_DT");
+        Assert.assertTrue(tdsService.checkColumnPermission(dataModel, newAuthColumns, dimensions, measurs));
+
+        Assert.assertTrue(tdsService.checkColumnPermission(dataModel, newAuthColumns, dimensions, measurs));
+    }
+
+    @Test
+    public void testConvertCCToNormalCols() {
+        val project = "default";
+        val modelId = "89af4ee2-2cdb-4b07-b39e-4c29856309aa";
+        NDataModelManager modelManager = NDataModelManager.getInstance(KylinConfig.getInstanceFromEnv(), project);
+        NDataModel dataModel = modelManager.getDataModelDesc(modelId);
+        NDataModel.Measure measure = dataModel.getEffectiveMeasures().values().stream()
+                .filter(x -> x.getName().equals("SUM_NEST4")).findFirst().get();
+        Set<String> measureColumns = measure.getFunction().getParameters().stream()
+                .filter(parameterDesc -> parameterDesc.getColRef() != null)
+                .map(parameterDesc -> parameterDesc.getColRef().getCanonicalName()).collect(Collectors.toSet());
+        ComputedColumnDesc sumNest4 = dataModel.getComputedColumnDescs().stream()
+                .filter(x -> measureColumns.contains(x.getIdentName())).findFirst().get();
+        Set<String> strings = tdsService.convertCCToNormalCols(dataModel, sumNest4);
+        Assert.assertEquals("TEST_KYLIN_FACT.PRICE, TEST_KYLIN_FACT.ITEM_COUNT", String.join(", ", strings));
+
+        sumNest4.setInnerExpression("1 + 2");
+        Set<String> set = tdsService.convertCCToNormalCols(dataModel, sumNest4);
+        Assert.assertEquals(Collections.emptySet(), set);
+
+        HashSet<Object> authColumns = Sets.newHashSet();
+        authColumns.add("DEFAULT.TEST_KYLIN_FACT.PRICE");
+        Assert.assertTrue(authColumns.containsAll(set));
+    }
+}
diff --git a/src/query-common/src/main/java/org/apache/kylin/tool/bisync/BISyncTool.java b/src/query-common/src/main/java/org/apache/kylin/tool/bisync/BISyncTool.java
index 9449dee777..4834af6595 100644
--- a/src/query-common/src/main/java/org/apache/kylin/tool/bisync/BISyncTool.java
+++ b/src/query-common/src/main/java/org/apache/kylin/tool/bisync/BISyncTool.java
@@ -24,26 +24,30 @@ import java.util.Set;
 import org.apache.kylin.tool.bisync.model.SyncModel;
 import org.apache.kylin.tool.bisync.tableau.TableauDataSourceConverter;
 
+import com.google.common.annotations.VisibleForTesting;
+
 public class BISyncTool {
 
     private BISyncTool() {
     }
 
+    @VisibleForTesting
     public static BISyncModel dumpToBISyncModel(SyncContext syncContext) {
         SyncModel syncModel = new SyncModelBuilder(syncContext).buildSourceSyncModel();
         return getBISyncModel(syncContext, syncModel);
     }
 
-    private static BISyncModel getBISyncModel(SyncContext syncContext, SyncModel syncModel) {
+    public static BISyncModel getBISyncModel(SyncContext syncContext, SyncModel syncModel) {
         switch (syncContext.getTargetBI()) {
-            case TABLEAU_ODBC_TDS:
-            case TABLEAU_CONNECTOR_TDS:
-                return new TableauDataSourceConverter().convert(syncModel, syncContext);
-            default:
-                throw new IllegalArgumentException();
+        case TABLEAU_ODBC_TDS:
+        case TABLEAU_CONNECTOR_TDS:
+            return new TableauDataSourceConverter().convert(syncModel, syncContext);
+        default:
+            throw new IllegalArgumentException();
         }
     }
 
+    @VisibleForTesting
     public static BISyncModel dumpHasPermissionToBISyncModel(SyncContext syncContext, Set<String> authTables,
             Set<String> authColumns, List<String> dimensions, List<String> measures) {
         SyncModel syncModel = new SyncModelBuilder(syncContext).buildHasPermissionSourceSyncModel(authTables,
@@ -51,8 +55,8 @@ public class BISyncTool {
         return getBISyncModel(syncContext, syncModel);
     }
 
-    public static BISyncModel dumpBISyncModel(SyncContext syncContext,
-            List<String> dimensions, List<String> measures) {
+    @VisibleForTesting
+    public static BISyncModel dumpBISyncModel(SyncContext syncContext, List<String> dimensions, List<String> measures) {
         SyncModel syncModel = new SyncModelBuilder(syncContext).buildSourceSyncModel(dimensions, measures);
         return getBISyncModel(syncContext, syncModel);
     }
diff --git a/src/query-common/src/main/java/org/apache/kylin/tool/bisync/SyncContext.java b/src/query-common/src/main/java/org/apache/kylin/tool/bisync/SyncContext.java
index b43a898726..895897e031 100644
--- a/src/query-common/src/main/java/org/apache/kylin/tool/bisync/SyncContext.java
+++ b/src/query-common/src/main/java/org/apache/kylin/tool/bisync/SyncContext.java
@@ -53,4 +53,6 @@ public class SyncContext {
     private NDataflow dataflow;
 
     private KylinConfig kylinConfig;
+
+    private boolean isAdmin;
 }
diff --git a/src/query-common/src/main/java/org/apache/kylin/tool/bisync/SyncModelBuilder.java b/src/query-common/src/main/java/org/apache/kylin/tool/bisync/SyncModelBuilder.java
index cb444fe016..74b402bc29 100644
--- a/src/query-common/src/main/java/org/apache/kylin/tool/bisync/SyncModelBuilder.java
+++ b/src/query-common/src/main/java/org/apache/kylin/tool/bisync/SyncModelBuilder.java
@@ -20,13 +20,13 @@ package org.apache.kylin.tool.bisync;
 
 import java.util.Arrays;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.stream.Collectors;
 
+import org.apache.commons.lang3.ArrayUtils;
 import org.apache.kylin.common.util.ImmutableBitSet;
 import org.apache.kylin.cube.model.SelectRule;
 import org.apache.kylin.metadata.cube.cuboid.NAggregationGroup;
@@ -44,6 +44,8 @@ import org.apache.kylin.tool.bisync.model.JoinTreeNode;
 import org.apache.kylin.tool.bisync.model.MeasureDef;
 import org.apache.kylin.tool.bisync.model.SyncModel;
 
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
 
@@ -56,22 +58,7 @@ public class SyncModelBuilder {
     }
 
     public SyncModel buildSourceSyncModel() {
-        NDataModel dataModelDesc = syncContext.getDataflow().getModel();
-        IndexPlan indexPlan = syncContext.getDataflow().getIndexPlan();
-
-        // init joinTree, dimension cols, measure cols, hierarchies
-        Map<String, ColumnDef> columnDefMap = getAllColumns(dataModelDesc);
-
-        List<MeasureDef> measureDefs = dataModelDesc.getEffectiveMeasures().values().stream().map(MeasureDef::new)
-                .collect(Collectors.toList());
-        markHasPermissionIndexedColumnsAndMeasures(columnDefMap, measureDefs, indexPlan, null, null,
-                syncContext.getModelElement());
-        markComputedColumnVisibility(columnDefMap, measureDefs, syncContext.getKylinConfig().exposeComputedColumn());
-
-        Set<String[]> hierarchies = getHierarchies(indexPlan);
-        JoinTreeNode joinTree = generateJoinTree(dataModelDesc.getJoinTables(), dataModelDesc.getRootFactTableName());
-
-        return getSyncModel(dataModelDesc, columnDefMap, measureDefs, hierarchies, joinTree);
+        return buildSourceSyncModel(ImmutableList.of(), ImmutableList.of());
     }
 
     public SyncModel buildSourceSyncModel(List<String> dimensions, List<String> measures) {
@@ -79,16 +66,17 @@ public class SyncModelBuilder {
         IndexPlan indexPlan = syncContext.getDataflow().getIndexPlan();
 
         // init joinTree, dimension cols, measure cols, hierarchies
-        Map<String, ColumnDef> columnDefMap = authColumns(dataModelDesc);
+        Map<String, ColumnDef> columnDefMap = authColumns(dataModelDesc, syncContext.isAdmin(), ImmutableSet.of(),
+                ImmutableSet.of());
+        List<MeasureDef> measureDefs = dataModelDesc.getEffectiveMeasures().values().stream() //
+                .map(MeasureDef::new).collect(Collectors.toList());
 
-        List<MeasureDef> measureDefs = dataModelDesc.getEffectiveMeasures().values().stream().map(MeasureDef::new)
-                .collect(Collectors.toList());
-        markHasPermissionIndexedColumnsAndMeasures(columnDefMap, measureDefs, indexPlan, dimensions, measures,
-                syncContext.getModelElement());
+        markHasPermissionIndexedColumnsAndMeasures(columnDefMap, measureDefs, indexPlan, ImmutableSet.of(), dimensions,
+                measures, syncContext.getModelElement());
         markComputedColumnVisibility(columnDefMap, measureDefs, syncContext.getKylinConfig().exposeComputedColumn());
+
         Set<String[]> hierarchies = getHierarchies(indexPlan);
         JoinTreeNode joinTree = generateJoinTree(dataModelDesc.getJoinTables(), dataModelDesc.getRootFactTableName());
-
         return getSyncModel(dataModelDesc, columnDefMap, measureDefs, hierarchies, joinTree);
     }
 
@@ -99,20 +87,22 @@ public class SyncModelBuilder {
 
         Set<String> allAuthColumns = addHasPermissionCCColumn(dataModelDesc, authColumns);
         // init joinTree, dimension cols, measure cols, hierarchies
-        Map<String, ColumnDef> columnDefMap = authColumns(dataModelDesc, authTables, allAuthColumns);
+        Map<String, ColumnDef> columnDefMap = authColumns(dataModelDesc, syncContext.isAdmin(), authTables,
+                allAuthColumns);
+        List<MeasureDef> measureDefs = dataModelDesc.getEffectiveMeasures().values().stream() //
+                .filter(measure -> checkMeasurePermission(allAuthColumns, measure)) //
+                .map(MeasureDef::new).collect(Collectors.toList());
 
-        List<MeasureDef> measureDefs = dataModelDesc.getEffectiveMeasures().values().stream()
-                .filter(measure -> checkMeasurePermission(allAuthColumns, measure)).map(MeasureDef::new)
-                .collect(Collectors.toList());
         markHasPermissionIndexedColumnsAndMeasures(columnDefMap, measureDefs, indexPlan, allAuthColumns, dimensions,
                 measures, syncContext.getModelElement());
         markComputedColumnVisibility(columnDefMap, measureDefs, syncContext.getKylinConfig().exposeComputedColumn());
+
+        Set<String> omitDbColSet = renameColumnName(allAuthColumns);
         Set<String[]> hierarchies = getHierarchies(indexPlan).stream()
-                .map(hierarchyArray -> Arrays.stream(hierarchyArray).filter(renameColumnName(allAuthColumns)::contains)
+                .map(hierarchyArray -> Arrays.stream(hierarchyArray).filter(omitDbColSet::contains)
                         .collect(Collectors.toSet()).toArray(new String[0]))
                 .collect(Collectors.toSet()).stream().filter(x -> !Arrays.asList(x).isEmpty())
                 .collect(Collectors.toSet());
-
         JoinTreeNode joinTree = generateJoinTree(dataModelDesc.getJoinTables(), dataModelDesc.getRootFactTableName());
         return getSyncModel(dataModelDesc, columnDefMap, measureDefs, hierarchies, joinTree);
     }
@@ -125,7 +115,7 @@ public class SyncModelBuilder {
         syncModel.setJoinTree(joinTree);
         syncModel.setMetrics(measureDefs);
         syncModel.setHierarchies(hierarchies);
-        syncModel.setProjectName(syncContext.getProjectName());
+        syncModel.setProject(syncContext.getProjectName());
         syncModel.setModelName(dataModelDesc.getAlias());
         syncModel.setHost(syncContext.getHost());
         syncModel.setPort(String.valueOf(syncContext.getPort()));
@@ -141,135 +131,105 @@ public class SyncModelBuilder {
 
     private void markComputedColumnVisibility(Map<String, ColumnDef> columnDefMap, List<MeasureDef> measureDefs,
             boolean exposeComputedColumns) {
-        if (!exposeComputedColumns) {
-            // hide all CC cols and related measures
-            for (ColumnDef columnDef : columnDefMap.values()) {
-                if (columnDef.isComputedColumn()) {
-                    columnDef.setHidden(true);
-                }
+        if (exposeComputedColumns) {
+            return;
+        }
+        // hide all CC cols and related measures
+        for (ColumnDef columnDef : columnDefMap.values()) {
+            if (columnDef.isComputedColumn()) {
+                columnDef.setHidden(true);
             }
-            for (MeasureDef measureDef : measureDefs) {
-                for (TblColRef paramColRef : measureDef.getMeasure().getFunction().getColRefs()) {
-                    if (columnDefMap.get(paramColRef.getAliasDotName()).isComputedColumn()) {
-                        measureDef.setHidden(true);
-                        break;
-                    }
+        }
+        for (MeasureDef measureDef : measureDefs) {
+            for (TblColRef paramColRef : measureDef.getMeasure().getFunction().getColRefs()) {
+                ColumnDef columnDef = columnDefMap.get(paramColRef.getAliasDotName());
+                if (columnDef != null && columnDef.isComputedColumn()) {
+                    measureDef.setHidden(true);
+                    break;
                 }
             }
         }
     }
 
     private void markHasPermissionIndexedColumnsAndMeasures(Map<String, ColumnDef> columnDefMap,
-            List<MeasureDef> measureDefs, IndexPlan indexPlan, Set<String> columns, List<String> dimensions,
+            List<MeasureDef> measureDefs, IndexPlan indexPlan, Set<String> authorizedCols, List<String> dimensions,
             List<String> measures, SyncContext.ModelElement modelElement) {
         Set<String> colsToShow = Sets.newHashSet();
         Set<String> measuresToShow = Sets.newHashSet();
         switch (modelElement) {
         case AGG_INDEX_COL:
-            ImmutableBitSet aggDimBitSet = indexPlan.getAllIndexes().stream().filter(index -> !index.isTableIndex())
-                    .map(IndexEntity::getDimensionBitset).reduce(ImmutableBitSet.EMPTY, ImmutableBitSet::or);
-            Set<TblColRef> tblColRefs = indexPlan.getEffectiveDimCols().entrySet().stream()
-                    .filter(entry -> aggDimBitSet.get(entry.getKey())).map(Map.Entry::getValue)
+            ImmutableBitSet aggDimBitSet = indexPlan.getAllIndexes().stream() //
+                    .filter(index -> !index.isTableIndex()) //
+                    .map(IndexEntity::getDimensionBitset) //
+                    .reduce(ImmutableBitSet.EMPTY, ImmutableBitSet::or);
+            Set<TblColRef> tblColRefs = indexPlan.getEffectiveDimCols().entrySet().stream() //
+                    .filter(entry -> aggDimBitSet.get(entry.getKey())) //
+                    .map(Map.Entry::getValue) //
                     .collect(Collectors.toSet());
-            colsToShow = tblColRefs.stream().filter(column -> columns.contains(column.getAliasDotName()))
-                    .map(TblColRef::getAliasDotName).collect(Collectors.toSet());
-            measuresToShow = indexPlan.getEffectiveMeasures().values().stream()
-                    .filter(measureDef -> checkMeasurePermission(columns, measureDef)).map(MeasureDesc::getName)
+            colsToShow = tblColRefs.stream() //
+                    .filter(colRef -> testAuthorizedCols(authorizedCols, colRef)) //
+                    .map(TblColRef::getAliasDotName) //
+                    .collect(Collectors.toSet());
+            measuresToShow = indexPlan.getEffectiveMeasures().values().stream() //
+                    .filter(measureDef -> testAuthorizedMeasures(authorizedCols, measureDef)) //
+                    .map(MeasureDesc::getName) //
                     .collect(Collectors.toSet());
             break;
         case AGG_INDEX_AND_TABLE_INDEX_COL:
-            colsToShow = indexPlan.getEffectiveDimCols().values().stream()
-                    .filter(column -> columns.contains(column.getAliasDotName())).map(TblColRef::getAliasDotName)
+            colsToShow = indexPlan.getEffectiveDimCols().values().stream() //
+                    .filter(colRef -> testAuthorizedCols(authorizedCols, colRef)) //
+                    .map(TblColRef::getAliasDotName) //
                     .collect(Collectors.toSet());
             measuresToShow = indexPlan.getEffectiveMeasures().values().stream()
-                    .filter(measureDef -> checkMeasurePermission(columns, measureDef)).map(MeasureDesc::getName)
+                    .filter(measureDef -> testAuthorizedMeasures(authorizedCols, measureDef)) //
+                    .map(MeasureDesc::getName) //
                     .collect(Collectors.toSet());
             break;
         case ALL_COLS:
-            colsToShow = indexPlan.getModel().getDimensionNameIdMap().keySet().stream()
-                    .filter(renameColumnName(columns)::contains).collect(Collectors.toSet());
-            measuresToShow = indexPlan.getModel().getEffectiveMeasures().values().stream()
-                    .filter(measureDef -> checkMeasurePermission(columns, measureDef)).map(MeasureDesc::getName)
+            colsToShow = indexPlan.getModel().getEffectiveDimensions().values().stream()
+                    .filter(colRef -> testAuthorizedCols(authorizedCols, colRef)) //
+                    .map(TblColRef::getAliasDotName) //
+                    .collect(Collectors.toSet());
+            measuresToShow = indexPlan.getModel().getEffectiveMeasures().values().stream() //
+                    .filter(measureDef -> testAuthorizedMeasures(authorizedCols, measureDef)) //
+                    .map(MeasureDesc::getName) //
                     .collect(Collectors.toSet());
-            for (MeasureDef measureDef : measureDefs) {
-                measureDef.setHidden(false);
-            }
             break;
         case CUSTOM_COLS:
-            colsToShow = indexPlan.getModel().getDimensionNameIdMap().keySet().stream()
-                    .filter(renameColumnName(new HashSet<>(dimensions))::contains).collect(Collectors.toSet());
-            measuresToShow = indexPlan.getModel().getEffectiveMeasures().values().stream()
-                    .filter(measureDef -> measures.contains(measureDef.getName())).map(MeasureDesc::getName)
+            Set<String> dimensionSet = Sets.newHashSet(dimensions);
+            colsToShow = indexPlan.getModel().getEffectiveDimensions().values().stream()
+                    .filter(colRef -> testAuthorizedDimensions(dimensionSet, colRef)) //
+                    .map(TblColRef::getAliasDotName) //
+                    .collect(Collectors.toSet());
+            measuresToShow = indexPlan.getModel().getEffectiveMeasures().values().stream() //
+                    .map(MeasureDesc::getName) //
+                    .filter(measures::contains) //
                     .collect(Collectors.toSet());
-
-            for (MeasureDef measureDef : measureDefs) {
-                if (measuresToShow.contains(measureDef.getMeasure().getName())
-                        && (measures != null && measures.contains(measureDef.getMeasure().getName()))) {
-                    measureDef.setHidden(false);
-                }
-            }
             break;
         default:
             break;
         }
-        setDimensionAndMeasureHidden(columnDefMap, measureDefs, modelElement, colsToShow, measuresToShow);
+        showDimsAndMeasures(columnDefMap, measureDefs, colsToShow, measuresToShow);
     }
 
-    private void markHasPermissionIndexedColumnsAndMeasures(Map<String, ColumnDef> columnDefMap,
-            List<MeasureDef> measureDefs, IndexPlan indexPlan, List<String> dimensions, List<String> measures,
-            SyncContext.ModelElement modelElement) {
-        Set<String> colsToShow = new HashSet<>();
-        Set<String> measuresToShow = new HashSet<>();
-        switch (modelElement) {
-        case AGG_INDEX_COL:
-            ImmutableBitSet aggDimBitSet = indexPlan.getAllIndexes().stream().filter(index -> !index.isTableIndex())
-                    .map(IndexEntity::getDimensionBitset).reduce(ImmutableBitSet.EMPTY, ImmutableBitSet::or);
-            Set<TblColRef> tblColRefs = indexPlan.getEffectiveDimCols().entrySet().stream()
-                    .filter(entry -> aggDimBitSet.get(entry.getKey())).map(Map.Entry::getValue)
-                    .collect(Collectors.toSet());
-            colsToShow = tblColRefs.stream().map(TblColRef::getAliasDotName).collect(Collectors.toSet());
-            measuresToShow = indexPlan.getEffectiveMeasures().values().stream().map(MeasureDesc::getName)
-                    .collect(Collectors.toSet());
-            break;
-        case AGG_INDEX_AND_TABLE_INDEX_COL:
-            colsToShow = indexPlan.getEffectiveDimCols().values().stream().map(TblColRef::getAliasDotName)
-                    .collect(Collectors.toSet());
-            measuresToShow = indexPlan.getEffectiveMeasures().values().stream().map(MeasureDesc::getName)
-                    .collect(Collectors.toSet());
-            break;
-        case ALL_COLS:
-            colsToShow = indexPlan.getModel().getDimensionNameIdMap().keySet();
-            measuresToShow = indexPlan.getModel().getEffectiveMeasures().values().stream().map(MeasureDesc::getName)
-                    .collect(Collectors.toSet());
-            for (MeasureDef measureDef : measureDefs) {
-                measureDef.setHidden(false);
-            }
-            break;
-        case CUSTOM_COLS:
-            colsToShow = indexPlan.getModel().getDimensionNameIdMap().keySet().stream()
-                    .filter(renameColumnName(new HashSet<>(dimensions))::contains).collect(Collectors.toSet());
-            measuresToShow = indexPlan.getModel().getEffectiveMeasures().values().stream()
-                    .filter(measureDef -> measures != null && measures.contains(measureDef.getName()))
-                    .map(MeasureDesc::getName).collect(Collectors.toSet());
-            for (MeasureDef measureDef : measureDefs) {
-                if (measuresToShow.contains(measureDef.getMeasure().getName())
-                        && (measures != null && measures.contains(measureDef.getMeasure().getName()))) {
-                    measureDef.setHidden(false);
-                }
-            }
-            break;
-        default:
-            break;
-        }
+    private boolean testAuthorizedCols(Set<String> authorizedCols, TblColRef colRef) {
+        return syncContext.isAdmin() || authorizedCols.contains(colRef.getColumnWithTableAndSchema())
+                || authorizedCols.contains(colRef.getAliasDotName());
+    }
 
-        setDimensionAndMeasureHidden(columnDefMap, measureDefs, modelElement, colsToShow, measuresToShow);
+    private boolean testAuthorizedDimensions(Set<String> dimensions, TblColRef colRef) {
+        return dimensions.contains(colRef.getColumnWithTableAndSchema())
+                || dimensions.contains(colRef.getAliasDotName());
     }
 
-    private void setDimensionAndMeasureHidden(Map<String, ColumnDef> columnDefMap, List<MeasureDef> measureDefs,
-            SyncContext.ModelElement modelElement, Set<String> colsToShow, Set<String> measuresToShow) {
-        colsToShow.forEach(colToShow -> columnDefMap.get(colToShow).setHidden(false));
-        if (modelElement.equals(SyncContext.ModelElement.CUSTOM_COLS)) {
-            return;
+    private boolean testAuthorizedMeasures(Set<String> authorizedCols, NDataModel.Measure measureDef) {
+        return syncContext.isAdmin() || checkMeasurePermission(authorizedCols, measureDef);
+    }
+
+    private void showDimsAndMeasures(Map<String, ColumnDef> columnDefMap, List<MeasureDef> measureDefs,
+            Set<String> colsToShow, Set<String> measuresToShow) {
+        for (String colToShow : colsToShow) {
+            columnDefMap.get(colToShow).setHidden(false);
         }
         for (MeasureDef measureDef : measureDefs) {
             if (measuresToShow.contains(measureDef.getMeasure().getName())) {
@@ -288,60 +248,38 @@ public class SyncModelBuilder {
         }).collect(Collectors.toSet());
     }
 
-    private Map<String, ColumnDef> getAllColumns(NDataModel modelDesc) {
-        Map<String, ColumnDef> modelColsMap = new HashMap<>();
-        for (TableRef tableRef : modelDesc.getAllTables()) {
-            for (TblColRef column : tableRef.getColumns()) {
-                ColumnDef columnDef = new ColumnDef("dimension", tableRef.getAlias(), null, column.getName(),
-                        column.getDatatype(), true, column.getColumnDesc().isComputedColumn());
-                String colName = tableRef.getAlias() + "." + column.getName();
-                modelColsMap.put(colName, columnDef);
+    private Map<String, ColumnDef> authColumns(NDataModel model, boolean isAdmin, Set<String> tables,
+            Set<String> columns) {
+        Map<String, ColumnDef> modelColsMap = Maps.newHashMap();
+        for (TableRef tableRef : model.getAllTables()) {
+            if (!isAdmin && !tables.contains(tableRef.getTableIdentity())) {
+                continue;
             }
-        }
-
-        // sync col alias
-        for (NDataModel.NamedColumn namedColumn : modelDesc.getAllNamedColumns()) {
-            if (modelColsMap.get(namedColumn.getAliasDotColumn()) != null) {
-                modelColsMap.get(namedColumn.getAliasDotColumn()).setColumnAlias(namedColumn.getName());
+            for (TblColRef colRef : tableRef.getColumns()) {
+                if (isAdmin || columns.contains(colRef.getAliasDotName())
+                        || columns.contains(colRef.getColumnWithTableAndSchema())) {
+                    ColumnDef columnDef = ColumnDef.builder() //
+                            .role("dimension") //
+                            .tableAlias(tableRef.getAlias()) //
+                            .columnName(colRef.getName()) //
+                            .columnType(colRef.getDatatype()) //
+                            .isHidden(true) //
+                            .isComputedColumn(colRef.getColumnDesc().isComputedColumn()) //
+                            .build();
+                    modelColsMap.put(colRef.getIdentity(), columnDef);
+                }
             }
         }
-        return modelColsMap;
-    }
-
-    private Map<String, ColumnDef> authColumns(NDataModel modelDesc) {
-        Map<String, ColumnDef> modelColsMap = Maps.newHashMap();
-        modelDesc.getAllTables().stream().forEach(tableRef -> tableRef.getColumns().stream().forEach(column -> {
-            ColumnDef columnDef = new ColumnDef("dimension", tableRef.getAlias(), null, column.getName(),
-                    column.getDatatype(), true, column.getColumnDesc().isComputedColumn());
-            String colName = tableRef.getAlias() + "." + column.getName();
-            modelColsMap.put(colName, columnDef);
-        }));
 
         // sync col alias
-        modelDesc.getAllNamedColumns().stream()
-                .filter(namedColumn -> modelColsMap.get(namedColumn.getAliasDotColumn()) != null)
-                .forEach(namedColumn -> modelColsMap.get(namedColumn.getAliasDotColumn())
-                        .setColumnAlias(namedColumn.getName()));
-        return modelColsMap;
-    }
-
-    private Map<String, ColumnDef> authColumns(NDataModel modelDesc, Set<String> tables, Set<String> columns) {
-        Map<String, ColumnDef> modelColsMap = Maps.newHashMap();
-        modelDesc.getAllTables().stream().filter(table -> tables.contains(table.getTableIdentity()))
-                .forEach(tableRef -> tableRef.getColumns().stream()
-                        .filter(column -> columns.contains(column.getAliasDotName())).forEach(column -> {
-                            ColumnDef columnDef = new ColumnDef("dimension", tableRef.getAlias(), null,
-                                    column.getName(), column.getDatatype(), true,
-                                    column.getColumnDesc().isComputedColumn());
-                            String colName = tableRef.getAlias() + "." + column.getName();
-                            modelColsMap.put(colName, columnDef);
-                        }));
-
-        // sync col alias
-        modelDesc.getAllNamedColumns().stream()
-                .filter(namedColumn -> modelColsMap.get(namedColumn.getAliasDotColumn()) != null)
-                .forEach(namedColumn -> modelColsMap.get(namedColumn.getAliasDotColumn())
-                        .setColumnAlias(namedColumn.getName()));
+        model.getAllNamedColumns().stream() //
+                .filter(NDataModel.NamedColumn::isExist) //
+                .forEach(namedColumn -> {
+                    ColumnDef columnDef = modelColsMap.get(namedColumn.getAliasDotColumn());
+                    if (columnDef != null) {
+                        columnDef.setColumnAlias(namedColumn.getName());
+                    }
+                });
         return modelColsMap;
     }
 
@@ -394,17 +332,17 @@ public class SyncModelBuilder {
         Set<String> hierarchyNameSet = Sets.newHashSet();
         for (NAggregationGroup group : indexPlan.getRuleBasedIndex().getAggregationGroups()) {
             SelectRule rule = group.getSelectRule();
-            if (rule != null) {
-                for (Integer[] hierarchyIds : rule.hierarchyDims) {
-                    if (hierarchyIds != null && hierarchyIds.length != 0) {
-
-                        String[] hierarchyNames = Arrays.stream(hierarchyIds)
-                                .map(id -> indexPlan.getModel().getColumnNameByColumnId(id)).toArray(String[]::new);
-                        String hierarchyNamesJoined = String.join(",", hierarchyNames);
-                        if (!hierarchyNameSet.contains(hierarchyNamesJoined)) {
-                            hierarchies.add(hierarchyNames);
-                            hierarchyNameSet.add(hierarchyNamesJoined);
-                        }
+            if (rule == null) {
+                continue;
+            }
+            for (Integer[] hierarchyIds : rule.hierarchyDims) {
+                if (ArrayUtils.isNotEmpty(hierarchyIds)) {
+                    String[] hierarchyNames = Arrays.stream(hierarchyIds)
+                            .map(id -> indexPlan.getModel().getColumnNameByColumnId(id)).toArray(String[]::new);
+                    String hierarchyNamesJoined = String.join(",", hierarchyNames);
+                    if (!hierarchyNameSet.contains(hierarchyNamesJoined)) {
+                        hierarchies.add(hierarchyNames);
+                        hierarchyNameSet.add(hierarchyNamesJoined);
                     }
                 }
             }
diff --git a/src/query-common/src/main/java/org/apache/kylin/tool/bisync/model/ColumnDef.java b/src/query-common/src/main/java/org/apache/kylin/tool/bisync/model/ColumnDef.java
index 0871f6ea5b..c67f56a432 100644
--- a/src/query-common/src/main/java/org/apache/kylin/tool/bisync/model/ColumnDef.java
+++ b/src/query-common/src/main/java/org/apache/kylin/tool/bisync/model/ColumnDef.java
@@ -17,6 +17,17 @@
  */
 package org.apache.kylin.tool.bisync.model;
 
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@Getter
+@Setter
 public class ColumnDef {
 
     private String role;
@@ -29,74 +40,11 @@ public class ColumnDef {
 
     private String columnType;
 
-    private boolean isHidden;
+    private boolean isHidden = true;
 
     private boolean isComputedColumn;
 
-    public ColumnDef(String role, String tableAlias, String columnAlias, String columnName, String columnType,
-            boolean isHidden, boolean isComputedColumn) {
-        this.role = role;
-        this.tableAlias = tableAlias;
-        this.columnAlias = columnAlias;
-        this.columnName = columnName;
-        this.columnType = columnType;
-        this.isHidden = isHidden;
-        this.isComputedColumn = isComputedColumn;
-    }
-
-    public String getRole() {
-        return role;
-    }
-
-    public void setRole(String role) {
-        this.role = role;
-    }
-
-    public String getTableAlias() {
-        return tableAlias;
-    }
-
-    public void setTableAlias(String tableAlias) {
-        this.tableAlias = tableAlias;
-    }
-
-    public boolean isHidden() {
-        return isHidden;
-    }
-
-    public void setHidden(boolean hidden) {
-        isHidden = hidden;
-    }
-
-    public String getColumnAlias() {
-        return columnAlias;
-    }
-
-    public void setColumnAlias(String columnAlias) {
-        this.columnAlias = columnAlias;
-    }
-
-    public String getColumnName() {
-        return columnName;
-    }
-
-    public void setColumnName(String columnName) {
-        this.columnName = columnName;
-    }
-
-    public String getColumnType() {
-        return columnType;
-    }
-
-    public void setColumnType(String columnType) {
-        this.columnType = columnType;
-    }
-
-    public boolean isComputedColumn() {
-        return isComputedColumn;
-    }
-
-    public void setComputedColumn(boolean computedColumn) {
-        isComputedColumn = computedColumn;
+    public boolean isDimension() {
+        return columnType.equalsIgnoreCase("nominal");
     }
 }
diff --git a/src/query-common/src/main/java/org/apache/kylin/tool/bisync/model/SyncModel.java b/src/query-common/src/main/java/org/apache/kylin/tool/bisync/model/SyncModel.java
index b8c8ddfafa..60151e17ef 100644
--- a/src/query-common/src/main/java/org/apache/kylin/tool/bisync/model/SyncModel.java
+++ b/src/query-common/src/main/java/org/apache/kylin/tool/bisync/model/SyncModel.java
@@ -21,9 +21,14 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
 public class SyncModel {
 
-    private String projectName;
+    private String project;
 
     private String modelName;
 
@@ -38,69 +43,4 @@ public class SyncModel {
     private List<MeasureDef> metrics;
 
     private Set<String[]> hierarchies;
-
-    public JoinTreeNode getJoinTree() {
-        return joinTree;
-    }
-
-    public void setJoinTree(JoinTreeNode joinTree) {
-        this.joinTree = joinTree;
-    }
-
-    public String getProjectName() {
-        return projectName;
-    }
-
-    public void setProjectName(String projectName) {
-        this.projectName = projectName;
-    }
-
-    public String getModelName() {
-        return modelName;
-    }
-
-    public void setModelName(String modelName) {
-        this.modelName = modelName;
-    }
-
-    public Map<String, ColumnDef> getColumnDefMap() {
-        return columnDefMap;
-    }
-
-    public void setColumnDefMap(Map<String, ColumnDef> columnDefMap) {
-        this.columnDefMap = columnDefMap;
-    }
-
-    public Set<String[]> getHierarchies() {
-        return hierarchies;
-    }
-
-    public void setHierarchies(Set<String[]> hierarchies) {
-        this.hierarchies = hierarchies;
-    }
-
-    public List<MeasureDef> getMetrics() {
-        return metrics;
-    }
-
-    public void setMetrics(List<MeasureDef> metrics) {
-        this.metrics = metrics;
-    }
-
-    public String getHost() {
-        return host;
-    }
-
-    public void setHost(String host) {
-        this.host = host;
-    }
-
-    public String getPort() {
-        return port;
-    }
-
-    public void setPort(String port) {
-        this.port = port;
-    }
-
 }
diff --git a/src/query-common/src/main/java/org/apache/kylin/tool/bisync/tableau/TableauDataSourceConverter.java b/src/query-common/src/main/java/org/apache/kylin/tool/bisync/tableau/TableauDataSourceConverter.java
index ced8616fa0..6540df9238 100644
--- a/src/query-common/src/main/java/org/apache/kylin/tool/bisync/tableau/TableauDataSourceConverter.java
+++ b/src/query-common/src/main/java/org/apache/kylin/tool/bisync/tableau/TableauDataSourceConverter.java
@@ -126,7 +126,7 @@ public class TableauDataSourceConverter implements BISyncModelConverter {
     }
 
     protected void fillTemplate(TableauDatasource tds, SyncModel syncModel) {
-        fillConnectionProperties(tds, syncModel.getHost(), syncModel.getPort(), syncModel.getProjectName(),
+        fillConnectionProperties(tds, syncModel.getHost(), syncModel.getPort(), syncModel.getProject(),
                 syncModel.getModelName());
         Map<String, Pair<Col, ColumnDef>> colMap = fillCols(tds, syncModel.getColumnDefMap());
         fillColumns(tds, colMap);
diff --git a/src/second-storage/clickhouse-it/src/test/java/io/kyligence/kap/secondstorage/SecondStorageLockTest.java b/src/second-storage/clickhouse-it/src/test/java/io/kyligence/kap/secondstorage/SecondStorageLockTest.java
index 026a056965..03c191ab16 100644
--- a/src/second-storage/clickhouse-it/src/test/java/io/kyligence/kap/secondstorage/SecondStorageLockTest.java
+++ b/src/second-storage/clickhouse-it/src/test/java/io/kyligence/kap/secondstorage/SecondStorageLockTest.java
@@ -77,6 +77,7 @@ import org.apache.kylin.common.util.JsonUtil;
 import org.apache.kylin.common.util.RandomUtil;
 import org.apache.kylin.common.util.Unsafe;
 import org.apache.kylin.engine.spark.IndexDataConstructor;
+import org.apache.kylin.engine.spark.job.NSparkCubingJob;
 import org.apache.kylin.job.SecondStorageCleanJobBuildParams;
 import org.apache.kylin.job.SecondStorageJobParamUtil;
 import org.apache.kylin.job.execution.AbstractExecutable;
@@ -92,6 +93,7 @@ import org.apache.kylin.metadata.cube.model.NDataSegment;
 import org.apache.kylin.metadata.cube.model.NDataflow;
 import org.apache.kylin.metadata.cube.model.NDataflowManager;
 import org.apache.kylin.metadata.cube.model.NIndexPlanManager;
+import org.apache.kylin.metadata.epoch.EpochManager;
 import org.apache.kylin.metadata.epoch.EpochOrchestrator;
 import org.apache.kylin.metadata.model.ColumnDesc;
 import org.apache.kylin.metadata.model.ManagementType;
@@ -191,10 +193,8 @@ import io.kyligence.kap.clickhouse.job.LoadContext;
 import io.kyligence.kap.clickhouse.job.S3TableSource;
 import io.kyligence.kap.clickhouse.management.ClickHouseConfigLoader;
 import io.kyligence.kap.clickhouse.parser.ShowDatabasesParser;
-import org.apache.kylin.engine.spark.job.NSparkCubingJob;
 import io.kyligence.kap.guava20.shaded.common.collect.ImmutableSet;
 import io.kyligence.kap.guava20.shaded.common.collect.Lists;
-import org.apache.kylin.metadata.epoch.EpochManager;
 import io.kyligence.kap.newten.clickhouse.ClickHouseSimpleITTestUtils;
 import io.kyligence.kap.newten.clickhouse.ClickHouseUtils;
 import io.kyligence.kap.newten.clickhouse.EmbeddedHttpServer;
@@ -349,6 +349,8 @@ public class SecondStorageLockTest implements JobWaiter {
         ReflectionTestUtils.setField(modelBuildService, "modelService", modelService);
         ReflectionTestUtils.setField(modelBuildService, "segmentHelper", segmentHelper);
         ReflectionTestUtils.setField(modelBuildService, "aclEvaluate", aclEvaluate);
+        ReflectionTestUtils.setField(modelBuildService, "accessService", accessService);
+        ReflectionTestUtils.setField(modelBuildService, "userGroupService", userGroupService);
 
         ReflectionTestUtils.setField(nModelController, "modelService", modelService);
         ReflectionTestUtils.setField(nModelController, "fusionModelService", fusionModelService);
diff --git a/src/tool/src/test/java/org/apache/kylin/tool/bisync/SyncModelBuilderTest.java b/src/tool/src/test/java/org/apache/kylin/tool/bisync/SyncModelBuilderTest.java
index 5a5b635d90..d45eb7b97c 100644
--- a/src/tool/src/test/java/org/apache/kylin/tool/bisync/SyncModelBuilderTest.java
+++ b/src/tool/src/test/java/org/apache/kylin/tool/bisync/SyncModelBuilderTest.java
@@ -19,16 +19,19 @@
 package org.apache.kylin.tool.bisync;
 
 import java.io.ByteArrayOutputStream;
+import java.io.File;
 import java.io.IOException;
-import java.io.InputStreamReader;
+import java.net.URL;
 import java.nio.charset.Charset;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Objects;
 import java.util.Set;
 import java.util.stream.Collectors;
 
+import org.apache.commons.io.FileUtils;
 import org.apache.kylin.common.KylinConfig;
 import org.apache.kylin.common.util.NLocalFileMetadataTestCase;
 import org.apache.kylin.metadata.acl.AclTCR;
@@ -44,10 +47,9 @@ import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 
-import com.google.common.base.Charsets;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
-import com.google.common.io.CharStreams;
 
 import lombok.val;
 
@@ -63,17 +65,22 @@ public class SyncModelBuilderTest extends NLocalFileMetadataTestCase {
         this.cleanupTestMetadata();
     }
 
+    private String getProject() {
+        return "default";
+    }
+
     @Test
     public void testBuildSyncModel() {
         val project = "default";
         val modelId = "cb596712-3a09-46f8-aea1-988b43fe9b6c";
         val syncContext = SyncModelTestUtil.createSyncContext(project, modelId, KylinConfig.getInstanceFromEnv());
         syncContext.setModelElement(SyncContext.ModelElement.ALL_COLS);
+        syncContext.setAdmin(true);
         val syncModel = new SyncModelBuilder(syncContext).buildSourceSyncModel();
         val df = NDataflowManager.getInstance(KylinConfig.getInstanceFromEnv(), project).getDataflow(modelId);
         val model = df.getModel();
 
-        Assert.assertEquals(project, syncModel.getProjectName());
+        Assert.assertEquals(project, syncModel.getProject());
         Assert.assertEquals(model.getAlias(), syncModel.getModelName());
         Assert.assertEquals("localhost", syncModel.getHost());
         Assert.assertEquals("7070", syncModel.getPort());
@@ -98,9 +105,9 @@ public class SyncModelBuilderTest extends NLocalFileMetadataTestCase {
 
         Assert.assertEquals(model.getAllMeasures().size(), syncModel.getMetrics().size());
         val syncMeasure = syncModel.getMetrics().get(0).getMeasure();
-        val modelMeasure = model.getAllMeasures().stream().filter(m -> m.getId() == syncMeasure.getId()).findFirst()
-                .get();
-        Assert.assertEquals(modelMeasure, syncMeasure);
+        val modelMeasure = model.getAllMeasures().stream().filter(m -> m.getId() == syncMeasure.getId()).findFirst();
+        Assert.assertTrue(modelMeasure.isPresent());
+        Assert.assertEquals(modelMeasure.get(), syncMeasure);
     }
 
     @Test
@@ -110,7 +117,7 @@ public class SyncModelBuilderTest extends NLocalFileMetadataTestCase {
         val project = "default";
         val modelId = "82fa7671-a935-45f5-8779-85703601f49a";
         val syncContext = SyncModelTestUtil.createSyncContext(project, modelId, KylinConfig.getInstanceFromEnv());
-        prepareBasic(project);
+        prepareBasic();
 
         Set<String> allAuthTables = Sets.newHashSet();
         Set<String> allAuthColumns = Sets.newHashSet();
@@ -157,6 +164,7 @@ public class SyncModelBuilderTest extends NLocalFileMetadataTestCase {
         measures.add("TEST_COUNT_DISTINCT_BITMAP");
         measures.add("GVM_PERCENTILE");
 
+        syncContext.setAdmin(false);
         syncContext.setModelElement(SyncContext.ModelElement.CUSTOM_COLS);
         TableauDatasourceModel datasource = (TableauDatasourceModel) BISyncTool
                 .dumpHasPermissionToBISyncModel(syncContext, allAuthTables, newAuthColumns, dimensions, measures);
@@ -165,35 +173,40 @@ public class SyncModelBuilderTest extends NLocalFileMetadataTestCase {
         Assert.assertEquals(getExpectedTds("/bisync_tableau/nmodel_full_measure_test.connector_permission.tds"),
                 outStream.toString(Charset.defaultCharset().name()));
 
+        syncContext.setAdmin(true);
+        syncContext.setModelElement(SyncContext.ModelElement.CUSTOM_COLS);
         TableauDatasourceModel datasource1 = (TableauDatasourceModel) BISyncTool.dumpBISyncModel(syncContext,
-                dimensions, null);
+                dimensions, ImmutableList.of());
         ByteArrayOutputStream outStream1 = new ByteArrayOutputStream();
         datasource1.dump(outStream1);
         Assert.assertEquals(
                 getExpectedTds("/bisync_tableau/nmodel_full_measure_test.connector_permission_no_measure.tds"),
                 outStream1.toString(Charset.defaultCharset().name()));
 
+        syncContext.setAdmin(true);
         syncContext.setModelElement(SyncContext.ModelElement.AGG_INDEX_COL);
-        TableauDatasourceModel datasource2 = (TableauDatasourceModel) BISyncTool.dumpBISyncModel(syncContext, null,
-                null);
+        TableauDatasourceModel datasource2 = (TableauDatasourceModel) BISyncTool.dumpBISyncModel(syncContext,
+                ImmutableList.of(), ImmutableList.of());
         ByteArrayOutputStream outStream2 = new ByteArrayOutputStream();
         datasource2.dump(outStream2);
         Assert.assertEquals(
                 getExpectedTds("/bisync_tableau/nmodel_full_measure_test.connector_permission_agg_index_col.tds"),
                 outStream2.toString(Charset.defaultCharset().name()));
 
+        syncContext.setAdmin(true);
         syncContext.setModelElement(SyncContext.ModelElement.AGG_INDEX_AND_TABLE_INDEX_COL);
-        TableauDatasourceModel datasource3 = (TableauDatasourceModel) BISyncTool.dumpBISyncModel(syncContext, null,
-                null);
+        TableauDatasourceModel datasource3 = (TableauDatasourceModel) BISyncTool.dumpBISyncModel(syncContext,
+                ImmutableList.of(), ImmutableList.of());
         ByteArrayOutputStream outStream3 = new ByteArrayOutputStream();
         datasource3.dump(outStream3);
         Assert.assertEquals(
                 getExpectedTds("/bisync_tableau/nmodel_full_measure_test.connector_permission_agg_index_col.tds"),
                 outStream3.toString(Charset.defaultCharset().name()));
 
+        syncContext.setAdmin(true);
         syncContext.setModelElement(SyncContext.ModelElement.ALL_COLS);
-        TableauDatasourceModel datasource4 = (TableauDatasourceModel) BISyncTool.dumpBISyncModel(syncContext, null,
-                null);
+        TableauDatasourceModel datasource4 = (TableauDatasourceModel) BISyncTool.dumpBISyncModel(syncContext,
+                ImmutableList.of(), ImmutableList.of());
         ByteArrayOutputStream outStream4 = new ByteArrayOutputStream();
         datasource4.dump(outStream4);
         Assert.assertEquals(getExpectedTds("/bisync_tableau/nmodel_full_measure_test.connector_permission_all_col.tds"),
@@ -206,7 +219,8 @@ public class SyncModelBuilderTest extends NLocalFileMetadataTestCase {
         val modelId = "cb596712-3a09-46f8-aea1-988b43fe9b6c";
         val syncContext = SyncModelTestUtil.createSyncContext(project, modelId, KylinConfig.getInstanceFromEnv());
         syncContext.setModelElement(SyncContext.ModelElement.ALL_COLS);
-        prepareBasic(project);
+        syncContext.setAdmin(true);
+        prepareBasic();
 
         TableauDatasourceModel datasource = (TableauDatasourceModel) BISyncTool.dumpToBISyncModel(syncContext);
         ByteArrayOutputStream outStream = new ByteArrayOutputStream();
@@ -230,6 +244,7 @@ public class SyncModelBuilderTest extends NLocalFileMetadataTestCase {
 
         val syncContext1 = SyncModelTestUtil.createSyncContext(project, "89af4ee2-2cdb-4b07-b39e-4c29856309aa",
                 KylinConfig.getInstanceFromEnv());
+        syncContext1.setAdmin(true);
         syncContext1.setModelElement(SyncContext.ModelElement.AGG_INDEX_COL);
         TableauDatasourceModel datasource3 = (TableauDatasourceModel) BISyncTool.dumpToBISyncModel(syncContext1);
         ByteArrayOutputStream outStream3 = new ByteArrayOutputStream();
@@ -254,7 +269,7 @@ public class SyncModelBuilderTest extends NLocalFileMetadataTestCase {
         groups.add("g1");
         val project = "default";
         val modelId = "741ca86a-1f13-46da-a59f-95fb68615e3a";
-        prepareBasic(project);
+        prepareBasic();
         Set<String> allAuthTables = Sets.newHashSet();
         Set<String> allAuthColumns = Sets.newHashSet();
         AclTCRManager aclTCRManager = AclTCRManager.getInstance(KylinConfig.getInstanceFromEnv(), project);
@@ -301,6 +316,7 @@ public class SyncModelBuilderTest extends NLocalFileMetadataTestCase {
         measures.add("GVM_PERCENTILE");
 
         cc_syncContext.setModelElement(SyncContext.ModelElement.CUSTOM_COLS);
+        cc_syncContext.setAdmin(false);
         TableauDatasourceModel datasource3 = (TableauDatasourceModel) BISyncTool
                 .dumpHasPermissionToBISyncModel(cc_syncContext, allAuthTables, newAuthColumns, dimensions, measures);
         ByteArrayOutputStream outStream3 = new ByteArrayOutputStream();
@@ -309,6 +325,7 @@ public class SyncModelBuilderTest extends NLocalFileMetadataTestCase {
                 outStream3.toString(Charset.defaultCharset().name()));
 
         cc_syncContext.setModelElement(SyncContext.ModelElement.CUSTOM_COLS);
+        cc_syncContext.setAdmin(true);
         TableauDatasourceModel datasource1 = (TableauDatasourceModel) BISyncTool.dumpBISyncModel(cc_syncContext,
                 dimensions, measures);
         ByteArrayOutputStream outStream1 = new ByteArrayOutputStream();
@@ -323,7 +340,7 @@ public class SyncModelBuilderTest extends NLocalFileMetadataTestCase {
         groups.add("g1");
         val project = "default";
         val modelId = "82fa7671-a935-45f5-8779-85703601f49a";
-        prepareBasic(project);
+        prepareBasic();
         Set<String> allAuthTables = Sets.newHashSet();
         Set<String> allAuthColumns = Sets.newHashSet();
         AclTCRManager aclTCRManager = AclTCRManager.getInstance(KylinConfig.getInstanceFromEnv(), project);
@@ -337,6 +354,7 @@ public class SyncModelBuilderTest extends NLocalFileMetadataTestCase {
         }
         val cc_syncContext = SyncModelTestUtil.createSyncContext(project, modelId, KylinConfig.getInstanceFromEnv());
         cc_syncContext.setModelElement(SyncContext.ModelElement.ALL_COLS);
+        cc_syncContext.setAdmin(true);
 
         Set<String> newAuthColumns = convertColumns(cc_syncContext.getDataflow().getModel(), allAuthColumns);
         List<String> dimensions = Lists.newArrayList();
@@ -372,6 +390,7 @@ public class SyncModelBuilderTest extends NLocalFileMetadataTestCase {
         measures.add("GVM_PERCENTILE");
 
         cc_syncContext.setModelElement(SyncContext.ModelElement.CUSTOM_COLS);
+        cc_syncContext.setAdmin(false);
         TableauDatasourceModel datasource3 = (TableauDatasourceModel) BISyncTool
                 .dumpHasPermissionToBISyncModel(cc_syncContext, allAuthTables, newAuthColumns, dimensions, measures);
         ByteArrayOutputStream outStream3 = new ByteArrayOutputStream();
@@ -380,6 +399,7 @@ public class SyncModelBuilderTest extends NLocalFileMetadataTestCase {
                 outStream3.toString(Charset.defaultCharset().name()));
 
         cc_syncContext.setModelElement(SyncContext.ModelElement.AGG_INDEX_COL);
+        cc_syncContext.setAdmin(false);
         TableauDatasourceModel datasource4 = (TableauDatasourceModel) BISyncTool.dumpHasPermissionToBISyncModel(
                 cc_syncContext, allAuthTables, newAuthColumns, new ArrayList<>(), new ArrayList<>());
         ByteArrayOutputStream outStream4 = new ByteArrayOutputStream();
@@ -388,6 +408,7 @@ public class SyncModelBuilderTest extends NLocalFileMetadataTestCase {
                 outStream4.toString(Charset.defaultCharset().name()));
 
         cc_syncContext.setModelElement(SyncContext.ModelElement.AGG_INDEX_AND_TABLE_INDEX_COL);
+        cc_syncContext.setAdmin(false);
         TableauDatasourceModel datasource5 = (TableauDatasourceModel) BISyncTool.dumpHasPermissionToBISyncModel(
                 cc_syncContext, allAuthTables, newAuthColumns, new ArrayList<>(), new ArrayList<>());
         ByteArrayOutputStream outStream5 = new ByteArrayOutputStream();
@@ -396,6 +417,7 @@ public class SyncModelBuilderTest extends NLocalFileMetadataTestCase {
                 outStream5.toString(Charset.defaultCharset().name()));
 
         cc_syncContext.setModelElement(SyncContext.ModelElement.ALL_COLS);
+        cc_syncContext.setAdmin(false);
         TableauDatasourceModel datasource6 = (TableauDatasourceModel) BISyncTool.dumpHasPermissionToBISyncModel(
                 cc_syncContext, allAuthTables, newAuthColumns, new ArrayList<>(), new ArrayList<>());
         ByteArrayOutputStream outStream6 = new ByteArrayOutputStream();
@@ -410,7 +432,7 @@ public class SyncModelBuilderTest extends NLocalFileMetadataTestCase {
         groups.add("g1");
         val project = "default";
         val modelId = "82fa7671-a935-45f5-8779-85703601f49a";
-        prepareBasicNoHierarchies(project);
+        prepareBasicNoHierarchies();
         Set<String> allAuthTables = Sets.newHashSet();
         Set<String> allAuthColumns = Sets.newHashSet();
         AclTCRManager aclTCRManager = AclTCRManager.getInstance(KylinConfig.getInstanceFromEnv(), project);
@@ -423,7 +445,6 @@ public class SyncModelBuilderTest extends NLocalFileMetadataTestCase {
             allAuthColumns.addAll(auths.getColumns());
         }
         val cc_syncContext = SyncModelTestUtil.createSyncContext(project, modelId, KylinConfig.getInstanceFromEnv());
-        cc_syncContext.setModelElement(SyncContext.ModelElement.ALL_COLS);
         Set<String> newAuthColumns = convertColumns(cc_syncContext.getDataflow().getModel(), allAuthColumns);
         List<String> dimensions = Lists.newArrayList();
         //"ORDER_ID", "PRICE", "CAL_DT", "PRICE", "ITEM_COUNT"
@@ -457,6 +478,7 @@ public class SyncModelBuilderTest extends NLocalFileMetadataTestCase {
         measures.add("TEST_COUNT_DISTINCT_BITMAP");
         measures.add("GVM_PERCENTILE");
         cc_syncContext.setModelElement(SyncContext.ModelElement.CUSTOM_COLS);
+        cc_syncContext.setAdmin(false);
         TableauDatasourceModel datasource3 = (TableauDatasourceModel) BISyncTool
                 .dumpHasPermissionToBISyncModel(cc_syncContext, allAuthTables, newAuthColumns, dimensions, measures);
         ByteArrayOutputStream outStream3 = new ByteArrayOutputStream();
@@ -466,11 +488,13 @@ public class SyncModelBuilderTest extends NLocalFileMetadataTestCase {
     }
 
     private String getExpectedTds(String path) throws IOException {
-        return CharStreams.toString(new InputStreamReader(getClass().getResourceAsStream(path), Charsets.UTF_8));
+        URL resource = getClass().getResource(path);
+        String fullPath = Objects.requireNonNull(resource).getPath();
+        return FileUtils.readFileToString(new File(fullPath), Charset.defaultCharset());
     }
 
-    private void prepareBasic(String project) {
-        AclTCRManager manager = AclTCRManager.getInstance(getTestConfig(), project);
+    private void prepareBasic() {
+        AclTCRManager manager = AclTCRManager.getInstance(getTestConfig(), getProject());
 
         AclTCR u1a1 = new AclTCR();
         AclTCR.Table u1t1 = new AclTCR.Table();
@@ -499,8 +523,8 @@ public class SyncModelBuilderTest extends NLocalFileMetadataTestCase {
         manager.updateAclTCR(g1a1, "g1", false);
     }
 
-    private void prepareBasicNoHierarchies(String project) {
-        AclTCRManager manager = AclTCRManager.getInstance(getTestConfig(), project);
+    private void prepareBasicNoHierarchies() {
+        AclTCRManager manager = AclTCRManager.getInstance(getTestConfig(), getProject());
 
         AclTCR u1a1 = new AclTCR();
         AclTCR.Table u1t1 = new AclTCR.Table();
@@ -554,8 +578,10 @@ public class SyncModelBuilderTest extends NLocalFileMetadataTestCase {
         val syncContext1 = SyncModelTestUtil.createSyncContext(project, model1Id, KylinConfig.getInstanceFromEnv());
         val syncContext2 = SyncModelTestUtil.createSyncContext(project, model2Id, KylinConfig.getInstanceFromEnv());
         syncContext1.setModelElement(SyncContext.ModelElement.AGG_INDEX_AND_TABLE_INDEX_COL);
+        syncContext1.setAdmin(true);
         syncContext2.setModelElement(SyncContext.ModelElement.AGG_INDEX_AND_TABLE_INDEX_COL);
-        prepareBasic(project);
+        syncContext2.setAdmin(true);
+        prepareBasic();
 
         TableauDatasourceModel datasource1 = (TableauDatasourceModel) BISyncTool.dumpToBISyncModel(syncContext1);
         ByteArrayOutputStream outStream1 = new ByteArrayOutputStream();
diff --git a/src/tool/src/test/java/org/apache/kylin/tool/bisync/SyncModelTestUtil.java b/src/tool/src/test/java/org/apache/kylin/tool/bisync/SyncModelTestUtil.java
index 91e3894873..28cf1cf270 100644
--- a/src/tool/src/test/java/org/apache/kylin/tool/bisync/SyncModelTestUtil.java
+++ b/src/tool/src/test/java/org/apache/kylin/tool/bisync/SyncModelTestUtil.java
@@ -33,6 +33,7 @@ public class SyncModelTestUtil {
         syncContext.setPort(7070);
         syncContext.setDataflow(NDataflowManager.getInstance(kylinConfig, project).getDataflow(modelId));
         syncContext.setKylinConfig(kylinConfig);
+        syncContext.setAdmin(true);
         return syncContext;
     }
 }