/*
 * Copyright 2017-2019 CodingApi .
 *
 * 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.codingapi.txlcn.tc.txmsg;

import com.codingapi.txlcn.logger.TxLogger;
import com.codingapi.txlcn.tc.core.transaction.txc.analy.def.bean.StatementInfo;
import com.codingapi.txlcn.txmsg.exception.RpcException;
import com.codingapi.txlcn.txmsg.params.TxExceptionParams;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.List;

/**
 * Description: 客户端上报Manager
 * Date: 2018/12/29
 *
 * @author ujued
 */
@Component
public class TMReporter {

    private final ReliableMessenger reliableMessenger;

    private static final TxLogger txLogger = TxLogger.newLogger(TMReporter.class);

    private static final String REPORT_ERROR_MESSAGE = "report transaction transactionState error";

    @Autowired
    public TMReporter(ReliableMessenger reliableMessenger) {
        this.reliableMessenger = reliableMessenger;
    }

    /**
     * Manager 记录事务状态
     *
     * @param groupId   groupId
     * @param unitId    unitId
     * @param registrar registrar
     * @param state     transactionState
     */
    public void reportTransactionState(String groupId, String unitId, Short registrar, int state) {
        TxExceptionParams txExceptionParams = new TxExceptionParams();
        txExceptionParams.setGroupId(groupId);
        txExceptionParams.setRegistrar(registrar);
        txExceptionParams.setTransactionState(state);
        txExceptionParams.setUnitId(unitId);
        report(txExceptionParams);
    }

    /**
     * Manager 记录TCC清理事务失败
     *
     * @param groupId groupId
     * @param unitId  unitId
     * @param state   state
     */
    public void reportTccCleanException(String groupId, String unitId, int state) {
        TxExceptionParams txExceptionParams = new TxExceptionParams();
        txExceptionParams.setGroupId(groupId);
        txExceptionParams.setRegistrar(TxExceptionParams.TCC_CLEAN_ERROR);
        txExceptionParams.setTransactionState(state);
        txExceptionParams.setUnitId(unitId);
        report(txExceptionParams);
    }

    private void report(TxExceptionParams exceptionParams) {
        try {
            reliableMessenger.request(MessageCreator.writeTxException(exceptionParams));
        } catch (RpcException e) {
            txLogger.trace(exceptionParams.getGroupId(), exceptionParams.getUnitId(), "TM report", REPORT_ERROR_MESSAGE);
        }
    }

    public void reportTxcUndoException(String groupId, String unitId, List<StatementInfo> statementInfoList) {
        TxExceptionParams exceptionParams = new TxExceptionParams();
        exceptionParams.setGroupId(groupId);
        exceptionParams.setUnitId(unitId);
        exceptionParams.setRegistrar(TxExceptionParams.TXC_UNDO_ERROR);
        exceptionParams.setTransactionState(0);
        exceptionParams.setRemark(statementInfoList.toString());
        report(exceptionParams);
    }
}


/*
 * Copyright 2017-2019 CodingApi .
 *
 * 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.codingapi.txlcn.tc.core.template;

import com.codingapi.txlcn.common.exception.TransactionClearException;
import com.codingapi.txlcn.logger.TxLogger;
import com.codingapi.txlcn.tc.core.checking.DTXChecking;
import com.codingapi.txlcn.tc.core.context.TCGlobalContext;
import com.codingapi.txlcn.tc.corelog.aspect.AspectLogger;
import com.codingapi.txlcn.tc.support.TxLcnBeanHelper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * Description: 事务清理模板
 * Date: 2018/12/13
 *
 * @author ujued
 */
@Component
public class TransactionCleanTemplate {

    private final TxLcnBeanHelper transactionBeanHelper;

    private final DTXChecking dtxChecking;

    private final AspectLogger aspectLogger;

    private static final TxLogger txLogger = TxLogger.newLogger(TransactionCleanTemplate.class);

    private final TCGlobalContext globalContext;

    @Autowired
    public TransactionCleanTemplate(TxLcnBeanHelper transactionBeanHelper, DTXChecking dtxChecking,
                                    AspectLogger aspectLogger, TCGlobalContext globalContext) {
        this.transactionBeanHelper = transactionBeanHelper;
        this.dtxChecking = dtxChecking;
        this.aspectLogger = aspectLogger;
        this.globalContext = globalContext;
    }

    /**
     * 清理事务
     *
     * @param groupId  groupId
     * @param unitId   unitId
     * @param unitType unitType
     * @param state    transactionState
     * @throws TransactionClearException TransactionClearException
     */
    public void clean(String groupId, String unitId, String unitType, int state) throws TransactionClearException {
        txLogger.txTrace(groupId, unitId, "clean transaction");
        try {
            cleanWithoutAspectLog(groupId, unitId, unitType, state);
            aspectLogger.clearLog(groupId, unitId);
        } catch (TransactionClearException e) {
            if (!e.isNeedCompensation()) {
                aspectLogger.clearLog(groupId, unitId);
            }
        } catch (Throwable throwable) {
            aspectLogger.clearLog(groupId, unitId);
        }
        txLogger.txTrace(groupId, unitId, "clean transaction over");
    }

    /**
     * 清理事务（不清理切面日志）
     *
     * @param groupId  groupId
     * @param unitId   unitId
     * @param unitType unitType
     * @param state    transactionState
     * @throws TransactionClearException TransactionClearException
     */
    public void cleanWithoutAspectLog(String groupId, String unitId, String unitType, int state) throws TransactionClearException {
        try {
            transactionBeanHelper.loadTransactionCleanService(unitType).clear(
                    groupId, state, unitId, unitType
            );
        } finally {
            globalContext.clearGroup(groupId);

            dtxChecking.stopDelayChecking(groupId, unitId);
        }
    }
}


/*
 * Copyright 2017-2019 CodingApi .
 *
 * 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.codingapi.txlcn.tm.txmsg;

import com.codingapi.txlcn.txmsg.MessageConstants;
import com.codingapi.txlcn.txmsg.dto.MessageDto;
import com.codingapi.txlcn.txmsg.params.DeleteAspectLogParams;
import com.codingapi.txlcn.txmsg.params.GetAspectLogParams;
import com.codingapi.txlcn.txmsg.params.NotifyConnectParams;
import com.codingapi.txlcn.txmsg.params.NotifyUnitParams;

import java.io.Serializable;

/**
 * 消息创建器
 *
 * @author lorne
 */
public class MessageCreator {

    /**
     * 通知TxClient连接
     *
     * @param notifyConnectParams notifyConnectParams
     * @return MessageDto
     */
    public static MessageDto newTxManager(NotifyConnectParams notifyConnectParams) {
        MessageDto msg = new MessageDto();
        msg.setAction(MessageConstants.ACTION_NEW_TXMANAGER);
        msg.setData(notifyConnectParams);
        return msg;
    }

    /**
     * 提交事务组
     *
     * @param notifyUnitParams notifyUnitParams
     * @return MessageDto
     */
    public static MessageDto notifyUnit(NotifyUnitParams notifyUnitParams) {
        MessageDto msg = new MessageDto();
        msg.setGroupId(notifyUnitParams.getGroupId());
        msg.setAction(MessageConstants.ACTION_NOTIFY_UNIT);
        msg.setData(notifyUnitParams);
        return msg;
    }

    /**
     * 正常响应
     *
     * @param action  action
     * @param message message
     * @return MessageDto
     */
    public static MessageDto okResponse(Serializable message, String action) {
        MessageDto messageDto = new MessageDto();
        messageDto.setState(MessageConstants.STATE_OK);
        messageDto.setAction(action);
        messageDto.setData(message);
        return messageDto;
    }

    /**
     * 失败响应
     *
     * @param action  action
     * @param message message
     * @return MessageDto
     */
    public static MessageDto failResponse(Serializable message, String action) {
        MessageDto messageDto = new MessageDto();
        messageDto.setAction(action);
        messageDto.setState(MessageConstants.STATE_EXCEPTION);
        messageDto.setData(message);
        return messageDto;
    }

    /**
     * 服务器错误
     *
     * @param action action
     * @return MessageDto
     */
    public static MessageDto serverException(String action) {
        MessageDto messageDto = new MessageDto();
        messageDto.setAction(action);
        messageDto.setState(MessageConstants.STATE_EXCEPTION);
        return messageDto;
    }

    /**
     * 获取切面日志
     *
     * @param groupId groupId
     * @param unitId  unitId
     * @return aspect log
     */
    public static MessageDto getAspectLog(String groupId, String unitId) {
        GetAspectLogParams getAspectLogParams = new GetAspectLogParams();
        getAspectLogParams.setGroupId(groupId);
        getAspectLogParams.setUnitId(unitId);

        MessageDto messageDto = new MessageDto();
        messageDto.setGroupId(groupId);
        messageDto.setAction(MessageConstants.ACTION_GET_ASPECT_LOG);
        messageDto.setData(getAspectLogParams);
        return messageDto;
    }

    /**
     * 删除切面日志
     *
     * @param groupId groupId
     * @param unitId  unitId
     * @return result
     */
    public static MessageDto deleteAspectLog(String groupId, String unitId) {
        DeleteAspectLogParams deleteAspectLogParams = new DeleteAspectLogParams();
        deleteAspectLogParams.setGroupId(groupId);
        deleteAspectLogParams.setUnitId(unitId);

        MessageDto messageDto = new MessageDto();
        messageDto.setData(deleteAspectLogParams);
        messageDto.setAction(MessageConstants.ACTION_DELETE_ASPECT_LOG);
        messageDto.setGroupId(groupId);
        return messageDto;
    }
}


/*
 * Copyright 2017-2019 CodingApi .
 *
 * 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.codingapi.txlcn.txmsg.netty.bean;

import com.codingapi.txlcn.txmsg.RpcConfig;
import lombok.extern.slf4j.Slf4j;

import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * Description:
 * Company: CodingApi
 * Date: 2018/12/10
 *
 * @author ujued
 */
@Slf4j
public class RpcCmdContext {

    private int cacheSize = 1024;

    private long waitTime = 1;

    private static RpcCmdContext context = null;

    private Map<String, RpcContent> map;

    private List<RpcContent> cacheList;

    private final LinkedList<RpcContent> freeList;

    public static RpcCmdContext getInstance() {
        if (context == null) {
            synchronized (RpcCmdContext.class) {
                if (context == null) {
                    context = new RpcCmdContext();
                }
            }
        }
        return context;
    }

    private RpcCmdContext() {
        map = new ConcurrentHashMap<>();

        cacheList = new CopyOnWriteArrayList<>();

        freeList = new LinkedList<>();
    }

    /**
     * 并发可能会出现脏读
     *
     * @param key key
     * @return hasKey
     */
    public synchronized boolean hasKey(String key) {
        return map.containsKey(key);
    }

    /**
     * 并发可能会出重复添加
     *
     * @param key key
     * @return RpcContent
     */
    public synchronized RpcContent addKey(String key) {
        RpcContent rpcContent = createRpcContent();
        map.put(key, rpcContent);
        return rpcContent;
    }

    /**
     * 空闲队列处理
     *
     * @return RpcContent
     */
    private RpcContent findRpcContent() {
        synchronized (freeList) {
            RpcContent cacheContent = freeList.getFirst();
            if (!cacheContent.isUsed()) {
                cacheContent.init();
                freeList.remove(cacheContent);
                return cacheContent;
            }
        }

        RpcContent rpcContent = new RpcContent(getWaitTime());
        rpcContent.init();
        return rpcContent;
    }

    private RpcContent createRpcContent() {
        if (cacheList.size() < cacheSize) {
            RpcContent rpcContent = new RpcContent(getWaitTime());
            rpcContent.init();
            cacheList.add(rpcContent);
            return rpcContent;
        } else {
            return findRpcContent();
        }
    }

    public RpcContent getKey(String key) {
        RpcContent rpcContent = map.get(key);
        clearKey(key);
        return rpcContent;
    }

    private void clearKey(String key) {
        RpcContent rpcContent = map.get(key);
        if (cacheList.contains(rpcContent)) {
            synchronized (freeList) {
                freeList.add(rpcContent);
            }
        }
        map.remove(key);
    }

    public void setRpcConfig(RpcConfig rpcConfig) {
        cacheSize = rpcConfig.getCacheSize();
        // TC or TX init after
        waitTime = rpcConfig.getWaitTime() == -1 ? 500 : rpcConfig.getWaitTime();
    }

    public long getWaitTime() {
        return waitTime;
    }
}


/*
 * Copyright 2017-2019 CodingApi .
 *
 * 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.codingapi.txlcn.tc.core.template;

import com.codingapi.txlcn.common.exception.LcnBusinessException;
import com.codingapi.txlcn.common.exception.TransactionClearException;
import com.codingapi.txlcn.common.exception.TransactionException;
import com.codingapi.txlcn.common.util.Transactions;
import com.codingapi.txlcn.logger.TxLogger;
import com.codingapi.txlcn.tc.aspect.TransactionInfo;
import com.codingapi.txlcn.tc.core.DTXLocalContext;
import com.codingapi.txlcn.tc.core.checking.DTXChecking;
import com.codingapi.txlcn.tc.core.checking.DTXExceptionHandler;
import com.codingapi.txlcn.tc.core.context.TCGlobalContext;
import com.codingapi.txlcn.tc.corelog.aspect.AspectLogger;
import com.codingapi.txlcn.tc.txmsg.ReliableMessenger;
import com.codingapi.txlcn.txmsg.exception.RpcException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.Arrays;

/**
 * Description:
 * Date: 2018/12/20
 *
 * @author ujued
 */
@Component
public class TransactionControlTemplate {

    private static final TxLogger txLogger = TxLogger.newLogger(TransactionControlTemplate.class);

    private final AspectLogger aspectLogger;

    private final DTXChecking dtxChecking;

    private final DTXExceptionHandler dtxExceptionHandler;

    private final TransactionCleanTemplate transactionCleanTemplate;

    private final ReliableMessenger reliableMessenger;

    private final TCGlobalContext globalContext;

    @Autowired
    public TransactionControlTemplate(AspectLogger aspectLogger, DTXChecking dtxChecking,
                                      DTXExceptionHandler dtxExceptionHandler,
                                      TransactionCleanTemplate transactionCleanTemplate,
                                      ReliableMessenger reliableMessenger, TCGlobalContext globalContext) {
        this.aspectLogger = aspectLogger;
        this.dtxChecking = dtxChecking;
        this.dtxExceptionHandler = dtxExceptionHandler;
        this.transactionCleanTemplate = transactionCleanTemplate;
        this.reliableMessenger = reliableMessenger;
        this.globalContext = globalContext;
    }

    /**
     * Client创建事务组操作集合
     *
     * @param groupId         groupId
     * @param unitId          unitId
     * @param transactionInfo txTrace
     * @param transactionType transactionType
     * @throws TransactionException 创建group失败时抛出
     */
    public void createGroup(String groupId, String unitId, TransactionInfo transactionInfo, String transactionType)
            throws TransactionException {
        //创建事务组
        try {
            // 日志
            txLogger.txTrace(groupId, unitId,
                    "create group > transaction type: {}", transactionType);
            // 创建事务组消息
            reliableMessenger.createGroup(groupId);
            // 缓存发起方切面信息
            aspectLogger.trace(groupId, unitId, transactionInfo);
        } catch (RpcException e) {
            // 通讯异常
            dtxExceptionHandler.handleCreateGroupMessageException(groupId, e);
        } catch (LcnBusinessException e) {
            // 创建事务组业务失败
            dtxExceptionHandler.handleCreateGroupBusinessException(groupId, e.getCause());
        }
        txLogger.txTrace(groupId, unitId, "create group over");
    }

    /**
     * Client加入事务组操作集合
     *
     * @param groupId         groupId
     * @param unitId          unitId
     * @param transactionType transactionType
     * @param transactionInfo txTrace
     * @throws TransactionException 加入事务组失败时抛出
     */
    public void joinGroup(String groupId, String unitId, String transactionType, TransactionInfo transactionInfo)
            throws TransactionException {
        try {
            txLogger.txTrace(groupId, unitId, "join group > transaction type: {}", transactionType);

            reliableMessenger.joinGroup(groupId, unitId, transactionType, DTXLocalContext.transactionState(globalContext.dtxState(groupId)));

            txLogger.txTrace(groupId, unitId, "join group message over.");

            // 异步检测
            dtxChecking.startDelayCheckingAsync(groupId, unitId, transactionType);

            // 缓存参与方切面信息
            aspectLogger.trace(groupId, unitId, transactionInfo);
        } catch (RpcException e) {
            dtxExceptionHandler.handleJoinGroupMessageException(Arrays.asList(groupId, unitId, transactionType), e);
        } catch (LcnBusinessException e) {
            dtxExceptionHandler.handleJoinGroupBusinessException(Arrays.asList(groupId, unitId, transactionType), e);
        }
        txLogger.txTrace(groupId, unitId, "join group logic over");
    }

    /**
     * Client通知事务组操作集合
     *
     * @param groupId         groupId
     * @param unitId          unitId
     * @param transactionType transactionType
     * @param state           transactionState
     */
    public void notifyGroup(String groupId, String unitId, String transactionType, int state) {
        try {
            txLogger.txTrace(
                    groupId, unitId, "notify group > transaction type: {}, state: {}.", transactionType, state);
            if (globalContext.isDTXTimeout()) {
                throw new LcnBusinessException("dtx timeout.");
            }
            state = reliableMessenger.notifyGroup(groupId, state);
            transactionCleanTemplate.clean(groupId, unitId, transactionType, state);
        } catch (TransactionClearException e) {
            txLogger.trace(groupId, unitId, Transactions.TE, "clean transaction fail.");
        } catch (RpcException e) {
            dtxExceptionHandler.handleNotifyGroupMessageException(Arrays.asList(groupId, state, unitId, transactionType), e);
        } catch (LcnBusinessException e) {
            // 关闭事务组失败
            dtxExceptionHandler.handleNotifyGroupBusinessException(Arrays.asList(groupId, state, unitId, transactionType), e.getCause());
        }
        txLogger.txTrace(groupId, unitId, "notify group exception state {}.", state);
    }
}


/**
 * P6Spy
 *
 * Copyright (C) 2002 - 2018 P6Spy
 *
 * 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.codingapi.txlcn.tc.support.p6spy.common;

import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * Value holder of the data passed to DB as well as of those retrieved capable
 * of binary data logging depending on the configuration property
 * {@code excludebinary}.
 *
 * @author Peter Butkovic
 *
 */
public class Value {

  private static final char[] HEX_CHARS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E',
      'F' };

  /**
   * Value itself.
   */
  private Object value;

  public Value(Object valueToSet) {
    this();
    this.value = valueToSet;
  }

  public Value() {
  }

  public Object getValue() {
    return value;
  }

  public void setValue(Object value) {
    this.value = value;
  }

  @Override
  public String toString() {
    return convertToString(this.value);
  }

  /**
   * Returns the {@link String} representation of the given value depending on
   * the value type. Formats:
   *

   *
{@link Date} values it in a way configured via configuration
   * property: {@code dateformat},

   *
{@code byte[]} values are converted to {@link String} Hexadecimal
   * representation, unless configuration property {@code exclidebinary=true} is
   * set.

   *
for other types string representation is simply returned.

   *

   *
   * @param value value
   * @return  String
   */
  public String convertToString(Object value) {
    String result;
    if (value == null) {
      result = "NULL";
    } else {

      if (value instanceof Timestamp) {
        result = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ").format(value);
      } else if (value instanceof Date) {
        result = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ").format(value);
      } else if (value instanceof Boolean) {
        if ("numeric".equals("boolean")) {
          result = Boolean.FALSE.equals(value) ? "0" : "1";
        } else {
          result = value.toString();
        }
      } else if (value instanceof byte[]) {
//        if (P6LogOptions.getActiveInstance().getExcludebinary()) {
//          result = "[binary]";
//        } else {
          result = toHexString((byte[]) value);
//        }

        // we should not do ((Blob) value).getBinaryStream(). ...
        // as inputstream might not be re-rea
//      } else  if (value instanceof Blob) {
//        if (P6LogOptions.getActiveInstance().getExcludebinary()) {
//          result = "[binary]";
//        } else {
//          result = value.toString();
//        }
      } else {
        result = value.toString();
      }

      result = quoteIfNeeded(result, value);
    }

    return result;
  }

  /**
   * @param bytes
   *          the bytes value to convert to {@link String}
   * @return the hexadecimal {@link String} representation of the given
   *         {@code bytes}.
   */
  private String toHexString(byte[] bytes) {
    StringBuilder sb = new StringBuilder(bytes.length * 2);
    for (byte b : bytes) {
      int temp = (int) b & 0xFF;
      sb.append(HEX_CHARS[temp / 16]);
      sb.append(HEX_CHARS[temp % 16]);
    }
    return sb.toString();
  }

  /**
   * Qoutes the passed {@code stringValue} if it's needed.
   *
   * @param stringValue
   * @param obj
   * @return
   */
  private String quoteIfNeeded(String stringValue, Object obj) {
    if (stringValue == null) {
      return null;
    }

    /*
     * The following types do not get quoted: numeric, boolean
     *
     * It is tempting to use ParameterMetaData.getParameterType() for this
     * purpose as it would be safer. However, this method will fail with some
     * JDBC drivers.
     *
     * Oracle: Not supported until ojdbc7 which was released with Oracle 12c.
     * https://forums.oracle.com/thread/2584886
     *
     * MySQL: The method call only works if service side prepared statements are
     * enabled. The URL parameter 'useServerPrepStmts=true' enables.
     */
    if (Number.class.isAssignableFrom(obj.getClass()) || Boolean.class.isAssignableFrom(obj.getClass())) {
      return stringValue;
    } else {
      return "'" + escape(stringValue) + "'";
    }
  }

  /**
   * Escapes special characters in SQL values. Currently is only {@code '}
   * escaped with {@code ''}.
   *
   * @param stringValue
   *          value to escape
   * @return escaped value.
   */
  private String escape(String stringValue) {
    return stringValue.replaceAll("'", "''");
  }

}


