All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.qiniu.process.Base Maven / Gradle / Ivy

package com.qiniu.process;

import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.qiniu.common.QiniuException;
import com.qiniu.interfaces.IFileChecker;
import com.qiniu.interfaces.ILineProcess;
import com.qiniu.persistence.FileSaveMapper;
import com.qiniu.util.*;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

/**
 * process 基类,实现 ILineProcess 的方法,并实现 Cloneable 接口支持 clone。该类为抽象类,且子类需要实现一些抽象方法(子类实现抽象方法时不需
 * 要同步代码),包内提供的子类均为非线程安全,且都调用该基类的 close 方法,多线程时需使用 clone 的对象,否则容易引发线程安全问题,同时,即使子类
 * 使用同步方法,多线程时调用了一次 close 会导致其他线程跑异常。
 * @param  line 数据范型
 */
public abstract class Base implements ILineProcess, Cloneable {

    protected String processName;
    protected String accessId;
    protected String secretKey;
    protected String bucket;
    protected int batchSize;
    protected int retryTimes = 5;
    protected boolean autoIncrease;
    protected int index;
    protected AtomicInteger saveIndex;
    protected String savePath;
    protected FileSaveMapper fileSaveMapper;
    protected IFileChecker iFileChecker;
    protected String checkType;
    protected boolean canceled;

    public Base(String processName, String accessId, String secretKey, String bucket) {
        this.processName = processName;
        this.accessId = accessId;
        this.secretKey = secretKey;
        this.bucket = bucket;
        this.iFileChecker = key -> null;
    }

    public Base(String processName, String accessKey, String secretKey, String bucket, String savePath, int saveIndex)
            throws IOException {
        this(processName, accessKey, secretKey, bucket);
        this.index = saveIndex;
        this.saveIndex = new AtomicInteger(saveIndex);
        this.savePath = savePath;
        this.fileSaveMapper = new FileSaveMapper(savePath, processName, String.valueOf(saveIndex));
        this.fileSaveMapper.preAddWriter("need_retry");
    }

    public String getProcessName() {
        return this.processName;
    }

    public void setAutoIncrease(boolean autoIncrease) {
        this.autoIncrease = autoIncrease;
    }

    public void setBatchSize(int batchSize) throws IOException {
        if (!ProcessUtils.canBatch(processName)) {
            throw new IOException(processName + " is not support batch operation.");
        } else if (batchSize > 1000 || batchSize < 1) {
            throw new IOException("batch size must less than 1000 and more than 1.");
        } else {
            this.batchSize = batchSize;
        }
    }

    public void setRetryTimes(int retryTimes) {
        this.retryTimes = retryTimes < 1 ? 5 : retryTimes;
    }

    @SuppressWarnings("unchecked")
    public Base clone() throws CloneNotSupportedException {
        Base base = (Base)super.clone();
        if (checkType != null && !"".equals(checkType)) base.iFileChecker = fileCheckerInstance();
        if (fileSaveMapper == null) return base;
        try {
            base.fileSaveMapper = autoIncrease ?
                    new FileSaveMapper(savePath, processName, String.valueOf(saveIndex.addAndGet(1))) :
                    new FileSaveMapper(savePath);
            base.fileSaveMapper.preAddWriter("need_retry");
        } catch (IOException e) {
            throw new CloneNotSupportedException(e.getMessage() + ", init writer failed.");
        }
        return base;
    }

    public void changeSaveOrder(String order) throws IOException {
        try {
            this.fileSaveMapper.changePrefixAndSuffix(processName, order);
        } catch (NullPointerException e) {
            throw new IOException("instance without savePath can not call changeSaveOrder method.");
        }
    }

    protected IFileChecker fileCheckerInstance() {
        return key -> null;
    }

    public void setCheckType(String checkType) {
        this.checkType = checkType;
        this.iFileChecker = fileCheckerInstance();
    }

    /**
     * 当处理结束应该从 line 中记录哪些关键信息
     * @param line 输入的 line
     * @return 返回需要记录的信息字符串
     */
    protected abstract String resultInfo(T line);

    protected List putBatchOperations(List processList) throws IOException {
        return processList;
    }

