Skip to content
This repository was archived by the owner on Dec 19, 2022. It is now read-only.

Commit 550570b

Browse files
Merge pull request #1 from ScriptQL/feat/connection-manager
feat: create connection manager endpoint
2 parents d0857b2 + c35a2f6 commit 550570b

12 files changed

+320
-1
lines changed

src/main/java/com/scriptql/scriptqlapi/ScriptqlApiApplication.java

+7
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package com.scriptql.scriptqlapi;
22

3+
import com.scriptql.scriptqlapi.utils.Snowflake;
34
import org.springframework.boot.SpringApplication;
45
import org.springframework.boot.autoconfigure.SpringBootApplication;
6+
import org.springframework.context.annotation.Bean;
57

68
@SpringBootApplication
79
public class ScriptqlApiApplication {
@@ -10,4 +12,9 @@ public static void main(String[] args) {
1012
SpringApplication.run(ScriptqlApiApplication.class, args);
1113
}
1214

15+
@Bean
16+
public Snowflake snowflake() {
17+
return new Snowflake();
18+
}
19+
1320
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.scriptql.scriptqlapi.advice;
2+
3+
import lombok.AllArgsConstructor;
4+
import lombok.Data;
5+
6+
@Data
7+
@AllArgsConstructor
8+
public class RestError {
9+
private int code;
10+
private String message;
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package com.scriptql.scriptqlapi.advice;
2+
3+
import com.fasterxml.jackson.databind.ObjectMapper;
4+
import org.springframework.http.HttpHeaders;
5+
import org.springframework.http.ResponseEntity;
6+
import org.springframework.web.bind.annotation.ControllerAdvice;
7+
import org.springframework.web.bind.annotation.ExceptionHandler;
8+
import org.springframework.web.context.request.WebRequest;
9+
10+
import java.io.IOException;
11+
12+
@ControllerAdvice
13+
public class RestErrorAdviser {
14+
private final HttpHeaders httpHeaders = new HttpHeaders();
15+
private static final ObjectMapper objectMapper = new ObjectMapper();
16+
17+
public RestErrorAdviser() {
18+
httpHeaders.add("Content-Type", "application/json");
19+
}
20+
21+
public static String createError(int code, String message) {
22+
try {
23+
RestError restError = new RestError(code, message);
24+
return objectMapper.writeValueAsString(restError);
25+
} catch (IOException ex) {
26+
return message;
27+
}
28+
}
29+
30+
@ExceptionHandler(value = IllegalArgumentException.class)
31+
protected ResponseEntity<Object> handleIllegalArgument(IllegalArgumentException ex, WebRequest request) {
32+
return ResponseEntity.badRequest()
33+
.headers(httpHeaders)
34+
.body(createError(400, ex.getMessage()));
35+
}
36+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.scriptql.scriptqlapi.config;
2+
3+
import com.scriptql.scriptqlapi.repositories.DatabaseConnectionRepository;
4+
import org.springframework.boot.autoconfigure.domain.EntityScan;
5+
import org.springframework.context.annotation.Configuration;
6+
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
7+
8+
@Configuration
9+
@EntityScan("com.scriptql.scriptqlapi.entities")
10+
@EnableJpaRepositories(basePackageClasses = DatabaseConnectionRepository.class)
11+
public class PostgresConfig {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package com.scriptql.scriptqlapi.controllers;
2+
3+
import com.scriptql.scriptqlapi.entities.DatabaseConnection;
4+
import com.scriptql.scriptqlapi.repositories.DatabaseConnectionRepository;
5+
import com.scriptql.scriptqlapi.services.ConnectionManagerService;
6+
import org.springframework.web.bind.annotation.*;
7+
8+
import java.util.List;
9+
10+
@RestController
11+
@RequestMapping("/connection_manager")
12+
public class ConnectionManagerController {
13+
private final DatabaseConnectionRepository repository;
14+
private final ConnectionManagerService service;
15+
16+
public ConnectionManagerController(DatabaseConnectionRepository repository, ConnectionManagerService service) {
17+
this.repository = repository;
18+
this.service = service;
19+
}
20+
21+
@GetMapping()
22+
public List<DatabaseConnection> list() {
23+
return repository.findAll();
24+
}
25+
26+
@PostMapping()
27+
public DatabaseConnection create(@RequestBody DatabaseConnection request) {
28+
return service.create(request);
29+
}
30+
31+
@PatchMapping()
32+
public DatabaseConnection update(@RequestBody DatabaseConnection request) {
33+
return service.update(request);
34+
}
35+
36+
@DeleteMapping("/{id:[0-9]+}")
37+
public DatabaseConnection delete(@PathVariable("id") long id) {
38+
return service.delete(id);
39+
}
40+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package com.scriptql.scriptqlapi.entities;
2+
3+
import com.scriptql.scriptqlapi.enums.DatabaseDriver;
4+
import lombok.Data;
5+
6+
import javax.persistence.*;
7+
8+
@Data
9+
@Table(name = "database_connection")
10+
@Entity
11+
public class DatabaseConnection {
12+
@Id
13+
private long id;
14+
private String host;
15+
private String database;
16+
private String username;
17+
private String password;
18+
@Enumerated(EnumType.STRING)
19+
private DatabaseDriver driver;
20+
private int port;
21+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package com.scriptql.scriptqlapi.enums;
2+
3+
public enum DatabaseDriver {
4+
POSTGRES,
5+
MYSQL
6+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package com.scriptql.scriptqlapi.repositories;
2+
3+
4+
import com.scriptql.scriptqlapi.entities.DatabaseConnection;
5+
import org.springframework.data.jpa.repository.JpaRepository;
6+
7+
public interface DatabaseConnectionRepository extends JpaRepository<DatabaseConnection, Long> {
8+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package com.scriptql.scriptqlapi.services;
2+
3+
import com.scriptql.scriptqlapi.entities.DatabaseConnection;
4+
import com.scriptql.scriptqlapi.repositories.DatabaseConnectionRepository;
5+
import com.scriptql.scriptqlapi.utils.Snowflake;
6+
import org.springframework.stereotype.Service;
7+
8+
@Service
9+
public class ConnectionManagerService {
10+
private final DatabaseConnectionRepository repository;
11+
private final Snowflake snowflake;
12+
13+
public ConnectionManagerService(DatabaseConnectionRepository repository, Snowflake snowflake) {
14+
this.repository = repository;
15+
this.snowflake = snowflake;
16+
}
17+
18+
public DatabaseConnection create(DatabaseConnection databaseConnection) {
19+
if (databaseConnection.getDatabase() == null || databaseConnection.getDatabase().isBlank()) {
20+
throw new IllegalArgumentException("Invalid database name");
21+
} else if (databaseConnection.getHost() == null || databaseConnection.getHost().isBlank()) {
22+
throw new IllegalArgumentException("Invalid host name");
23+
} else if (databaseConnection.getPassword() == null || databaseConnection.getPassword().isBlank()) {
24+
throw new IllegalArgumentException("Invalid password");
25+
} else if (databaseConnection.getPort() < 0 || databaseConnection.getPort() > 65_534) {
26+
throw new IllegalArgumentException("Port must be valid");
27+
} else if (databaseConnection.getDriver() == null) {
28+
throw new IllegalArgumentException("Invalid driver");
29+
}
30+
31+
databaseConnection.setId(snowflake.next());
32+
return repository.save(databaseConnection);
33+
}
34+
35+
public DatabaseConnection update(DatabaseConnection request) {
36+
if (repository.findById(request.getId()).isPresent()) {
37+
return repository.save(request);
38+
}
39+
40+
throw new IllegalArgumentException("Database Connection not found");
41+
}
42+
43+
public DatabaseConnection delete(long id) {
44+
DatabaseConnection conn = repository.findById(id)
45+
.orElseThrow(() -> new IllegalArgumentException("Database Connection not found"));
46+
47+
repository.delete(conn);
48+
49+
return conn;
50+
}
51+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
package com.scriptql.scriptqlapi.utils;
2+
3+
import java.net.NetworkInterface;
4+
import java.security.SecureRandom;
5+
import java.time.Instant;
6+
import java.util.Enumeration;
7+
8+
/**
9+
* Distributed Sequence Generator.
10+
* Inspired by Twitter snowflake: https://github.com/twitter/snowflake/tree/snowflake-2010
11+
* <p>
12+
* Modified from https://github.com/callicoder/java-snowflake
13+
* <p>
14+
* This class should be used as a Singleton.
15+
* Make sure that you create and reuse a Single instance of Snowflake per node in your distributed system cluster.
16+
*/
17+
public class Snowflake {
18+
19+
// (2^42)-1 = 4398046511103 + EPOCH = Will die on Thu May 15 2160 07:35:11Z
20+
private static final int EPOCH_BITS = 42;
21+
// Max 1024 Nodes
22+
private static final int NODE_ID_BITS = 10;
23+
// Max 4096 Increments per Millis
24+
private static final int SEQUENCE_BITS = 12;
25+
26+
private static final long maxNodeId = (1L << NODE_ID_BITS) - 1;
27+
private static final long maxSequence = (1L << SEQUENCE_BITS) - 1;
28+
29+
// Custom Epoch (January 1, 2021 Midnight UTC = 2021-01-01T00:00:00Z)
30+
private static final long DEFAULT_CUSTOM_EPOCH = 1609459200000L;
31+
32+
private final long nodeId;
33+
private final long customEpoch;
34+
35+
private volatile long lastTimestamp = -1L;
36+
private volatile long sequence = 0L;
37+
38+
// Let Snowflake generate a nodeId
39+
public Snowflake() {
40+
this.nodeId = createNodeId();
41+
this.customEpoch = DEFAULT_CUSTOM_EPOCH;
42+
}
43+
44+
public synchronized long next() {
45+
long currentTimestamp = timestamp();
46+
if (currentTimestamp < lastTimestamp) {
47+
throw new IllegalStateException("Invalid System Clock!");
48+
}
49+
if (currentTimestamp == lastTimestamp) {
50+
sequence = (sequence + 1) & maxSequence;
51+
if (sequence == 0) {
52+
// Sequence Exhausted, wait till next millisecond.
53+
currentTimestamp = waitNextMillis(currentTimestamp);
54+
}
55+
} else {
56+
// reset sequence to start with zero for the next millisecond
57+
sequence = 0;
58+
}
59+
lastTimestamp = currentTimestamp;
60+
return currentTimestamp << (NODE_ID_BITS + SEQUENCE_BITS)
61+
| (nodeId << SEQUENCE_BITS)
62+
| sequence;
63+
}
64+
65+
// Get current timestamp in milliseconds, adjust for the custom epoch.
66+
private long timestamp() {
67+
return Instant.now().toEpochMilli() - customEpoch;
68+
}
69+
70+
// Block and wait till next millisecond
71+
private long waitNextMillis(long currentTimestamp) {
72+
while (currentTimestamp == lastTimestamp) {
73+
currentTimestamp = timestamp();
74+
}
75+
return currentTimestamp;
76+
}
77+
78+
private long createNodeId() {
79+
long nodeId;
80+
try {
81+
StringBuilder sb = new StringBuilder();
82+
Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
83+
while (networkInterfaces.hasMoreElements()) {
84+
NetworkInterface networkInterface = networkInterfaces.nextElement();
85+
byte[] mac = networkInterface.getHardwareAddress();
86+
if (mac != null) {
87+
for (byte macPort : mac) {
88+
sb.append(String.format("%02X", macPort));
89+
}
90+
}
91+
}
92+
nodeId = sb.toString().hashCode();
93+
} catch (Exception ex) {
94+
nodeId = (new SecureRandom().nextInt());
95+
}
96+
nodeId = nodeId & maxNodeId;
97+
return nodeId;
98+
}
99+
100+
public long[] parse(long id) {
101+
long maskNodeId = ((1L << NODE_ID_BITS) - 1) << SEQUENCE_BITS;
102+
long maskSequence = (1L << SEQUENCE_BITS) - 1;
103+
104+
long timestamp = (id >> (NODE_ID_BITS + SEQUENCE_BITS)) + customEpoch;
105+
long nodeId = (id & maskNodeId) >> SEQUENCE_BITS;
106+
long sequence = id & maskSequence;
107+
108+
return new long[]{timestamp, nodeId, sequence};
109+
}
110+
111+
@Override
112+
public String toString() {
113+
return "Snowflake Settings [EPOCH_BITS=" + EPOCH_BITS + ", NODE_ID_BITS=" + NODE_ID_BITS
114+
+ ", SEQUENCE_BITS=" + SEQUENCE_BITS + ", CUSTOM_EPOCH=" + customEpoch
115+
+ ", NodeId=" + nodeId + "]";
116+
}
117+
118+
}

src/main/resources/application.properties

-1
This file was deleted.

src/main/resources/application.yml

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
spring:
2+
datasource:
3+
url: ''
4+
password: ''
5+
username: ''
6+
driver-class-name: org.postgresql.Driver
7+
jpa:
8+
database-platform: org.hibernate.dialect.PostgreSQL10Dialect
9+
show-sql: true
10+
hibernate:
11+
ddl-auto: create-drop

0 commit comments

Comments
 (0)