/*
 * Copyright 2017-2019 CodingApi .
 *
 * 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.codingapi.txlcn.tc.core.transaction.txc.analy.util;

import com.codingapi.txlcn.common.exception.SerializerException;
import com.codingapi.txlcn.common.util.serializer.SerializerContext;
import com.codingapi.txlcn.tc.core.transaction.txc.analy.def.bean.ModifiedRecord;
import com.codingapi.txlcn.tc.core.transaction.txc.analy.def.bean.FieldCluster;
import com.codingapi.txlcn.tc.core.transaction.txc.analy.def.bean.FieldValue;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.Objects;

/**
 * Description:
 * Date: 2018/12/13
 *
 * @author ujued
 */
public class SqlUtils {

    public static final String SQL_COMMA_SEPARATOR = ", ";

    public static final String DOT = ".";

    public static final String AND = " and ";

    public static final String OR = " or ";

    public static final String UPDATE = "UPDATE ";

    public static final String INSERT = "INSERT INTO ";

    public static final String DELETE = "DELETE ";

    public static final String SELECT = "SELECT ";

    public static final String FROM = " FROM ";

    public static final String WHERE = " WHERE ";

    public static final String SET = " SET ";

    public static final int MYSQL_TABLE_NOT_EXISTS_CODE = 1146;

    public static final String FOR_UPDATE = "FOR UPDATE";

    public static final String LOCK_IN_SHARE_MODE = "LOCK IN SHARE MODE";

    public static final int SQL_TYPE_INSERT = 1;
    public static final int SQL_TYPE_DELETE = 2;
    public static final int SQL_TYPE_UPDATE = 3;
    public static final int SQL_TYPE_SELECT = 4;

    /**
     * 从完全标识字段名获取表名
     *
     * @param fieldFullyQualifiedName fieldFullyQualifiedName
     * @return tableName
     */
    public static String tableName(String fieldFullyQualifiedName) {
        if (fieldFullyQualifiedName.contains(".")) {
            return fieldFullyQualifiedName.substring(0, fieldFullyQualifiedName.indexOf("."));
        }
        return null;
    }

    /**
     * 从字符串构造器剪掉结束符
     *
     * @param suffix        suffix
     * @param stringBuilder stringBuilder
     */
    public static void cutSuffix(String suffix, StringBuilder stringBuilder) {
        if (stringBuilder.substring(stringBuilder.length() - suffix.length()).equals(suffix)) {
            stringBuilder.delete(stringBuilder.length() - suffix.length(), stringBuilder.length());
        }
    }

    /**
     * 获取修改记录
     *
     * @param rs      rs
     * @param columns columns
     * @return ModifiedRecord
     * @throws SQLException SQLException
     */
    public static ModifiedRecord recordByColumns(ResultSet rs, List<String> columns) throws SQLException {
        ModifiedRecord record = new ModifiedRecord();
        for (String column : columns) {
            FieldValue fieldValue = new FieldValue();
            fieldValue.setFieldName(column);
            fieldValue.setTableName(SqlUtils.tableName(column));
            fieldValue.setValue(rs.getObject(column));
            fieldValue.setValueType(Objects.isNull(fieldValue.getValue()) ? Void.class : fieldValue.getValue().getClass());
            if (record.getFieldClusters().get(fieldValue.getTableName()) != null) {
                record.getFieldClusters().get(fieldValue.getTableName()).getFields().add(fieldValue);
            } else {
                FieldCluster fieldCluster = new FieldCluster();
                fieldCluster.getFields().add(fieldValue);
                record.getFieldClusters().put(fieldValue.getTableName(), fieldCluster);
            }
        }
        return record;
    }