    /**
     * 对 lineList 执行 batch 的操作,因为默认是实现单个资源请求的操作,部分操作不支持 batch,因此需要 batch 操作时子类需要重写该方法。
     * @param lineList 代执行的文件信息列表
     * @return 返回执行响应信息的字符串
     * @throws Exception 执行失败抛出的异常
     */
    protected String batchResult(List lineList) throws Exception {
        throw new IOException("no default batch operation, please implements batch processing by yourself.");
    }

    /**
     * 处理 batchOperations 执行的结果,将输入的文件信息和结果对应地记录下来
     * @param processList batch 操作的资源列表
     * @param result batch 操作之后的响应结果
     * @return 返回需要进行重试的记录列表
     * @throws Exception 处理结果失败抛出的异常
     */
    protected List parseBatchResult(List processList, String result) throws Exception {
        if (result == null || "".equals(result)) throw new IOException("not valid json.");
        List retryList = null;
        JsonArray jsonArray = JsonUtils.fromJson(result, JsonArray.class);
        // 正常情况下 jsonArray 和 processList 的长度是相同的,将输入行信息和执行结果一一对应记录
        if (processList.size() != jsonArray.size()) throw new IOException("not matched process and return size.");
        JsonObject jsonObject;
        for (int j = 0; j < processList.size(); j++) {
            jsonObject = jsonArray.get(j).getAsJsonObject();
            switch (HttpRespUtils.checkStatusCode(jsonObject.get("code").getAsInt())) {
                case 1:
                    fileSaveMapper.writeSuccess(resultInfo(processList.get(j)) + "\t" + jsonObject, false);
                    break;
                case 0:
                    if (retryList == null) retryList = new ArrayList<>();
                    retryList.add(processList.get(j)); // 放回重试列表
                    break;
                case -1:
                    fileSaveMapper.writeError(resultInfo(processList.get(j)) + "\t" + jsonObject, false);
                    break;
            }
        }
        return retryList;
    }

    /**
     * 批量处理输入行,具体执行的操作取决于 batchResult 方法的实现。
     * @param lineList 输入列表
     * @param batchSize 一次批量处理请求处理的文件个数
     * @param retryTimes 每一行信息处理时需要的重试次数
     * @throws IOException 处理失败可能抛出的异常
     */
    private void batchProcess(List lineList, int batchSize, int retryTimes) throws IOException {
        int times = (lineList.size() + batchSize - 1) / batchSize;
        List processList;
        int retry;
        String message = null;
        QiniuException exception = null;
        for (int i = 0; i < times; i++) {
            if (canceled) break;
            retry = retryTimes;
            processList = lineList.subList(batchSize * i, i == times - 1 ? lineList.size() : batchSize * (i + 1));
            // 加上 processList.size() > 0 的选择原因是会在每一次处理 batch 操作的结果时将需要重试的记录加入重试列表进行返回,并且在
            // 没有异常的情况下当前的 processList 会执行到没有重试记录返回时才结束,parseBatchResult 会返回可以重试的列表,无重试记录则返回
            // 空,重试次数小于 0 时设置 processList = null
            while (processList != null && processList.size() > 0) {
                try {
                    processList = putBatchOperations(processList);
                    if (processList.size() > 0) {
                        processList = parseBatchResult(processList, batchResult(processList));
                    }
                } catch (QiniuException e) {
                    retry = HttpRespUtils.checkException(e, retry);
                    message = HttpRespUtils.getMessage(e);
                    exception = e;
                } catch (Exception e) {
                    retry = 0;
                    message = e.getMessage();
                    exception = null;
                }
                switch (retry) {
                    case 0: fileSaveMapper.writeError(String.join("\n", processList.stream()
                            .map(this::resultInfo).collect(Collectors.toList())) + "\t" + message, false);
                        processList = null; break;
                    case -1: fileSaveMapper.writeToKey("need_retry", String.join("\n", processList
                            .stream().map(this::resultInfo).collect(Collectors.toList())) + "\t" + message, false);
                        processList = null; break;
                    case -2: fileSaveMapper.writeError(String.join("\n", lineList
                            .subList(batchSize * i, lineList.size()).stream()
                            .map(this::resultInfo).collect(Collectors.toList())) + "\t" + message, false);
                        if (exception != null) throw exception;
                }
                if (exception != null && exception.response != null) exception.response.close();
            }
        }
    }

