diff --git a/sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/client/SentinelApiClient.java b/sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/client/SentinelApiClient.java index cafb46f0..e3aef79b 100755 --- a/sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/client/SentinelApiClient.java +++ b/sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/client/SentinelApiClient.java @@ -16,54 +16,67 @@ package com.alibaba.csp.sentinel.dashboard.client; import java.io.UnsupportedEncodingException; -import java.net.URI; -import java.net.URISyntaxException; import java.net.URLEncoder; import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.Map.Entry; import java.util.Optional; import java.util.Set; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; import com.alibaba.csp.sentinel.command.CommandConstants; import com.alibaba.csp.sentinel.config.SentinelConfig; import com.alibaba.csp.sentinel.command.vo.NodeVo; import com.alibaba.csp.sentinel.dashboard.util.AsyncUtils; +import com.alibaba.csp.sentinel.slots.block.Rule; +import com.alibaba.csp.sentinel.slots.block.authority.AuthorityRule; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; +import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule; import com.alibaba.csp.sentinel.slots.system.SystemRule; import com.alibaba.csp.sentinel.util.AssertUtil; import com.alibaba.csp.sentinel.util.StringUtil; import com.alibaba.fastjson.JSON; - +import com.alibaba.csp.sentinel.dashboard.datasource.entity.SentinelVersion; import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.AuthorityRuleEntity; import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.DegradeRuleEntity; import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity; import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.ParamFlowRuleEntity; +import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.RuleEntity; import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.SystemRuleEntity; +import com.alibaba.csp.sentinel.dashboard.discovery.AppManagement; import com.alibaba.csp.sentinel.dashboard.domain.cluster.ClusterClientInfoVO; import com.alibaba.csp.sentinel.dashboard.domain.cluster.state.ClusterServerStateVO; import com.alibaba.csp.sentinel.dashboard.domain.cluster.state.ClusterStateSimpleEntity; import com.alibaba.csp.sentinel.dashboard.domain.cluster.config.ClusterClientConfig; import com.alibaba.csp.sentinel.dashboard.domain.cluster.config.ServerFlowConfig; import com.alibaba.csp.sentinel.dashboard.domain.cluster.config.ServerTransportConfig; -import com.alibaba.csp.sentinel.dashboard.util.RuleUtils; +import com.alibaba.csp.sentinel.dashboard.util.VersionUtils; + import org.apache.http.HttpResponse; +import org.apache.http.NameValuePair; +import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpGet; -import org.apache.http.client.utils.URIBuilder; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.concurrent.FutureCallback; import org.apache.http.entity.ContentType; import org.apache.http.impl.client.DefaultRedirectStrategy; import org.apache.http.impl.nio.client.CloseableHttpAsyncClient; import org.apache.http.impl.nio.client.HttpAsyncClients; import org.apache.http.impl.nio.reactor.IOReactorConfig; +import org.apache.http.message.BasicNameValuePair; import org.apache.http.util.EntityUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.lang.Nullable; import org.springframework.stereotype.Component; /** @@ -73,7 +86,6 @@ import org.springframework.stereotype.Component; */ @Component public class SentinelApiClient { - private static Logger logger = LoggerFactory.getLogger(SentinelApiClient.class); private static final Charset DEFAULT_CHARSET = Charset.forName(SentinelConfig.charset()); @@ -104,7 +116,10 @@ public class SentinelApiClient { private CloseableHttpAsyncClient httpClient; - private final boolean enableHttps = false; + private static final SentinelVersion version160 = new SentinelVersion(1, 6, 0); + + @Autowired + private AppManagement appManagement; public SentinelApiClient() { IOReactorConfig ioConfig = IOReactorConfig.custom().setConnectTimeout(3000).setSoTimeout(10000) @@ -121,19 +136,140 @@ public class SentinelApiClient { private boolean isSuccess(int statusCode) { return statusCode >= 200 && statusCode < 300; } - + private boolean isCommandNotFound(int statusCode, String body) { return statusCode == 400 && StringUtil.isNotEmpty(body) && body.contains(CommandConstants.MSG_UNKNOWN_COMMAND_PREFIX); } + + private StringBuilder queryString(Map params) { + StringBuilder queryStringBuilder = new StringBuilder(); + for (Entry entry : params.entrySet()) { + if (StringUtil.isEmpty(entry.getValue())) { + continue; + } + String name = urlEncode(entry.getKey()); + String value = urlEncode(entry.getValue()); + if (name != null && value != null) { + if (queryStringBuilder.length() > 0) { + queryStringBuilder.append('&'); + } + queryStringBuilder.append(name).append('=').append(value); + } + } + return queryStringBuilder; + } + + private HttpUriRequest postRequest(String url, Map params) { + HttpPost httpPost = new HttpPost(url); + if (params != null && params.size() > 0) { + List list = new ArrayList<>(params.size()); + for (Entry entry : params.entrySet()) { + list.add(new BasicNameValuePair(entry.getKey(), entry.getValue())); + } + try { + httpPost.setEntity(new UrlEncodedFormEntity(list)); + } catch (UnsupportedEncodingException e) { + logger.warn("httpPostContent encode entity error: {}", params, e); + return null; + } + } + return httpPost; + } + + private String urlEncode(String str) { + try { + return URLEncoder.encode(str, DEFAULT_CHARSET.name()); + } catch (UnsupportedEncodingException e) { + logger.info("encode string error: {}", str, e); + return null; + } + } + + private String getBody(HttpResponse response) throws Exception { + Charset charset = null; + try { + String contentTypeStr = response.getFirstHeader("Content-type").getValue(); + if (StringUtil.isNotEmpty(contentTypeStr)) { + ContentType contentType = ContentType.parse(contentTypeStr); + charset = contentType.getCharset(); + } + } catch (Exception ignore) { + } + return EntityUtils.toString(response.getEntity(), charset != null ? charset : DEFAULT_CHARSET); + } + + /** + * With no param + * + * @param ip + * @param port + * @param api + * @return + */ + private CompletableFuture executeCommand(String ip, int port, String api, boolean useHttpPost) { + return executeCommand(ip, port, api, null, useHttpPost); + } + + /** + * No app specified, force to GET + * + * @param ip + * @param port + * @param api + * @param params + * @return + */ + private CompletableFuture executeCommand(String ip, int port, String api, Map params, boolean useHttpPost) { + return executeCommand(null, ip, port, api, params, useHttpPost); + } - private CompletableFuture executeCommand(String command, URI uri) { + /** + * Prefer to execute request using POST + * + * @param app + * @param ip + * @param port + * @param api + * @param params + * @return + */ + private CompletableFuture executeCommand(String app, String ip, int port, String api, Map params, boolean useHttpPost) { CompletableFuture future = new CompletableFuture<>(); - if (StringUtil.isBlank(command) || uri == null) { + if (StringUtil.isBlank(ip) || StringUtil.isBlank(api)) { future.completeExceptionally(new IllegalArgumentException("Bad URL or command name")); return future; } - final HttpGet httpGet = new HttpGet(uri); - httpClient.execute(httpGet, new FutureCallback() { + StringBuilder urlBuilder = new StringBuilder(); + urlBuilder.append("http://"); + urlBuilder.append(ip).append(':').append(port).append('/').append(api); + if (params == null) { + params = Collections.emptyMap(); + } + boolean supportPost = StringUtil.isNotEmpty(app) && Optional.ofNullable(appManagement.getDetailApp(app)) + .flatMap(e -> e.getMachine(ip, port)) + .flatMap(m -> VersionUtils.parseVersion(m.getVersion()) + .map(v -> v.greaterOrEqual(version160))) + .orElse(false); + if (!useHttpPost || !supportPost) { + // Using GET in older versions, append parameters after url + if (!params.isEmpty()) { + if (urlBuilder.indexOf("?") == -1) { + urlBuilder.append('?'); + } else { + urlBuilder.append('&'); + } + urlBuilder.append(queryString(params)); + } + return executeCommand(new HttpGet(urlBuilder.toString())); + } else { + // Using POST + return executeCommand(postRequest(urlBuilder.toString(), params)); + } + } + + private CompletableFuture executeCommand(HttpUriRequest request) { + CompletableFuture future = new CompletableFuture<>(); + httpClient.execute(request, new FutureCallback() { @Override public void completed(final HttpResponse response) { int statusCode = response.getStatusLine().getStatusCode(); @@ -143,7 +279,7 @@ public class SentinelApiClient { future.complete(value); } else { if (isCommandNotFound(statusCode, value)) { - future.completeExceptionally(new CommandNotFoundException(command)); + future.completeExceptionally(new CommandNotFoundException(request.getURI().getPath())); } else { future.completeExceptionally(new CommandFailedException(value)); } @@ -151,14 +287,14 @@ public class SentinelApiClient { } catch (Exception ex) { future.completeExceptionally(ex); - logger.error("HTTP request failed: " + uri.toString(), ex); + logger.error("HTTP request failed: {}", request.getURI().toString(), ex); } } @Override public void failed(final Exception ex) { future.completeExceptionally(ex); - logger.error("HTTP request failed: " + uri.toString(), ex); + logger.error("HTTP request failed: {}", request.getURI().toString(), ex); } @Override @@ -168,73 +304,77 @@ public class SentinelApiClient { }); return future; } - - private String httpGetContent(String url) { - final HttpGet httpGet = new HttpGet(url); - final CountDownLatch latch = new CountDownLatch(1); - final AtomicReference reference = new AtomicReference<>(); - httpClient.execute(httpGet, new FutureCallback() { - @Override - public void completed(final HttpResponse response) { - try { - reference.set(getBody(response)); - } catch (Exception e) { - logger.info("httpGetContent " + url + " error:", e); - } finally { - latch.countDown(); - } - } - - @Override - public void failed(final Exception ex) { - latch.countDown(); - logger.info("httpGetContent " + url + " failed:", ex); - } - - @Override - public void cancelled() { - latch.countDown(); - } - }); - try { - latch.await(5, TimeUnit.SECONDS); - } catch (Exception e) { - logger.info("wait http client error:", e); + + public void close() throws Exception { + httpClient.close(); + } + + @Nullable + private CompletableFuture> fetchItemsAsync(String ip, int port, String api, String type, Class ruleType) { + AssertUtil.notEmpty(ip, "Bad machine IP"); + AssertUtil.isTrue(port > 0, "Bad machine port"); + Map params = null; + if (StringUtil.isNotEmpty(type)) { + params = new HashMap<>(1); + params.put("type", type); } - return reference.get(); + return executeCommand(ip, port, api, params, false) + .thenApply(json -> JSON.parseArray(json, ruleType)); } - - private String getBody(HttpResponse response) throws Exception { - Charset charset = null; + + @Nullable + private List fetchItems(String ip, int port, String api, String type, Class ruleType) { try { - String contentTypeStr = response.getFirstHeader("Content-type").getValue(); - if (StringUtil.isNotEmpty(contentTypeStr)) { - ContentType contentType = ContentType.parse(contentTypeStr); - charset = contentType.getCharset(); + AssertUtil.notEmpty(ip, "Bad machine IP"); + AssertUtil.isTrue(port > 0, "Bad machine port"); + Map params = null; + if (StringUtil.isNotEmpty(type)) { + params = new HashMap<>(1); + params.put("type", type); } - } catch (Exception ignore) { + return fetchItemsAsync(ip, port, api, type, ruleType).get(); + } catch (InterruptedException | ExecutionException e) { + logger.error("Error when fetching items from api: {} -> {}", api, type, e); + return null; + } catch (Exception e) { + logger.error("Error when fetching items: {} -> {}", api, type, e); + return null; } - return EntityUtils.toString(response.getEntity(), charset != null ? charset : DEFAULT_CHARSET); } - - public void close() throws Exception { - httpClient.close(); + + private List fetchRules(String ip, int port, String type, Class ruleType) { + return fetchItems(ip, port, GET_RULES_PATH, type, ruleType); } - - public List fetchResourceOfMachine(String ip, int port, String type) { - String url = "http://" + ip + ":" + port + "/" + RESOURCE_URL_PATH + "?type=" + type; - String body = httpGetContent(url); - if (body == null) { - return null; + + private boolean setRules(String app, String ip, int port, String type, List entities) { + if (entities == null) { + return true; } try { - return JSON.parseArray(body, NodeVo.class); + AssertUtil.notEmpty(app, "Bad app name"); + AssertUtil.notEmpty(ip, "Bad machine IP"); + AssertUtil.isTrue(port > 0, "Bad machine port"); + String data = JSON.toJSONString( + entities.stream().map(r -> r.toRule()).collect(Collectors.toList())); + Map params = new HashMap<>(2); + params.put("type", type); + params.put("data", data); + String result = executeCommand(app, ip, port, SET_RULES_PATH, params, true).get(); + logger.info("setRules: {}", result); + return true; + } catch (InterruptedException | ExecutionException e) { + logger.warn("setRules api failed: {}", type, e); + return false; } catch (Exception e) { - logger.info("parse ResourceOfMachine error", e); - return null; + logger.warn("setRules failed", e); + return false; } } + public List fetchResourceOfMachine(String ip, int port, String type) { + return fetchItems(ip, port, RESOURCE_URL_PATH, type, NodeVo.class); + } + /** * Fetch cluster node. * @@ -248,24 +388,11 @@ public class SentinelApiClient { if (includeZero) { type = "zero"; } - String url = "http://" + ip + ":" + port + "/" + CLUSTER_NODE_PATH + "?type=" + type; - String body = httpGetContent(url); - if (body == null) { - return null; - } - try { - return JSON.parseArray(body, NodeVo.class); - } catch (Exception e) { - logger.info("parse ClusterNodeOfMachine error", e); - return null; - } + return fetchItems(ip, port, CLUSTER_NODE_PATH, type, NodeVo.class); } public List fetchFlowRuleOfMachine(String app, String ip, int port) { - String url = "http://" + ip + ":" + port + "/" + GET_RULES_PATH + "?type=" + FLOW_RULE_TYPE; - String body = httpGetContent(url); - logger.info("FlowRule Body:{}", body); - List rules = RuleUtils.parseFlowRule(body); + List rules = fetchRules(ip, port, FLOW_RULE_TYPE, FlowRule.class); if (rules != null) { return rules.stream().map(rule -> FlowRuleEntity.fromFlowRule(app, ip, port, rule)) .collect(Collectors.toList()); @@ -275,10 +402,7 @@ public class SentinelApiClient { } public List fetchDegradeRuleOfMachine(String app, String ip, int port) { - String url = "http://" + ip + ":" + port + "/" + GET_RULES_PATH + "?type=" + DEGRADE_RULE_TYPE; - String body = httpGetContent(url); - logger.info("Degrade Body:{}", body); - List rules = RuleUtils.parseDegradeRule(body); + List rules = fetchRules(ip, port, DEGRADE_RULE_TYPE, DegradeRule.class); if (rules != null) { return rules.stream().map(rule -> DegradeRuleEntity.fromDegradeRule(app, ip, port, rule)) .collect(Collectors.toList()); @@ -288,10 +412,7 @@ public class SentinelApiClient { } public List fetchSystemRuleOfMachine(String app, String ip, int port) { - String url = "http://" + ip + ":" + port + "/" + GET_RULES_PATH + "?type=" + SYSTEM_RULE_TYPE; - String body = httpGetContent(url); - logger.info("SystemRule Body:{}", body); - List rules = RuleUtils.parseSystemRule(body); + List rules = fetchRules(ip, port, SYSTEM_RULE_TYPE, SystemRule.class); if (rules != null) { return rules.stream().map(rule -> SystemRuleEntity.fromSystemRule(app, ip, port, rule)) .collect(Collectors.toList()); @@ -314,12 +435,7 @@ public class SentinelApiClient { AssertUtil.notEmpty(app, "Bad app name"); AssertUtil.notEmpty(ip, "Bad machine IP"); AssertUtil.isTrue(port > 0, "Bad machine port"); - URIBuilder uriBuilder = new URIBuilder(); - String commandName = GET_PARAM_RULE_PATH; - uriBuilder.setScheme("http").setHost(ip).setPort(port) - .setPath(commandName); - return executeCommand(commandName, uriBuilder.build()) - .thenApply(RuleUtils::parseParamFlowRule) + return fetchItemsAsync(ip, port, GET_PARAM_RULE_PATH, null, ParamFlowRule.class) .thenApply(rules -> rules.stream() .map(e -> ParamFlowRuleEntity.fromAuthorityRule(app, ip, port, e)) .collect(Collectors.toList()) @@ -343,23 +459,13 @@ public class SentinelApiClient { AssertUtil.notEmpty(app, "Bad app name"); AssertUtil.notEmpty(ip, "Bad machine IP"); AssertUtil.isTrue(port > 0, "Bad machine port"); - URIBuilder uriBuilder = new URIBuilder(); - uriBuilder.setScheme("http").setHost(ip).setPort(port) - .setPath(GET_RULES_PATH) - .setParameter("type", AUTHORITY_TYPE); - try { - String body = httpGetContent(uriBuilder.build().toString()); - return Optional.ofNullable(body) - .map(RuleUtils::parseAuthorityRule) - .map(rules -> rules.stream() + Map params = new HashMap<>(1); + params.put("type", AUTHORITY_TYPE); + List rules = fetchRules(ip, port, AUTHORITY_TYPE, AuthorityRule.class); + return Optional.ofNullable(rules).map(r -> r.stream() .map(e -> AuthorityRuleEntity.fromAuthorityRule(app, ip, port, e)) .collect(Collectors.toList()) - ) - .orElse(null); - } catch (URISyntaxException e) { - logger.error("Error when fetching authority rules", e); - return null; - } + ).orElse(null); } /** @@ -373,23 +479,7 @@ public class SentinelApiClient { * @return whether successfully set the rules. */ public boolean setFlowRuleOfMachine(String app, String ip, int port, List rules) { - if (rules == null) { - return true; - } - if (ip == null) { - throw new IllegalArgumentException("ip is null"); - } - String data = JSON.toJSONString(rules.stream().map(FlowRuleEntity::toFlowRule).collect(Collectors.toList())); - try { - data = URLEncoder.encode(data, DEFAULT_CHARSET.name()); - } catch (UnsupportedEncodingException e) { - logger.info("encode rule error", e); - return false; - } - String url = "http://" + ip + ":" + port + "/" + SET_RULES_PATH + "?type=" + FLOW_RULE_TYPE + "&data=" + data; - String result = httpGetContent(url); - logger.info("setFlowRule: " + result); - return true; + return setRules(app, ip, port, FLOW_RULE_TYPE, rules); } /** @@ -403,25 +493,7 @@ public class SentinelApiClient { * @return whether successfully set the rules. */ public boolean setDegradeRuleOfMachine(String app, String ip, int port, List rules) { - if (rules == null) { - return true; - } - if (ip == null) { - throw new IllegalArgumentException("ip is null"); - } - String data = JSON.toJSONString( - rules.stream().map(DegradeRuleEntity::toDegradeRule).collect(Collectors.toList())); - try { - data = URLEncoder.encode(data, DEFAULT_CHARSET.name()); - } catch (UnsupportedEncodingException e) { - logger.info("encode rule error", e); - return false; - } - String url = "http://" + ip + ":" + port + "/" + SET_RULES_PATH + "?type=" + DEGRADE_RULE_TYPE + "&data=" - + data; - String result = httpGetContent(url); - logger.info("setDegradeRule: " + result); - return true; + return setRules(app, ip, port, DEGRADE_RULE_TYPE, rules); } /** @@ -435,45 +507,11 @@ public class SentinelApiClient { * @return whether successfully set the rules. */ public boolean setSystemRuleOfMachine(String app, String ip, int port, List rules) { - if (rules == null) { - return true; - } - if (ip == null) { - throw new IllegalArgumentException("ip is null"); - } - String data = JSON.toJSONString( - rules.stream().map(SystemRuleEntity::toSystemRule).collect(Collectors.toList())); - try { - data = URLEncoder.encode(data, DEFAULT_CHARSET.name()); - } catch (UnsupportedEncodingException e) { - logger.info("encode rule error", e); - return false; - } - String url = "http://" + ip + ":" + port + "/" + SET_RULES_PATH + "?type=" + SYSTEM_RULE_TYPE + "&data=" + data; - String result = httpGetContent(url); - logger.info("setSystemRule: " + result); - return true; + return setRules(app, ip, port, SYSTEM_RULE_TYPE, rules); } public boolean setAuthorityRuleOfMachine(String app, String ip, int port, List rules) { - if (rules == null) { - return true; - } - if (StringUtil.isBlank(ip) || port <= 0) { - throw new IllegalArgumentException("Invalid IP or port"); - } - String data = JSON.toJSONString( - rules.stream().map(AuthorityRuleEntity::getRule).collect(Collectors.toList())); - try { - data = URLEncoder.encode(data, DEFAULT_CHARSET.name()); - } catch (UnsupportedEncodingException e) { - logger.info("Encode rule error", e); - return false; - } - String url = "http://" + ip + ":" + port + "/" + SET_RULES_PATH + "?type=" + AUTHORITY_TYPE + "&data=" + data; - String result = httpGetContent(url); - logger.info("Push authority rules: " + result); - return true; + return setRules(app, ip, port, AUTHORITY_TYPE, rules); } public CompletableFuture setParamFlowRuleOfMachine(String app, String ip, int port, List rules) { @@ -487,12 +525,9 @@ public class SentinelApiClient { String data = JSON.toJSONString( rules.stream().map(ParamFlowRuleEntity::getRule).collect(Collectors.toList()) ); - data = URLEncoder.encode(data, DEFAULT_CHARSET.name()); - URIBuilder uriBuilder = new URIBuilder(); - uriBuilder.setScheme("http").setHost(ip).setPort(port) - .setPath(SET_PARAM_RULE_PATH) - .setParameter("data", data); - return executeCommand(SET_PARAM_RULE_PATH, uriBuilder.build()) + Map params = new HashMap<>(1); + params.put("data", data); + return executeCommand(app, ip, port, SET_PARAM_RULE_PATH, params, true) .thenCompose(e -> { if (CommandConstants.MSG_SUCCESS.equals(e)) { return CompletableFuture.completedFuture(null); @@ -514,10 +549,7 @@ public class SentinelApiClient { return AsyncUtils.newFailedFuture(new IllegalArgumentException("Invalid parameter")); } try { - URIBuilder uriBuilder = new URIBuilder(); - uriBuilder.setScheme("http").setHost(ip).setPort(port) - .setPath(FETCH_CLUSTER_MODE_PATH); - return executeCommand(FETCH_CLUSTER_MODE_PATH, uriBuilder.build()) + return executeCommand(ip, port, FETCH_CLUSTER_MODE_PATH, false) .thenApply(r -> JSON.parseObject(r, ClusterStateSimpleEntity.class)); } catch (Exception ex) { logger.warn("Error when fetching cluster mode", ex); @@ -530,11 +562,9 @@ public class SentinelApiClient { return AsyncUtils.newFailedFuture(new IllegalArgumentException("Invalid parameter")); } try { - URIBuilder uriBuilder = new URIBuilder(); - uriBuilder.setScheme("http").setHost(ip).setPort(port) - .setPath(MODIFY_CLUSTER_MODE_PATH) - .setParameter("mode", String.valueOf(mode)); - return executeCommand(MODIFY_CLUSTER_MODE_PATH, uriBuilder.build()) + Map params = new HashMap<>(1); + params.put("mode", String.valueOf(mode)); + return executeCommand(ip, port, MODIFY_CLUSTER_MODE_PATH, params, false) .thenCompose(e -> { if (CommandConstants.MSG_SUCCESS.equals(e)) { return CompletableFuture.completedFuture(null); @@ -554,10 +584,7 @@ public class SentinelApiClient { return AsyncUtils.newFailedFuture(new IllegalArgumentException("Invalid parameter")); } try { - URIBuilder uriBuilder = new URIBuilder(); - uriBuilder.setScheme("http").setHost(ip).setPort(port) - .setPath(FETCH_CLUSTER_CLIENT_CONFIG_PATH); - return executeCommand(FETCH_CLUSTER_CLIENT_CONFIG_PATH, uriBuilder.build()) + return executeCommand(ip, port, FETCH_CLUSTER_CLIENT_CONFIG_PATH, false) .thenApply(r -> JSON.parseObject(r, ClusterClientInfoVO.class)); } catch (Exception ex) { logger.warn("Error when fetching cluster client config", ex); @@ -570,11 +597,9 @@ public class SentinelApiClient { return AsyncUtils.newFailedFuture(new IllegalArgumentException("Invalid parameter")); } try { - URIBuilder uriBuilder = new URIBuilder(); - uriBuilder.setScheme("http").setHost(ip).setPort(port) - .setPath(MODIFY_CLUSTER_CLIENT_CONFIG_PATH) - .setParameter("data", JSON.toJSONString(config)); - return executeCommand(MODIFY_CLUSTER_MODE_PATH, uriBuilder.build()) + Map params = new HashMap<>(1); + params.put("data", JSON.toJSONString(config)); + return executeCommand(app, ip, port, MODIFY_CLUSTER_CLIENT_CONFIG_PATH, params, true) .thenCompose(e -> { if (CommandConstants.MSG_SUCCESS.equals(e)) { return CompletableFuture.completedFuture(null); @@ -594,11 +619,9 @@ public class SentinelApiClient { return AsyncUtils.newFailedFuture(new IllegalArgumentException("Invalid parameter")); } try { - URIBuilder uriBuilder = new URIBuilder(); - uriBuilder.setScheme("http").setHost(ip).setPort(port) - .setPath(MODIFY_CLUSTER_SERVER_FLOW_CONFIG_PATH) - .setParameter("data", JSON.toJSONString(config)); - return executeCommand(MODIFY_CLUSTER_SERVER_FLOW_CONFIG_PATH, uriBuilder.build()) + Map params = new HashMap<>(1); + params.put("data", JSON.toJSONString(config)); + return executeCommand(app, ip, port, MODIFY_CLUSTER_SERVER_FLOW_CONFIG_PATH, params, true) .thenCompose(e -> { if (CommandConstants.MSG_SUCCESS.equals(e)) { return CompletableFuture.completedFuture(null); @@ -618,12 +641,10 @@ public class SentinelApiClient { return AsyncUtils.newFailedFuture(new IllegalArgumentException("Invalid parameter")); } try { - URIBuilder uriBuilder = new URIBuilder(); - uriBuilder.setScheme("http").setHost(ip).setPort(port) - .setPath(MODIFY_CLUSTER_SERVER_TRANSPORT_CONFIG_PATH) - .setParameter("port", config.getPort().toString()) - .setParameter("idleSeconds", config.getIdleSeconds().toString()); - return executeCommand(MODIFY_CLUSTER_SERVER_TRANSPORT_CONFIG_PATH, uriBuilder.build()) + Map params = new HashMap<>(2); + params.put("port", config.getPort().toString()); + params.put("idleSeconds", config.getIdleSeconds().toString()); + return executeCommand(app, ip, port, MODIFY_CLUSTER_SERVER_TRANSPORT_CONFIG_PATH, params, false) .thenCompose(e -> { if (CommandConstants.MSG_SUCCESS.equals(e)) { return CompletableFuture.completedFuture(null); @@ -643,16 +664,14 @@ public class SentinelApiClient { return AsyncUtils.newFailedFuture(new IllegalArgumentException("Invalid parameter")); } try { - URIBuilder uriBuilder = new URIBuilder(); - uriBuilder.setScheme("http").setHost(ip).setPort(port) - .setPath(MODIFY_CLUSTER_SERVER_NAMESPACE_SET_PATH) - .setParameter("data", JSON.toJSONString(set)); - return executeCommand(MODIFY_CLUSTER_SERVER_NAMESPACE_SET_PATH, uriBuilder.build()) + Map params = new HashMap<>(1); + params.put("data", JSON.toJSONString(set)); + return executeCommand(app, ip, port, MODIFY_CLUSTER_SERVER_NAMESPACE_SET_PATH, params, true) .thenCompose(e -> { if (CommandConstants.MSG_SUCCESS.equals(e)) { return CompletableFuture.completedFuture(null); } else { - logger.warn("Error when modifying cluster server NamespaceSet: " + e); + logger.warn("Error when modifying cluster server NamespaceSet", e); return AsyncUtils.newFailedFuture(new RuntimeException(e)); } }); @@ -667,10 +686,7 @@ public class SentinelApiClient { return AsyncUtils.newFailedFuture(new IllegalArgumentException("Invalid parameter")); } try { - URIBuilder uriBuilder = new URIBuilder(); - uriBuilder.setScheme("http").setHost(ip).setPort(port) - .setPath(FETCH_CLUSTER_SERVER_BASIC_INFO_PATH); - return executeCommand(FETCH_CLUSTER_SERVER_BASIC_INFO_PATH, uriBuilder.build()) + return executeCommand(ip, port, FETCH_CLUSTER_SERVER_BASIC_INFO_PATH, false) .thenApply(r -> JSON.parseObject(r, ClusterServerStateVO.class)); } catch (Exception ex) { logger.warn("Error when fetching cluster sever all config and basic info", ex); diff --git a/sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/datasource/entity/SentinelVersion.java b/sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/datasource/entity/SentinelVersion.java index 7fc105ee..08fcaf25 100644 --- a/sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/datasource/entity/SentinelVersion.java +++ b/sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/datasource/entity/SentinelVersion.java @@ -20,11 +20,32 @@ package com.alibaba.csp.sentinel.dashboard.datasource.entity; * @since 0.2.1 */ public class SentinelVersion { - private int majorVersion; private int minorVersion; private int fixVersion; private String postfix; + + public SentinelVersion() { + this(0, 0, 0); + } + + public SentinelVersion(int major, int minor, int fix) { + this(major, minor, fix, null); + } + + public SentinelVersion(int major, int minor, int fix, String postfix) { + this.majorVersion = major; + this.minorVersion = minor; + this.fixVersion = fix; + this.postfix = postfix; + } + + /** + * 000, 000, 000 + */ + public int getFullVersion() { + return majorVersion * 1000000 + minorVersion * 1000 + fixVersion; + } public int getMajorVersion() { return majorVersion; @@ -66,18 +87,14 @@ public class SentinelVersion { if (version == null) { return true; } - return this.majorVersion > version.majorVersion - || this.minorVersion > version.minorVersion - || this.fixVersion > version.fixVersion; + return getFullVersion() > version.getFullVersion(); } public boolean greaterOrEqual(SentinelVersion version) { if (version == null) { return true; } - return this.majorVersion >= version.majorVersion - || this.minorVersion >= version.minorVersion - || this.fixVersion >= version.fixVersion; + return getFullVersion() >= version.getFullVersion(); } @Override @@ -87,9 +104,7 @@ public class SentinelVersion { SentinelVersion that = (SentinelVersion)o; - if (majorVersion != that.majorVersion) { return false; } - if (minorVersion != that.minorVersion) { return false; } - if (fixVersion != that.fixVersion) { return false; } + if (getFullVersion() != that.getFullVersion()) { return false; } return postfix != null ? postfix.equals(that.postfix) : that.postfix == null; } diff --git a/sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/datasource/entity/rule/AuthorityRuleEntity.java b/sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/datasource/entity/rule/AuthorityRuleEntity.java index 92496201..2a77d32f 100644 --- a/sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/datasource/entity/rule/AuthorityRuleEntity.java +++ b/sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/datasource/entity/rule/AuthorityRuleEntity.java @@ -15,6 +15,7 @@ */ package com.alibaba.csp.sentinel.dashboard.datasource.entity.rule; +import com.alibaba.csp.sentinel.slots.block.Rule; import com.alibaba.csp.sentinel.slots.block.authority.AuthorityRule; import com.alibaba.csp.sentinel.util.AssertUtil; @@ -55,4 +56,9 @@ public class AuthorityRuleEntity extends AbstractRuleEntity { public int getStrategy() { return rule.getStrategy(); } + + @Override + public Rule toRule() { + return rule; + } } diff --git a/sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/datasource/entity/rule/DegradeRuleEntity.java b/sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/datasource/entity/rule/DegradeRuleEntity.java index f77d6080..2b86a373 100755 --- a/sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/datasource/entity/rule/DegradeRuleEntity.java +++ b/sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/datasource/entity/rule/DegradeRuleEntity.java @@ -145,7 +145,8 @@ public class DegradeRuleEntity implements RuleEntity { this.gmtModified = gmtModified; } - public DegradeRule toDegradeRule() { + @Override + public DegradeRule toRule() { DegradeRule rule = new DegradeRule(); rule.setResource(resource); rule.setLimitApp(limitApp); diff --git a/sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/datasource/entity/rule/FlowRuleEntity.java b/sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/datasource/entity/rule/FlowRuleEntity.java index 65e9acc1..a076bd42 100755 --- a/sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/datasource/entity/rule/FlowRuleEntity.java +++ b/sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/datasource/entity/rule/FlowRuleEntity.java @@ -223,7 +223,8 @@ public class FlowRuleEntity implements RuleEntity { this.gmtModified = gmtModified; } - public FlowRule toFlowRule() { + @Override + public FlowRule toRule() { FlowRule flowRule = new FlowRule(); flowRule.setCount(this.count); flowRule.setGrade(this.grade); diff --git a/sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/datasource/entity/rule/ParamFlowRuleEntity.java b/sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/datasource/entity/rule/ParamFlowRuleEntity.java index 5cb845ba..e2bbb03b 100644 --- a/sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/datasource/entity/rule/ParamFlowRuleEntity.java +++ b/sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/datasource/entity/rule/ParamFlowRuleEntity.java @@ -17,6 +17,7 @@ package com.alibaba.csp.sentinel.dashboard.datasource.entity.rule; import java.util.List; +import com.alibaba.csp.sentinel.slots.block.Rule; import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowClusterConfig; import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowItem; import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule; @@ -84,4 +85,9 @@ public class ParamFlowRuleEntity extends AbstractRuleEntity { public ParamFlowClusterConfig getClusterConfig() { return rule.getClusterConfig(); } + + @Override + public Rule toRule() { + return rule; + } } diff --git a/sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/datasource/entity/rule/RuleEntity.java b/sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/datasource/entity/rule/RuleEntity.java index 0f22ec53..0ad764fe 100755 --- a/sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/datasource/entity/rule/RuleEntity.java +++ b/sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/datasource/entity/rule/RuleEntity.java @@ -17,6 +17,8 @@ package com.alibaba.csp.sentinel.dashboard.datasource.entity.rule; import java.util.Date; +import com.alibaba.csp.sentinel.slots.block.Rule; + /** * @author leyou */ @@ -33,4 +35,6 @@ public interface RuleEntity { Integer getPort(); Date getGmtCreate(); + + Rule toRule(); } diff --git a/sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/datasource/entity/rule/SystemRuleEntity.java b/sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/datasource/entity/rule/SystemRuleEntity.java index 1c6e3bfd..08041b0a 100755 --- a/sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/datasource/entity/rule/SystemRuleEntity.java +++ b/sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/datasource/entity/rule/SystemRuleEntity.java @@ -135,7 +135,8 @@ public class SystemRuleEntity implements RuleEntity { this.gmtModified = gmtModified; } - public SystemRule toSystemRule() { + @Override + public SystemRule toRule() { SystemRule rule = new SystemRule(); rule.setHighestSystemLoad(avgLoad); rule.setAvgRt(avgRt); diff --git a/sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/util/RuleUtils.java b/sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/util/RuleUtils.java deleted file mode 100644 index 8761c048..00000000 --- a/sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/util/RuleUtils.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright 1999-2018 Alibaba Group Holding Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.alibaba.csp.sentinel.dashboard.util; - -import java.util.List; - -import com.alibaba.csp.sentinel.slots.block.authority.AuthorityRule; -import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; -import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; -import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule; -import com.alibaba.csp.sentinel.slots.system.SystemRule; -import com.alibaba.csp.sentinel.util.StringUtil; -import com.alibaba.fastjson.JSON; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * @author Eric Zhao - * @since 0.2.1 - */ -public final class RuleUtils { - - private static final Logger LOGGER = LoggerFactory.getLogger(RuleUtils.class); - - public static List parseFlowRule(String body) { - try { - return JSON.parseArray(body, FlowRule.class); - } catch (Exception e) { - LOGGER.error("parser FlowRule error: ", e); - return null; - } - } - - public static List parseDegradeRule(String body) { - try { - return JSON.parseArray(body, DegradeRule.class); - } catch (Exception e) { - LOGGER.error("parser DegradeRule error: ", e); - return null; - } - } - - public static List parseAuthorityRule(String body) { - if (StringUtil.isBlank(body)) { - return null; - } - try { - return JSON.parseArray(body, AuthorityRule.class); - } catch (Exception e) { - LOGGER.error("Error when parsing authority rules", e); - return null; - } - } - - /** - * Parse parameter flow rules. - * - * @param body raw string content - * @return parsed rule list; null if error occurs or empty content - */ - public static List parseParamFlowRule(String body) { - if (StringUtil.isBlank(body)) { - return null; - } - try { - return JSON.parseArray(body, ParamFlowRule.class); - } catch (Exception e) { - LOGGER.error("Error when parsing parameter flow rules", e); - return null; - } - } - - public static List parseSystemRule(String body) { - try { - return JSON.parseArray(body, SystemRule.class); - } catch (Exception e) { - LOGGER.info("parser SystemRule error: ", e); - return null; - } - } - - private RuleUtils() {} -} diff --git a/sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/util/VersionUtils.java b/sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/util/VersionUtils.java index ad7b6265..cb013a12 100644 --- a/sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/util/VersionUtils.java +++ b/sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/util/VersionUtils.java @@ -32,7 +32,7 @@ public final class VersionUtils { /** * Parse version of Sentinel from raw string. * - * @param s version string + * @param versionFull version string * @return parsed {@link SentinelVersion} if the version is valid; empty if * there is something wrong with the format */ @@ -41,25 +41,50 @@ public final class VersionUtils { return Optional.empty(); } try { + String versionFull = s; SentinelVersion version = new SentinelVersion(); - String[] postArr = s.split("-"); - if (postArr.length > 1) { - version.setPostfix(postArr[1]); + + // postfix + int index = versionFull.indexOf("-"); + if (index == 0) { + // Start with "-" + return Optional.empty(); } - String[] arr = postArr[0].split("\\."); - if (arr.length == 2) { - version.setMajorVersion(Integer.valueOf(arr[0])) - .setMinorVersion(Integer.valueOf(arr[1])) - .setFixVersion(0); - } else if (arr.length == 3) { - version.setMajorVersion(Integer.valueOf(arr[0])) - .setMinorVersion(Integer.valueOf(arr[1])) - .setFixVersion(Integer.valueOf(arr[2])); - } else { + if (index == versionFull.length() - 1) { + // End with "-" + } else if (index > 0) { + version.setPostfix(versionFull.substring(index + 1)); + } + + if (index >= 0) { + versionFull = versionFull.substring(0, index); + } + + // x.x.x + int segment = 0; + int[] ver = new int[3]; + while (segment < ver.length) { + index = versionFull.indexOf('.'); + if (index < 0) { + if (versionFull.length() > 0) { + ver[segment] = Integer.valueOf(versionFull); + } + break; + } + ver[segment] = Integer.valueOf(versionFull.substring(0, index)); + versionFull = versionFull.substring(index + 1); + segment ++; + } + + if (ver[0] < 1) { // Wrong format, return empty. return Optional.empty(); + } else { + return Optional.of(version + .setMajorVersion(ver[0]) + .setMinorVersion(ver[1]) + .setFixVersion(ver[2])); } - return Optional.of(version); } catch (Exception ex) { // Parse fail, return empty. return Optional.empty(); diff --git a/sentinel-dashboard/src/test/java/com/alibaba/csp/sentinel/dashboard/datasource/entity/SentinelVersionTest.java b/sentinel-dashboard/src/test/java/com/alibaba/csp/sentinel/dashboard/datasource/entity/SentinelVersionTest.java new file mode 100644 index 00000000..ce287b2f --- /dev/null +++ b/sentinel-dashboard/src/test/java/com/alibaba/csp/sentinel/dashboard/datasource/entity/SentinelVersionTest.java @@ -0,0 +1,33 @@ +package com.alibaba.csp.sentinel.dashboard.datasource.entity; + +import static org.junit.Assert.*; + +import org.junit.Test; + +public class SentinelVersionTest { + @Test + public void testEqual() { + assertEquals(new SentinelVersion(1, 0, 0), new SentinelVersion(1, 0, 0)); + assertNotEquals(new SentinelVersion(1, 0, 0), new SentinelVersion(1, 2, 3)); + assertNotEquals(new SentinelVersion(1, 0, 0), new SentinelVersion(1, 0, 0, "")); + assertEquals(new SentinelVersion(1, 0, 0, ""), new SentinelVersion(1, 0, 0, "")); + assertNotEquals(new SentinelVersion(1, 0, 0, ""), new SentinelVersion(1, 0, 0, null)); + assertEquals(new SentinelVersion(1, 0, 0, null), new SentinelVersion(1, 0, 0, null)); + } + + @Test + public void testGreater() { + assertTrue(new SentinelVersion(2, 0, 0).greaterThan(new SentinelVersion(1, 0, 0))); + assertTrue(new SentinelVersion(1, 1, 0).greaterThan(new SentinelVersion(1, 0, 0))); + assertTrue(new SentinelVersion(1, 1, 2).greaterThan(new SentinelVersion(1, 1, 0))); + assertTrue(new SentinelVersion(1, 1, 4).greaterThan(new SentinelVersion(1, 1, 3))); + assertFalse(new SentinelVersion(1, 0, 0).greaterThan(new SentinelVersion(1, 0, 0))); + assertFalse(new SentinelVersion(1, 0, 0).greaterThan(new SentinelVersion(1, 1, 0))); + assertFalse(new SentinelVersion(1, 1, 3).greaterThan(new SentinelVersion(1, 1, 3))); + assertFalse(new SentinelVersion(1, 1, 2).greaterThan(new SentinelVersion(1, 1, 3))); + assertFalse(new SentinelVersion(1, 0, 0, "").greaterThan(new SentinelVersion(1, 0, 0))); + assertTrue(new SentinelVersion(1, 0, 1).greaterThan(new SentinelVersion(1, 0, 0))); + assertTrue(new SentinelVersion(1, 0, 1, "a").greaterThan(new SentinelVersion(1, 0, 0, "b"))); + assertFalse(new SentinelVersion(1, 0, 0, "b").greaterThan(new SentinelVersion(1, 0, 0, "a"))); + } +} diff --git a/sentinel-dashboard/src/test/java/com/alibaba/csp/sentinel/dashboard/util/VersionUtilsTest.java b/sentinel-dashboard/src/test/java/com/alibaba/csp/sentinel/dashboard/util/VersionUtilsTest.java new file mode 100644 index 00000000..ce297c45 --- /dev/null +++ b/sentinel-dashboard/src/test/java/com/alibaba/csp/sentinel/dashboard/util/VersionUtilsTest.java @@ -0,0 +1,89 @@ +package com.alibaba.csp.sentinel.dashboard.util; + +import static org.junit.Assert.*; + +import java.util.Optional; + +import org.junit.Test; + +import com.alibaba.csp.sentinel.dashboard.datasource.entity.SentinelVersion; + +public class VersionUtilsTest { + @Test + public void test() { + Optional version = VersionUtils.parseVersion("1.2.3"); + assertTrue(version.isPresent()); + assertEquals(1, version.get().getMajorVersion()); + assertEquals(2, version.get().getMinorVersion()); + assertEquals(3, version.get().getFixVersion()); + assertNull(version.get().getPostfix()); + + version = VersionUtils.parseVersion("1.2"); + assertTrue(version.isPresent()); + assertEquals(1, version.get().getMajorVersion()); + assertEquals(2, version.get().getMinorVersion()); + assertEquals(0, version.get().getFixVersion()); + assertNull(version.get().getPostfix()); + + version = VersionUtils.parseVersion("1."); + assertTrue(version.isPresent()); + assertEquals(1, version.get().getMajorVersion()); + assertEquals(0, version.get().getMinorVersion()); + assertEquals(0, version.get().getFixVersion()); + assertNull(version.get().getPostfix()); + + version = VersionUtils.parseVersion("1.2."); + assertTrue(version.isPresent()); + assertEquals(1, version.get().getMajorVersion()); + assertEquals(2, version.get().getMinorVersion()); + assertEquals(0, version.get().getFixVersion()); + assertNull(version.get().getPostfix()); + + version = VersionUtils.parseVersion("1.2.3."); + assertTrue(version.isPresent()); + assertEquals(1, version.get().getMajorVersion()); + assertEquals(2, version.get().getMinorVersion()); + assertEquals(3, version.get().getFixVersion()); + assertNull(version.get().getPostfix()); + + version = VersionUtils.parseVersion("1.2.3.4"); + assertTrue(version.isPresent()); + assertEquals(1, version.get().getMajorVersion()); + assertEquals(2, version.get().getMinorVersion()); + assertEquals(3, version.get().getFixVersion()); + assertNull(version.get().getPostfix()); + + version = VersionUtils.parseVersion("1"); + assertTrue(version.isPresent()); + assertEquals(1, version.get().getMajorVersion()); + assertEquals(0, version.get().getMinorVersion()); + assertEquals(0, version.get().getFixVersion()); + assertNull(version.get().getPostfix()); + + version = VersionUtils.parseVersion("1.2.3-"); + assertTrue(version.isPresent()); + assertEquals(1, version.get().getMajorVersion()); + assertEquals(2, version.get().getMinorVersion()); + assertEquals(3, version.get().getFixVersion()); + assertNull(version.get().getPostfix()); + + version = VersionUtils.parseVersion("-"); + assertFalse(version.isPresent()); + + version = VersionUtils.parseVersion("-t"); + assertFalse(version.isPresent()); + + version = VersionUtils.parseVersion(""); + assertFalse(version.isPresent()); + + version = VersionUtils.parseVersion(null); + assertFalse(version.isPresent()); + + version = VersionUtils.parseVersion("1.2.3-SNAPSHOTS"); + assertTrue(version.isPresent()); + assertEquals(1, version.get().getMajorVersion()); + assertEquals(2, version.get().getMinorVersion()); + assertEquals(3, version.get().getFixVersion()); + assertEquals("SNAPSHOTS", version.get().getPostfix()); + } +}