
org.apache.kylin.rest.controller.BaseController Maven / Gradle / Ivy
The newest version!
/*
* 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.controller;
import static org.apache.kylin.common.exception.ServerErrorCode.ACCESS_DENIED;
import static org.apache.kylin.common.exception.ServerErrorCode.EMPTY_PROJECT_NAME;
import static org.apache.kylin.common.exception.ServerErrorCode.FAILED_CONNECT_CATALOG;
import static org.apache.kylin.common.exception.ServerErrorCode.FAILED_DOWNLOAD_FILE;
import static org.apache.kylin.common.exception.ServerErrorCode.INVALID_PARAMETER;
import static org.apache.kylin.common.exception.ServerErrorCode.UNSUPPORTED_STREAMING_OPERATION;
import static org.apache.kylin.common.exception.ServerErrorCode.USER_UNAUTHORIZED;
import static org.apache.kylin.common.exception.code.ErrorCodeServer.ARGS_TYPE_CHECK;
import static org.apache.kylin.common.exception.code.ErrorCodeServer.INTEGER_NON_NEGATIVE_CHECK;
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.REQUEST_PARAMETER_EMPTY_OR_VALUE_EMPTY;
import static org.apache.kylin.common.exception.code.ErrorCodeServer.SEGMENT_CONFLICT_PARAMETER;
import static org.apache.kylin.common.exception.code.ErrorCodeServer.SEGMENT_EMPTY_PARAMETER;
import static org.apache.kylin.guava30.shaded.common.net.HttpHeaders.CONTENT_DISPOSITION;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.kylin.common.KylinConfig;
import org.apache.kylin.common.exception.FeignErrorResponse;
import org.apache.kylin.common.exception.FeignRpcException;
import org.apache.kylin.common.exception.KylinException;
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.AddressUtil;
import org.apache.kylin.common.util.JsonUtil;
import org.apache.kylin.guava30.shaded.common.collect.Lists;
import org.apache.kylin.job.JobContext;
import org.apache.kylin.job.constant.JobActionEnum;
import org.apache.kylin.job.dao.ExecutablePO;
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.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.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.tool.restclient.RestClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.transaction.CannotCreateTransactionException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import lombok.SneakyThrows;
import lombok.val;
public class BaseController {
private static final Logger logger = LoggerFactory.getLogger(BaseController.class);
protected static final int MAX_NAME_LENGTH = 50;
@Autowired
@Qualifier("normalRestTemplate")
private RestTemplate restTemplate;
@Autowired
private ProjectService projectService;
@Autowired(required = false)
private JobContext jobContext;
@Autowired
protected UserService userService;
protected Logger getLogger() {
return logger;
}
public ProjectInstance getProject(String project) {
if (null != project) {
List projectInstanceList = projectService.getReadableProjects(project, true);
if (CollectionUtils.isNotEmpty(projectInstanceList)) {
return projectInstanceList.get(0);
}
}
throw new KylinException(PROJECT_NOT_EXIST, project);
}
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler(Exception.class)
@ResponseBody
ErrorResponse handleError(HttpServletRequest req, Throwable ex) {
getLogger().error("", ex);
Message msg = MsgPicker.getMsg();
Throwable cause = ex;
KylinException kylinException = null;
while (cause != null && cause.getCause() != null) {
if (cause instanceof CannotCreateTransactionException) {
kylinException = new KylinException(FAILED_CONNECT_CATALOG, msg.getConnectDatabaseError(), false);
}
if (cause instanceof KylinException) {
kylinException = (KylinException) cause;
}
cause = cause.getCause();
}
if (kylinException != null) {
cause = kylinException;
}
return new ErrorResponse(req.getRequestURL().toString(), cause);
}
@ResponseStatus(HttpStatus.FORBIDDEN)
@ExceptionHandler(ForbiddenException.class)
@ResponseBody
ErrorResponse handleForbidden(HttpServletRequest req, Exception ex) {
getLogger().error("", ex);
return new ErrorResponse(req.getRequestURL().toString(), ex);
}
@ResponseStatus(HttpStatus.NOT_FOUND)
@ExceptionHandler(NotFoundException.class)
@ResponseBody
ErrorResponse handleNotFound(HttpServletRequest req, Exception ex) {
getLogger().error("", ex);
return new ErrorResponse(req.getRequestURL().toString(), ex);
}
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler(TransactionException.class)
@ResponseBody
ErrorResponse handleTransaction(HttpServletRequest req, Throwable ex) {
getLogger().error("", ex);
Throwable root = ExceptionUtils.getRootCause(ex) == null ? ex : ExceptionUtils.getRootCause(ex);
if (root instanceof AccessDeniedException) {
return handleAccessDenied(req, root);
} else if (root instanceof KylinException) {
return handleErrorCode(req, root);
} else {
return handleError(req, ex);//use ex , not root
}
}
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(AccessDeniedException.class)
@ResponseBody
ErrorResponse handleAccessDenied(HttpServletRequest req, Throwable ex) {
getLogger().error("", ex);
KylinException e = new KylinException(ACCESS_DENIED, MsgPicker.getMsg().getAccessDeny());
return new ErrorResponse(req.getRequestURL().toString(), e);
}
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler({ MethodArgumentTypeMismatchException.class, IllegalArgumentException.class })
@ResponseBody
ErrorResponse handleInvalidRequestParam(HttpServletRequest req, Throwable ex) {
KylinException e = new KylinException(INVALID_PARAMETER, ex);
getLogger().error("", e);
return new ErrorResponse(req.getRequestURL().toString(), e);
}
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(MissingServletRequestParameterException.class)
@ResponseBody
ErrorResponse handleMissingServletRequestParam(HttpServletRequest req, MissingServletRequestParameterException ex) {
String parameterName = ex.getParameterName();
KylinException e = new KylinException(REQUEST_PARAMETER_EMPTY_OR_VALUE_EMPTY, parameterName);
getLogger().error("", e);
return new ErrorResponse(req.getRequestURL().toString(), e);
}
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler(KylinException.class)
@ResponseBody
ErrorResponse handleErrorCode(HttpServletRequest req, Throwable ex) {
getLogger().error("", ex);
KylinException cause = (KylinException) ex;
return new ErrorResponse(req.getRequestURL().toString(), cause);
}
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseBody
ErrorResponse handleInvalidArgument(HttpServletRequest request, MethodArgumentNotValidException ex) {
val response = new ErrorResponse(request.getRequestURL().toString(), ex);
val target = ex.getBindingResult().getTarget();
if (target instanceof Validation) {
response.setMsg(((Validation) target).getErrorMessage(ex.getBindingResult().getFieldErrors()));
} else {
response.setMsg(ex.getBindingResult().getFieldErrors().stream()
.map(e -> e.getField() + ":" + e.getDefaultMessage()).collect(Collectors.joining(",")));
}
return response;
}
@ResponseStatus(HttpStatus.UNAUTHORIZED)
@ExceptionHandler(UnauthorizedException.class)
@ResponseBody
ErrorResponse handleUnauthorized(HttpServletRequest req, Throwable ex) {
KylinException e = new KylinException(USER_UNAUTHORIZED, ex);
getLogger().error("", e);
return new ErrorResponse(req.getRequestURL().toString(), ex);
}
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler(FeignRpcException.class)
@ResponseBody
FeignErrorResponse handleFeignRpcException(HttpServletRequest req, Throwable ex) {
getLogger().error("", ex);
FeignRpcException cause = (FeignRpcException) ex;
String msg = "Exception happened when using feign rpc: " + req.getRequestURL().toString();
return new FeignErrorResponse(msg, cause.getExceptionSerialized());
}
protected void checkRequiredArg(String fieldName, Object fieldValue) {
if (fieldValue == null || StringUtils.isEmpty(String.valueOf(fieldValue))) {
throw new KylinException(REQUEST_PARAMETER_EMPTY_OR_VALUE_EMPTY, fieldName);
}
}
public String makeUserNameCaseInSentive(String userName) {
UserDetails userDetails = userService.loadUserByUsername(userName);
if (userDetails == null) {
return userName;
}
return userDetails.getUsername();
}
protected void checkNonNegativeIntegerArg(String fieldName, Object fieldValue) {
checkRequiredArg(fieldName, fieldValue);
try {
int i = Integer.parseInt(String.valueOf(fieldValue));
if (i < 0) {
throw new NumberFormatException();
}
} catch (NumberFormatException e) {
throw new KylinException(INTEGER_NON_NEGATIVE_CHECK, fieldName);
}
}
protected void checkBooleanArg(String fieldName, Object fieldValue) {
checkRequiredArg(fieldName, fieldValue);
String booleanString = String.valueOf(fieldValue);
if (!"true".equalsIgnoreCase(booleanString) && !"false".equalsIgnoreCase(booleanString)) {
throw new KylinException(ARGS_TYPE_CHECK, booleanString, "Boolean");
}
}
protected void setDownloadResponse(File file, String fileName, String contentType,
final HttpServletResponse response) {
try (FileInputStream fileInputStream = new FileInputStream(file)) {
setDownloadResponse(fileInputStream, fileName, contentType, response);
response.setContentLength((int) file.length());
} catch (IOException e) {
throw new KylinException(FAILED_DOWNLOAD_FILE, e);
}
}
protected void setDownloadResponse(InputStream inputStream, String fileName, String contentType,
final HttpServletResponse response) {
try (OutputStream output = response.getOutputStream()) {
response.reset();
response.setContentType(contentType);
response.setHeader(CONTENT_DISPOSITION, "attachment; filename=\"" + fileName + "\"");
IOUtils.copyLarge(inputStream, output);
output.flush();
} catch (IOException e) {
throw new KylinException(FAILED_DOWNLOAD_FILE, e);
} finally {
try {
inputStream.close();
} catch (IOException ignore) {
}
}
}
public HashMap getDataResponse(String name, List> result, int offset, int limit) {
return getDataResponse(name, result, result.size(), offset, limit);
}
public HashMap getDataResponse(String name, List> result, int count, int offset, int limit) {
HashMap data = new HashMap<>();
data.put(name, PagingUtil.cutPage(result, offset, limit));
data.put("size", count);
return data;
}
public String checkProjectName(String project) {
if (StringUtils.isEmpty(project)) {
throw new KylinException(EMPTY_PROJECT_NAME, MsgPicker.getMsg().getEmptyProjectName());
}
NProjectManager projectManager = NProjectManager.getInstance(KylinConfig.getInstanceFromEnv());
ProjectInstance prjInstance = projectManager.getProject(project);
if (prjInstance == null) {
throw new KylinException(PROJECT_NOT_EXIST, project);
}
return prjInstance.getName();
}
protected void checkCollectionRequiredArg(String fieldName, Collection> fieldValue) {
if (CollectionUtils.isEmpty(fieldValue)) {
throw new KylinException(REQUEST_PARAMETER_EMPTY_OR_VALUE_EMPTY, fieldName);
}
}
@SneakyThrows
public void checkParamLength(String paramName, Object param, int length) {
if (param == null) {
return;
}
String paramStr = JsonUtil.writeValueAsString(param);
if (paramStr.length() * 2 > length * 1024) {
throw new KylinException(INVALID_PARAMETER,
String.format(Locale.ROOT, MsgPicker.getMsg().getParamTooLarge(), paramName, length));
}
}
protected void checkSegmentParams(String[] ids, String[] names) {
//both not empty
if (ArrayUtils.isNotEmpty(ids) && ArrayUtils.isNotEmpty(names)) {
throw new KylinException(SEGMENT_CONFLICT_PARAMETER);
}
//both empty
if (ArrayUtils.isEmpty(ids) && ArrayUtils.isEmpty(names)) {
throw new KylinException(SEGMENT_EMPTY_PARAMETER);
}
}
public List formatStatus(List status, Class enumClass) {
if (status == null) {
return Lists.newArrayList();
}
Set enumStrSet = Arrays.stream(enumClass.getEnumConstants()).map(Objects::toString)
.collect(Collectors.toSet());
List formattedStatus = status.stream().filter(Objects::nonNull)
.map(item -> item.toUpperCase(Locale.ROOT)).collect(Collectors.toList());
List illegalStatus = formattedStatus.stream().filter(item -> !enumStrSet.contains(item))
.collect(Collectors.toList());
if (!illegalStatus.isEmpty()) {
throw new KylinException(PARAMETER_INVALID_SUPPORT_LIST, "status", "ONLINE, OFFLINE, WARNING, BROKEN");
}
return formattedStatus;
}
public void validatePriority(int priority) {
if (!ExecutablePO.isPriorityValid(priority)) {
throw new KylinException(PARAMETER_INVALID_SUPPORT_LIST, "priority", "0, 1, 2, 3, 4");
}
}
public void checkStreamingOperation(String project, String table) {
val config = KylinConfig.getInstanceFromEnv();
val kafkaConf = KafkaConfigManager.getInstance(config, project).getKafkaConfig(table);
if (kafkaConf != null) {
throw new KylinException(UNSUPPORTED_STREAMING_OPERATION,
MsgPicker.getMsg().getStreamingOperationNotSupport());
}
}
protected boolean needRouteToOtherInstance(Map> nodeWithJobs, String action) {
if (JobActionEnum.RESUME.name().equalsIgnoreCase(action)) {
return false;
}
Set targetNodes = nodeWithJobs.keySet();
String local = AddressUtil.getLocalInstance();
return targetNodes.size() > 1 || targetNodes.size() == 1 && !targetNodes.contains(local);
}
protected void forwardRequestToTargetNode(byte[] requestEntity, HttpHeaders headers, String node, String url)
throws IOException {
RestClient client = new RestClient(node);
client.forwardPut(requestEntity, headers, url, true);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy