/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.ignite.console.agent.rest;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.net.ConnectException;
import java.util.HashMap;
import java.util.Map;
import okhttp3.FormBody;
import okhttp3.HttpUrl;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import org.apache.ignite.console.demo.*;
import org.apache.ignite.internal.processors.rest.protocols.http.jetty.GridJettyObjectMapper;
import org.apache.log4j.Logger;
import static org.apache.ignite.internal.processors.rest.GridRestResponse.STATUS_AUTH_FAILED;
import static org.apache.ignite.internal.processors.rest.GridRestResponse.STATUS_FAILED;
import static org.apache.ignite.internal.processors.rest.GridRestResponse.STATUS_SUCCESS;
/**
* API to translate REST requests to Ignite cluster.
*/
public class RestExecutor {
/** */
private static final Logger log = Logger.getLogger(RestExecutor.class);
/** JSON object mapper. */
private static final ObjectMapper mapper = new GridJettyObjectMapper();
/** */
private final OkHttpClient httpClient;
/** Node URL. */
private String nodeUrl;
/**
* Default constructor.
*/
public RestExecutor(String nodeUrl) {
this.nodeUrl = nodeUrl;
httpClient = new OkHttpClient.Builder().build();
}
/**
* Stop HTTP client.
*/
public void stop() {
if (httpClient != null)
httpClient.dispatcher().executorService().shutdown();
}
/** */
private RestResult sendRequest(boolean demo, String path, Map<String, Object> params,
String mtd, Map<String, Object> headers, String body) throws IOException {
if (demo && AgentClusterDemo.getDemoUrl() == null) {
try {
AgentClusterDemo.tryStart().await();
}
catch (InterruptedException ignore) {
throw new IllegalStateException("Failed to execute request because of embedded node for demo mode is not started yet.");
}
}
String url = demo ? AgentClusterDemo.getDemoUrl() : nodeUrl;
HttpUrl.Builder urlBuilder = HttpUrl.parse(url)
.newBuilder();
if (path != null)
urlBuilder.addPathSegment(path);
final Request.Builder reqBuilder = new Request.Builder();
if (headers != null) {
for (Map.Entry<String, Object> entry : headers.entrySet())
if (entry.getValue() != null)
reqBuilder.addHeader(entry.getKey(), entry.getValue().toString());
}
if ("GET".equalsIgnoreCase(mtd)) {
if (params != null) {
for (Map.Entry<String, Object> entry : params.entrySet()) {
if (entry.getValue() != null)
urlBuilder.addQueryParameter(entry.getKey(), entry.getValue().toString());
}
}
}
else if ("POST".equalsIgnoreCase(mtd)) {
if (body != null) {
MediaType contentType = MediaType.parse("text/plain");
reqBuilder.post(RequestBody.create(contentType, body));
}
else {
FormBody.Builder formBody = new FormBody.Builder();
if (params != null) {
for (Map.Entry<String, Object> entry : params.entrySet()) {
if (entry.getValue() != null)
formBody.add(entry.getKey(), entry.getValue().toString());
}
}
reqBuilder.post(formBody.build());
}
}
else
throw new IllegalArgumentException("Unknown HTTP-method: " + mtd);
reqBuilder.url(urlBuilder.build());
try (Response resp = httpClient.newCall(reqBuilder.build()).execute()) {
String content = resp.body().string();
if (resp.isSuccessful()) {
JsonNode node = mapper.readTree(content);
int status = node.get("successStatus").asInt();
switch (status) {
case STATUS_SUCCESS:
return RestResult.success(node.get("response").toString());
default:
return RestResult.fail(status, node.get("error").asText());
}
}
if (resp.code() == 401)
return RestResult.fail(STATUS_AUTH_FAILED, "Failed to authenticate in grid. Please check agent\'s login and password or node port.");
return RestResult.fail(STATUS_FAILED, "Failed connect to node and execute REST command.");
}
catch (ConnectException ignore) {
throw new ConnectException("Failed connect to node and execute REST command [url=" + urlBuilder + "]");
}
}
/**
* @param demo Is demo node request.
* @param path Path segment.
* @param params Params.
* @param mtd Method.
* @param headers Headers.
* @param body Body.
*/
public RestResult execute(boolean demo, String path, Map<String, Object> params,
String mtd, Map<String, Object> headers, String body) {
log.debug("Start execute REST command [method=" + mtd + ", uri=/" + (path == null ? "" : path) +
", parameters=" + params + "]");
try {
return sendRequest(demo, path, params, mtd, headers, body);
}
catch (Exception e) {
log.info("Failed to execute REST command [method=" + mtd + ", uri=/" + (path == null ? "" : path) +
", parameters=" + params + "]", e);
return RestResult.fail(404, e.getMessage());
}
}
/**
* @param demo Is demo node request.
*/
public RestResult topology(boolean demo, boolean full) throws IOException {
Map<String, Object> params = new HashMap<>(3);
params.put("cmd", "top");
params.put("attr", full);
params.put("mtr", full);
return sendRequest(demo, "ignite", params, "GET", null, null);
}
}