public class InfluxDBRepositoryFactoryBean implements FactoryBean<InfluxDBRepository>, InitializingBean {

	public static final String SCHEME_FIELD_NAME = "scheme";
	public static final String HOST_FIELD_NAME = "host";
	public static final String PORT_FIELD_NAME = "port";
	public static final String USERNAME_FIELD_NAME = "username";
	public static final String DATABASE_FIELD_NAME = "databaseName";
	public static final String PASSWORD_FIELD_NAME = "password";
	public static final String ENABLE_GZIP_FIELD_NAME = "enableGzip";
	public static final String ENABLE_BATCH_FIELD_NAME = "enableBatch";
	public static final String BATCH_ACTIONS_FIELD_NAME = "batchActions";
	public static final String BATCH_FLUSH_DURATION_FIELD_NAME = "batchFlushDuration";
	public static final String CONNECT_TIMEOUT_SECONDS = "connectTimeoutSeconds";
	public static final String READ_TIMEOUT_SECONDS = "readTimeoutSeconds";
	public static final String WRITE_TIMEOUT_SECONDS = "writeTimeoutSeconds";
	public static final String INFLUX_QL_RESOURCES = "influxQLResources";


	public static final String MEASUREMENT_SCANNER = "measurementScanner";

	private String scheme = "http";
	private String host = "127.0.0.1";
	private Integer port = 8086;
	private String username;
	private String password;

	private boolean enableGzip = false;

	private boolean enableBatch;
	private Integer batchActions;
	private Integer batchFlushDuration;

	private Long connectTimeoutSeconds = 10_000L;
	private Long readTimeoutSeconds = 10_000L;
	private Long writeTimeoutSeconds = 10_000L;
	private String databaseName;
	private String[] influxQLResources;


	private InfluxDBRepository repository;
	private ClassPathMeasurementScanner measurementScanner;

	@SuppressWarnings("unchecked")
	@Override
	public void afterPropertiesSet() {

		String url = String.format("%s://%s:%d", scheme, host, port);
		Builder builder = new OkHttpClient.Builder() //
				.connectTimeout(connectTimeoutSeconds, TimeUnit.MILLISECONDS) //
				.readTimeout(readTimeoutSeconds, TimeUnit.MILLISECONDS) //
				.writeTimeout(writeTimeoutSeconds, TimeUnit.MILLISECONDS);

		InfluxDB influxDB = InfluxDBFactory.connect(url, username, password, builder);

		if (enableBatch) {
			batchActions = ObjectUtils.defaultIfNull(batchActions, 2000);
			batchFlushDuration = ObjectUtils.defaultIfNull(batchFlushDuration, 100);
			influxDB.enableBatch(batchActions, batchFlushDuration, TimeUnit.MILLISECONDS);
		}

		if (enableGzip) {
			influxDB.enableGzip();
		} else if (influxDB.isGzipEnabled()) {
			influxDB.disableGzip();
		}

		List<String> exitingDatabases = influxDB.describeDatabases();
		if (!exitingDatabases.contains(databaseName)) {
			throw new InfluxDBException(String.format("database not exit: %s, datasource: %s", databaseName, url));
		}

		Set<Class<?>> measurementClasses = measurementScanner.scan();

		Map<String, MeasurementClassMetadata> metadatas = new HashMap<>(measurementClasses.size());

		String defaultRetentionPolicy = null;

		String influxDBVersion = influxDB.version();

		if ("1.0".compareTo(influxDBVersion) >= 0) {
			defaultRetentionPolicy = "default";
		} else {
			defaultRetentionPolicy = "autogen";
		}

		for (Class<?> measurementClass : measurementClasses) {
			metadatas.put(measurementClass.getName(),
					new MeasurementClassMetadata((Class<? extends Serializable>) measurementClass, defaultRetentionPolicy));
		}

		InfluxQLMapper mapper = InfluxQLMapper.empty();

		if (influxQLResources != null) {
			for (String resourceLocation : influxQLResources) {
				InfluxQLMapper parsed = InfluxQLMapper.parseFrom(resourceLocation);
				mapper = mapper.union(parsed);
			}
		}

		repository = new InfluxDBRepository(influxDB, databaseName, metadatas, mapper);
	}