    /**
     * java Object to bytes
     *
     * @param o o
     * @return byte[]
     */
    public static byte[] objectToBlob(Object o) {
        try {
            return SerializerContext.getInstance().serialize(o);
        } catch (SerializerException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * bytes to java object.
     *
     * @param blob blob
     * @param type type
     * @param <T>  T
     * @return T
     */
    public static <T> T blobToObject(byte[] blob, Class<T> type) {
        try {
            return SerializerContext.getInstance().deSerialize(blob, type);
        } catch (SerializerException e) {
            throw new IllegalStateException(e);
        }
    }
}


/*
 * Copyright 2017-2019 CodingApi .
 *
 * 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.codingapi.txlcn.tc.corelog.txc;

import com.codingapi.txlcn.tc.core.transaction.txc.analy.def.bean.UndoLogDO;
import com.codingapi.txlcn.tc.corelog.H2DbHelper;
import com.codingapi.txlcn.tc.corelog.LogHelper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

/**
 * Description:
 * Date: 19-1-21 上午9:31
 *
 * @author ujued
 */
@Component
@Slf4j
public class TxcLogHelper implements LogHelper {

    private final H2DbHelper h2DbHelper;

    @Autowired
    public TxcLogHelper(H2DbHelper h2DbHelper) {
        this.h2DbHelper = h2DbHelper;
    }

    /**
     * TXC撤销日志存储表准备
     */
    @Override
    public void init() {
        h2DbHelper.update("CREATE TABLE IF NOT EXISTS TXC_UNDO_LOG (" +
                "ID BIGINT NOT NULL AUTO_INCREMENT, " +
                "UNIT_ID VARCHAR(32) NOT NULL," +
                "GROUP_ID VARCHAR(64) NOT NULL," +
                "SQL_TYPE INT NOT NULL," +
                "ROLLBACK_INFO BLOB NOT NULL," +
                "CREATE_TIME CHAR(23) NOT NULL, " +
                "PRIMARY KEY(ID) )");
        log.info("Txc log table finish (H2 DATABASE)");
    }

    /**
     * 保存TXC撤销日志
     *
     * @param undoLogDO 撤销日志对象
     * @throws SQLException 数据库操作失败
     */
    public void saveUndoLog(UndoLogDO undoLogDO) throws SQLException {
        String sql = "INSERT INTO TXC_UNDO_LOG (UNIT_ID,GROUP_ID,SQL_TYPE,ROLLBACK_INFO,CREATE_TIME) VALUES(?,?,?,?,?)";
        h2DbHelper.queryRunner().update(sql, undoLogDO.getUnitId(), undoLogDO.getGroupId(), undoLogDO.getSqlType(),
                undoLogDO.getRollbackInfo(), undoLogDO.getCreateTime());
    }

    /**
     * 获取某个事务下具体UNIT的TXC撤销日志
     *
     * @param groupId groupId
     * @param unitId  unitId
     * @return undo log list
     * @throws SQLException 数据库操作失败
     */
    public List<UndoLogDO> getUndoLogByGroupAndUnitId(String groupId, String unitId) throws SQLException {
        String sql = "SELECT * FROM TXC_UNDO_LOG WHERE GROUP_ID = ? and UNIT_ID = ?";
        return h2DbHelper.queryRunner().query(sql, rs -> {
            List<UndoLogDO> undoLogDOList = new ArrayList<>();
            while (rs.next()) {
                UndoLogDO undoLogDO = new UndoLogDO();
                undoLogDO.setSqlType(rs.getInt("SQL_TYPE"));
                undoLogDO.setRollbackInfo(rs.getBytes("ROLLBACK_INFO"));
                undoLogDO.setUnitId(rs.getString("UNIT_ID"));
                undoLogDO.setGroupId("GROUP_ID");
                undoLogDO.setCreateTime(rs.getString("CREATE_TIME"));
                undoLogDOList.add(undoLogDO);
            }
            return undoLogDOList;
        }, groupId, unitId);
    }

    /**
     * 删除TXC撤销日志
     *
     * @param groupId groupId
     * @param unitId  unitId
     * @throws SQLException 数据库操作失败
     */
    public void deleteUndoLog(String groupId, String unitId) throws SQLException {
        String sql = "DELETE FROM TXC_UNDO_LOG WHERE GROUP_ID=? AND UNIT_ID=?";
        h2DbHelper.queryRunner().update(sql, groupId, unitId);
    }
}


/*
 * Copyright 2017-2019 CodingApi .
 *
 * 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.codingapi.txlcn.tc.core.transaction.txc.analy;

import com.codingapi.txlcn.common.exception.TCGlobalContextException;
import com.codingapi.txlcn.common.exception.TxcLogicException;
import com.codingapi.txlcn.common.lock.DTXLocks;
import com.codingapi.txlcn.tc.core.transaction.txc.analy.def.bean.*;
import com.codingapi.txlcn.txmsg.exception.RpcException;
import com.codingapi.txlcn.tc.core.DTXLocalContext;
import com.codingapi.txlcn.tc.core.transaction.txc.analy.def.TxcService;
import com.codingapi.txlcn.tc.core.transaction.txc.analy.def.TxcSqlExecutor;
import com.codingapi.txlcn.tc.core.transaction.txc.analy.undo.TableRecord;
import com.codingapi.txlcn.tc.core.transaction.txc.analy.undo.TableRecordList;
import com.codingapi.txlcn.tc.core.transaction.txc.analy.undo.UndoLogAnalyser;
import com.codingapi.txlcn.tc.core.transaction.txc.analy.util.SqlUtils;
import com.codingapi.txlcn.tc.corelog.txc.TxcLogHelper;
import com.codingapi.txlcn.tc.txmsg.ReliableMessenger;
import com.codingapi.txlcn.tc.core.context.TCGlobalContext;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.dbutils.DbUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.DigestUtils;

import java.nio.charset.StandardCharsets;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.*;

/**
 * Description:
 * Date: 2018/12/17
 *
 * @author ujued
 */

@Slf4j
@Component
public class TxcServiceImpl implements TxcService {

    private final TxcSqlExecutor txcSqlExecutor;

    private final TxcLogHelper txcLogHelper;

    private final ReliableMessenger reliableMessenger;

    private final TCGlobalContext globalContext;

    @Autowired
    public TxcServiceImpl(TxcSqlExecutor txcSqlExecutor, TxcLogHelper txcLogHelper,
                          ReliableMessenger reliableMessenger, TCGlobalContext globalContext) {
        this.txcSqlExecutor = txcSqlExecutor;
        this.txcLogHelper = txcLogHelper;
        this.reliableMessenger = reliableMessenger;
        this.globalContext = globalContext;
    }

    /**
     * lock data line
     *
     * @param groupId   groupId
     * @param unitId    unitId
     * @param lockIdSet lockIdSet
     * @param isXLock   isXLock
     * @throws TxcLogicException 业务异常
     */
    private void lockDataLine(String groupId, String unitId, Set<String> lockIdSet, boolean isXLock) throws TxcLogicException {
        try {
            if (!reliableMessenger.acquireLocks(groupId, lockIdSet, isXLock ? DTXLocks.X_LOCK : DTXLocks.S_LOCK)) {
                throw new TxcLogicException("resource is locked! place try again later.");
            }
            globalContext.addTxcLockId(groupId, unitId, lockIdSet);
        } catch (RpcException e) {
            throw new TxcLogicException("can't contact to any TM for lock info. default error.");
        }
    }

    /**
     * save sql undo log
     *
     * @param groupId    groupId
     * @param unitId     unitId
     * @param sqlType    sqlType
     * @param recordList recordList
     * @throws TxcLogicException 业务异常
     */
    private void saveUndoLog(String groupId, String unitId, int sqlType, TableRecordList recordList) throws TxcLogicException {
        UndoLogDO undoLogDO = new UndoLogDO();
        undoLogDO.setRollbackInfo(SqlUtils.objectToBlob(recordList));
        undoLogDO.setUnitId(unitId);
        undoLogDO.setGroupId(groupId);
        undoLogDO.setSqlType(sqlType);
        try {
            txcLogHelper.saveUndoLog(undoLogDO);
        } catch (SQLException e) {
            throw new TxcLogicException(e);
        }
    }

    /**
     * Hex String
     *
     * @param content origin string
     * @return hex
     */
    private String hex(String content) {
        return DigestUtils.md5DigestAsHex(content.getBytes(StandardCharsets.UTF_8));
    }

    /**
     * 解决受影响数据
     *
     * @param modifiedRecords 受影响的数据
     * @param sqlType         SQL 类型
     * @throws TxcLogicException 业务异常
     */
    private void resolveModifiedRecords(List<ModifiedRecord> modifiedRecords, int sqlType) throws TxcLogicException {

        TableRecordList tableRecords = new TableRecordList();
        Set<String> lockIdSet = new HashSet<>();

        // Build reverse sql
        for (ModifiedRecord modifiedRecord : modifiedRecords) {
            for (Map.Entry<String, FieldCluster> entry : modifiedRecord.getFieldClusters().entrySet()) {
                TableRecord tableRecord = new TableRecord();
                tableRecord.setTableName(entry.getKey());
                tableRecord.setFieldCluster(entry.getValue());

                tableRecords.getTableRecords().add(tableRecord);

                lockIdSet.add(hex(tableRecord.getFieldCluster().getPrimaryKeys().toString()));
            }
        }

        // 受影响记录为0
        if (lockIdSet.isEmpty()) {
            return;
        }

        String groupId = DTXLocalContext.cur().getGroupId();
        String unitId = DTXLocalContext.cur().getUnitId();

        // lock
        lockDataLine(groupId, unitId, lockIdSet, true);

        // save to h2db
        saveUndoLog(groupId, unitId, sqlType, tableRecords);
    }

    @Override
    public void lockSelect(SelectImageParams selectImageParams, boolean isxLock) throws TxcLogicException {
        Connection connection = (Connection) DTXLocalContext.cur().getResource();
        try {
            List<ModifiedRecord> modifiedRecords = txcSqlExecutor.selectSqlPreviousPrimaryKeys(connection, selectImageParams);
            Set<String> lockIdSet = new HashSet<>();
            for (ModifiedRecord modifiedRecord : modifiedRecords) {
                for (Map.Entry<String, FieldCluster> entry : modifiedRecord.getFieldClusters().entrySet()) {
                    FieldCluster v = entry.getValue();
                    // key value MD5 HEX to store
                    lockIdSet.add(hex(v.getPrimaryKeys().toString()));
                }
            }
            lockDataLine(DTXLocalContext.cur().getGroupId(), DTXLocalContext.cur().getUnitId(), lockIdSet, isxLock);
        } catch (SQLException e) {
            throw new TxcLogicException(e);
        }
    }

    @Override
    public void resolveUpdateImage(UpdateImageParams updateImageParams) throws TxcLogicException {
        // 前置镜像数据集
        List<ModifiedRecord> modifiedRecords;
        Connection connection = (Connection) DTXLocalContext.cur().getResource();
        try {
            modifiedRecords = txcSqlExecutor.updateSqlPreviousData(connection, updateImageParams);
        } catch (SQLException e) {
            throw new TxcLogicException(e);
        }

        resolveModifiedRecords(modifiedRecords, SqlUtils.SQL_TYPE_UPDATE);
    }

    @Override
    public void resolveDeleteImage(DeleteImageParams deleteImageParams) throws TxcLogicException {
        // 前置数据
        List<ModifiedRecord> modifiedRecords;
        Connection connection = (Connection) DTXLocalContext.cur().getResource();
        try {
            modifiedRecords = txcSqlExecutor.deleteSqlPreviousData(connection, deleteImageParams);
        } catch (SQLException e) {
            throw new TxcLogicException(e);
        }

        resolveModifiedRecords(modifiedRecords, SqlUtils.SQL_TYPE_DELETE);
    }

    @Override
    public void resolveInsertImage(InsertImageParams insertImageParams) throws TxcLogicException {
        List<FieldValue> primaryKeys = new ArrayList<>();
        FieldCluster fieldCluster = new FieldCluster();
        fieldCluster.setPrimaryKeys(primaryKeys);
        ResultSet resultSet = null;
        try {
            resultSet = insertImageParams.getStatement().getGeneratedKeys();
        } catch (SQLException ignored) {
            // ignored non gen key.
        }
        try {
            for (int i = 0; i < insertImageParams.getPrimaryKeyValuesList().size(); i++) {
                Map<String, Object> pks = insertImageParams.getPrimaryKeyValuesList().get(i);
                for (String key : insertImageParams.getFullyQualifiedPrimaryKeys()) {
                    FieldValue fieldValue = new FieldValue();
                    fieldValue.setFieldName(key);
                    if (pks.containsKey(key)) {
                        fieldValue.setValue(pks.get(key));
                    } else if (Objects.nonNull(resultSet)) {
                        try {
                            resultSet.next();
                            fieldValue.setValue(resultSet.getObject(1));
                        } catch (SQLException ignored) {
                        }
                    }
                    primaryKeys.add(fieldValue);
                }
            }
        } finally {
            try {
                DbUtils.close(resultSet);
            } catch (SQLException ignored) {
            }
        }

        // save to db
        TableRecordList tableRecords = new TableRecordList();
        tableRecords.getTableRecords().add(new TableRecord(insertImageParams.getTableName(), fieldCluster));
        saveUndoLog(DTXLocalContext.cur().getGroupId(), DTXLocalContext.cur().getUnitId(), SqlUtils.SQL_TYPE_INSERT, tableRecords);
    }

    @Override
    public void cleanTxc(String groupId, String unitId) throws TxcLogicException {
        // 清理事务单元相关锁
        try {
            reliableMessenger.releaseLocks(globalContext.findTxcLockSet(groupId, unitId));
        } catch (RpcException e) {
            throw new TxcLogicException(e);
        } catch (TCGlobalContextException e) {
            // ignored, if non lock to release.
        }

        // 清理事务单元相关undo_log
        try {
            txcLogHelper.deleteUndoLog(groupId, unitId);
        } catch (SQLException e) {
            throw new TxcLogicException(e);
        }
    }

    @Override
    public void undo(String groupId, String unitId) throws TxcLogicException {
        DTXLocalContext.makeUnProxy();
        List<StatementInfo> statementInfoList = new ArrayList<>();
        try {
            List<UndoLogDO> undoLogDOList = txcLogHelper.getUndoLogByGroupAndUnitId(groupId, unitId);

            for (UndoLogDO undoLogDO : undoLogDOList) {
                TableRecordList tableRecords = SqlUtils.blobToObject(undoLogDO.getRollbackInfo(), TableRecordList.class);
                switch (undoLogDO.getSqlType()) {
                    case SqlUtils.SQL_TYPE_UPDATE:
                        tableRecords.getTableRecords().forEach(tableRecord -> statementInfoList.add(UndoLogAnalyser.update(tableRecord)));
                        break;
                    case SqlUtils.SQL_TYPE_DELETE:
                        tableRecords.getTableRecords().forEach(tableRecord -> statementInfoList.add(UndoLogAnalyser.delete(tableRecord)));
                        break;
                    case SqlUtils.SQL_TYPE_INSERT:
                        tableRecords.getTableRecords().forEach(tableRecord -> statementInfoList.add(UndoLogAnalyser.insert(tableRecord)));
                        break;
                    default:
                        break;
                }
            }
            txcSqlExecutor.applyUndoLog(statementInfoList);
        } catch (SQLException e) {
            TxcLogicException exception = new TxcLogicException(e);
            exception.setAttachment(statementInfoList);
            throw exception;
        } finally {
            DTXLocalContext.undoProxyStatus();
        }
    }
}


/*
 * Copyright 2017-2019 CodingApi .
 *
 * 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.codingapi.txlcn.tm.core.storage.redis;

import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
 * Description:
 * Date: 19-1-22 上午11:53
 *
 * @author ujued
 */
@Configuration
public class RedisConfiguration {

    //default configuration redis template when have RedisConnectionFactory

    @Bean
    @ConditionalOnClass(name = "org.springframework.data.redis.connection.RedisConnectionFactory")
    public RedisTemplate<String, Object> functionDomainRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
        JdkSerializationRedisSerializer serializationRedisSerializer = new JdkSerializationRedisSerializer();
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setKeySerializer(stringRedisSerializer);
        redisTemplate.setHashKeySerializer(stringRedisSerializer);
        redisTemplate.setHashValueSerializer(serializationRedisSerializer);
        redisTemplate.setValueSerializer(serializationRedisSerializer);
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        return redisTemplate;
    }
}


/*
 * Copyright 2017-2019 CodingApi .
 *
 * 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.codingapi.txlcn.tc.txmsg;

import com.codingapi.txlcn.common.util.ApplicationInformation;
import com.codingapi.txlcn.common.util.Transactions;
import com.codingapi.txlcn.common.util.id.ModIdProvider;
import com.codingapi.txlcn.txmsg.RpcClientInitializer;
import com.codingapi.txlcn.txmsg.dto.TxManagerHost;
import com.codingapi.txlcn.txmsg.exception.RpcException;
import com.codingapi.txlcn.tc.config.TxClientConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

/**
 * Description:
 * Date: 19-1-23 下午5:54
 *
 * @author ujued
 */
@Component
@Slf4j
public class TMSearcher {

    private static RpcClientInitializer RPC_CLIENT_INITIALIZER;

    private static ReliableMessenger RELIABLE_MESSENGER;

    private static volatile CountDownLatch clusterCountLatch;

    private static int knownTMClusterSize = 1;

    @Autowired
    public TMSearcher(RpcClientInitializer rpcClientInitializer, TxClientConfig clientConfig,
                      ReliableMessenger reliableMessenger, ModIdProvider modIdProvider) {
        // 1. util class init
        Transactions.setApplicationIdWhenRunning(modIdProvider.modId());

        // 2. TMSearcher init
        RPC_CLIENT_INITIALIZER = rpcClientInitializer;
        RELIABLE_MESSENGER = reliableMessenger;
        knownTMClusterSize = clientConfig.getManagerAddress().size();
    }

    /**
     * 重新搜寻TM
     */
    public static void search() {
        Objects.requireNonNull(RPC_CLIENT_INITIALIZER);
        log.info("Searching for more TM...");
        try {
            HashSet<String> cluster = RELIABLE_MESSENGER.queryTMCluster();
            if (cluster.isEmpty()) {
                log.info("No more TM.");
                echoTMClusterSuccessful();
                return;
            }
            clusterCountLatch = new CountDownLatch(cluster.size() - knownTMClusterSize);
            log.debug("wait connect size is {}", cluster.size() - knownTMClusterSize);
            RPC_CLIENT_INITIALIZER.init(TxManagerHost.parserList(new ArrayList<>(cluster)), true);
            clusterCountLatch.await(10, TimeUnit.SECONDS);
            echoTMClusterSuccessful();
        } catch (RpcException | InterruptedException e) {
            throw new IllegalStateException("There is no normal TM.");
        }
    }

    /**
     * 搜索到一个
     * @return is searched one
     */
    public static boolean searchedOne() {
        if (Objects.nonNull(clusterCountLatch)) {
            if (clusterCountLatch.getCount() == 0) {
                return false;
            }
            clusterCountLatch.countDown();
            return true;
        }
        return false;
    }

    private static void echoTMClusterSuccessful() {
        log.info("TC[{}] established TM cluster successfully!", Transactions.APPLICATION_ID_WHEN_RUNNING);
        echoTmClusterSize();
    }

    public static void echoTmClusterSize() {
        log.info("TM cluster's size: {}", RELIABLE_MESSENGER.clusterSize());
    }
}


/*
 * Copyright 2017-2019 CodingApi .
 *
 * 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.codingapi.txlcn.tc.config;

import com.codingapi.txlcn.tc.core.transaction.txc.TxcConfiguration;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.lang.NonNull;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
 * Description:
 * Date: 1/19/19
 *
 * @author ujued
 */
public class DependenciesImportSelector implements ImportSelector {

    /**
     * resolve the spi classes
     *
     * @param importingClassMetadata importingClassMetadata
     * @return spi classes
     */
    @Override
    @NonNull
    public String[] selectImports(@NonNull AnnotationMetadata importingClassMetadata) {
        boolean enabledTxc = Boolean.valueOf(
                Objects.requireNonNull(
                        importingClassMetadata.getAnnotationAttributes(EnableDistributedTransaction.class.getName()))
                        .get("enableTxc").toString());
        List<String> importClasses = new ArrayList<>();
        importClasses.add("com.codingapi.txlcn.txmsg.MessageConfiguration");
        if (enabledTxc) {
            importClasses.add(TxcConfiguration.class.getName());
        }
        return importClasses.toArray(new String[0]);
    }
}


/*
 * Copyright 2017-2019 CodingApi .
 *
 * 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.codingapi.txlcn.common.util;

import com.sun.org.apache.regexp.internal.RE;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.util.StringUtils;

import java.util.Objects;

/**
 * Description:
 * Date: 19-1-24 下午1:44
 *
 * @author ujued
 */
public class ApplicationInformation {

    /**
     * 模块标识
     *
     * @param environment      Spring Env
     * @param serverProperties serverProperties
     * @return 标识
     */
    public static String modId(ConfigurableEnvironment environment, ServerProperties serverProperties) {

        String applicationName = environment.getProperty("spring.application.name");
        applicationName = StringUtils.hasText(applicationName) ? applicationName : "application";
        return applicationName + ":" + serverPort(serverProperties);
    }

    /**
     * 拆分网络地址为host and port
     *
     * @param hostAndPort 主机和端口
     * @return 主机端口数组
     */
    public static String[] splitAddress(String hostAndPort) {
        if (hostAndPort.indexOf(':') == -1) {
            throw new IllegalStateException("non exists port");
        }
        String[] result = hostAndPort.split(":");
        if (StringUtils.isEmpty(result[0])) {
            result[0] = "0.0.0.0";
            return result;
        }
        if (result[0].charAt(0) == '/') {
            result[0] = result[0].substring(1);
            return result;
        }
        return result;
    }

    /**
     * 模块HTTP端口号
     *
     * @param serverProperties serverProperties
     * @return int
     */
    public static int serverPort(ServerProperties serverProperties) {
        return Objects.isNull(serverProperties) ? 0 : (Objects.isNull(serverProperties.getPort()) ? 8080 :
                serverProperties.getPort());
    }
}


/*
 * Copyright 2017-2019 CodingApi .
 *
 * 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.codingapi.txlcn.tc.core.context;

import com.codingapi.txlcn.common.exception.TCGlobalContextException;
import com.codingapi.txlcn.common.exception.TransactionException;
import com.codingapi.txlcn.common.util.function.Supplier;
import com.codingapi.txlcn.tc.config.TxClientConfig;
import com.codingapi.txlcn.tc.core.TccTransactionInfo;
import com.codingapi.txlcn.tc.core.transaction.lcn.resource.LcnConnectionProxy;
import com.codingapi.txlcn.tc.core.transaction.txc.analy.def.PrimaryKeysProvider;
import com.codingapi.txlcn.tc.core.transaction.txc.analy.def.bean.TableStruct;
import com.codingapi.txlcn.tracing.TracingContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.sql.SQLException;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * Description:
 * Date: 19-1-22 下午6:17
 *
 * @author ujued
 * @see AttachmentCache
 * @see PrimaryKeysProvider
 */
@Component
@Slf4j
public class DefaultGlobalContext implements TCGlobalContext {

    private final AttachmentCache attachmentCache;

    private final List<PrimaryKeysProvider> primaryKeysProviders;

    private final TxClientConfig clientConfig;

    @Autowired
    public DefaultGlobalContext(AttachmentCache attachmentCache, TxClientConfig clientConfig,
                                @Autowired(required = false) List<PrimaryKeysProvider> primaryKeysProviders) {
        this.attachmentCache = attachmentCache;
        this.primaryKeysProviders = primaryKeysProviders;
        this.clientConfig = clientConfig;
    }

    @Override
    public void setLcnConnection(String groupId, LcnConnectionProxy connectionProxy) {
        attachmentCache.attach(groupId, LcnConnectionProxy.class.getName(), connectionProxy);
    }

    @Override
    public LcnConnectionProxy getLcnConnection(String groupId) throws TCGlobalContextException {
        if (attachmentCache.containsKey(groupId, LcnConnectionProxy.class.getName())) {
            return attachmentCache.attachment(groupId, LcnConnectionProxy.class.getName());
        }
        throw new TCGlobalContextException("non exists lcn connection");
    }

    @Override
    public TccTransactionInfo tccTransactionInfo(String unitId, Supplier<TccTransactionInfo, TransactionException> supplier)
            throws TransactionException {
        String unitTransactionInfoKey = unitId + ".tcc.transaction";
        if (Objects.isNull(supplier)) {
            return attachmentCache.attachment(unitTransactionInfoKey);
        }

        if (attachmentCache.containsKey(unitTransactionInfoKey)) {
            return attachmentCache.attachment(unitTransactionInfoKey);
        }

        TccTransactionInfo tccTransactionInfo = supplier.get();
        attachmentCache.attach(unitTransactionInfoKey, tccTransactionInfo);
        return tccTransactionInfo;
    }

    @Override
    @SuppressWarnings("unchecked")
    public void addTxcLockId(String groupId, String unitId, Set<String> lockIdList) {
        String lockKey = unitId + ".txc.lock";
        if (attachmentCache.containsKey(groupId, lockKey)) {
            ((Set) attachmentCache.attachment(groupId, lockKey)).addAll(lockIdList);
            return;
        }
        Set<String> lockList = new HashSet<>(lockIdList);
        attachmentCache.attach(groupId, lockKey, lockList);
    }

    @Override
    public Set<String> findTxcLockSet(String groupId, String unitId) throws TCGlobalContextException {
        String lockKey = unitId + ".txc.lock";
        if (attachmentCache.containsKey(groupId, lockKey)) {
            return attachmentCache.attachment(groupId, lockKey);
        }
        throw new TCGlobalContextException("non exists lock id.");
    }

    @Override
    public TableStruct tableStruct(String table, Supplier<TableStruct, SQLException> structSupplier) throws SQLException {
        String tableStructKey = table + ".struct";
        if (attachmentCache.containsKey(tableStructKey)) {
            log.debug("cache hit! table {}'s struct.", table);
            return attachmentCache.attachment(tableStructKey);
        }
        TableStruct tableStruct = structSupplier.get();
        if (Objects.nonNull(primaryKeysProviders)) {
            primaryKeysProviders.forEach(primaryKeysProvider -> {
                List<String> users = primaryKeysProvider.provide().get(table);
                if (Objects.nonNull(users)) {
                    List<String> primaryKes = tableStruct.getPrimaryKeys();
                    primaryKes.addAll(users.stream()
                            .filter(key -> !primaryKes.contains(key))
                            .filter(key -> tableStruct.getColumns().keySet().contains(key)).collect(Collectors.toList()));
                    tableStruct.setPrimaryKeys(primaryKes);
                }
            });
        }
        attachmentCache.attach(tableStructKey, tableStruct);
        return tableStruct;
    }

    @Override
    public TxContext startTx() {
        TxContext txContext = new TxContext();
        // 事务发起方判断
        txContext.setDtxStart(!TracingContext.tracing().hasGroup());
        if (txContext.isDtxStart()) {
            TracingContext.tracing().beginTransactionGroup();
        }
        txContext.setGroupId(TracingContext.tracing().groupId());
        String txContextKey = txContext.getGroupId() + ".dtx";
        attachmentCache.attach(txContextKey, txContext);
        log.debug("Start TxContext[{}]", txContext.getGroupId());
        return txContext;
    }

    /**
     * 在用户业务前生成，业务后销毁
     *
     * @param groupId groupId
     */
    @Override
    public void destroyTx(String groupId) {
        attachmentCache.remove(groupId + ".dtx");
        log.debug("Destroy TxContext[{}]", groupId);
    }

    @Override
    public TxContext txContext(String groupId) {
        return attachmentCache.attachment(groupId + ".dtx");
    }

    @Override
    public TxContext txContext() {
        return txContext(TracingContext.tracing().groupId());
    }

    @Override
    public void destroyTx() {
        if (!hasTxContext()) {
            throw new IllegalStateException("non TxContext.");
        }
        destroyTx(txContext().getGroupId());
    }

    @Override
    public boolean hasTxContext() {
        return TracingContext.tracing().hasGroup() && txContext(TracingContext.tracing().groupId()) != null;
    }

    @Override
    public boolean isDTXTimeout() {
        if (!hasTxContext()) {
            throw new IllegalStateException("non TxContext.");
        }
        return (System.currentTimeMillis() - txContext().getCreateTime()) >= clientConfig.getDtxTime();
    }

    @Override
    public int dtxState(String groupId) {
        return this.attachmentCache.containsKey(groupId, "rollback-only") ? 0 : 1;
    }

    @Override
    public void setRollbackOnly(String groupId) {
        this.attachmentCache.attach(groupId, "rollback-only", true);
    }

    /**
     * 清理事务时调用
     *
     * @param groupId groupId
     */
    @Override
    public void clearGroup(String groupId) {
        // 事务组相关的数据
        this.attachmentCache.removeAll(groupId);
    }
}


/*
 * Copyright 2017-2019 CodingApi .
 *
 * 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.codingapi.txlcn.tc.core;

import com.codingapi.txlcn.common.exception.TransactionException;
import com.codingapi.txlcn.common.util.Transactions;
import com.codingapi.txlcn.logger.TxLogger;
import com.codingapi.txlcn.tc.core.propagation.DTXPropagationResolver;
import com.codingapi.txlcn.tc.support.TxLcnBeanHelper;
import com.codingapi.txlcn.tc.core.context.TCGlobalContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.Set;

/**
 * LCN分布式事务业务执行器
 * Created by lorne on 2017/6/8.
 */
@Component
@Slf4j
public class DTXServiceExecutor {

    private static final TxLogger txLogger = TxLogger.newLogger(DTXServiceExecutor.class);

    private final TCGlobalContext globalContext;

    private final TxLcnBeanHelper txLcnBeanHelper;

    private final DTXPropagationResolver propagationResolver;

    @Autowired
    public DTXServiceExecutor(TxLcnBeanHelper txLcnBeanHelper, TCGlobalContext globalContext,
                              DTXPropagationResolver propagationResolver) {
        this.txLcnBeanHelper = txLcnBeanHelper;
        this.globalContext = globalContext;
        this.propagationResolver = propagationResolver;
    }

    /**
     * 事务业务执行
     *
     * @param info info
     * @return Object
     * @throws Throwable Throwable
     */
    public Object transactionRunning(TxTransactionInfo info) throws Throwable {

        // 1. 获取事务类型
        String transactionType = info.getTransactionType();

        // 2. 获取事务传播状态
        DTXPropagationState propagationState = propagationResolver.resolvePropagationState(info);

        // 2.1 如果不参与分布式事务立即终止
        if (propagationState.isIgnored()) {
            return info.getBusinessCallback().call();
        }

        // 3. 获取本地分布式事务控制器
        DTXLocalControl dtxLocalControl = txLcnBeanHelper.loadDTXLocalControl(transactionType, propagationState);

        // 4. 织入事务操作
        try {
            // 4.1 记录事务类型到事务上下文
            Set<String> transactionTypeSet = globalContext.txContext(info.getGroupId()).getTransactionTypes();
            transactionTypeSet.add(transactionType);

            dtxLocalControl.preBusinessCode(info);

            // 4.2 业务执行前
            txLogger.txTrace(
                    info.getGroupId(), info.getUnitId(), "pre business code, unit type: {}", transactionType);

            // 4.3 执行业务
            Object result = dtxLocalControl.doBusinessCode(info);

            // 4.4 业务执行成功
            txLogger.txTrace(info.getGroupId(), info.getUnitId(), "business success");
            dtxLocalControl.onBusinessCodeSuccess(info, result);
            return result;
        } catch (TransactionException e) {
            txLogger.error(info.getGroupId(), info.getUnitId(), "before business code error");
            throw e;
        } catch (Throwable e) {
            // 4.5 业务执行失败
            txLogger.error(info.getGroupId(), info.getUnitId(), Transactions.TAG_TRANSACTION,
                    "business code error");
            dtxLocalControl.onBusinessCodeError(info, e);
            throw e;
        } finally {
            // 4.6 业务执行完毕
            dtxLocalControl.postBusinessCode(info);
        }
    }

}


/*
 * Copyright 2017-2019 CodingApi .
 *
 * 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.codingapi.txlcn.tc.core.transaction.txc.analy.def.bean;

import lombok.AllArgsConstructor;
import lombok.Data;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Description: 描述数据表结构
 * <p>
 * Date: 2018/12/11
 *
 * @author ujued
 */
@AllArgsConstructor
@Data
public class TableStruct {

    public TableStruct(String tableName) {
        this.tableName = tableName;
    }

    /**
     * 数据表名
     */
    private String tableName;

    /**
     * 主键列表(非全限定名)
     */
    private List<String> primaryKeys = new ArrayList<>();

    /**
     * 主键列表(全限定名)
     */
    private List<String> fullyQualifiedPrimaryKeys;

    /**
     * 所有字段及类型（包括主键）
     */
    private Map<String, String> columns = new HashMap<>();

    /**
     * 重写Getter
     *
     * @return getFullyQualifiedPrimaryKeys
     */
    public List<String> getFullyQualifiedPrimaryKeys() {
        if (this.fullyQualifiedPrimaryKeys != null) {
            return this.fullyQualifiedPrimaryKeys;
        }
        List<String> pks = new ArrayList<>();
        this.getPrimaryKeys().forEach(key -> pks.add(tableName + '.' + key));
        this.fullyQualifiedPrimaryKeys = pks;
        return this.fullyQualifiedPrimaryKeys;
    }
}


/*
 * Copyright 2017-2019 CodingApi .
 *
 * 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.codingapi.txlcn.logger.helper;

import com.codingapi.txlcn.common.util.Maps;
import com.codingapi.txlcn.common.util.Strings;
import com.codingapi.txlcn.logger.db.LogDbHelper;
import com.codingapi.txlcn.logger.db.LogDbProperties;
import com.codingapi.txlcn.logger.db.TxLog;
import com.codingapi.txlcn.logger.exception.NotEnableLogException;
import com.codingapi.txlcn.logger.exception.TxLoggerException;
import com.codingapi.txlcn.logger.model.*;
import org.apache.commons.dbutils.BasicRowProcessor;
import org.apache.commons.dbutils.GenerousBeanProcessor;
import org.apache.commons.dbutils.RowProcessor;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.apache.commons.dbutils.handlers.ScalarHandler;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
 * Description:
 * Company: CodingApi
 * Date: 2018/12/26
 *
 * @author codingapi
 */
public class MysqlLoggerHelper implements TxLcnLogDbHelper {

    /**
     * 当 开启enable时才能获取到.
     */
    @Autowired(required = false)
    private LogDbHelper dbHelper;

    @Autowired
    private LogDbProperties logDbProperties;

    private RowProcessor processor = new BasicRowProcessor(new GenerousBeanProcessor());

    @Override
    public void init() {
        if (logDbProperties.isEnabled()) {
            String sql = "CREATE TABLE IF NOT EXISTS `t_logger`  (\n" +
                    "  `id` bigint(20) NOT NULL AUTO_INCREMENT,\n" +
                    "  `group_id` varchar(64)  NOT NULL ,\n" +
                    "  `unit_id` varchar(32)  NOT NULL ,\n" +
                    "  `tag` varchar(50)  NOT NULL ,\n" +
                    "  `content` varchar(1024)  NOT NULL ,\n" +
                    "  `create_time` varchar(30) NOT NULL,\n" +
                    "  `app_name` varchar(128) NOT NULL,\n" +
                    "  PRIMARY KEY (`id`) USING BTREE\n" +
                    ") ";
            dbHelper.update(sql);
        }

    }

