org.redkalex.source.search.OpenSearchSource Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of redkale-plugins Show documentation
Show all versions of redkale-plugins Show documentation
Redkale-Plugins -- java framework
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package org.redkalex.source.search;
import java.io.Serializable;
import java.lang.reflect.Type;
import java.net.*;
import java.net.http.*;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.*;
import java.util.concurrent.*;
import java.util.function.IntFunction;
import java.util.logging.*;
import java.util.stream.*;
import org.redkale.annotation.AutoLoad;
import org.redkale.annotation.ResourceChanged;
import org.redkale.annotation.ResourceType;
import org.redkale.convert.json.*;
import org.redkale.inject.Resourcable;
import org.redkale.inject.ResourceEvent;
import org.redkale.persistence.Entity;
import org.redkale.service.*;
import org.redkale.source.*;
import static org.redkale.source.DataSources.*;
import org.redkale.util.*;
/**
* ElasticSearch实现
*
* @author zhangjx
* @since 2.4.0
*/
@Local
@AutoLoad(false)
@SuppressWarnings("unchecked")
@ResourceType(SearchSource.class)
public final class OpenSearchSource extends AbstractService implements SearchSource, AutoCloseable, Resourcable {
protected final Logger logger = Logger.getLogger(this.getClass().getSimpleName());
protected final IntFunction serialArrayFunc = Utility.serialArrayFunc();
// key: table, 不能是class,因为存在分表的情况
protected final ConcurrentHashMap checkedIndexClasses = new ConcurrentHashMap();
protected Properties confProps;
protected String name;
protected URI[] uris;
protected HttpClient httpClient;
@Override
public void init(AnyValue config) {
super.init(config);
Properties props = new Properties();
config.forEach((k, v) -> props.put(k, decryptProperty(k, v)));
initFromProperties(props);
}
protected void initFromProperties(Properties props) {
ProxySelector proxySelector = null;
Authenticator authenticator = null;
List us = new ArrayList<>();
String url = props.getProperty(DATA_SOURCE_URL);
if (url.startsWith("search://")) {
url = url.replace("search://", "http://");
} else if (url.startsWith("searchs://")) {
url = url.replace("searchs://", "https://");
}
for (String str : url.split(";")) {
if (str.trim().isEmpty()) {
continue;
}
us.add(URI.create(str.trim()));
}
String proxyAddr = props.getProperty(DATA_SOURCE_PROXY_ADDRESS);
if (proxyAddr != null
&& !proxyAddr.isEmpty()
&& proxyAddr.contains(":")
&& "true".equalsIgnoreCase(props.getProperty(DATA_SOURCE_PROXY_ENABLE, "true"))) {
String proxyType = props.getProperty(DATA_SOURCE_PROXY_TYPE, "HTTP").toUpperCase();
int pos = proxyAddr.indexOf(':');
SocketAddress addr =
new InetSocketAddress(proxyAddr.substring(0, pos), Integer.parseInt(proxyAddr.substring(pos + 1)));
proxySelector = SimpleProxySelector.create(new Proxy(Proxy.Type.valueOf(proxyType), addr));
}
String proxyUser = props.getProperty(DATA_SOURCE_PROXY_USER);
if (proxyUser != null && !proxyUser.isEmpty()) {
char[] proxyPassword =
props.getProperty(DATA_SOURCE_PROXY_PASSWORD, "").toCharArray();
authenticator = new Authenticator() {
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(proxyUser, proxyPassword);
}
};
}
HttpClient.Builder builder = HttpClient.newBuilder();
if (proxySelector != null) {
builder = builder.proxy(proxySelector);
}
if (authenticator != null) {
builder = builder.authenticator(authenticator);
}
HttpClient c = builder.build();
this.uris = us.toArray(new URI[us.size()]);
this.httpClient = c;
this.confProps = props;
}
@ResourceChanged
public void onResourceChange(ResourceEvent[] events) {
if (Utility.isEmpty(events)) {
return;
}
StringBuilder sb = new StringBuilder();
Properties newProps = new Properties();
newProps.putAll(this.confProps);
for (ResourceEvent event : events) { // 可能需要解密
String newValue = decryptProperty(event.name(), event.newValue().toString());
newProps.put(event.name(), newValue);
sb.append("DataSource(name=")
.append(resourceName())
.append(") change '")
.append(event.name())
.append("' to '")
.append(event.coverNewValue())
.append("'\r\n");
}
initFromProperties(newProps);
if (sb.length() > 0) {
logger.log(Level.INFO, sb.toString());
}
}
// 解密可能存在的加密字段, 可重载
protected String decryptProperty(String key, String value) {
return value;
}
@Override
public void destroy(AnyValue config) {
super.destroy(config);
}
@Override
public void close() throws Exception {}
@Override
public String toString() {
if (confProps == null) {
return getClass().getSimpleName() + "{}"; // compileMode模式下会为null
}
return getClass().getSimpleName() + "{url = " + confProps.getProperty(DATA_SOURCE_URL) + "}";
}
@Override
@Local
public void compile(Class clazz) {
SearchInfo.compile(clazz, this);
JsonFactory.root().findDecoder(TypeToken.createParameterizedType(null, FindResult.class, clazz));
JsonFactory.root().findDecoder(TypeToken.createParameterizedType(null, SearchResult.class, clazz));
}
@Override
public String getType() {
return "search";
}
@Override
public String resourceName() {
return name;
}
// 检查对象是否都是同一个Entity类
protected void checkEntity(String action, T... entitys) {
Class clazz = null;
for (T val : entitys) {
if (clazz == null) {
clazz = val.getClass();
if (clazz.getAnnotation(Entity.class) == null) {
throw new SourceException("Entity Class " + clazz + " must be on Annotation @Entity");
}
} else if (clazz != val.getClass()) {
throw new SourceException("DataSource." + action + " must the same Class Entity, but diff is " + clazz
+ " and " + val.getClass());
}
}
}
protected CompletableFuture> deleteAsync(CharSequence path) {
HttpRequest.Builder builder = HttpRequest.newBuilder(URI.create(uris[0].toString() + path))
.timeout(Duration.ofMillis(10_000))
.header("Content-Type", "application/json");
return httpClient
.sendAsync(builder.DELETE().build(), HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8))
.thenApply(resp -> {
if (logger.isLoggable(Level.FINEST)) {
logger.log(Level.FINEST, path + " delete --> " + resp.body());
}
return new RetResult(resp.body()).retcode(resp.statusCode());
});
}
protected CompletableFuture> getAsync(CharSequence path) {
HttpRequest.Builder builder = HttpRequest.newBuilder(URI.create(uris[0].toString() + path))
.timeout(Duration.ofMillis(10_000))
.header("Content-Type", "application/json");
return httpClient
.sendAsync(builder.GET().build(), HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8))
.thenApply(resp -> {
if (logger.isLoggable(Level.FINEST)) {
logger.log(Level.FINEST, path + " get --> " + resp.body());
}
return new RetResult(resp.body()).retcode(resp.statusCode());
});
}
protected CompletableFuture> postAsync(
CharSequence path, SearchInfo info, SearchRequest body) {
return postAsync(path, convertSearchRequest(info, body));
}
protected CompletableFuture> postAsync(
CharSequence path, SearchInfo info, UpdatePart body) {
return postAsync(path, info.getConvert().convertToBytes(body));
}
protected CompletableFuture> postEntityAsync(
CharSequence path, SearchInfo info, T entity) {
return postAsync(path, info.getConvert().convertToBytes(entity));
}
protected CompletableFuture> postAsync(CharSequence path, byte[] body) {
HttpRequest.Builder builder = HttpRequest.newBuilder(URI.create(uris[0].toString() + path))
.timeout(Duration.ofMillis(10_000))
.header("Content-Type", "application/json");
HttpRequest.BodyPublisher publisher =
body == null ? HttpRequest.BodyPublishers.noBody() : HttpRequest.BodyPublishers.ofByteArray(body);
return httpClient
.sendAsync(builder.POST(publisher).build(), HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8))
.thenApply(resp -> {
// SearchSource可能作为logging输出源,故此insert不能打印日志
// if (finest) logger.log(Level.FINEST, path + " post: " + (body == null ? null : new String(body,
// StandardCharsets.UTF_8)) + " --> " + resp.body());
return new RetResult(resp.body()).retcode(resp.statusCode());
});
}
protected CompletableFuture> putAsync(
CharSequence path, SearchInfo info, Map map) {
HttpRequest.Builder builder = HttpRequest.newBuilder(URI.create(uris[0].toString() + path))
.timeout(Duration.ofMillis(10_000))
.header("Content-Type", "application/json");
return httpClient
.sendAsync(
builder.PUT(HttpRequest.BodyPublishers.ofByteArray(
info.getConvert().convertToBytes(map)))
.build(),
HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8))
.thenApply(resp -> {
// SearchSource可能作为logging输出源,故此insert不能打印日志
// if (finest) logger.log(Level.FINEST, path + " put: " + info.getConvert().convertTo(map) + " --> "
// + resp.body());
return new RetResult(resp.body()).retcode(resp.statusCode());
});
}
protected CompletableFuture> bulkAsync(CharSequence path, CharSequence body) {
HttpRequest.Builder builder = HttpRequest.newBuilder(URI.create(uris[0].toString() + path))
.timeout(Duration.ofMillis(10_000))
.header("Content-Type", "application/x-ndjson");
return httpClient
.sendAsync(
builder.POST(HttpRequest.BodyPublishers.ofString(body.toString()))
.build(),
HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8))
.thenApply(resp -> {
// SearchSource可能作为logging输出源,故此insert不能打印日志
// if (finest) logger.log(Level.FINEST, path + " bulk: " + body + " --> " + resp.body());
return new RetResult(resp.body()).retcode(resp.statusCode());
});
}
protected CharSequence getQueryTable(SearchInfo info, FilterNode node) {
if (node == null) {
return info.getTable(node);
}
SearchQuery bean = (SearchQuery) node.findValue(SearchQuery.SEARCH_FILTER_NAME);
if (bean == null || bean.searchClasses() == null) {
return info.getTable(node);
}
StringBuilder sb = new StringBuilder();
for (Class clazz : bean.searchClasses()) {
if (clazz == null) {
continue;
}
if (sb.length() > 0) {
sb.append(',');
}
sb.append(SearchInfo.load(clazz).getTable(node));
}
return sb.length() > 0 ? sb : info.getTable(node);
}
protected SearchRequest createSearchRequest(
SearchInfo info, SelectColumn selects, Flipper flipper, FilterNode node) {
if (flipper == null && node == null) {
return SearchRequest.createMatchAll();
}
return new SearchRequest().flipper(flipper).filterNode(info, node);
}
protected byte[] convertSearchRequest(final SearchInfo info, SearchRequest bean) {
if (bean == null) {
return null;
}
return info.getConvert().convertToBytes(bean);
}
protected SearchInfo loadSearchInfo(Class clazz) {
return SearchInfo.load(clazz);
}
protected void checkIndexSync(final SearchInfo info, String table0) {
if (info.isVirtual()) {
return;
}
String table = table0 == null ? info.getOriginTable() : table0;
checkedIndexClasses.computeIfAbsent(table, c -> {
final StringBuilder path =
new StringBuilder().append('/').append(table).append("/_mapping");
CompletableFuture future = getAsync(path).thenCompose(resp -> {
if (resp.getRetcode() == 404) { // 还没有表结构
return putAsync("/" + table, info, info.createIndexMap())
.thenApply(resp2 -> resp2.getRetcode() != 200 ? null : resp);
}
if (resp.getRetcode() != 200) {
return null;
}
Map rs =
JsonConvert.root().convertFrom(SearchMapping.MAPPING_MAP_TYPE, resp.getResult());
SearchMapping sm = rs == null ? null : rs.get(table);
if (sm == null || sm.mappings == null || !sm.mappings.equal(info.getMappingTypes())) {
return updateMappingAsync(info.getType(), table)
.thenCompose(v -> v == 1 ? CompletableFuture.completedFuture(1) : null);
}
return CompletableFuture.completedFuture(1);
});
return future == null ? null : future.join();
});
}
protected CompletableFuture insertOneAsync(final SearchInfo info, T entity) {
final StringBuilder path = new StringBuilder()
.append('/')
.append(info.getTable(entity))
.append("/_create/")
.append(info.getPrimary().get(entity));
return postEntityAsync(path, info, entity).thenApply(resp -> {
if (resp.getRetcode() != 200 && resp.getRetcode() != 201) {
throw new SourceException(
"insert response code = " + resp.getRetcode() + ", body = " + resp.getResult());
} else {
ActionResult rs = info.getConvert().convertFrom(ActionResult.class, resp.getResult());
return rs == null || rs._shards == null ? 0 : rs._shards.successful;
}
});
}
@Override
public int batch(final DataBatch batch) {
return batchAsync(batch).join();
}
@Override
public CompletableFuture batchAsync(final DataBatch batch) {
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public int insert(T... entitys) {
return insertAsync(entitys).join();
}
@Override
public CompletableFuture insertAsync(T... entitys) {
checkEntity("insert", entitys);
final SearchInfo info = loadSearchInfo(entitys[0].getClass());
if (entitys.length == 1) {
return insertOneAsync(info, entitys[0]);
}
final Attribute primary = info.getPrimary();
final Map tables = new LinkedHashMap<>();
for (T entity : entitys) {
String table = info.getTable(entity);
StringBuilder sb = tables.computeIfAbsent(table, t -> new StringBuilder());
sb.append("{\"create\":{\"_id\":\"")
.append(primary.get(entity))
.append("\"}}\n")
.append(info.getConvert().convertTo(entity))
.append('\n');
}
final List> futures = new ArrayList<>(tables.size());
tables.forEach((table, sb) -> {
checkIndexSync(info, table);
final StringBuilder path =
new StringBuilder().append('/').append(table).append("/_bulk");
futures.add(bulkAsync(path, sb).thenApply(resp -> {
if (resp.getRetcode() != 200) {
throw new SourceException(
"insert response code = " + resp.getRetcode() + ", body = " + resp.getResult());
} else {
BulkResult rs = JsonConvert.root().convertFrom(BulkResult.class, resp.getResult());
return rs == null ? -1 : rs.successCount();
}
}));
});
if (futures.size() == 1) {
return futures.get(0);
}
return Utility.allOfFutures(futures).thenApply(list -> {
int count = 0;
for (Integer c : list) {
if (c < 0) {
return c; // 存在失败的直接返回,已成功的暂时无法进行回滚
}
count += c;
}
return count;
});
}
protected CompletableFuture deleteOneAsync(final SearchInfo info, Class clazz, Serializable pk) {
final StringBuilder path = new StringBuilder()
.append('/')
.append(info.getTable(pk))
.append("/_doc/")
.append(pk);
return deleteAsync(path).thenApply(resp -> {
if (resp.getRetcode() == 404) {
return 0;
}
if (resp.getRetcode() != 200) {
throw new SourceException(
"delete response code = " + resp.getRetcode() + ", body = " + resp.getResult());
} else {
ActionResult rs = JsonConvert.root().convertFrom(ActionResult.class, resp.getResult());
return rs == null || rs._shards == null ? -1 : rs._shards.successful;
}
});
}
@Override
public int delete(T... entitys) {
return deleteAsync(entitys).join();
}
@Override
public CompletableFuture deleteAsync(T... entitys) {
checkEntity("delete", entitys);
final SearchInfo info = loadSearchInfo(entitys[0].getClass());
final Attribute primary = info.getPrimary();
if (entitys.length == 1) {
return deleteOneAsync(info, (Class) entitys[0].getClass(), primary.get(entitys[0]));
}
final Map tables = new LinkedHashMap<>();
for (T entity : entitys) {
String table = info.getTable(entity);
StringBuilder sb = tables.computeIfAbsent(table, t -> new StringBuilder());
sb.append("{\"delete\":{\"_id\":\"").append(primary.get(entity)).append("\"}}\n");
}
final List> futures = new ArrayList<>(tables.size());
tables.forEach((table, sb) -> {
checkIndexSync(info, table);
final StringBuilder path =
new StringBuilder().append('/').append(table).append("/_bulk");
futures.add(bulkAsync(path, sb).thenApply(resp -> {
if (resp.getRetcode() == 404) {
return 0;
}
if (resp.getRetcode() != 200) {
throw new SourceException(
"delete response code = " + resp.getRetcode() + ", body = " + resp.getResult());
} else {
BulkResult rs = JsonConvert.root().convertFrom(BulkResult.class, resp.getResult());
return rs == null ? -1 : rs.successCount();
}
}));
});
if (futures.size() == 1) {
return futures.get(0);
}
return Utility.allOfFutures(futures).thenApply(list -> {
int count = 0;
for (Integer c : list) {
if (c < 0) {
return c; // 存在失败的直接返回,已成功的暂时无法进行回滚
}
count += c;
}
return count;
});
}
@Override
public int delete(Class clazz, Serializable... pks) {
return deleteAsync(clazz, pks).join();
}
@Override
public CompletableFuture deleteAsync(Class clazz, Serializable... pks) {
final SearchInfo info = loadSearchInfo(clazz);
if (pks.length == 1) {
return deleteOneAsync(info, clazz, pks[0]);
}
final Map tables = new LinkedHashMap<>();
for (Serializable pk : pks) {
String table = info.getTable(pk);
StringBuilder sb = tables.computeIfAbsent(table, t -> new StringBuilder());
sb.append("{\"delete\":{\"_id\":\"").append(pk).append("\"}}\n");
}
final List> futures = new ArrayList<>(tables.size());
tables.forEach((table, sb) -> {
checkIndexSync(info, table);
final StringBuilder path =
new StringBuilder().append('/').append(table).append("/_bulk");
futures.add(bulkAsync(path, sb).thenApply(resp -> {
if (resp.getRetcode() == 404) {
return 0;
}
if (resp.getRetcode() != 200) {
throw new SourceException(
"delete response code = " + resp.getRetcode() + ", body = " + resp.getResult());
} else {
BulkResult rs = JsonConvert.root().convertFrom(BulkResult.class, resp.getResult());
return rs == null ? -1 : rs.successCount();
}
}));
});
if (futures.size() == 1) {
return futures.get(0);
}
return Utility.allOfFutures(futures).thenApply(list -> {
int count = 0;
for (Integer c : list) {
if (c < 0) {
return c; // 存在失败的直接返回,已成功的暂时无法进行回滚
}
count += c;
}
return count;
});
}
@Override
public int delete(Class clazz, FilterNode node) {
return deleteAsync(clazz, (Flipper) null, node).join();
}
@Override
public CompletableFuture deleteAsync(Class clazz, FilterNode node) {
return deleteAsync(clazz, (Flipper) null, node);
}
@Override
public int delete(Class clazz, Flipper flipper, FilterNode node) {
return deleteAsync(clazz, flipper, node).join();
}
@Override
public CompletableFuture deleteAsync(Class clazz, Flipper flipper, FilterNode node) {
final SearchInfo info = loadSearchInfo(clazz);
final StringBuilder path = new StringBuilder()
.append('/')
.append(getQueryTable(info, node))
.append("/_delete_by_query");
return postAsync(path, info, createSearchRequest(info, null, flipper, node))
.thenApply(resp -> {
if (resp.getRetcode() == 404) {
return 0;
}
if (resp.getRetcode() != 200) {
throw new SourceException(
"delete response code = " + resp.getRetcode() + ", body = " + resp.getResult());
} else {
ActionResult rs = JsonConvert.root().convertFrom(ActionResult.class, resp.getResult());
return rs == null || rs._shards == null ? -1 : rs._shards.successful;
}
});
}
@Override
public int clearTable(Class clazz) {
return clearTableAsync(clazz, (FilterNode) null).join();
}
@Override
public CompletableFuture clearTableAsync(Class clazz) {
return clearTableAsync(clazz, (FilterNode) null);
}
@Override
public int clearTable(Class clazz, FilterNode node) {
return clearTableAsync(clazz, node).join();
}
@Override
public CompletableFuture clearTableAsync(Class clazz, FilterNode node) {
// final SearchInfo info = loadSearchInfo(clazz);
// final String path = "/" + info.getTable() + "/_delete_by_query";
//
// //{"took":24,"timed_out":false,"total":3,"deleted":3,"batches":1,"version_conflicts":0,"noops":0,"retries":{"bulk":0,"search":0},"throttled_millis":0,"requests_per_second":-1,"throttled_until_millis":0,"failures":[]}
// return postAsync(path, HttpRequest.BodyPublishers.ofByteArray(BYTES_QUERY_MATCH_ALL)).thenApply(resp
// -> {
// if (resp.getRetcode() != 200) {
// throw new SourceException("clearTable response code = " + resp.getRetcode() + ", body = " +
// resp.getResult());
// } else {
// ActionResult rs = JsonConvert.root().convertFrom(ActionResult.class, resp.getResult());
// return rs == null || rs._shards == null ? -1 : rs._shards.successful;
// }
// });
final SearchInfo info = loadSearchInfo(clazz);
final StringBuilder path = new StringBuilder().append("/").append(getQueryTable(info, node));
return deleteAsync(path).thenApply(resp -> {
if (resp.getRetcode() == 404) {
return 0;
}
if (resp.getRetcode() != 200) {
throw new SourceException(
"clearTable response code = " + resp.getRetcode() + ", body = " + resp.getResult());
} else { // {"acknowledged" : true}
Map rs =
JsonConvert.root().convertFrom(JsonConvert.TYPE_MAP_STRING_STRING, resp.getResult());
return rs == null || !"true".equals(rs.get("acknowledged")) ? -1 : 1;
}
});
}
@Override
public int createTable(final Class clazz, final Serializable pk) {
return createTableAsync(clazz, pk).join();
}
@Override
public CompletableFuture createTableAsync(final Class clazz, final Serializable pk) {
return CompletableFuture.completedFuture(0);
}
@Override
public int dropTable(Class clazz) {
return dropTableAsync(clazz, (FilterNode) null).join();
}
@Override
public CompletableFuture dropTableAsync(Class clazz) {
return dropTableAsync(clazz, (FilterNode) null);
}
@Override
public int dropTable(Class clazz, FilterNode node) {
return dropTableAsync(clazz, node).join();
}
@Override
public CompletableFuture dropTableAsync(Class clazz, FilterNode node) {
final SearchInfo info = loadSearchInfo(clazz);
final StringBuilder path = new StringBuilder().append('/').append(getQueryTable(info, node));
return deleteAsync(path).thenApply(resp -> {
if (resp.getRetcode() == 404) {
return 0;
}
if (resp.getRetcode() != 200) {
throw new SourceException(
"dropTable response code = " + resp.getRetcode() + ", body = " + resp.getResult());
} else { // {"acknowledged" : true}
checkedIndexClasses.remove(clazz);
Map rs =
JsonConvert.root().convertFrom(JsonConvert.TYPE_MAP_STRING_STRING, resp.getResult());
return rs == null || !"true".equals(rs.get("acknowledged")) ? -1 : 1;
}
});
}
protected CompletableFuture updateOneAsync(final SearchInfo info, T entity) {
final StringBuilder path = new StringBuilder()
.append('/')
.append(info.getTable(entity))
.append('/')
.append(info.getPrimary().get(entity))
.append("/_update");
return postEntityAsync(path, info, entity).thenApply(resp -> {
if (resp.getRetcode() == 404) {
return 0;
}
if (resp.getRetcode() != 200) {
throw new SourceException(
"update response code = " + resp.getRetcode() + ", body = " + resp.getResult());
} else {
ActionResult rs = JsonConvert.root().convertFrom(ActionResult.class, resp.getResult());
return rs == null || rs._shards == null ? -1 : rs._shards.successful;
}
});
}
@Override
public int update(T... entitys) {
return updateAsync(entitys).join();
}
@Override
public CompletableFuture updateAsync(T... entitys) {
checkEntity("update", entitys);
final SearchInfo info = loadSearchInfo(entitys[0].getClass());
if (entitys.length == 1) {
return updateOneAsync(info, entitys[0]);
}
final Attribute primary = info.getPrimary();
final Map tables = new LinkedHashMap<>();
for (T entity : entitys) {
String table = info.getTable(entity);
StringBuilder sb = tables.computeIfAbsent(table, t -> new StringBuilder());
sb.append("{\"update\":{\"_id\":\"")
.append(primary.get(entity))
.append("\"}}\n")
.append(info.getConvert().convertTo(entity))
.append('\n');
}
final List> futures = new ArrayList<>(tables.size());
tables.forEach((table, sb) -> {
final StringBuilder path =
new StringBuilder().append('/').append(table).append("/_bulk");
futures.add(bulkAsync(path, sb).thenApply(resp -> {
if (resp.getRetcode() == 404) {
return 0;
}
if (resp.getRetcode() != 200) {
throw new SourceException(
"update response code = " + resp.getRetcode() + ", body = " + resp.getResult());
} else {
BulkResult rs = JsonConvert.root().convertFrom(BulkResult.class, resp.getResult());
return rs == null ? -1 : rs.successCount();
}
}));
});
if (futures.size() == 1) {
return futures.get(0);
}
return Utility.allOfFutures(futures).thenApply(list -> {
int count = 0;
for (Integer c : list) {
if (c < 0) {
return c; // 存在失败的直接返回,已成功的暂时无法进行回滚
}
count += c;
}
return count;
});
}
@Override
public int updateColumn(Class clazz, Serializable pk, String column, Serializable value) {
return updateColumnAsync(clazz, pk, column, value).join();
}
@Override
public CompletableFuture updateColumnAsync(
Class clazz, Serializable pk, String column, Serializable value) {
final SearchInfo info = loadSearchInfo(clazz);
final StringBuilder path = new StringBuilder()
.append('/')
.append(info.getTable(pk))
.append('/')
.append(pk)
.append("/_update");
return postAsync(path, info, new UpdatePart(info, column, value)).thenApply(resp -> {
if (resp.getRetcode() == 404) {
return 0;
}
if (resp.getRetcode() != 200) {
throw new SourceException(
"update response code = " + resp.getRetcode() + ", body = " + resp.getResult());
} else {
ActionResult rs = JsonConvert.root().convertFrom(ActionResult.class, resp.getResult());
return rs == null || rs._shards == null ? -1 : rs._shards.successful;
}
});
}
@Override
public int updateColumn(Class clazz, String column, Serializable value, FilterNode node) {
return updateColumnAsync(clazz, column, value, node).join();
}
@Override
public CompletableFuture updateColumnAsync(
Class clazz, String column, Serializable value, FilterNode node) {
final SearchInfo info = loadSearchInfo(clazz);
final StringBuilder path =
new StringBuilder().append('/').append(info.getTable(node)).append("/_update_by_query");
return postAsync(path, info, new UpdatePart(info, column, value)).thenApply(resp -> {
if (resp.getRetcode() == 404) {
return 0;
}
if (resp.getRetcode() != 200) {
throw new SourceException(
"update response code = " + resp.getRetcode() + ", body = " + resp.getResult());
} else {
ActionResult rs = JsonConvert.root().convertFrom(ActionResult.class, resp.getResult());
return rs == null || rs._shards == null ? -1 : rs._shards.successful;
}
});
}
@Override
public int updateColumn(Class clazz, Serializable pk, ColumnValue... values) {
if (values.length == 0) {
return 0;
}
return updateColumnAsync(clazz, pk, values).join();
}
@Override
public CompletableFuture updateColumnAsync(Class clazz, Serializable pk, ColumnValue... values) {
if (values.length == 0) {
return CompletableFuture.completedFuture(0);
}
final SearchInfo info = loadSearchInfo(clazz);
final StringBuilder path = new StringBuilder()
.append('/')
.append(info.getTable(pk))
.append('/')
.append(pk)
.append("/_update");
return postAsync(path, info, new UpdatePart(info, values)).thenApply(resp -> {
if (resp.getRetcode() == 404) {
return 0;
}
if (resp.getRetcode() != 200) {
throw new SourceException(
"updateColumn response code = " + resp.getRetcode() + ", body = " + resp.getResult());
} else {
ActionResult rs = JsonConvert.root().convertFrom(ActionResult.class, resp.getResult());
return rs == null || rs._shards == null ? -1 : rs._shards.successful;
}
});
}
@Override
public int updateColumn(Class clazz, FilterNode node, ColumnValue... values) {
return updateColumnAsync(clazz, node, (Flipper) null, values).join();
}
@Override
public CompletableFuture updateColumnAsync(Class clazz, FilterNode node, ColumnValue... values) {
return updateColumnAsync(clazz, node, (Flipper) null, values);
}
@Override
public int updateColumn(Class clazz, FilterNode node, Flipper flipper, ColumnValue... values) {
return updateColumnAsync(clazz, node, flipper, values).join();
}
@Override
public CompletableFuture updateColumnAsync(
Class clazz, FilterNode node, Flipper flipper, ColumnValue... values) {
if (values.length == 0) {
return CompletableFuture.completedFuture(0);
}
final SearchInfo info = loadSearchInfo(clazz);
final StringBuilder path =
new StringBuilder().append('/').append(info.getTable(node)).append("/_update_by_query");
SearchRequest bean = createSearchRequest(info, null, flipper, node);
if (bean == null) {
bean = new SearchRequest();
}
bean.script = new UpdatePart(info, values).script;
return postAsync(path, info, bean).thenApply(resp -> {
if (resp.getRetcode() == 404) {
return 0;
}
if (resp.getRetcode() != 200) {
throw new SourceException(
"updateColumn response code = " + resp.getRetcode() + ", body = " + resp.getResult());
} else {
ActionResult rs = JsonConvert.root().convertFrom(ActionResult.class, resp.getResult());
return rs == null || rs._shards == null ? -1 : rs._shards.successful;
}
});
}
@Override
public int updateColumn(T entity, String... columns) {
if (columns.length == 0) {
return 0;
}
return updateColumnAsync(entity, columns).join();
}
@Override
public CompletableFuture updateColumnAsync(T entity, String... columns) {
if (columns.length == 0) {
return CompletableFuture.completedFuture(0);
}
final SearchInfo info = loadSearchInfo(entity.getClass());
final StringBuilder path = new StringBuilder()
.append('/')
.append(info.getTable(entity))
.append('/')
.append(info.getPrimary().get(entity))
.append("/_update");
final Map map = new LinkedHashMap<>();
for (String col : columns) {
Attribute attr = info.getUpdateAttribute(col);
map.put(col, attr.get(entity));
}
return postAsync(path, info, new UpdatePart(info, map)).thenApply(resp -> {
if (resp.getRetcode() == 404) {
return 0;
}
if (resp.getRetcode() != 200) {
throw new SourceException(
"updateColumn response code = " + resp.getRetcode() + ", body = " + resp.getResult());
} else {
ActionResult rs = JsonConvert.root().convertFrom(ActionResult.class, resp.getResult());
return rs == null || rs._shards == null ? -1 : rs._shards.successful;
}
});
}
@Override
public int updateColumn(T entity, FilterNode node, String... columns) {
return updateColumnAsync(entity, node, SelectColumn.includes(columns)).join();
}
@Override
public CompletableFuture updateColumnAsync(T entity, FilterNode node, String... columns) {
return updateColumnAsync(entity, node, SelectColumn.includes(columns));
}
@Override
public int updateColumn(T entity, FilterNode node, SelectColumn selects) {
return updateColumnAsync(entity, node, selects).join();
}
@Override
public CompletableFuture updateColumnAsync(T entity, FilterNode node, SelectColumn selects) {
if (entity == null) {
return CompletableFuture.completedFuture(0);
}
final SearchInfo info = loadSearchInfo(entity.getClass());
final StringBuilder path =
new StringBuilder().append('/').append(info.getTable(node)).append("/_update_by_query");
SearchRequest bean = createSearchRequest(info, null, null, node);
if (bean == null) {
bean = new SearchRequest();
}
bean.script = new UpdatePart(info, entity, selects);
return postAsync(path, info, bean).thenApply(resp -> {
if (resp.getRetcode() == 404) {
return 0;
}
if (resp.getRetcode() != 200) {
throw new SourceException(
"updateColumn response code = " + resp.getRetcode() + ", body = " + resp.getResult());
} else {
ActionResult rs = JsonConvert.root().convertFrom(ActionResult.class, resp.getResult());
return rs == null || rs._shards == null ? -1 : rs._shards.successful;
}
});
}
@Override
public Number getNumberResult(Class entityClass, FilterFunc func, Number defVal, String column, FilterNode node) {
return getNumberResultAsync(entityClass, func, defVal, column, node).join();
}
@Override
public CompletableFuture getNumberResultAsync(
Class entityClass, FilterFunc func, Number defVal, String column, FilterNode node) {
final SearchInfo> info = loadSearchInfo(entityClass);
final Attribute keyAttr = (Attribute) info.getAttribute(column);
if (keyAttr == null) {
return CompletableFuture.failedFuture(
new RuntimeException("not found column " + column + " in " + entityClass));
}
final StringBuilder path =
new StringBuilder().append('/').append(info.getTable(node)).append("/_search?_source=false");
SearchRequest bean = createSearchRequest(info, null, null, node);
SearchRequest.QueryFilterItem aggs = new SearchRequest.QueryFilterItem();
String funcesname = func == FilterFunc.COUNT
? "value_count"
: (func == FilterFunc.DISTINCTCOUNT
? "cardinality"
: func.name().toLowerCase()); // func_count:为BucketItem类其中的字段名
String field = column == null ? info.getPrimary().field() : column;
aggs.put("func_count", Utility.ofMap(funcesname, Utility.ofMap("field", field)));
bean.aggs = aggs;
bean.size = 0;
return postAsync(path, info, bean).thenApply(resp -> {
if (resp.getRetcode() == 404) {
return defVal;
}
if (resp.getRetcode() != 200) {
throw new SourceException(
"getNumberResult response code = " + resp.getRetcode() + ", body = " + resp.getResult());
} else {
SearchResult rs = JsonConvert.root().convertFrom(info.getSearchResultType(), resp.getResult());
if (rs == null || rs.timed_out || rs.aggregations == null) {
return defVal;
}
SearchResult.Aggregations aggrs = (SearchResult.Aggregations) rs.aggregations.get("func_count");
if (aggrs == null) {
return defVal;
}
Number d = aggrs.value;
if (func == FilterFunc.COUNT || func == FilterFunc.DISTINCTCOUNT) {
d = d.intValue();
} else if (keyAttr != null) {
if (keyAttr != null && (func == FilterFunc.MIN || func == FilterFunc.MAX)) {
if (keyAttr.type() == short.class) {
d = d.shortValue();
} else if (keyAttr.type() == int.class || keyAttr.type() == char.class) {
d = d.intValue();
} else if (keyAttr.type() == long.class) {
d = d.longValue();
} else if (keyAttr.type() == float.class) {
d = d.floatValue();
}
}
}
return d;
}
});
}
@Override
public Map getNumberMap(
Class entityClass, FilterNode node, FilterFuncColumn... columns) {
return (Map) getNumberMapAsync(entityClass, node, columns).join();
}
@Override
public CompletableFuture