package org.jetbrains.ruby.codeInsight.types.storage.server;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3Client;
import com.amazonaws.services.s3.model.*;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import javafx.util.Pair;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.ruby.codeInsight.types.signature.*;
import java.io.*;
import java.sql.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class RSignatureStorageServerImpl extends RSignatureStorageServer {
@NotNull
private static final String EXPORT_BUCKET_NAME = "ruby-type-stat-export";
@NotNull
private static final String IMPORT_BUCKET_NAME = "ruby-type-stat-import";
@NotNull
private final AmazonS3 myClient = new AmazonS3Client();
@Nullable
private final Connection myConnection;
public RSignatureStorageServerImpl() throws SQLException, ClassNotFoundException {
myConnection = null;
}
public RSignatureStorageServerImpl(@NotNull final String host, @NotNull final String port,
@NotNull final String login, @NotNull final String password,
@NotNull final String dbName) throws SQLException, ClassNotFoundException {
myConnection = getConnectionToStorage(host, port, login, password, dbName);
}
@Override
@NotNull
public List<Pair<String, String>> getGemNamesAndVersionsFromStorage() throws SQLException {
if (myConnection == null) {
throw new SQLException();
}
final String sql = "SELECT DISTINCT gem_name, gem_version FROM rsignature " +
"WHERE gem_name <> '' AND gem_version <> ''";
final List<Pair<String, String>> gemNamesAndVersions = new ArrayList<>();
try (final Statement statement = myConnection.createStatement()) {
final ResultSet resultSet = statement.executeQuery(sql);
while (resultSet.next()) {
gemNamesAndVersions.add(new Pair<>(resultSet.getString("gem_name"),
resultSet.getString("gem_version")));
}
}
return gemNamesAndVersions;
}
@Override
@NotNull
public List<RSignature> getSignaturesFromStorage(@NotNull final String gemName, @NotNull final String gemVersion)
throws SQLException, ClassNotFoundException {
final String sql = String.format("SELECT * FROM rsignature WHERE gem_name = '%s' AND gem_version = '%s'",
gemName, gemVersion);
return executeQuery(sql);
}
@Override
public void insertSignaturesToStorage(@NotNull final List<RSignature> signatures) throws SQLException {
if (myConnection == null) {
throw new SQLException();
}
final List<String> sqls = signatures.stream()
.map(RSignatureStorageServerImpl::signatureToSqlString)
.collect(Collectors.toList());
try (final Statement statement = myConnection.createStatement()) {
for (final String sql : sqls) {
statement.execute(sql);
}
}
}
@Override
public List<RSignature> getSignaturesFromStatFile(@NotNull final String statFileName,
final boolean fromExportBucket)
throws IOException {
if (myClient.doesObjectExist(fromExportBucket ? EXPORT_BUCKET_NAME : IMPORT_BUCKET_NAME, statFileName)) {
final GetObjectRequest request = new GetObjectRequest(fromExportBucket ? EXPORT_BUCKET_NAME : IMPORT_BUCKET_NAME,
statFileName);
final S3Object s3object = myClient.getObject(request);
final Gson gson = new Gson();
try (final BufferedReader reader = new BufferedReader(new InputStreamReader(s3object.getObjectContent()))) {
return gson.fromJson(reader, new TypeToken<List<RSignature>>() {
}.getType());
}
}
return new ArrayList<>();
}
@Override
public void insertSignaturesToStatFile(@NotNull final List<RSignature> signatures,
@NotNull final String statFileName,
final boolean toExportBucket) {
final Gson gson = new GsonBuilder().create();
final String json = gson.toJson(signatures);
final InputStream jsonStream = new ByteArrayInputStream(json.getBytes());
final ObjectMetadata metadata = new ObjectMetadata();
metadata.setContentType("application/json");
myClient.putObject(toExportBucket ? EXPORT_BUCKET_NAME : IMPORT_BUCKET_NAME,
statFileName, jsonStream, metadata);
}
@Override
@NotNull
public List<StatFileInfo> getStatFileInfos(final boolean fromExportBucket) {
final List<StatFileInfo> statFileInfos = new ArrayList<>();
final ListObjectsV2Request request = new ListObjectsV2Request()
.withBucketName(fromExportBucket ? EXPORT_BUCKET_NAME : IMPORT_BUCKET_NAME);
ListObjectsV2Result result;
do {
result = myClient.listObjectsV2(request);
result.getObjectSummaries().forEach(s ->
statFileInfos.add(new StatFileInfo(s.getKey(), s.getLastModified().getTime())));
request.setContinuationToken(result.getNextContinuationToken());
} while (result.isTruncated());
return statFileInfos;
}
@Override
public void deleteStatFile(@NotNull final String statFileName, final boolean fromExportBucket) {
myClient.deleteObject(fromExportBucket ? EXPORT_BUCKET_NAME : IMPORT_BUCKET_NAME, statFileName);
}
@NotNull
private List<RSignature> executeQuery(@NotNull final String sql) throws SQLException, ClassNotFoundException {
if (myConnection == null) {
throw new SQLException();
}
final List<RSignature> signatures = new ArrayList<>();
try (final Statement statement = myConnection.createStatement()) {
final ResultSet resultSet = statement.executeQuery(sql);
while (resultSet.next()) {
final GemInfo gemInfo = GemInfoKt.GemInfo(resultSet.getString("gem_name"),
resultSet.getString("gem_version"));
final RSignature signature = new RSignature(
MethodInfoKt.MethodInfo(ClassInfoKt.ClassInfo(gemInfo, resultSet.getString("receiver_name")),
resultSet.getString("method_name"),
RVisibility.valueOf(resultSet.getString("visibility"))),
parseArgsInfo(resultSet.getString("args_info")),
Arrays.asList(resultSet.getString("args_type_name").split(";")),
resultSet.getString("return_type_name"));
signatures.add(signature);
}
}
return signatures;
}
@NotNull
private static List<ParameterInfo> parseArgsInfo(@NotNull final String argsInfoSerialized) {
return Arrays.stream(argsInfoSerialized.split(";"))
.map(argInfo -> Arrays.asList(argInfo.split(",")))
.filter(list -> list.size() == 3)
.map(argInfo -> new ParameterInfo(argInfo.get(1),
ParameterInfo.Type.valueOf(argInfo.get(0).toUpperCase())))
.collect(Collectors.toList());
}
@NotNull
private static String signatureToSqlString(@NotNull final RSignature signature) {
final String argsInfoSerialized = signature.getArgsInfo().stream()
.map(argInfo -> String.join(",", argInfo.getModifier().toString().toLowerCase(), argInfo.getName()))
.collect(Collectors.joining(";"));
final MethodInfo methodInfo = signature.getMethodInfo();
final ClassInfo classInfo = methodInfo.getClassInfo();
return String.format("INSERT INTO rsignature values('%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', FALSE) " +
"ON CONFLICT DO NOTHING;", methodInfo.getName(), classInfo.getClassFQN(),
methodInfo.getVisibility(), String.join(";", signature.getArgsTypes()), argsInfoSerialized,
signature.getReturnTypeName(), classInfo.getGemInfo().getName(), classInfo.getGemInfo().getVersion());
}
@NotNull
private static Connection getConnectionToStorage(@NotNull final String host, @NotNull final String port,
@NotNull final String login, @NotNull final String password,
@NotNull final String dbName)
throws SQLException, ClassNotFoundException {
final String url = String.format("jdbc:postgresql://%s:%s/%s", host, port, dbName);
Class.forName("org.postgresql.Driver");
return DriverManager.getConnection(url, login, password);
}
}