	public void setScheme(String scheme) {
		if (StringUtils.isNotBlank(scheme)) {
			this.scheme = scheme;
		}
	}

	public void setHost(String host) {
		if (StringUtils.isNotBlank(host)) {
			this.host = host;
		}
	}

	public void setPort(Integer port) {
		this.port = port;
	}

	public void setUsername(String username) {
		this.username = username;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	public void setEnableGzip(boolean enableGzip) {
		this.enableGzip = enableGzip;
	}

	public void setEnableBatch(boolean enableBatch) {
		this.enableBatch = enableBatch;
	}

	public void setBatchActions(Integer batchActions) {
		this.batchActions = batchActions;
	}

	public void setBatchFlushDuration(Integer batchFlushDuration) {
		this.batchFlushDuration = batchFlushDuration;
	}

	public void setConnectTimeoutSeconds(Long connectTimeoutSeconds) {
		this.connectTimeoutSeconds = connectTimeoutSeconds;
	}

	public void setReadTimeoutSeconds(Long readTimeoutSeconds) {
		this.readTimeoutSeconds = readTimeoutSeconds;
	}

	public void setWriteTimeoutSeconds(Long writeTimeoutSeconds) {
		this.writeTimeoutSeconds = writeTimeoutSeconds;
	}

	public void setDatabaseName(String databaseName) {
		this.databaseName = databaseName;
	}

	public void setMeasurementScanner(ClassPathMeasurementScanner measurementScanner) {
		this.measurementScanner = measurementScanner;
	}

	public void setInfluxQLResources(String[] influxQLResources) {
		this.influxQLResources = influxQLResources;
	}

	public void setInfluxQLResource(String influxQLResource) {
		this.setInfluxQLResources(new String[] { influxQLResource });
	}

	@Override
	public InfluxDBRepository getObject() throws Exception {
		return repository;
	}

	@Override
	public Class<?> getObjectType() {
		return InfluxDBRepository.class;
	}

	@Override
	public boolean isSingleton() {
		return true;
	}
} public class ParameterSignature {
	private final int index;
	private final String name;
	private final Class<?> type;

	private ParameterSignature(int index, String name, Class<?> type) {
		super();
		this.index = index;
		this.name = name;
		this.type = type;
	}

	public static ParameterSignature signature(int index, Parameter parameter, String aliasName) {
		Class<?> type = parameter.getType();
		return new ParameterSignature(index, aliasName, type);
	}

	public static ParameterSignature signature(int index, Parameter parameter) {
		String name = parameter.getName();
		Class<?> type = parameter.getType();
		return new ParameterSignature(index, name, type);
	}

	public static List<ParameterSignature> signatures(Method method) {
		Parameter[] parameters = method.getParameters();
		List<ParameterSignature> signatures = new ArrayList<>(parameters.length);

		for (int i = 0; i < parameters.length; i++) {
			Parameter parameter = parameters[i];
			signatures.add(signature(i, parameter));
		}

		return signatures;
	}

	public int getIndex() {
		return index;
	}

	public String getName() {
		return name;
	}

	public Class<?> getType() {
		return type;
	}

	public ParameterValue value(Object value) {
		return new ParameterValue(value, this);
	}
} public class ParameterValue {
	private final Object value;
	private final ParameterSignature signature;

	public ParameterValue(Object value, ParameterSignature signature) {
		super();
		this.value = value;
		this.signature = signature;
	}

	public Object getValue() {
		return value;
	}

	public ParameterSignature getSignature() {
		return signature;
	}

} public class InfluxDBRepository {

	private static final Logger logger = Logger.getLogger(InfluxDBRepository.class.getCanonicalName());

	public static final TimeUnit DEFAULT_TIME_UNIT = TimeUnit.MILLISECONDS;

	private final InfluxDB influxDB;
	private final String databaseName;

	/**
	 * map<classname, metadata>
	 */
	private final Map<String, MeasurementClassMetadata> classmetadata;
	/**
	 * map<key{measurementName, retentionPolicy}, metadata>
	 */
	private final Map<MeasurementKey, MeasurementClassMetadata> classmetadataMapByKey;

	private final TypeConverterSupport typeConverter;

	private final InfluxQLMapper qlMapper;

	public InfluxDBRepository(final InfluxDB influxDB, final String databaseName, final Map<String, MeasurementClassMetadata> classmetadata,
			InfluxQLMapper qlMapper) {
		super();
		this.influxDB = influxDB;
		this.databaseName = databaseName;
		this.classmetadata = classmetadata;
		this.qlMapper = qlMapper;
		this.classmetadataMapByKey = new HashMap<>();
		this.typeConverter = new SimpleTypeConverter();

		this.typeConverter.registerCustomEditor(Date.class, new DatePropertyEditor());

		transform(classmetadata, classmetadataMapByKey);

		scanAndRegisterEnumTypeEditors(this.typeConverter, classmetadata);
	}

	private void scanAndRegisterEnumTypeEditors(TypeConverterSupport typeConverter, Map<String, MeasurementClassMetadata> classmetadatas) {
		for(MeasurementClassMetadata classMetadata: classmetadatas.values()) {
			checkAndRegisterEnumTypeEditors(typeConverter, classMetadata.getTagColumnDescriptors().values());
			checkAndRegisterEnumTypeEditors(typeConverter, classMetadata.getFieldColumnDescriptors().values());
		}
	}

	private void checkAndRegisterEnumTypeEditors(TypeConverterSupport typeConverter, Collection<PropertyDescriptor> values) {
		for(PropertyDescriptor dspr: values) {
			Class propertyType = dspr.getReadMethod().getReturnType();
			if(!EnumType.class.isAssignableFrom(propertyType)) {
				continue;
			}
			if(!typeConverter.hasCustomEditorForElement(propertyType, null)) {
				typeConverter.registerCustomEditor(propertyType, new EnumsValuePropertyEditor(propertyType));
			}
		}
	}


	public void execute(String command, final Map<String, ParameterValue> parameters) {
		command = CommandUtils.parseCommand(command, parameters);
		if (logger.isLoggable(Level.INFO)) {
			logger.info("execute → " + command);
		}
		final Query query = new Query(command, databaseName);
		influxDB.query(query, DEFAULT_TIME_UNIT);
	}

	public ResultContext query(String command, final Map<String, ParameterValue> parameters) {
		command = CommandUtils.parseCommand(command, parameters);
		if (logger.isLoggable(Level.INFO)) {
			logger.info("query → " + command);
		}
		final Query query = new Query(command, databaseName);
		final QueryResult queryResult = influxDB.query(query, DEFAULT_TIME_UNIT);
		return new NativeResultContext(queryResult);
	}

	public void insert(final Object entity) {
		if (entity == null) {
			return;
		}
		final String entityClassName = entity.getClass().getName();
		final MeasurementClassMetadata classMetadata = getClassMetadata(entityClassName);
		if (classMetadata == null) {
			throw new InfluxDBException("Class not mapped: " + entityClassName);
		}
		final Point point = buildPoint(entity, classMetadata);
		influxDB.write(databaseName, classMetadata.getRetentionPolicy(), point);
	}

	public void batchInsert(final Collection<Object> entities) {
		if (CollectionUtils.isEmpty(entities)) {
			return;
		}
		final Map<MeasurementKey, Collection<Object>> entitiesGrouped = new HashMap<>();
		for (final Object entity : entities) {
			if (entity == null) {
				return;
			}
			final String entityClassName = entity.getClass().getName();
			final MeasurementClassMetadata classMetadata = getClassMetadata(entityClassName);
			if (classMetadata == null) {
				throw new InfluxDBException("Class not mapped: " + entityClassName);
			}
			final MeasurementKey key = new MeasurementKey(classMetadata.getMeasurementName(), classMetadata.getRetentionPolicy());

			Collection<Object> gEntities = entitiesGrouped.get(key);
			if (gEntities == null) {
				gEntities = new LinkedList<>();
				entitiesGrouped.put(key, gEntities);
			}
			gEntities.add(entity);
		}
		for (final Map.Entry<MeasurementKey, Collection<Object>> entry : entitiesGrouped.entrySet()) {
			batchInsert(entry.getKey(), entry.getValue());
		}
	}

	private static void transform(final Map<String, MeasurementClassMetadata> classmetadata,
			final Map<MeasurementKey, MeasurementClassMetadata> classmetadataMapByKey) {
		for (final MeasurementClassMetadata metadata : classmetadata.values()) {
			classmetadataMapByKey.put(new MeasurementKey(metadata.getMeasurementName(), metadata.getRetentionPolicy()), metadata);
		}
	}

	private void batchInsert(final MeasurementKey key, final Collection<Object> entities) {
		if (CollectionUtils.isEmpty(entities)) {
			return;
		}
		final MeasurementClassMetadata classMetadata = getClassMetadata(key);

		final BatchPoints.Builder batchPointsBuilder = BatchPoints //
				.database(databaseName) //
				.retentionPolicy(key.retentionPolicy);
		for (final Object entity : entities) {
			if (entity == null) {
				return;
			}
			final Point point = buildPoint(entity, classMetadata);
			batchPointsBuilder.point(point);
		}
		influxDB.write(batchPointsBuilder.build());
	}

	private Point buildPoint(final Object entity, final MeasurementClassMetadata classMetadata) {
		final String measurementName = getShardingMeasurementName(classMetadata, entity);
		final Point.Builder pointBuilder = Point.measurement(measurementName);

		final Date time = readTimeField(classMetadata, entity);
		pointBuilder.time(DEFAULT_TIME_UNIT.toNanos(time.getTime()), TimeUnit.NANOSECONDS);

		resolveMeasurementTags(entity, classMetadata, pointBuilder);
		resolveMeasurementFields(entity, classMetadata, pointBuilder);
		final Point point = pointBuilder.build();
		return point;
	}

	private Date readTimeField(final MeasurementClassMetadata classMetadata, final Object entity) {
		final Method readMethod = classMetadata.getTimeFieldDescriptor().getReadMethod();
		try {
			final Object result = readMethod.invoke(entity);

			if (classMetadata.isJavaUtilDateTimeField()) {
				return (Date) result;
			} else if (classMetadata.isNumberTimeField()) {
				return new Date(((Number) result).longValue());
			} else if (classMetadata.isStringTimeField()) {
				return DateUtils.parseDate(result.toString(), classMetadata.getDatetimePatterns());
			}
			return null;
		} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException | ParseException e) {
			throw new InfluxDBException("An error occurred while read time field value", e);
		}
	}

