-
Notifications
You must be signed in to change notification settings - Fork 533
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[INLONG-8290][Manager] Add tenant condition into query by SQL interce…
…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
Showing
7 changed files
with
274 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
36 changes: 36 additions & 0 deletions
36
...anager-common/src/main/java/org/apache/inlong/manager/common/tenant/MultiTenantQuery.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
121 changes: 121 additions & 0 deletions
121
...r-dao/src/main/java/org/apache/inlong/manager/dao/interceptor/MultiTenantInterceptor.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) { | ||
|
||
} | ||
} |
100 changes: 100 additions & 0 deletions
100
...r-dao/src/main/java/org/apache/inlong/manager/dao/interceptor/MultiTenantQueryFilter.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters