package com.uas.search.service.impl;

import com.alibaba.fastjson.JSONException;
import com.alibaba.fastjson.JSONObject;
import com.uas.search.DruidDBConfiguration;
import com.uas.search.annotation.NotEmpty;
import com.uas.search.constant.SearchConstants;
import com.uas.search.constant.model.PageInfo;
import com.uas.search.constant.model.PageParams;
import com.uas.search.constant.model.SPage;
import com.uas.search.dao.*;
import com.uas.search.exception.SearchException;
import com.uas.search.jms.JmsListener;
import com.uas.search.jms.QueueMessageParser;
import com.uas.search.model.*;
import com.uas.search.service.IndexService;
import com.uas.search.support.DownloadHelper;
import com.uas.search.support.DownloadService;
import com.uas.search.support.IndexSearcherManager;
import com.uas.search.support.IndexWriterManager;
import com.uas.search.util.*;
import org.apache.lucene.document.Document;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanQuery;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.stereotype.Service;

import java.io.*;
import java.util.*;

import static com.uas.search.constant.SearchConstants.COMPONENT_TABLE_NAME;
import static com.uas.search.constant.SearchConstants.GOODS_TABLE_NAME;

/**
 * 创建索引
 *
 * @author sunyj
 * @since 2016年8月5日 下午2:23:22
 */
@Service
public class IndexServiceImpl implements IndexService {

    @Autowired
    private DruidDBConfiguration druidDBConfiguration;

	@Autowired
	private QueueMessageParser queueMessageParser;

	@Autowired
	private KindDao kindDao;

	@Autowired
	private BrandDao brandDao;

	@Autowired
	private ComponentDao componentDao;

    @Autowired
    private TradeGoodsDao tradeGoodsDao;

	@Autowired
	private GoodsDao goodsDao;

	@Autowired
	private OrderDao orderDao;

	@Autowired
	private OrderInvoiceDao orderInvoiceDao;

	@Autowired
	private PurchaseDao purchaseDao;

	@Autowired
	private PurchaseInvoiceDao purchaseInvoiceDao;

	private IndexWriter indexWriter;

	private static IndexWriterManager indexWriterManager = new IndexWriterManager();

	@Autowired
	private JmsListener jmsListener;

	private IndexSearcherManager indexSearcherManager = IndexSearcherManager.getInstance();

	/**
	 * 是否正在创建索引
	 */
	private boolean creatingIndex = false;

	/**
	 * 从数据库获取数据时的分页大小
	 */
	public static final int PAGE_SIZE = 1000;

	/**
	 * 单个文件存储的最大数据数目，需是PAGE_SIZE的整数倍
	 */
	public static final int SINGLE_FILE_MAX_SIZE = PAGE_SIZE;

	private static Logger logger = LoggerFactory.getLogger(IndexServiceImpl.class);

	@Override
	public Long createIndexes(List<String> tableNames, Boolean componentFromFiles) {
		if (creatingIndex) {
			throw new SearchException("已存在线程在创建索引，不可重复请求");
		}
		creatingIndex = true;
		// 如果索引实时更新处于开启状态，需要关闭（以免两者同时操作索引出现问题）
		if (jmsListener.isRunning()) {
			logger.info("索引实时更新服务正在运行，尝试关闭索引实时更新服务...");
			jmsListener.stop();
		}

		if (CollectionUtils.isEmpty(tableNames)) {
			tableNames = SearchUtils.getTableNames();
		}

		try {
			Long startTime = new Date().getTime();

			for (String tableName : tableNames) {
                try {
                    Long start = new Date().getTime();
                    Long size = 0L;
                    deleteIndexes(tableName);
                    if (tableName.equals(SearchConstants.KIND_TABLE_NAME)) {
                        size = createKindIndexes();
                    } else if (tableName.equals(SearchConstants.BRAND_TABLE_NAME)) {
                        size = createBrandIndexes();
                    } else if (tableName.equals(COMPONENT_TABLE_NAME)) {
                        // 只有明确指定不从本地文件读取数据时，才从数据库获取数据建立索引
                        if (componentFromFiles != null && !componentFromFiles) {
                            size = createComponentIndexes();
                        } else {
                            size = createComponentIndexesFromFiles();
                        }
                    } else if (tableName.equals(SearchConstants.GOODS_TABLE_NAME)) {
                        size = createGoodsIndexesFromFiles();
                    } else if (tableName.equals(SearchConstants.ORDER_TABLE_NAME)) {
                        size = createOrderIndexes();
                    } else if (tableName.equals(SearchConstants.ORDER_INVOICE_TABLE_NAME)) {
                        size = createOrderInvoiceIndexes();
                    } else if (tableName.equals(SearchConstants.PURCHASE_TABLE_NAME)) {
                        size = createPurchaseIndexes();
                    } else if (tableName.equals(SearchConstants.PURCHASE_INVOICE_TABLE_NAME)) {
                        size = createPurchaseInvoiceIndexes();
                    } else {
                        logger.error("不支持创建该表索引：" + tableName);
                        continue;
                    }
                    indexSearcherManager.flushCache(tableName, indexWriter, null);
                    Long end = new Date().getTime();
                    logger.info(String.format("创建%s索引： %s条，耗时%.2fs\n ", tableName, size, (end - start) / 1000.0));
                }finally {
                    indexWriterManager.release(tableName);
                }
            }

			Long endTime = new Date().getTime();
			logger.info(String.format("索引创建成功， 共用时间%.2fs\n", (endTime - startTime) / 1000.0));
			return endTime - startTime;
		} catch (Exception e) {
			// 防止SQLRecoverableException导致应用终止
			throw new IllegalStateException("索引创建失败", e);
		} finally {
			creatingIndex = false;
		}
	}