	private void resolveMeasurementFields(final Object entity, final MeasurementClassMetadata classMetadata, final Builder pointBuilder) {
		final Map<String, PropertyDescriptor> fieldColumnDescriptors = classMetadata.getFieldColumnDescriptors();
		Map<String, Object> fields = new HashMap<>(fieldColumnDescriptors.size());

		for (final Map.Entry<String, PropertyDescriptor> entry : fieldColumnDescriptors.entrySet()) {
			final String fieldName = entry.getKey();
			final PropertyDescriptor valueDescriptor = entry.getValue();
			final Method readMethod = valueDescriptor.getReadMethod();
			final Object value = valueOfField(readMethod, entity);

			Class<?> fieldJavaType = readMethod.getReturnType();

			Object convertedValue = convertFieldValue(value, fieldJavaType);

			fields.put(fieldName, convertedValue);
		}
		pointBuilder.fields(fields);
	}

	private Object convertFieldValue(Object value, Class<?> fieldJavaType) {
		if (value == null) {
			return null;
		}
		Object convertedValue = null;
		if (fieldJavaType.isPrimitive()) {
			convertedValue = value;
		} else if (Number.class.isAssignableFrom(fieldJavaType)) {
			convertedValue = ((Number) value).doubleValue();
		} else {
			convertedValue = this.typeConverter.convertIfNecessary(value, String.class);
		}
		if (convertedValue == null && value != null) {
			throw new InfluxDBException("cannot convert type \"" + fieldJavaType + "\" to String");
		}
		return convertedValue;
	}

	private void resolveMeasurementTags(final Object entity, final MeasurementClassMetadata classMetadata, final Builder pointBuilder) {
		final Map<String, PropertyDescriptor> tagColumnDescriptors = classMetadata.getTagColumnDescriptors();
		for (final Map.Entry<String, PropertyDescriptor> entry : tagColumnDescriptors.entrySet()) {
			final String tagName = entry.getKey();
			final PropertyDescriptor valueDescriptor = entry.getValue();
			final Method readMethod = valueDescriptor.getReadMethod();
			final Object value = valueOfTag(readMethod, entity);
			final String converted = this.typeConverter.convertIfNecessary(value, String.class);
			if (converted == null && value != null) {
				throw new InfluxDBException("cannot convert type \"" + readMethod.getReturnType() + "\" to String");
			}
			pointBuilder.tag(tagName, converted);
		}
	}

	private Object valueOfField(final Method readMethod, final Object entity) {
		try {
			return readMethod.invoke(entity);
		} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
			throw new InfluxDBException("An error occorred while reading field value", e);
		}
	}

	private Object valueOfTag(final Method readMethod, final Object entity) {
		try {
			return readMethod.invoke(entity);
		} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
			throw new InfluxDBException("An error occorred while reading tag field value", e);
		}
	}

	public String getShardingMeasurementName(final MeasurementClassMetadata metadata, final Object entity) {
		final PropertyDescriptor shardingFieldDescriptor = metadata.getShardingFieldDescriptor();
		if (shardingFieldDescriptor == null) {
			return metadata.getMeasurementName();
		}
		final Method readMethod = shardingFieldDescriptor.getReadMethod();
		try {
			final Object value = readMethod.invoke(entity);
			return new StringBuilder() //
					.append(metadata.getMeasurementName()) //
					.append('_') //
					.append(value) //
					.toString();
		} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
			throw new InfluxDBException("An error occurred while reading shard field value", e);
		}
	}

	public InfluxDB getInfluxDB() {
		return influxDB;
	}

	public InfluxQLStatement getStatement(String name) {
		return qlMapper.getStatement(name);
	}

	public MeasurementClassMetadata getClassMetadata(final String measurementClassName) {
		return classmetadata.get(measurementClassName);
	}

	public MeasurementClassMetadata getClassMetadata(final String measurementName, final String retentionPolicy) {
		return classmetadataMapByKey.get(new MeasurementKey(measurementName, retentionPolicy));
	}

	private MeasurementClassMetadata getClassMetadata(final MeasurementKey key) {
		return classmetadataMapByKey.get(key);
	}

	public MeasurementClassMetadata getClassMetadataByMeasurementName(final String measurementName) {
		return classmetadataMapByKey.get(new MeasurementKey(measurementName));
	}

	public MeasurementClassMetadata getClassMetadata(final Class<?> measurementClass) {
		return getClassMetadata(measurementClass.getName());
	}

	public Map<String, MeasurementClassMetadata> getAllClassMetadata() {
		return classmetadata;
	}

	public <T> T convert(Map<String, Object> map, Class<T> targetType) {
		MeasurementClassMetadata classMetadata = getClassMetadata(targetType);
		if (classMetadata != null) {
			return BeanConvertUtils.convert(map, classMetadata, this.typeConverter);
		}
		return BeanConvertUtils.convert(map, targetType, this.typeConverter);
	}

	public <T> T convert(Object source, Class<T> targetType) {
		return this.typeConverter.convertIfNecessary(source, targetType);
	}

	private static final class MeasurementKey {
		private final String measurementName;
		private final String retentionPolicy;
		private final int _hash;

		public MeasurementKey(final String measurementName, final String retentionPolicy) {
			super();
			this.measurementName = measurementName;
			this.retentionPolicy = retentionPolicy;
			this._hash = hash();
		}

		public MeasurementKey(final String measurementName) {
			this(measurementName, "autogen");
		}

		private int hash() {
			final int prime = 31;
			int result = 1;
			result = prime * result + ((measurementName == null) ? 0 : measurementName.hashCode());
			result = prime * result + ((retentionPolicy == null) ? 0 : retentionPolicy.hashCode());
			return result;
		}

		@Override
		public int hashCode() {
			return _hash;
		}

		@Override
		public boolean equals(final Object obj) {
			if (this == obj)
				return true;
			if (obj == null)
				return false;
			if (getClass() != obj.getClass())
				return false;
			final MeasurementKey other = (MeasurementKey) obj;
			if (measurementName == null) {
				if (other.measurementName != null)
					return false;
			} else if (!measurementName.equals(other.measurementName))
				return false;
			if (retentionPolicy == null) {
				if (other.retentionPolicy != null)
					return false;
			} else if (!retentionPolicy.equals(other.retentionPolicy))
				return false;
			return true;
		}

	}
} public class NativeResultContext implements ResultContext {

	private static final Map<String, List<Map<String, Object>>> EMPTY_SERIES_MAP = Collections.unmodifiableMap(new HashMap<>());
	private static final Set<String> EMPTY_SET = Collections.emptySet();

	private String errorMessage;
	private boolean hasResult;
	private Set<String> measurementNames;
	private Map<String, List<Map<String, Object>>> seriesMap;

	public NativeResultContext(QueryResult queryResult) {
		this.errorMessage = queryResult.getError();
		List<Result> results = queryResult.getResults();
		if (isNotEmpty(results)) {
			if (results.size() > 1) {
				throw new InfluxDBException("Multiple results is not suppported!");
			}

			Result result = results.get(0);
			if (StringUtils.isNotBlank(result.getError())) {
				errorMessage = result.getError();
				hasResult = false;
			} else if (isNotEmpty(result.getSeries())) {
				hasResult = true;
				seriesMap = transformResult(result);
			}
		}
		if (!hasResult) {
			measurementNames = EMPTY_SET;
			seriesMap = EMPTY_SERIES_MAP;
		}
	}

	public boolean hasResult() {
		return hasResult;
	}

	public boolean hasError() {
		return StringUtils.isNotBlank(errorMessage);
	}

	public String getErrorMessage() {
		return errorMessage;
	}

	public Object getValue() {
		return seriesMap;
	}

	public Set<String> getMeasurementNames() {
		return measurementNames;
	}

	private Map<String, List<Map<String, Object>>> transformResult(Result result) {
		List<Series> seriesList = result.getSeries();
		if (isNotEmpty(seriesList)) {
			Set<String> measurementNames = new HashSet<>();
			Map<String, List<Map<String, Object>>> seriesMap = new HashMap<>();
			for (Series series : seriesList) {
				measurementNames.add(series.getName());
				List<Map<String, Object>> dataList = seriesMap.get(series.getName());
				if (dataList == null) {
					dataList = new LinkedList<>();
					seriesMap.put(series.getName(), dataList);
				}
				transformTo(series, dataList);
			}
			return seriesMap;
		}
		return EMPTY_SERIES_MAP;
	}

	private void transformTo(Series series, List<Map<String, Object>> dataList) {
		List<String> columns = series.getColumns();
		List<List<Object>> valuesList = series.getValues();
		if (isNotEmpty(columns) && isNotEmpty(valuesList)) {
			int columnCount = columns.size();
			for (List<Object> values : valuesList) {
				Map<String, Object> data = new HashMap<String, Object>();
				for (int i = 0; i < columnCount; i++) {
					String columnName = columns.get(i);
					Object value = values.get(i);
					data.put(columnName, value);
				}
				dataList.add(data);
			}
		}
	}

	private static final boolean isNotEmpty(Collection<?> coll) {
		return !isEmpty(coll);
	}

	private static final boolean isEmpty(Collection<?> col) {
		return col == null || col.isEmpty();
	}
} public class MockDatas {
    public static QueryResult.Series createSeries(String measurementName, List<String> columns, List<List<Object>> values) {
        QueryResult.Series series = new QueryResult.Series();
        series.setColumns(columns);
        series.setName(measurementName);
        series.setValues(values);
        return series;
    }
    public static QueryResult createSingleMeasurementQueryResult(String measurementName, List<String> columns, List<List<Object>> values) {
        QueryResult.Result queryResultItem = new QueryResult.Result();
        queryResultItem.setSeries(
                Arrays.asList(createSeries(measurementName, columns, values))
        );
        QueryResult result = new QueryResult();
        result.setResults(Arrays.asList(
                queryResultItem
        ));
        return result;
    }
} public class MockParameterMap extends HashMap<String, ParameterValue> {
    public <T> ParameterValue putParameter(int index, String name, Class<T> type, T value) {
        ParameterSignature signature = createMockParameterSignature(index, name, type);
        ParameterValue parameterValue = new ParameterValue(value, signature);
        return this.put(name, parameterValue);
    }
    private static ParameterSignature createMockParameterSignature(int index, String name, Class<?> type) {
        ParameterSignature signature = mock(ParameterSignature.class);
        when(signature.getIndex()).thenReturn(index);
        when(signature.getName()).thenReturn(name);
        when((Object)signature.getType()).thenReturn(type);
        return signature;
    }
} public class MockitoMatchers {
    private static ParameterSignature createMockParameterSignature(int index, String name, Class<?> type) {
        ParameterSignature signature = mock(ParameterSignature.class);
        when(signature.getIndex()).thenReturn(index);
        when(signature.getName()).thenReturn(name);
        when((Object)signature.getType()).thenReturn(type);
        return signature;
    }

    public static Map<String, ParameterValue> parameters(Map<String, ParameterValue> value) {
        return customize(
                new ParametersArgumentMatcher(value),
                value
        );
    }
    public static <T> T customize(ArgumentMatcher<T> matcher, T value) {
        ThreadSafeMockingProgress.mockingProgress().getArgumentMatcherStorage().reportMatcher(matcher);
        return value == null ? null : (T)Primitives.defaultValue(value.getClass());
    }
    private static class ParametersArgumentMatcher implements ArgumentMatcher<Map<String, ParameterValue>> {
        private Map<String, ParameterValue> excepted;

        public ParametersArgumentMatcher(Map<String, ParameterValue> excepted) {
            this.excepted = excepted;
        }

        @Override
        public boolean matches(Map<String, ParameterValue> other) {
            if (this.excepted.size() != other.size()) {
                return false;
            }
            for (Map.Entry<String, ParameterValue> entry : this.excepted.entrySet()) {
                ParameterValue otherValue = other.get(entry.getKey());
                if (!compare(entry.getValue(), otherValue)) {
                    return false;
                }
            }
            return true;
        }

        private boolean compare(ParameterValue a, ParameterValue b) {
            if (a == b) {
                return true;
            }
            if (a == null || b == null) {
                return false;
            }
            if (Objects.equals(a.getValue(), b.getValue())) {
                return true;
            }
            ParameterSignature signatureA = a.getSignature();
            ParameterSignature signatureB = b.getSignature();
            if (signatureA == signatureB) {
                return true;
            }
            if (signatureA == null || signatureB == null) {
                return false;
            }
            if (signatureA.getIndex() != signatureB.getIndex()) {
                return false;
            }
            if (!StringUtils.equals(signatureA.getName(), signatureB.getName())) {
                return false;
            }
            if (signatureA.getType() != signatureB.getType()) {
                return false;
            }
            return true;
        }
    }
} public class MockInfluxDBRepositoryFactoryBean extends InfluxDBRepositoryFactoryBean {

    public interface RepositoryGenerator {
        default void setup(){}
        public InfluxDBRepository generate(InfluxDBRepositoryFactoryBean factoryBean);
    }

    private static RepositoryGenerator generator;
    private InfluxDBRepository repository;
    @Override
    public void afterPropertiesSet() {
        if (generator == null) {
            throw new RuntimeException("Generator has not been assigned");
        } else {
            repository = generator.generate(this);
        }
    }

    @Override
    public InfluxDBRepository getObject() throws Exception {
        return repository;
    }
    public static void setGenerator(RepositoryGenerator generator) {
        MockInfluxDBRepositoryFactoryBean.generator = generator;
    }
} public interface BandwithMonitorDao extends InfluxDBDao<BandwithMonitorEntity> {
    String SELECT_BANDWIDTH_BY_IP_ADDRESS = "select from bandwidth_monitor where ipAddress=#{ipAddress}";
    @InfluxDBSelect(SELECT_BANDWIDTH_BY_IP_ADDRESS)
    public BandwithMonitorEntity selectBandwidthByIpAddress(@InfluxDBParam("ipAddress") String ipAddress);
} @Measurement(name = "bandwidth_monitor")
public class BandwithMonitorEntity implements Serializable {

    @TagColumn("ip")
    private String ipAddress;
    @FieldColumn("bandwidth")
    private Integer bandwidth;

    public String getIpAddress() {
        return ipAddress;
    }

    public void setIpAddress(String ipAddress) {
        this.ipAddress = ipAddress;
    }

    public Integer getBandwidth() {
        return bandwidth;
    }

    public void setBandwidth(Integer bandwidth) {
        this.bandwidth = bandwidth;
    }
}
import com.vgerbot.orm.influxdb.factory.InfluxDBRepositoryFactoryBean;
import com.vgerbot.orm.influxdb.param.ParameterSignature;
import com.vgerbot.orm.influxdb.param.ParameterValue;
import com.vgerbot.orm.influxdb.repo.InfluxDBRepository;
import com.vgerbot.orm.influxdb.result.NativeResultContext;
import com.vgerbot.test.orm.influxdb.common.MockDatas;
import com.vgerbot.test.orm.influxdb.common.MockParameterMap;
import com.vgerbot.test.orm.influxdb.common.MockitoMatchers;
import com.vgerbot.test.orm.influxdb.mockrepository.MockInfluxDBRepositoryFactoryBean;
import com.vgerbot.test.orm.influxdb.mockrepository.query_annotation.dao.BandwithMonitorDao;
import com.vgerbot.test.orm.influxdb.mockrepository.query_annotation.entity.BandwithMonitorEntity;
import static org.hamcrest.CoreMatchers.*;
import org.influxdb.dto.QueryResult;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
import org.mockito.ArgumentMatcher;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import static org.mockito.Mockito.*;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import javax.annotation.Resource;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
package com.vgerbot.test.orm.influxdb.mockrepository.query_annotation;





@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:test-query-application-context.xml")
public class InfluxDBSelectAnnotationTest {
    private static final class MockRepositoryGenerator implements MockInfluxDBRepositoryFactoryBean.RepositoryGenerator {

        @Mock
        private InfluxDBRepository repository;

        @Override
        public void setup() {
            MockitoAnnotations.initMocks(this);
        }

        @Override
        public InfluxDBRepository generate(InfluxDBRepositoryFactoryBean factoryBean) {
            return repository;
        }
    }
    private static final MockRepositoryGenerator generator = new MockRepositoryGenerator();
    @BeforeClass
    public static void prepareFactoryBean() {
        generator.setup();
        MockInfluxDBRepositoryFactoryBean.setGenerator(generator);
    }
    @Resource
    private BandwithMonitorDao dao;

    @Before
    public void setup() {
        Map<String, ParameterValue> parameters = new MockParameterMap(){{