content: /*
   * Copyright 1999-2018 Alibaba Group Holding Ltd.
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  package com.alibaba.druid.stat;
  
  import com.alibaba.druid.DbType;
  import com.alibaba.druid.sql.SQLUtils;
  import com.alibaba.druid.sql.ast.SQLStatement;
  import com.alibaba.druid.sql.visitor.SchemaStatVisitor;
  import com.alibaba.druid.support.http.stat.WebAppStatManager;
  import com.alibaba.druid.support.json.JSONUtils;
  import com.alibaba.druid.support.logging.Log;
  import com.alibaba.druid.support.logging.LogFactory;
  import com.alibaba.druid.support.spring.stat.SpringStatManager;
  import com.alibaba.druid.util.MapComparator;
  import com.alibaba.druid.util.StringUtils;
  
  import javax.management.JMException;
  import javax.management.MBeanServer;
  import javax.management.ObjectName;
  
  import java.lang.management.ManagementFactory;
  import java.text.DateFormat;
  import java.text.SimpleDateFormat;
  import java.util.*;
  
  /**
   * 注意：避免直接调用Druid相关对象例如DruidDataSource等，相关调用要到DruidStatManagerFacade里用反射实现
   *
   * @author sandzhang[sandzhangtoo@gmail.com]
   */
  public final class DruidStatService implements DruidStatServiceMBean {
      private static final Log LOG = LogFactory.getLog(DruidStatService.class);
  
      public static final String MBEAN_NAME = "com.alibaba.druid:type=DruidStatService";
  
      private static final DruidStatService instance = new DruidStatService();
  
      private static DruidStatManagerFacade statManagerFacade = DruidStatManagerFacade.getInstance();
  
      public static final int RESULT_CODE_SUCCESS = 1;
      public static final int RESULT_CODE_ERROR = -1;
  
      private static final int DEFAULT_PAGE = 1;
      private static final int DEFAULT_PER_PAGE_COUNT = Integer.MAX_VALUE;
      private static final String ORDER_TYPE_DESC = "desc";
      private static final String ORDER_TYPE_ASC = "asc";
      private static final String DEFAULT_ORDER_TYPE = ORDER_TYPE_ASC;
      private static final String DEFAULT_ORDERBY = "SQL";
  
      private DruidStatService() {
      }
  
      public static DruidStatService getInstance() {
          return instance;
      }
  
      public boolean isResetEnable() {
          return statManagerFacade.isResetEnable();
      }
  
      public void setResetEnable(boolean value) {
          statManagerFacade.setResetEnable(value);
      }
  
      public String service(String url) {
          Map<String, String> parameters = getParameters(url);
  
          if (url.equals("/basic.json")) {
              return returnJSONResult(RESULT_CODE_SUCCESS, statManagerFacade.returnJSONBasicStat());
          }
  
          if (url.equals("/reset-all.json")) {
              statManagerFacade.resetAll();
  
              return returnJSONResult(RESULT_CODE_SUCCESS, null);
          }
  
          if (url.equals("/log-and-reset.json")) {
              statManagerFacade.logAndResetDataSource();
  
              return returnJSONResult(RESULT_CODE_SUCCESS, null);
          }
  
          if (url.equals("/datasource.json")) {
              return returnJSONResult(RESULT_CODE_SUCCESS, statManagerFacade.getDataSourceStatDataList());
          }
  
          if (url.equals("/activeConnectionStackTrace.json")) {
              return returnJSONResult(RESULT_CODE_SUCCESS, statManagerFacade.getActiveConnStackTraceList());
          }
  
          if (url.startsWith("/datasource-")) {
              Integer id = StringUtils.subStringToInteger(url, "datasource-", ".");
              Object result = statManagerFacade.getDataSourceStatData(id);
              return returnJSONResult(result == null ? RESULT_CODE_ERROR : RESULT_CODE_SUCCESS, result);
          }
  
          if (url.startsWith("/connectionInfo-") && url.endsWith(".json")) {
              Integer id = StringUtils.subStringToInteger(url, "connectionInfo-", ".");
              List<?> connectionInfoList = statManagerFacade.getPoolingConnectionInfoByDataSourceId(id);
              return returnJSONResult(connectionInfoList == null ? RESULT_CODE_ERROR : RESULT_CODE_SUCCESS,
                      connectionInfoList);
          }
  
          if (url.startsWith("/activeConnectionStackTrace-") && url.endsWith(".json")) {
              Integer id = StringUtils.subStringToInteger(url, "activeConnectionStackTrace-", ".");
              return returnJSONActiveConnectionStackTrace(id);
          }
  
          if (url.startsWith("/sql.json")) {
              return returnJSONResult(RESULT_CODE_SUCCESS, getSqlStatDataList(parameters));
          }
  
          if (url.startsWith("/wall.json")) {
              return returnJSONResult(RESULT_CODE_SUCCESS, getWallStatMap(parameters));
          }
  
          if (url.startsWith("/wall-") && url.indexOf(".json") > 0) {
              Integer dataSourceId = StringUtils.subStringToInteger(url, "wall-", ".json");
              Object result = statManagerFacade.getWallStatMap(dataSourceId);
              return returnJSONResult(result == null ? RESULT_CODE_ERROR : RESULT_CODE_SUCCESS, result);
          }
  
          if (url.startsWith("/sql-") && url.indexOf(".json") > 0) {
              Integer id = StringUtils.subStringToInteger(url, "sql-", ".json");
              return getSqlStat(id);
          }
  
          if (url.startsWith("/weburi.json")) {
              return returnJSONResult(RESULT_CODE_SUCCESS, getWebURIStatDataList(parameters));
          }
  
          if (url.startsWith("/weburi-") && url.indexOf(".json") > 0) {
              String uri = StringUtils.subString(url, "weburi-", ".json", true);
              return returnJSONResult(RESULT_CODE_SUCCESS, getWebURIStatData(uri));
          }
  
          if (url.startsWith("/webapp.json")) {
              return returnJSONResult(RESULT_CODE_SUCCESS, getWebAppStatDataList(parameters));
          }
  
          if (url.startsWith("/websession.json")) {
              return returnJSONResult(RESULT_CODE_SUCCESS, getWebSessionStatDataList(parameters));
          }
  
          if (url.startsWith("/websession-") && url.indexOf(".json") > 0) {
              String id = StringUtils.subString(url, "websession-", ".json");
              return returnJSONResult(RESULT_CODE_SUCCESS, getWebSessionStatData(id));
          }
  
          if (url.startsWith("/spring.json")) {
              return returnJSONResult(RESULT_CODE_SUCCESS, getSpringStatDataList(parameters));
          }
  
          if (url.startsWith("/spring-detail.json")) {
              String clazz = parameters.get("class");
              String method = parameters.get("method");
              return returnJSONResult(RESULT_CODE_SUCCESS, getSpringMethodStatData(clazz, method));
          }
  
          return returnJSONResult(RESULT_CODE_ERROR, "Do not support this request, please contact with administrator.");
      }
  
      private List<Map<String, Object>> getSpringStatDataList(Map<String, String> parameters) {
          List<Map<String, Object>> array = SpringStatManager.getInstance().getMethodStatData();
          return comparatorOrderBy(array, parameters);
      }
  
      private List<Map<String, Object>> getWebURIStatDataList(Map<String, String> parameters) {
          List<Map<String, Object>> array = WebAppStatManager.getInstance().getURIStatData();
          return comparatorOrderBy(array, parameters);
      }
  
      private Map<String, Object> getWebURIStatData(String uri) {
          return WebAppStatManager.getInstance().getURIStatData(uri);
      }
  
      private Map<String, Object> getWebSessionStatData(String sessionId) {
          return WebAppStatManager.getInstance().getSessionStat(sessionId);
      }
  
      private Map<String, Object> getSpringMethodStatData(String clazz, String method) {
          return SpringStatManager.getInstance().getMethodStatData(clazz, method);
      }
  
      private List<Map<String, Object>> getWebSessionStatDataList(Map<String, String> parameters) {
          List<Map<String, Object>> array = WebAppStatManager.getInstance().getSessionStatData();
          return comparatorOrderBy(array, parameters);
      }
  
      private List<Map<String, Object>> getWebAppStatDataList(Map<String, String> parameters) {
          List<Map<String, Object>> array = WebAppStatManager.getInstance().getWebAppStatData();
          return comparatorOrderBy(array, parameters);
      }
  
      private List<Map<String, Object>> comparatorOrderBy(List<Map<String, Object>> array,
                                                          Map<String, String> parameters) {
          // when open the stat page before executing some sql
          if (array == null || array.isEmpty()) {
              return null;
          }
  
          // when parameters is null
          String orderBy, orderType = null;
          Integer page = DEFAULT_PAGE;
          Integer perPageCount = DEFAULT_PER_PAGE_COUNT;
          if (parameters == null) {
              orderBy = DEFAULT_ORDERBY;
              orderType = DEFAULT_ORDER_TYPE;
              page = DEFAULT_PAGE;
              perPageCount = DEFAULT_PER_PAGE_COUNT;
          } else {
              orderBy = parameters.get("orderBy");
              orderType = parameters.get("orderType");
              String pageParam = parameters.get("page");
              if (pageParam != null && pageParam.length() != 0) {
                  page = Integer.parseInt(pageParam);
              }
              String pageCountParam = parameters.get("perPageCount");
              if (pageCountParam != null && pageCountParam.length() > 0) {
                  perPageCount = Integer.parseInt(pageCountParam);
              }
          }
  
          // others,such as order
          orderBy = orderBy == null ? DEFAULT_ORDERBY : orderBy;
          orderType = orderType == null ? DEFAULT_ORDER_TYPE : orderType;
  
          if (!ORDER_TYPE_DESC.equals(orderType)) {
              orderType = ORDER_TYPE_ASC;
          }
  
          // orderby the statData array
          if (orderBy.trim().length() != 0) {
              Collections.sort(array, new MapComparator<String, Object>(orderBy, ORDER_TYPE_DESC.equals(orderType)));
          }
  
          // page
          int fromIndex = (page - 1) * perPageCount;
          int toIndex = page * perPageCount;
          if (toIndex > array.size()) {
              toIndex = array.size();
          }
  
          return array.subList(fromIndex, toIndex);
      }
  
      private List<Map<String, Object>> getSqlStatDataList(Map<String, String> parameters) {
          Integer dataSourceId = null;
  
          String dataSourceIdParam = parameters.get("dataSourceId");
          if (dataSourceIdParam != null && dataSourceIdParam.length() > 0) {
              dataSourceId = Integer.parseInt(dataSourceIdParam);
          }
  
          List<Map<String, Object>> array = statManagerFacade.getSqlStatDataList(dataSourceId);
          List<Map<String, Object>> sortedArray = comparatorOrderBy(array, parameters);
          return sortedArray;
      }
  
      @SuppressWarnings("unchecked")
      public Map<String, Object> getWallStatMap(Map<String, String> parameters) {
          Integer dataSourceId = null;
  
          String dataSourceIdParam = parameters.get("dataSourceId");
          if (dataSourceIdParam != null && dataSourceIdParam.length() > 0) {
              dataSourceId = Integer.parseInt(dataSourceIdParam);
          }
  
          Map<String, Object> result = statManagerFacade.getWallStatMap(dataSourceId);
  
          if (result != null) {
              List<Map<String, Object>> tables = (List<Map<String, Object>>) result.get("tables");
              if (tables != null) {
                  List<Map<String, Object>> sortedArray = comparatorOrderBy(tables, parameters);
                  result.put("tables", sortedArray);
              }
  
              List<Map<String, Object>> functions = (List<Map<String, Object>>) result.get("functions");
              if (functions != null) {
                  List<Map<String, Object>> sortedArray = comparatorOrderBy(functions, parameters);
                  result.put("functions", sortedArray);
              }
          } else {
              result = Collections.emptyMap();
          }
  
          return result;
      }
  
      private String getSqlStat(Integer id) {
          Map<String, Object> map = statManagerFacade.getSqlStatData(id);
  
          if (map == null) {
              return returnJSONResult(RESULT_CODE_ERROR, null);
          }
  
          DbType dbType = DbType.of((String) map.get("DbType"));
          String sql = (String) map.get("SQL");
  
          map.put("formattedSql", SQLUtils.format(sql, dbType));
          List<SQLStatement> statementList = SQLUtils.parseStatements(sql, dbType);
  
          if (!statementList.isEmpty()) {
              SQLStatement sqlStmt = statementList.get(0);
              SchemaStatVisitor visitor = SQLUtils.createSchemaStatVisitor(dbType);
              sqlStmt.accept(visitor);
              map.put("parsedTable", visitor.getTables().toString());
              map.put("parsedFields", visitor.getColumns().toString());
              map.put("parsedConditions", visitor.getConditions().toString());
              map.put("parsedRelationships", visitor.getRelationships().toString());
              map.put("parsedOrderbycolumns", visitor.getOrderByColumns().toString());
          }
  
          DateFormat format = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss:SSS");
          Date maxTimespanOccurTime = (Date) map.get("MaxTimespanOccurTime");
          if (maxTimespanOccurTime != null) {
              map.put("MaxTimespanOccurTime", format.format(maxTimespanOccurTime));
          }
  
          return returnJSONResult(map == null ? RESULT_CODE_ERROR : RESULT_CODE_SUCCESS, map);
      }
  
      private String returnJSONActiveConnectionStackTrace(Integer id) {
          List<String> result = statManagerFacade.getActiveConnectionStackTraceByDataSourceId(id);
  
          if (result == null) {
              return returnJSONResult(RESULT_CODE_ERROR, "require set removeAbandoned=true");
          }
          return returnJSONResult(RESULT_CODE_SUCCESS, result);
      }
  
      public static String returnJSONResult(int resultCode, Object content) {
          Map<String, Object> dataMap = new LinkedHashMap<String, Object>();
          dataMap.put("ResultCode", resultCode);
          dataMap.put("Content", content);
          return JSONUtils.toJSONString(dataMap);
      }
  
      public static void registerMBean() {
          MBeanServer mbeanServer = ManagementFactory.getPlatformMBeanServer();
          try {
              ObjectName objectName = new ObjectName(MBEAN_NAME);
              if (!mbeanServer.isRegistered(objectName)) {
                  mbeanServer.registerMBean(instance, objectName);
              }
          } catch (JMException ex) {
              LOG.error("register mbean error", ex);
          }
      }
  
      public static void unregisterMBean() {
          MBeanServer mbeanServer = ManagementFactory.getPlatformMBeanServer();
  
          try {
              mbeanServer.unregisterMBean(new ObjectName(MBEAN_NAME));
          } catch (JMException ex) {
              LOG.error("unregister mbean error", ex);
          }
      }
  
      public static Map<String, String> getParameters(String url) {
          if (url == null || (url = url.trim()).length() == 0) {
              return Collections.<String, String>emptyMap();
          }
  
          String parametersStr = StringUtils.subString(url, "?", null);
          if (parametersStr == null || parametersStr.length() == 0) {
              return Collections.<String, String>emptyMap();
          }
  
          String[] parametersArray = parametersStr.split("&");
          Map<String, String> parameters = new LinkedHashMap<String, String>();
  
          for (String parameterStr : parametersArray) {
              int index = parameterStr.indexOf("=");
              if (index <= 0) {
                  continue;
              }
  
              String name = parameterStr.substring(0, index);
              String value = parameterStr.substring(index + 1);
              parameters.put(name, value);
          }
          return parameters;
      }
  }
  