	/**
	 * 删除指定表的索引
	 *
	 * @param tableName
	 *            表名
	 * @throws InterruptedException
	 * @throws IOException
	 */
	private void deleteIndexes(String tableName) throws InterruptedException, IOException {
		indexWriter = indexWriterManager.get(tableName);
		logger.info("正在清理旧索引..." + tableName);
		indexWriter.deleteAll();
		logger.info("旧索引清理完毕..." + tableName);
	}

	/**
	 * 创建类目索引
	 *
	 * @return 写入的类目索引数
	 * @throws IOException
	 */
	private Long createKindIndexes() throws IOException {
		logger.info("正在创建类目索引...");
		List<Kind> kinds = kindDao.findAll();
		logger.info("发现数据：" + kinds.size() + "条");
		return createIndexesWithObjects(kinds.toArray());
	}

	/**
	 * 创建品牌索引
	 *
	 * @return 写入的品牌索引数
	 * @throws IOException
	 */
	private Long createBrandIndexes() throws IOException {
		logger.info("正在创建品牌索引...");
		List<Brand> brands = brandDao.findAll();
		logger.info("发现数据：" + brands.size() + "条");
		return createIndexesWithObjects(brands.toArray());
	}

    private Long createComponentIndexesFromFiles() throws IOException {
        logger.info("正在创建器件索引...");
        Long size = 0L;
        BufferedReader bufferedReader = null;
        try {
            // 从本地路径读取器件数据
            String componentDataPath = SearchUtils.getDataPath(COMPONENT_TABLE_NAME);
            File[] files = new File(componentDataPath).listFiles();
            if (files == null || files.length == 0) {
                logger.info("创建器件索引失败，原因：器件数据文件不存在！");
                return 0L;
            }
            for (File file : files) {
                logger.info("读取文件: " + file.getName());
                bufferedReader = new BufferedReader(new FileReader(file));
                String line;
                while (!StringUtils.isEmpty(line = bufferedReader.readLine())) {
                    Component component;
                    try {
                        component = JSONObject.parseObject(line, Component.class);
                    } catch (JSONException e) {
                        throw new IllegalArgumentException(line, e);
                    }
                    Document document = ObjectToDocumentUtils.toDocument(component);
                    if (document != null) {
                        size++;
                        // 每创建10000条，打印一次进度
                        if (size % 10000 == 0) {
                            logger.info("Component indexed..................." + size);
                        }
                        indexWriter.addDocument(document);
                    }
                }
                indexWriter.commit();
            }
        } catch (FileNotFoundException e) {
            logger.error("创建器件索引失败，原因：器件数据文件不存在！");
            return 0L;
        }  finally {
            if (bufferedReader != null) {
                bufferedReader.close();
            }
        }
        return size;
    }

	/**
	 * 创建器件索引，从数据库取数据
	 *
	 * @return 写入的器件索引数
	 * @throws IOException
	 */
	public Long createComponentIndexes() throws IOException {
		logger.info("正在创建器件索引...");
		Long size = 0L;
		PageParams params = new PageParams();
		int page = 1;
		params.setSize(PAGE_SIZE);
		params.setPage(page);

		PageInfo info = new PageInfo(params);
		Page<Component> pageResult = componentDao.findAll(info);
		long totalElements = pageResult.getTotalElements();
		logger.info("Number of components: " + totalElements);
		// 用于记录上次提交索引时的创建进度
		double recordProgress = 0;
		while (totalElements > size) {
			List<Component> components = pageResult.getContent();
			for (Component component : components) {
				Document document = ObjectToDocumentUtils.toDocument(component);
				if (document != null) {
					indexWriter.addDocument(document);
				}
			}
			size += components.size();

			// 器件索引的创建进度（百分比）
			double indexProgress = size * 100.0 / totalElements;
			logger.info(String.format("Component indexed...................%.2f%%", indexProgress));
			// 每创建5%，提交一次，避免内存耗尽，发生OutOfMemoryError
			if (indexProgress - recordProgress > 5) {
				indexWriter.commit();
				recordProgress = indexProgress;
			}

			page++;
			params.setPage(page);
			info = new PageInfo(params);
			pageResult = componentDao.findAll(info);
		}
		indexWriter.commit();
		return size;
	}

	private Long createGoodsIndexesFromFiles() throws IOException {
		return createGoodsIndexesFromFiles(true);
	}

    /**
     * 创建批次索引
     *
     * @param updateFromDbAfterCreating 创建索引后是否从批次表获取数据，更新本地索引（此种情况下，用于创建索引的数据来自器件，并不包括批次，因此需要单独进行更新）
     * @return 写入的索引总数（不包括创建之后，所更新的数目）
     * @throws IOException
     */
    private Long createGoodsIndexesFromFiles(boolean updateFromDbAfterCreating) throws IOException {
        if(updateFromDbAfterCreating){
            logger.info("正在创建批次索引...");
        }else{
            logger.info("正在更新批次索引...");
        }
        Long size = 0L;
        BufferedReader bufferedReader=null;
        try {
            // 如果创建后需要更新本地索引，说明还未创建索引，本次便是在进行创建索引，需要转换器件数据，用来建索引
            if(updateFromDbAfterCreating){
                convertGoodsFromComponent();
            }
            // 从本地路径读取批次数据
            File[] files = new File(SearchUtils.getDataPath(SearchConstants.GOODS_TABLE_NAME)).listFiles();
            if (files == null || files.length == 0) {
                logger.info("创建批次索引失败，原因：批次数据文件不存在！");
                return 0L;
            }
            for (File file : files) {
                logger.info("读取文件: " + file.getName());
                bufferedReader = new BufferedReader(new FileReader(file));
                String line;
                while (!StringUtils.isEmpty(line = bufferedReader.readLine())) {
                    Goods goods;
                    try {
                        goods = JSONObject.parseObject(line, Goods.class);
                    } catch (JSONException e) {
                        throw new IllegalArgumentException(line, e);
                    }
                    // 如果创建后不需要更新本地索引，说明已经创建了索引，本次便是在进行更新索引
                    if (!updateFromDbAfterCreating) {
                        indexWriterManager.release(SearchConstants.GOODS_TABLE_NAME);
                        maintainIndexes(new ParsedQueueMessage(ParsedQueueMessage.UPDATE, goods));
                        try {
                            indexWriter = indexWriterManager.get(SearchConstants.GOODS_TABLE_NAME);
                        } catch (InterruptedException e) {
                            logger.error("", e);
                        }
                    } else {
                        Document document = ObjectToDocumentUtils.toDocument(goods);
                        if (document != null) {
                            size++;
                            // 每创建10000条，打印一次进度
                            if (size % 10000 == 0) {
                                logger.info("Goods indexed..................." + size);
                            }
                            indexWriter.addDocument(document);
                        }
                    }
                }
                indexWriter.commit();
                bufferedReader.close();
            }
        } catch (FileNotFoundException e) {
            logger.error("创建批次索引失败，原因：批次数据文件不存在！");
            return 0L;
        } finally {
            indexWriter.commit();
            if (bufferedReader != null) {
                bufferedReader.close();
            }
        }
        // 如果需要从批次表获取数据，更新本地索引
        if(updateFromDbAfterCreating){
            try {
                multiDownloadGoods(null, null, null, DownloadHelper.ValidateResult.CURRENT);
                createGoodsIndexesFromFiles(false);
            } catch (Throwable e) {
                throw new IllegalStateException("批次索引建立完成后，获取批次表数据用来更新索引时出错", e);
            }
        }
        return size;
    }

    /**
     * 根据本地器件的数据文件，转为批次数据
     *
     * @return 花费总时间 ms
     */
    private Long convertGoodsFromComponent() {
        Long startTime = new Date().getTime();
        Long size = 0L;
        logger.info("转换批次... ");
        BufferedReader bufferedReader = null;
        try {
            // 从本地路径读取器件数据
            String componentDataPath = SearchUtils.getDataPath(COMPONENT_TABLE_NAME);
            String goodsDataPath = SearchUtils.getDataPath(SearchConstants.GOODS_TABLE_NAME);
            File[] files = new File(componentDataPath).listFiles();
            if (files == null || files.length == 0) {
                logger.info("转换批次失败，原因：器件数据文件不存在！");
                return 0L;
            }
            FileUtils.deleteSubFiles(new File(goodsDataPath));
            int fileIndex = 1;
            for (File file : files) {
                logger.info("读取器件文件: " + file.getName());
                bufferedReader = new BufferedReader(new FileReader(file));
                String goodsFileName = String.format("%010d", fileIndex) + ".txt";
                PrintWriter printWriter = new PrintWriter(goodsDataPath + "/" + goodsFileName);
                String line;
                while (!StringUtils.isEmpty(line = bufferedReader.readLine())) {
                    Component component;
                    try {
                        component = JSONObject.parseObject(line, Component.class);
                    } catch (JSONException e) {
                        throw new IllegalArgumentException(line, e);
                    }
                    // 器件作为主体，得到批次
                    printWriter.println(JSONObject.toJSONString(new Goods(null, null, component, null)));
                    size++;
                }
                logger.info(goodsFileName + " - Converted..................." + size);
                printWriter.flush();
                printWriter.close();
                fileIndex++;
                bufferedReader.close();
            }
            long endStartTime = new Date().getTime();
            logger.info(String.format("转换完成，耗时%.2fs\n ", (endStartTime - startTime) / 1000.0));
            return endStartTime - startTime;
        } catch (Throwable e) {
            throw new IllegalStateException("批次转换失败", e);
        }finally{
            if(bufferedReader!=null){
                try {
                    bufferedReader.close();
                } catch (IOException e) {
                    throw new IllegalStateException("批次转换失败", e);
                }
            }
        }
    }

	private Long createOrderIndexes() {
		logger.info("正在创建销售单索引...");
		List<Order> orders = orderDao.findAll();
		logger.info("发现数据：" + orders.size() + "条");
		return createIndexesWithObjects(orders.toArray());
	}

	private Long createOrderInvoiceIndexes() {
		logger.info("正在创建销售发货单索引...");
		List<OrderInvoice> orderInvoices = orderInvoiceDao.findAll();
		logger.info("发现数据：" + orderInvoices.size() + "条");
		return createIndexesWithObjects(orderInvoices.toArray());
	}

	private Long createPurchaseIndexes() {
		logger.info("正在创建采购单索引...");
		List<Purchase> purchases = purchaseDao.findAll();
		logger.info("发现数据：" + purchases.size() + "条");
		return createIndexesWithObjects(purchases.toArray());
	}

	private Long createPurchaseInvoiceIndexes() {
		logger.info("正在创建采购发货单索引...");
		List<PurchaseInvoice> purchaseInvoices = purchaseInvoiceDao.findAll();
		logger.info("发现数据：" + purchaseInvoices.size() + "条");
		return createIndexesWithObjects(purchaseInvoices.toArray());
	}

	/**
	 * 利用对象数组创建索引
	 *
	 * @param objects
	 *            对象数组，可为Kind、Brand、Order、
	 *            OrderInvoice、Purchase、
	 *            PurchaseInvoice
	 * @return 对象数组的数量
	 */
	private Long createIndexesWithObjects(Object[] objects) {
		if (objects == null || objects.length < 1)
			return 0L;

		long count = 0;
		try {
			for (Object object : objects) {
				Document document = ObjectToDocumentUtils.toDocument(object);
				if (document != null) {
					indexWriter.addDocument(document);
					count++;
				}
			}
			indexWriter.commit();
		} catch (IOException e) {
			logger.error("", e);
		}
		return count;
	}