    @Override
    public int insert(TxLog txLoggerInfo) {
        if (logDbProperties.isEnabled()) {
            String sql = "insert into t_logger(group_id,unit_id,tag,content,create_time,app_name) values(?,?,?,?,?,?)";
            return dbHelper.update(sql, txLoggerInfo.getGroupId(), txLoggerInfo.getUnitId(), txLoggerInfo.getTag(),
                    Strings.format(txLoggerInfo.getContent(), Maps.of("xid", txLoggerInfo.getGroupId(),
                            "uid", txLoggerInfo.getUnitId()), txLoggerInfo.getArgs()),
                    txLoggerInfo.getCreateTime(), txLoggerInfo.getAppName());
        } else {
            throw new NotEnableLogException("not enable logger");
        }
    }

    /**
     * 按筛选条件获取记录数
     *
     * @param where  where条件部分
     * @param params 参数
     * @return 总共记录数
     */
    private long total(String where, Object... params) {
        if (logDbProperties.isEnabled()) {
            return dbHelper.query("select count(*) from t_logger where " + where, new ScalarHandler<>(), params);
        } else {
            throw new NotEnableLogException("not enable logger");
        }
    }

    /**
     * 时间排序SQL
     *
     * @param timeOrder 排序方式
     * @return orderSql
     */
    private String timeOrderSql(int timeOrder) {
        return "order by create_time " + (timeOrder == 1 ? "asc" : "desc");
    }

    @Override
    public void deleteByFields(List<Field> fields) throws TxLoggerException {
        if (Objects.isNull(dbHelper)) {
            throw new TxLoggerException("系统日志被禁用");
        }
        StringBuilder sql = new StringBuilder("delete from t_logger where 1=1 and ");
        List<String> values = whereSqlAppender(sql, fields);
        dbHelper.update(sql.toString(), values.toArray(new Object[0]));
    }

    private List<String> whereSqlAppender(StringBuilder sql, List<Field> fields) {
        List<String> values = new ArrayList<>(fields.size());
        fields.forEach(field -> {
            if (field instanceof GroupId) {
                sql.append("group_id=? and ");
                values.add(((GroupId) field).getGroupId());
            } else if (field instanceof Tag) {
                sql.append("tag=? and ");
                values.add(((Tag) field).getTag());
            } else if (field instanceof StartTime) {
                sql.append("create_time > ? and ");
                values.add(((StartTime) field).getStartTime());
            } else if (field instanceof StopTime) {
                sql.append("create_time < ? and ");
                values.add(((StopTime) field).getStopTime());
            }
        });
        sql.delete(sql.length() - 4, sql.length());
        return values;
    }

    @Override
    public LogList findByLimitAndFields(int page, int limit, int timeOrder, List<Field> list) throws TxLoggerException {
        if (Objects.isNull(dbHelper)) {
            throw new TxLoggerException("系统日志被禁用");
        }
        StringBuilder countSql = new StringBuilder("select count(*) from t_logger where 1=1 and ");
        StringBuilder sql = new StringBuilder("select * from t_logger where 1=1 and ");
        List<String> values = whereSqlAppender(sql, list);
        whereSqlAppender(countSql, list);
        Object[] params = values.toArray(new Object[0]);
        long total = dbHelper.query(countSql.toString(), new ScalarHandler<>(), params);
        if (total < (page - 1) * limit) {
            page = 1;
        }
        sql.append(timeOrderSql(timeOrder)).append(" limit ").append((page - 1) * limit).append(", ").append(limit);
        List<TxLog> txLogs = dbHelper.query(sql.toString(), new BeanListHandler<>(TxLog.class, processor), params);

        LogList logList = new LogList();
        logList.setTotal(total);
        logList.setTxLogs(txLogs);
        return logList;
    }

}


/*
 * Copyright 2017-2019 CodingApi .
 *
 * 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.codingapi.txlcn.tc.core.transaction.txc.analy;

import com.codingapi.txlcn.tc.core.DTXLocalContext;
import com.codingapi.txlcn.tc.core.transaction.txc.analy.def.bean.TableStruct;
import org.apache.commons.dbutils.DbUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;

/**
 * Description: 数据库表结构分析
 * <p>
 * Date: 2018/12/10
 *
 * @author ujued
 */
@Component
public class TableStructAnalyser {

    private final DataSource dataSource;

    @Autowired
    public TableStructAnalyser(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    public TableStruct analyse(Connection connection, String table) throws SQLException {
        ResultSet structRs = null;
        ResultSet columnSet = null;
        TableStruct tableStruct = new TableStruct(table);
        try {
            structRs = connection.getMetaData().getPrimaryKeys(connection.getCatalog(), null, table);
            columnSet = connection.getMetaData().getColumns(null, "%", table, "%");
            while (structRs.next()) {
                tableStruct.getPrimaryKeys().add(structRs.getString("COLUMN_NAME"));
            }
            while (columnSet.next()) {
                tableStruct.getColumns().put(columnSet.getString("COLUMN_NAME"), columnSet.getString("TYPE_NAME"));
            }
        } catch (SQLException e) {
            try {
                DbUtils.close(structRs);
                DbUtils.close(columnSet);
            } catch (SQLException ignored) {
            }
            throw e;
        }
        return tableStruct;
    }

    public TableStruct analyse(String table) throws SQLException {
        Connection connection = null;
        try {
            DTXLocalContext.makeUnProxy();
            connection = dataSource.getConnection();
            connection.setAutoCommit(true);
            return analyse(connection, table);
        } finally {
            DTXLocalContext.undoProxyStatus();
            DbUtils.close(connection);
        }
    }

    public boolean existsTable(Connection connection, String table) throws SQLException {
        ResultSet resultSet = null;
        try {
            resultSet = connection.getMetaData().getTables(null, null, table, null);
            if (resultSet.next()) {
                return true;
            }
        } catch (SQLException e) {
            throw e;
        } finally {
            DbUtils.close(resultSet);
        }
        return false;
    }

    /**
     * 存在数据表判断
     *
     * @param tableName tableName
     * @return exists
     * @throws  SQLException SQLException
     */
    public boolean existsTable(String tableName) throws SQLException {
        Connection connection = null;
        try {
            DTXLocalContext.makeUnProxy();
            connection = dataSource.getConnection();
            connection.setAutoCommit(true);
            return existsTable(connection, tableName);
        } finally {
            DbUtils.close(connection);
            DTXLocalContext.undoProxyStatus();
        }
    }
}


/*
 * Copyright 2017-2019 CodingApi .
 *
 * 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.codingapi.txlcn.txmsg.netty.bean;

import com.codingapi.txlcn.txmsg.RpcConfig;
import com.codingapi.txlcn.txmsg.dto.AppInfo;
import com.codingapi.txlcn.txmsg.dto.MessageDto;
import com.codingapi.txlcn.txmsg.dto.RpcCmd;
import com.codingapi.txlcn.txmsg.dto.RpcResponseState;
import com.codingapi.txlcn.txmsg.exception.RpcException;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.util.concurrent.GlobalEventExecutor;
import lombok.extern.slf4j.Slf4j;

import java.net.SocketAddress;
import java.util.*;
import java.util.concurrent.*;

/**
 * Created by lorne on 2017/6/30.
 */
@Slf4j
public class SocketManager {

    private Map<String, AppInfo> appNames;

    private ScheduledExecutorService executorService;

    private ChannelGroup channels;

    private static SocketManager manager = null;

    private long attrDelayTime = 1000 * 60;

    private SocketManager() {
        channels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
        appNames = new ConcurrentHashMap<>();
        executorService = Executors.newSingleThreadScheduledExecutor();

        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            executorService.shutdown();
            try {
                executorService.awaitTermination(10, TimeUnit.MINUTES);
            } catch (InterruptedException ignored) {
            }
        }));
    }

    public static SocketManager getInstance() {
        if (manager == null) {
            synchronized (SocketManager.class) {
                if (manager == null) {
                    manager = new SocketManager();
                }
            }
        }
        return manager;
    }

    public void addChannel(Channel channel) {
        channels.add(channel);
    }

    public void removeChannel(Channel channel) {
        channels.remove(channel);
        String key = channel.remoteAddress().toString();

        // 未设置过期时间，立即过期
        if (attrDelayTime < 0) {
            appNames.remove(key);
            return;
        }

        // 设置了过期时间，到时间后清除
        try {
            executorService.schedule(() -> {
                appNames.remove(key);
            }, attrDelayTime, TimeUnit.MILLISECONDS);
        } catch (RejectedExecutionException ignored) {
            // caused down server.
        }
    }

    private Channel getChannel(String key) throws RpcException {
        for (Channel channel : channels) {
            String val = channel.remoteAddress().toString();
            if (key.equals(val)) {
                return channel;
            }
        }
        throw new RpcException("channel not online.");
    }

    public RpcResponseState send(String key, RpcCmd cmd) throws RpcException {
        Channel channel = getChannel(key);
        ChannelFuture future = channel.writeAndFlush(cmd).syncUninterruptibly();
        return future.isSuccess() ? RpcResponseState.success : RpcResponseState.fail;
    }

    public MessageDto request(String key, RpcCmd cmd, long timeout) throws RpcException {
        NettyRpcCmd nettyRpcCmd = (NettyRpcCmd) cmd;
        log.debug("get channel, key:{}", key);
        Channel channel = getChannel(key);
        channel.writeAndFlush(nettyRpcCmd);
        log.debug("await response");
        if (timeout < 1) {
            nettyRpcCmd.await();
        } else {
            nettyRpcCmd.await(timeout);
        }
        MessageDto res = cmd.loadResult();
        log.debug("response is: {}", res);
        nettyRpcCmd.loadRpcContent().clear();
        return res;
    }

    public MessageDto request(String key, RpcCmd cmd) throws RpcException {
        return request(key, cmd, -1);
    }

    public List<String> loadAllRemoteKey() {
        List<String> allKeys = new ArrayList<>();
        for (Channel channel : channels) {
            allKeys.add(channel.remoteAddress().toString());
        }
        return allKeys;
    }

    public ChannelGroup getChannels() {
        return channels;
    }

    public int currentSize() {
        return channels.size();
    }

    public boolean noConnect(SocketAddress socketAddress) {
        for (Channel channel : channels) {
            if (channel.remoteAddress().toString().equals(socketAddress.toString())) {
                return false;
            }
        }
        return true;
    }

    /**
     * 获取模块的远程标识keys
     *
     * @param moduleName 模块名称
     * @return remoteKeys
     */
    public List<String> removeKeys(String moduleName) {
        List<String> allKeys = new ArrayList<>();
        for (Channel channel : channels) {
            if (moduleName.equals(getModuleName(channel))) {
                allKeys.add(channel.remoteAddress().toString());
            }
        }
        return allKeys;
    }

    /**
     * 绑定连接数据
     *
     * @param remoteKey  远程标识
     * @param appName  模块名称
     * @param labelName TC标识名称
     */
    public void bindModuleName(String remoteKey, String appName,String labelName) throws RpcException{
        AppInfo appInfo = new AppInfo();
        appInfo.setAppName(appName);
        appInfo.setLabelName(labelName);
        appInfo.setCreateTime(new Date());
        if(containsLabelName(labelName)){
            throw new RpcException("labelName:"+labelName+" has exist.");
        }
        appNames.put(remoteKey, appInfo);
    }

    public boolean containsLabelName(String moduleName){
        Set<String> keys =  appNames.keySet();
        for(String key:keys){
            AppInfo appInfo = appNames.get(key);
            if(moduleName.equals(appInfo.getAppName())){
                return true;
            }
        }
        return false;
    }

    public void setRpcConfig(RpcConfig rpcConfig) {
        attrDelayTime = rpcConfig.getAttrDelayTime();
    }

    /**
     * 获取模块名称
     *
     * @param channel 管道信息
     * @return 模块名称
     */
    public String getModuleName(Channel channel) {
        String key = channel.remoteAddress().toString();
        return getModuleName(key);
    }

    /**
     * 获取模块名称
     *
     * @param remoteKey 远程唯一标识
     * @return 模块名称
     */
    public String getModuleName(String remoteKey) {
        AppInfo appInfo = appNames.get(remoteKey);
        return appInfo == null ? null : appInfo.getAppName();
    }

    public List<AppInfo> appInfos() {
        return new ArrayList<>(appNames.values());
    }
}


/*
 * Copyright 2017-2019 CodingApi .
 *
 * 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.codingapi.txlcn.tracing;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.codingapi.txlcn.common.util.Maps;
import com.codingapi.txlcn.common.util.id.RandomUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;

/**
 * Description:
 * 1. {@code fields}为 {@code null}。发起方出现，未开始事务组
 * 2. {@code fields}不为空，fields.get(TracingConstants.GROUP_ID) 是 {@code empty}。参与方出现，未开启事务组。
 * Date: 19-1-28 下午4:21
 *
 * @author ujued
 */
@Slf4j
public class TracingContext {

    private static ThreadLocal<TracingContext> tracingContextThreadLocal = new ThreadLocal<>();

    private TracingContext() {

    }

    public static TracingContext tracing() {
        if (tracingContextThreadLocal.get() == null) {
            tracingContextThreadLocal.set(new TracingContext());
        }
        return tracingContextThreadLocal.get();
    }

    private Map<String, String> fields;

    public void beginTransactionGroup() {
        if (hasGroup()) {
            return;
        }
        init(Maps.newHashMap(TracingConstants.GROUP_ID, RandomUtils.randomKey(), TracingConstants.APP_MAP, "{}"));
    }

    public static void init(Map<String, String> initFields) {

        // return if null fields.
        if (Objects.isNull(initFields)) {
            log.warn("init tracingContext fail. null init fields.");
            return;
        }
        TracingContext tracingContext = tracing();
        if (Objects.isNull(tracingContext.fields)) {
            tracingContext.fields = new HashMap<>();
        }
        tracingContext.fields.putAll(initFields);
    }

    /**
     * 判断是否有事务组
     *
     * @return result
     */
    public boolean hasGroup() {
        return Objects.nonNull(fields) && fields.containsKey(TracingConstants.GROUP_ID) &&
                StringUtils.hasText(fields.get(TracingConstants.GROUP_ID));
    }

    /**
     * 获取事务组ID
     *
     * @return groupId
     */
    public String groupId() {
        if (hasGroup()) {
            return fields.get(TracingConstants.GROUP_ID);
        }
        raiseNonGroupException();
        return "";
    }

    /**
     * 所有的字段
     *
     * @return fields
     */
    public Map<String, String> fields() {
        return this.fields;
    }

    /**
     * 添加App
     *
     * @param serviceId serviceId
     * @param address   address
     */
    public void addApp(String serviceId, String address) {
        if (hasGroup()) {
            JSONObject map = JSON.parseObject(this.fields.get(TracingConstants.APP_MAP));
            if (map.containsKey(serviceId)) {
                return;
            }
            map.put(serviceId, address);
            this.fields.put(TracingConstants.APP_MAP, JSON.toJSONString(map));
            return;
        }
        raiseNonGroupException();
    }

    /**
     * JSON Type App map.
     *
     * @return appMap
     */
    public JSONObject appMap() {
        return JSON.parseObject(appMapString());
    }

    /**
     * String Type App map.
     *
     * @return appMap
     */
    public String appMapString() {
        if (hasGroup()) {
            String appMap = Optional.ofNullable(this.fields.get(TracingConstants.APP_MAP)).orElse("");
            log.debug("App map: {}", appMap);
            return appMap;
        }
        raiseNonGroupException();
        return "{}";
    }

    /**
     * 销毁当前线程Tracing信息
     */
    public void destroy() {
        if (Objects.nonNull(tracingContextThreadLocal.get())) {
            tracingContextThreadLocal.remove();
        }
    }

    private void raiseNonGroupException() {
        throw new IllegalStateException("non group id.");
    }
}


/**
 * P6Spy
 *
 * Copyright (C) 2002 - 2018 P6Spy
 *
 * 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.codingapi.txlcn.tc.support.p6spy.common;

import java.io.File;
import java.net.URL;
import java.util.*;
import java.util.Map.Entry;
import java.util.regex.Pattern;

public class P6Util {
    static Pattern lineBreakPattern = Pattern.compile("(\\r?\\n)+");
    public static String singleLine(String str) {
        return lineBreakPattern.matcher(str).replaceAll(" ");
    }

    private P6Util() {
      // preventing instantiation of the util class
    }

    public static int parseInt(String i, int defaultValue) {
        if (i == null || i.isEmpty()) {
            return defaultValue;
        }
        try {
            return (Integer.parseInt(i));
        }
        catch(NumberFormatException nfe) {
//            P6LogQuery.error("NumberFormatException occured parsing value "+i);
            return defaultValue;
        }
    }

    public static boolean isTrue(String s, boolean defaultValue) {
        if (s == null) {
            return defaultValue;
        }
        return("1".equals(s) || "true".equalsIgnoreCase(s.trim()));
    }

  /**
   * Locates a file on the file system or on the classpath.
   *

   * Search order:
   *

   *
current working directory (for relative filePath) or any directory (for absolute filePath)

   *
class filePath

   *

   *
   * @param file the relative filePath of the file to locate
   * @return A URL to the file or null if not found
   */
  public static URL locateFile(String file) {
    File fp;
    URL result = null;

    try {
      // try to find relative to current working directory first
      fp = new File(file);
      if (fp.exists()) {
        result = fp.toURI().toURL();
      }

      // next try to load from context class loader
      if (result == null) {
        result = locateOnClassPath(file);
      }
    } catch (Exception e) {
    }

    return result;

  }

  /**
   * Locates a file on the classpath.
   *
   * @param filename the relative filePath of the file to load
   * @return the URL of the file or null if not found
   */
  public static URL locateOnClassPath(String filename) {
    URL result;
    // first try to load from context class loader
    result = Thread.currentThread().getContextClassLoader().getResource(filename);

    // next try the current class loader which loaded p6spy
    if (result == null) {
      result = P6Util.class.getClassLoader().getResource(filename);
    }

    // finally try the system class loader
    if (result == null) {
      result = ClassLoader.getSystemResource(filename);
    }

    return result;
  }

    /**
     * A utility for using the current class loader (rather than the
     * system class loader) when instantiating a new class.
     *

     * The idea is that the thread's current loader might have an
     * obscure notion of what your class filePath is (e.g. an app server) that
     * will not be captured properly by the system class loader.
     *

     * taken from http://sourceforge.net/forum/message.php?msg_id=1720229
     *
     * @param name class name to load
     * @return the newly loaded class
     * @throws  ClassNotFoundException ClassNotFoundException
     */
    public static Class<?> forName(String name) throws ClassNotFoundException {
        ClassLoader ctxLoader = null;
        try {
            ctxLoader = Thread.currentThread().getContextClassLoader();
            return Class.forName(name, true, ctxLoader);

        } catch(ClassNotFoundException ex) {
            // try to fall through and use the default
            // Class.forName
            //if(ctxLoader == null) { throw ex; }
        } catch(SecurityException ex) {
        }
        return Class.forName(name);
    }

    public static String getPath(URL theURL) {
     	String file = theURL.getFile();
     	String path = null;
     	if (file != null) {
			int q = file.lastIndexOf('?');
	       	if (q != -1) {
	         path = file.substring(0, q);
			} else {
	       		path = file;
	     	}
   		}
     	return path;
     }

	/**
	 * @param properties
	 *            to be converted to {@link Map}.
	 * @return {@link Map} of the properties. Please note, that properties that
	 *         have no value specified are contained in a result map, but with
	 *         value equal to "" (empty {@link String}).
	 */
	public static Map<String, String> getPropertiesMap(final Properties properties) {
		if (null == properties) {
			return null;
		}

		final Map<String, String> map = new HashMap<String, String>();
		for (Entry<Object, Object> entry : properties.entrySet()) {
			map.put((String) entry.getKey(), (String) entry.getValue());
		}
		return map;
	}

    public static List<String> parseCSVList(String csv) {
      if (csv == null || csv.isEmpty()) {
    	  return Collections.emptyList();
      }

      return new ArrayList<String>(Arrays.asList(csv.split(",")));
    }

    public static Properties getProperties(Map<String, String> map) {
      if (map == null) {
        return null;
      }

      final Properties properties = new Properties();
      properties.putAll(map);
      return properties;
    }

  /**
   * @param collection
   *          to be joined elements.
   * @param separator
   *          used in join.
   * @return {@code collections} elements joined via {@code separator}.
   */
  public static String joinNullSafe(Collection<String> collection, String separator) {
    if (null == collection || collection.isEmpty()) {
      return "";
    }

    if (null == separator) {
      separator = "";
    }

    final StringBuilder sb = new StringBuilder();
    for (String str : collection) {
      if (sb.length() > 0) {
        sb.append(separator);
      }
      sb.append(str);
    }
    return sb.toString();
  }

}