Skip to content

Commit

Permalink
[INLONG-8290][Manager] Add tenant condition into query by SQL interce…
Browse files Browse the repository at this point in the history
…ptor (#8304)

* [INLONG-8290][Manager] Support sql interceptor to add tenant into each query

* [INLONG-8290][Manager] Optimize multi-tenant code structure

* [INLONG-8290][Manager] Format the code style

---------

Co-authored-by: healchow <healchow@gmail.com>
  • Loading branch information
vernedeng and healchow authored Jun 26, 2023
1 parent 7214861 commit d3d1a0b
Show file tree
Hide file tree
Showing 7 changed files with 274 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ public class InlongConstants {

public static final String SEMICOLON = ";";

public static final String HYPHEN = "-";

public static final String UNDERSCORE = "_";

public static final String LEFT_BRACKET = "(";

public static final String PERCENT = "%";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* 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.inlong.manager.common.tenant;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* This annotation indicate that SQL queries from this type or method should
* be conditioned by tenant, which is obtained from the login user.
*/
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface MultiTenantQuery {

boolean with() default true;
}
4 changes: 4 additions & 0 deletions inlong-manager/manager-dao/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,10 @@
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.github.jsqlparser</groupId>
<artifactId>jsqlparser</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@

package org.apache.inlong.manager.dao.config;

import org.apache.inlong.manager.dao.interceptor.MultiTenantInterceptor;

import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
Expand Down Expand Up @@ -52,7 +54,7 @@ public SqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource());
bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mappers/*.xml"));

Objects.requireNonNull(bean.getObject()).getConfiguration().addInterceptor(new MultiTenantInterceptor());
Objects.requireNonNull(bean.getObject()).getConfiguration().setMapUnderscoreToCamelCase(true);
return bean.getObject();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
/*
* 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.inlong.manager.dao.interceptor;

import org.apache.inlong.manager.common.consts.InlongConstants;
import org.apache.inlong.manager.pojo.user.LoginUserUtils;
import org.apache.inlong.manager.pojo.user.UserInfo;

import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.parser.CCJSqlParserManager;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.statement.select.PlainSelect;
import net.sf.jsqlparser.statement.select.Select;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;

import java.io.StringReader;
import java.sql.Connection;
import java.util.Properties;

/**
* Interceptor for multi-tenant.
*/
@Intercepts({
@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})
})
public class MultiTenantInterceptor implements Interceptor {

private static final String TENANT_CONDITION = "tenant=";

@Override
public Object intercept(Invocation invocation) throws Throwable {
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
MetaObject metaObject = MetaObject.forObject(statementHandler, SystemMetaObject.DEFAULT_OBJECT_FACTORY,
SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY, new DefaultReflectorFactory());
MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");

String fullMethodName = mappedStatement.getId();
if (!MultiTenantQueryFilter.isMultiTenantQuery(fullMethodName.split(InlongConstants.UNDERSCORE)[0])) {
return invocation.proceed();
}

BoundSql boundSql = statementHandler.getBoundSql();
String sql = boundSql.getSql();

CCJSqlParserManager parserManager = new CCJSqlParserManager();
Select select = (Select) parserManager.parse(new StringReader(sql));
PlainSelect plain = (PlainSelect) select.getSelectBody();

StringBuilder whereSql = new StringBuilder();
whereSql.append(TENANT_CONDITION).append(getTenant());

Expression where = plain.getWhere();
if (where == null) {
Expression expression = CCJSqlParserUtil.parseCondExpression(whereSql.toString());
plain.setWhere(expression);
} else {
if (where.toString().contains(TENANT_CONDITION)) {
return invocation.proceed();
}

// else, append the tenant condition
whereSql.append(" and ( ").append(where).append(" )");
Expression expression = CCJSqlParserUtil.parseCondExpression(whereSql.toString());
plain.setWhere(expression);
}
metaObject.setValue("delegate.boundSql.sql", select.toString());
return invocation.proceed();
}

private static String getTenant() {
UserInfo userInfo = LoginUserUtils.getLoginUser();
if (userInfo == null) {
throw new IllegalStateException("current login user is null, please login first");
}
String tenant = userInfo.getTenant();
if (StringUtils.isBlank(tenant)) {
throw new IllegalStateException("get no target tenant of userInfo=" + userInfo);
}
return tenant;
}

@Override
public Object plugin(Object target) {
if (target instanceof StatementHandler) {
return Plugin.wrap(target, this);
} else {
return target;
}
}

@Override
public void setProperties(Properties properties) {

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/*
* 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.inlong.manager.dao.interceptor;

import org.apache.inlong.manager.common.tenant.MultiTenantQuery;

import lombok.extern.slf4j.Slf4j;
import org.reflections.Reflections;
import org.reflections.scanners.Scanners;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
* Filter to check if SQLs from some method should add tenant condition or not.
*/
@Slf4j
@Component
public class MultiTenantQueryFilter {

private static final String METHOD_FILTER_PATH = "org.apache.inlong.manager.dao.mapper";

private static final Set<String> METHOD_SET = new HashSet<>();

/**
* Check whether the specified method supports multi-tenant queries.
*
* @param methodName method name
* @return true if supports multi-tenant query, false if not
*/
public static boolean isMultiTenantQuery(String methodName) {
return METHOD_SET.contains(methodName);
}

/**
* Find all methods that support multi-tenant queries - used MultiTenantQuery annotation.
*/
@PostConstruct
private void init() {
Reflections methodReflections = new Reflections(METHOD_FILTER_PATH, Scanners.MethodsAnnotated);
// process methods
Set<Method> methodSet = methodReflections.getMethodsAnnotatedWith(MultiTenantQuery.class);
markMethods(methodSet);

// process classes
Reflections reflections = new Reflections(METHOD_FILTER_PATH, Scanners.TypesAnnotated);
Set<Class<?>> clazzSet = reflections.getTypesAnnotatedWith(MultiTenantQuery.class);
clazzSet.stream()
.filter(Class::isInterface)
.forEach(clazz -> {
// Get the JsonTypeDefine annotation
MultiTenantQuery annotation = clazz.getAnnotation(MultiTenantQuery.class);
if (annotation == null || !annotation.with()) {
return;
}
List<Method> methods = Arrays.asList(clazz.getMethods());
markMethods(methods);
});

log.debug("success to find all methods that support multi-tenant queries, methods={}", METHOD_SET);
}

private static void markMethods(Collection<Method> methods) {
methods.forEach(method -> {
MultiTenantQuery annotation = method.getAnnotation(MultiTenantQuery.class);
if (annotation != null && !annotation.with()) {
METHOD_SET.remove(getMethodFullName(method));
} else {
METHOD_SET.add(getMethodFullName(method));
}
});
}

private static String getMethodFullName(Method method) {
return method.getDeclaringClass().getName() + "." + method.getName();
}

}
6 changes: 6 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@
<spring.fox.version>3.0.0</spring.fox.version>
<pagehelper.springboot.version>1.4.2</pagehelper.springboot.version>
<pagehelper.version>5.3.1</pagehelper.version>
<jsqlparser.version>4.6</jsqlparser.version>

<h2.version>2.1.214</h2.version>
<h2.mysql.version>2.0.0</h2.mysql.version>
Expand Down Expand Up @@ -636,6 +637,11 @@
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>${pagehelper.springboot.version}</version>
</dependency>
<dependency>
<groupId>com.github.jsqlparser</groupId>
<artifactId>jsqlparser</artifactId>
<version>${jsqlparser.version}</version>
</dependency>

<!--netty dependency-->
<dependency>
Expand Down

0 comments on commit d3d1a0b

Please sign in to comment.