    @Override
    public long multiDownloadComponent(Integer threads, Integer startFileIndex, Integer endFileIndex, DownloadHelper.ValidateResult validateResult) {
        return multiDownloadData(COMPONENT_TABLE_NAME,threads,startFileIndex,endFileIndex, validateResult);
    }

    @Override
    public long multiDownloadGoods(Integer threads, Integer startFileIndex, Integer endFileIndex, DownloadHelper.ValidateResult validateResult) {
        return multiDownloadData(GOODS_TABLE_NAME,threads,startFileIndex,endFileIndex, validateResult);
    }

    /**
     * 多线程下载数据
     *
     * @param tableName      表名
     * @param threads        线程数量，默认为 1
     * @param startFileIndex 开始的文件，默认为 1
     * @param endFileIndex   结束的文件，默认为 1024 * 1024 * 1024
     * @param validateResult 下载完成后，是否对结果进行校验
     * @return 下载的数据条数
     */
    private long multiDownloadData(String tableName, Integer threads, Integer startFileIndex, Integer endFileIndex, DownloadHelper.ValidateResult validateResult) {
        threads = threads == null || threads < 1 ? 1 : threads;
        if (threads > druidDBConfiguration.getMaxActive()) {
            throw new IllegalArgumentException("线程数量不可超过 " + druidDBConfiguration.getMaxActive());
        }

        if (tableName.equals(COMPONENT_TABLE_NAME)) {
            DownloadHelper<Component> downloadHelper = new DownloadHelper<>(threads, startFileIndex, endFileIndex, tableName, "id",componentDao, new DownloadService<Component>(), validateResult);
            return downloadHelper.getResult();

        } else if (tableName.equals(GOODS_TABLE_NAME)) {
            DownloadHelper<TradeGoods> downloadHelper = new DownloadHelper<>(threads, startFileIndex, endFileIndex, tableName, "id",tradeGoodsDao, new DownloadService<TradeGoods>(){
                @Override
                protected void println(PrintWriter printWriter, TradeGoods element) {
                    Goods goods = goodsDao.findByTradeGoods(element);
                    printWriter.println(JSONObject.toJSONString(goods));
                }
            }, validateResult);
            return downloadHelper.getResult();
        } else {
            throw new IllegalArgumentException("多线程下载不支持该表：" + tableName);
        }
    }

	@Override
	public Object save(Object obj) {
		if (obj != null) {
			String tableName = SearchUtils.getTableName(obj.getClass());
			Document document = ObjectToDocumentUtils.toDocument(obj);
			if (document != null) {
				try {
					indexWriter = indexWriterManager.get(tableName);
					indexWriter.addDocument(document);
					indexSearcherManager.flushCache(tableName, indexWriter, null);
					return obj;
				} catch (IOException | InterruptedException e) {
					logger.error("", e);
				} finally {
					indexWriterManager.release(tableName);
				}
			} else {
				logger.info("对象转为Document时为null：" + obj);
			}
		}
		return null;
	}

	@Override
	public Object update(Object obj) {
		if (obj != null) {
			String tableName = SearchUtils.getTableName(obj.getClass());
			Document document = ObjectToDocumentUtils.toDocument(obj);
			if (document != null) {
				try {
					indexWriter = indexWriterManager.get(tableName);
					if (obj instanceof Kind) {
						indexWriter.updateDocument(
								new Term(SearchConstants.KIND_ID_FIELD, String.valueOf(((Kind) obj).getId())),
								document);
					} else if (obj instanceof Brand) {
						indexWriter.updateDocument(new Term(SearchConstants.BRAND_ID_FIELD,
								String.valueOf(((Brand) obj).getId())), document);
					} else if (obj instanceof Component) {
						indexWriter.updateDocument(new Term(SearchConstants.COMPONENT_ID_FIELD,
								String.valueOf(((Component) obj).getId())), document);
					} else if (obj instanceof Order) {
						indexWriter.updateDocument(new Term(SearchConstants.ORDER_ID_FIELD,
								String.valueOf(((Order) obj).getId())), document);
					} else if (obj instanceof OrderInvoice) {
						indexWriter.updateDocument(new Term(SearchConstants.ORDER_INVOICE_ID_FIELD,
								String.valueOf(((OrderInvoice) obj).getId())), document);
					} else if (obj instanceof Purchase) {
						indexWriter.updateDocument(new Term(SearchConstants.PURCHASE_ID_FIELD,
								String.valueOf(((Purchase) obj).getId())), document);
					} else if (obj instanceof PurchaseInvoice) {
						indexWriter.updateDocument(new Term(SearchConstants.PURCHASE_INVOICE_ID_FIELD,
								String.valueOf(((PurchaseInvoice) obj).getId())), document);
					} else {
						throw new SearchException("Message parsing failed!");
					}
					indexSearcherManager.flushCache(tableName, indexWriter, null);
					return obj;
				} catch (IOException | InterruptedException e) {
					logger.error("", e);
				} finally {
					indexWriterManager.release(tableName);
				}
			} else {
				logger.info("对象转为Document时为null：" + obj);
			}
		}
		return null;
	}

	@Override
	public Object delete(Object obj) {
		if (obj != null) {
			String tableName = SearchUtils.getTableName(obj.getClass());
			try {
				indexWriter = indexWriterManager.get(tableName);
				if (obj instanceof Kind) {
					indexWriter.deleteDocuments(
							new Term(SearchConstants.KIND_ID_FIELD, String.valueOf(((Kind) obj).getId())));
				} else if (obj instanceof Brand) {
					indexWriter.deleteDocuments(
							new Term(SearchConstants.BRAND_ID_FIELD, String.valueOf(((Brand) obj).getId())));
				} else if (obj instanceof Component) {
					indexWriter.deleteDocuments(new Term(SearchConstants.COMPONENT_ID_FIELD,
							String.valueOf(((Component) obj).getId())));
				} else if (obj instanceof Goods) {
					indexWriter.deleteDocuments(toTerm((Goods) obj));
				} else if (obj instanceof Order) {
					indexWriter.deleteDocuments(
							new Term(SearchConstants.ORDER_ID_FIELD, String.valueOf(((Order) obj).getId())));
				} else if (obj instanceof OrderInvoice) {
					indexWriter.deleteDocuments(new Term(SearchConstants.ORDER_INVOICE_ID_FIELD,
							String.valueOf(((OrderInvoice) obj).getId())));
				} else if (obj instanceof Purchase) {
					indexWriter.deleteDocuments(new Term(SearchConstants.PURCHASE_ID_FIELD,
							String.valueOf(((Purchase) obj).getId())));
				} else if (obj instanceof PurchaseInvoice) {
					indexWriter.deleteDocuments(new Term(SearchConstants.PURCHASE_INVOICE_ID_FIELD,
							String.valueOf(((PurchaseInvoice) obj).getId())));
				} else {
					throw new SearchException("Message parsing failed!");
				}
				indexSearcherManager.flushCache(tableName, indexWriter, null);
				return obj;
			} catch (IOException | InterruptedException e) {
				logger.error("", e);
			} finally {
				indexWriterManager.release(tableName);
			}
		}
		return null;
	}

	/**
	 * 根据goods构造Term
	 *
	 * @param goods
	 * @return
	 */
	private Term toTerm(Goods goods) {
        if (goods.getComponent() != null) {
            return new Term(SearchConstants.GOODS_CMP_ID_FIELD, String.valueOf(goods.getComponent().getId()));
        } else if (goods.getTradeGoods() != null) {
            return new Term(SearchConstants.GOODS_GO_ID_FIELD, String.valueOf(goods.getTradeGoods().getId()));
        }
		return null;
	}

	@Override
	public List<Object> maintainIndexes(@NotEmpty("parsedQueueMessage") ParsedQueueMessage parsedQueueMessage) {
		Object object = parsedQueueMessage.getObject();
		if (object == null) {
			return null;
		}
		List<Object> maintainedObjects = new ArrayList<>();
        // 新增、更新索引
        if (parsedQueueMessage.isInsert() || parsedQueueMessage.isUpdate()) {
            if (object instanceof Goods) {
                // 先删除相关批次和器件，再重新写入
                delete(object);
                List<Goods> goodsesList = goodsDao.find((Goods) object);
                for (Goods goods : goodsesList) {
                    Object maintainedObject = save(goods);
                    if (maintainedObject != null) {
                        maintainedObjects.add(maintainedObject);
                    }
                }
            } else {
                Object maintainedObject = update(object);
                if (maintainedObject != null) {
                    maintainedObjects.add(maintainedObject);
                }
            }
        }
        // 删除索引
        else if (parsedQueueMessage.isDelete()) {
            if (object instanceof Goods) {
                // 先删除相关批次和器件，再重新写入
                delete(object);
                Goods goodsObject = (Goods) object;
                if (goodsObject.getComponent() != null && goodsObject.getComponent().getId() != null) {
                    // 如果是器件，再重新写入
                    List<Goods> goodsesList = goodsDao.find((Goods) object);
                    for (Goods goods : goodsesList) {
                        Object maintainedObject = save(goods);
                        if (maintainedObject != null) {
                            maintainedObjects.add(maintainedObject);
                        }
                    }
                }
            } else {
                Object maintainedObject = delete(object);
                if (maintainedObject != null) {
                    maintainedObjects.add(maintainedObject);
                }
            }
        } else {
            throw new IllegalStateException("message parsing failed!");
        }
		return maintainedObjects;
	}

	@Override
	public List<Object> maintainIndexes(@NotEmpty("tableName") String tableName, @NotEmpty("dataId") Long dataId, @NotEmpty("methodType") String methodType, String data) {
		ParsedQueueMessage parsedQueueMessage = queueMessageParser.parse(tableName, dataId, methodType, data);
		return maintainIndexes(parsedQueueMessage);
	}

	@Override
	public Map<String, List<Object>> updateIndexByNewWords(List<String> newWords) {
		Map<String, List<Object>> updatedObjects = new HashMap<>();
		// 类目
		updatedObjects.put(SearchConstants.KIND_TABLE_NAME, updateIndexByNewWords(newWords,
				SearchConstants.KIND_TABLE_NAME, SearchConstants.KIND_ID_FIELD,
				SearchConstants.KIND_NAMECN_FIELD));
		// 品牌
		updatedObjects.put(SearchConstants.BRAND_TABLE_NAME, updateIndexByNewWords(newWords,
				SearchConstants.BRAND_TABLE_NAME, SearchConstants.BRAND_ID_FIELD,
				SearchConstants.BRAND_NAMECN_FIELD, SearchConstants.BRAND_NAMEEN_FIELD));
		// 器件
		updatedObjects.put(SearchConstants.COMPONENT_TABLE_NAME, updateIndexByNewWords(newWords,
				SearchConstants.COMPONENT_TABLE_NAME, SearchConstants.COMPONENT_ID_FIELD,
				SearchConstants.COMPONENT_CODE_FIELD, SearchConstants.COMPONENT_KI_NAME_FIELD,
				SearchConstants.COMPONENT_BR_NAMECN_FIELD, SearchConstants.COMPONENT_BR_NAMEEN_FIELD));
		// 批次
		updatedObjects.put(SearchConstants.GOODS_TABLE_NAME, updateIndexByNewWords(newWords,
				SearchConstants.GOODS_TABLE_NAME, SearchConstants.GOODS_CMP_ID_FIELD,
				SearchConstants.GOODS_CMP_CODE_FIELD, SearchConstants.GOODS_KI_NAME_CN_FIELD,
				SearchConstants.GOODS_BR_NAME_CN_UNTOKENIZED_FIELD, SearchConstants.GOODS_BR_NAME_EN_UNTOKENIZED_FIELD));
		return updatedObjects;
	}

    /**
     * 根据（录入的词典）新词更新指定表的索引
     *
     * @param newWords  词典新词
     * @param tableName 表名
     * @param idField   id 字段名称
     * @param fields    新词可能牵涉到的字段名称
     * @return 更新的对象
     */
    private List<Object> updateIndexByNewWords(List<String> newWords, String tableName, String idField, String... fields) {
        List<Object> updatedObjects = new ArrayList<>();
        if (!CollectionUtils.isEmpty(newWords)) {
            BooleanQuery booleanQuery = new BooleanQuery();
            for (String newWord : newWords) {
                for (String field : fields) {
                    booleanQuery.add(SearchUtils.getBooleanQuery(field, newWord, false, false, BooleanClause.Occur.MUST), BooleanClause.Occur.SHOULD);
                }
            }
            logger.info(booleanQuery.toString());
            SPage<Document> documents = SearchUtils.getDocuments(tableName, booleanQuery, null,
                    SearchConstants.TOP_NUM);
            for (Document document : documents.getContent()) {
                updatedObjects.addAll(maintainIndexes(tableName, Long.parseLong(document.get(idField)), "update", null));
            }
        }
        return updatedObjects;
    }

}