    /**
     * 单个文件进行操作的方法,返回操作的结果字符串,要求子类必须实现该方法,支持单个资源依次请求操作
     * @param line 输入 line
     * @return 操作结果的字符串
     * @throws Exception 操作失败时的返回
     */
    abstract protected String singleResult(T line) throws Exception;

    /**
     * 处理 singleProcess 执行的结果,默认情况下直接使用 resultInfo 拼接 result 成一行执行持久化写入,部分 process 可能对结果做进一步判断
     * 需要重写该方法
     * @param line 输入的 map 数据
     * @param result singleResult 的结果字符串
     * @throws Exception 处理结果失败抛出异常
     */
    protected void parseSingleResult(T line, String result) throws Exception {
        fileSaveMapper.writeSuccess(result, false);
    }

    /**
     * 对输入的文件信息列表单个进行操作,具体的操作方法取决于 singleResult 方法
     * @param lineList 输入列表
     * @param retryTimes 每一行信息处理时需要的重试次数
     * @throws IOException 处理失败可能抛出的异常
     */
    private void singleProcess(List lineList, int retryTimes) throws IOException {
        int retry;
        T line;
        String message;
        QiniuException exception;
        for (int i = 0; i < lineList.size(); i++) {
            line = lineList.get(i);
            retry = retryTimes;
            while (retry > 0) {
                try {
                    parseSingleResult(line, singleResult(line));
                    break;
                } catch (QiniuException e) {
                    retry = HttpRespUtils.checkException(e, retry);
                    message = HttpRespUtils.getMessage(e);
                    exception = e;
                } catch (Exception e) {
                    retry = 0;
                    message = e.getMessage();
                    exception = null;
                }
                switch (retry) {
                    case 0: fileSaveMapper.writeError(resultInfo(line) + "\t" + message, false); break;
                    case -1: fileSaveMapper.writeToKey("need_retry", resultInfo(line) + "\t" + message, false);
                        break;
                    case -2: fileSaveMapper.writeError(String.join("\n", lineList.subList(i, lineList.size())
                            .stream().map(this::resultInfo).collect(Collectors.toList())) + "\t" + message, false);
                        throw exception;
                }
                if (exception != null && exception.response != null) exception.response.close();
            }
        }
    }

    public String processLine(T line) throws IOException {
        try {
            return singleResult(line);
        } catch (NullPointerException e) {
            if (canceled) {
                throw new IOException("processor in canceled state.");
            } else if (batchSize < 0) { // 如果是关闭了那么 batchSize 应该小于 0
                throw new IOException("input is empty or the processor may be already closed.");
            } else {
                throw new IOException("instance without savePath can not call this batch process method.");
            }
        } catch (IOException e) {
            throw e;
        } catch (Exception e) {
            throw new IOException(e.getMessage(), e);
        }
    }

    /**
     * 公开的操作调用方法入口,通过判断 batch size 来决定调用哪个方法
     * @param lineList 输入的文件信息列表
     * @throws IOException 处理过程中出现的异常
     */
    public void processLine(List lineList) throws IOException {
        try {
            if (batchSize > 1) batchProcess(lineList, batchSize, retryTimes);
            else singleProcess(lineList, retryTimes);
        } catch (NullPointerException e) {
            if (canceled) {
////            // nothing to do
            } else if (batchSize < 0) { // 如果是关闭了那么 batchSize 应该小于 0
                throw new IOException("input is empty or the processor may be already closed.");
            } else {
                throw new IOException("instance without savePath can not call this batch process method.");
            }
        } catch (IOException e) {
            throw e;
        } catch (Exception e) {
            throw new IOException(e.getMessage());
        }
    }

    public void closeResource() {
        accessId = null;
        secretKey = null;
        bucket = null;
        batchSize = -1;
        saveIndex = null;
        savePath = null;
        if (fileSaveMapper != null) fileSaveMapper.closeWriters();
        fileSaveMapper = null;
        iFileChecker = null;
    }

    public void cancel() {
        canceled = true;
        closeResource();
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy