Skip to content

Commit b254edd

Browse files
committed
update demo app to use Oracle Database 23ai
1 parent 13fb5fa commit b254edd

File tree

11 files changed

+471
-221
lines changed

11 files changed

+471
-221
lines changed

graphviz-demo/README.md

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
An example Java web application based on [Micronaut](https://docs.micronaut.io/) which embeds Oracle's Graph
44
Visualization library. The server queries the graph data from an Oracle Database using
5-
[PGQL](https://pgql-lang.org/).
5+
SQL.
66

77
The key source files to look at are
88

@@ -14,13 +14,11 @@ The key source files to look at are
1414
## Pre-requisites
1515

1616
1. Oracle JDK 11 (or OpenJDK 11)
17-
2. A running Oracle Graph Server. Download [from oracle.com](https://www.oracle.com/database/technologies/spatialandgraph/property-graph-features/graph-server-and-client/graph-server-and-client-downloads.html)
18-
and install [as per documentation](https://docs.oracle.com/en/database/oracle/property-graph/22.4/spgdg/deploying-graph-visualization-application.html).
19-
3. A running Oracle Database (e.g. [Autonomous Database](https://www.oracle.com/autonomous-database/))
20-
4. Import the Human Resources dataset and create a PG View by running `./gradlew createHrDatasetAndPgView -Pjdbc_url=<jdbc_url> -Pusername=<username> -Ppassword=<password>`
17+
2. A running Oracle Database, version 23.4 or newer (e.g. [Autonomous Database](https://www.oracle.com/autonomous-database/))
18+
3. Import the Human Resources dataset and create a Property Graph by running `./gradlew createHrDatasetAndPropertyGraph -Pjdbc_url=<jdbc_url> -Pusername=<username> -Ppassword=<password>`
2119

2220
* Note: The user must have `GRANT CREATE SESSION, CREATE TABLE, CREATE VIEW, CREATE SEQUENCE grants on the Database`
23-
* Note 2: You can drop the HR dataset and the created PG View by running the following command: `./gradlew dropHrDatasetAndPgView -Pjdbc_url=<jdbc_url> -Pusername=<username> -Ppassword=<password>`
21+
* Note 2: You can drop the HR dataset and the created Property Graph by running the following command: `./gradlew dropHrDatasetAndPropertyGraph -Pjdbc_url=<jdbc_url> -Pusername=<username> -Ppassword=<password>`
2422

2523
## Usage
2624

@@ -29,31 +27,30 @@ The key source files to look at are
2927
3. Unzip the library into the `src/main/resources/public` directory. For example:
3028

3129
```
32-
unzip oracle-graph-visualization-library-23.1.0.zip -d src/main/resources/public/
30+
unzip oracle-graph-visualization-library-24.2.0.zip -d src/main/resources/public/
3331
```
3432

3533
4. Run the following command to start the example app locally:
3634

3735
```
38-
./gradlew run --args='-oracle.graph-server.url=<graph-server-url> -oracle.graph-server.jdbc-url=<jdbc-url> -oracle.graph-server.username=<username> -oracle.graph-server.password=<password>'
36+
./gradlew run --args='-oracle.jdbc-url=<jdbc-url> -oracle.username=<username> -oracle.password=<password>'
3937
```
4038

4139
with
4240

43-
* `<graph-server-url>` being the URL of the Graph Server, e.g. `https://myhost:7007`
44-
* `<jdbc-url>` being the JDBC URL of the Oracle Database the Graph Server should connect to, e.g. `jdbc:oracle:thin:@myhost:1521/orcl`
45-
* `<username>` being the Oracle Database username to authenticate the example application with the Graph Server, e.g. `scott`
46-
* `<password>` being the Oracle Database password to authenticate the example application with the Graph Server, e.g. `tiger`
41+
* `<jdbc-url>` being the JDBC URL of the Oracle Database should connect to, e.g. `jdbc:oracle:thin:@myhost:1521/orcl`
42+
* `<username>` being the Oracle Database username to authenticate the example application, e.g. `scott`
43+
* `<password>` being the Oracle Database password to authenticate the example application, e.g. `tiger`
4744

4845
Then open your browser at `http://localhost:8080`.
4946

50-
When you click on the <em>Query</em> button, a request is made to `/hr/neighbors`, which fetches the direct reports of
51-
the given employee (by default `SKING`) from the HR graph using a PGQL query.
47+
When you click on the <em>Query</em> button, a request is made to `/hr/directs`, which fetches the direct reports of
48+
the given employee (by default `SKING`) from the HR graph using a SQL query.
5249

5350
![](screenshot.jpg)
5451

55-
When you right-click on one of the resulting nodes and then select <em>Expand</em>, a request to `/hr/directs` is being
56-
made, which fetches the neighbors of that node via another PGQL query.
52+
When you right-click on one of the resulting nodes and then select <em>Expand</em>, a request to `/hr/neighbors` is being
53+
made, which fetches the neighbors of that node via another SQL query.
5754

5855
## Troubleshooting
5956

graphviz-demo/build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ micronaut {
4949
}
5050
}
5151

52-
task createHrDataSetAndPgView(type: JavaExec) {
52+
task createHrDataSetAndPropertyGraph(type: JavaExec) {
5353
outputs.upToDateWhen { false }
5454

5555
environment("JDBC_URL", jdbc_url)
@@ -59,7 +59,7 @@ task createHrDataSetAndPgView(type: JavaExec) {
5959
classpath = sourceSets.main.runtimeClasspath
6060
}
6161

62-
task dropHrDataSetAndPgView(type: JavaExec) {
62+
task dropHrDataSetAndPropertyGraph(type: JavaExec) {
6363
outputs.upToDateWhen { false }
6464

6565
environment("JDBC_URL", jdbc_url)
Lines changed: 33 additions & 155 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,19 @@
11
package com.oracle.example;
22

3-
import javax.security.auth.login.LoginException;
4-
5-
import java.util.ArrayList;
6-
import java.util.HashMap;
7-
import java.util.List;
8-
import java.util.Map;
3+
import java.nio.charset.StandardCharsets;
4+
import java.sql.Connection;
5+
import java.sql.DriverManager;
6+
import java.sql.PreparedStatement;
7+
import java.sql.ResultSet;
98

109
import io.micronaut.context.annotation.Value;
11-
import io.micronaut.http.HttpResponse;
12-
import io.micronaut.http.HttpStatus;
13-
import io.micronaut.http.MediaType;
1410
import io.micronaut.http.client.HttpClient;
15-
import io.micronaut.http.client.annotation.Client;
16-
import io.micronaut.http.client.exceptions.HttpClientResponseException;
1711
import jakarta.inject.Singleton;
1812

13+
import org.apache.commons.io.IOUtils;
1914
import org.slf4j.Logger;
2015
import org.slf4j.LoggerFactory;
2116

22-
import com.fasterxml.jackson.core.JsonProcessingException;
23-
import com.fasterxml.jackson.databind.ObjectMapper;
24-
import com.oracle.example.response.Response;
25-
import com.oracle.example.response.Result;
26-
import com.oracle.example.response.TokenResponse;
27-
28-
import static io.micronaut.http.HttpRequest.POST;
29-
import static io.micronaut.http.HttpRequest.PUT;
30-
3117
/**
3218
* Micronaut client wrapper around Oracle Graph Visualization REST endpoints.
3319
* See the Oracle Graph documentation
@@ -42,152 +28,44 @@ public class GraphClient {
4228

4329
HttpClient httpClient;
4430

45-
@Value("${oracle.graph-server.jdbc-url}")
31+
@Value("${oracle.jdbc-url}")
4632
String jdbcUrl;
4733

48-
@Value("${oracle.graph-server.username}")
34+
@Value("${oracle.username}")
4935
String username;
5036

51-
@Value("${oracle.graph-server.password}")
37+
@Value("${oracle.password}")
5238
String password;
5339

54-
// JSON parser
55-
private final static ObjectMapper objectMapper = new ObjectMapper();
56-
57-
// Authentication token;
58-
String token;
59-
60-
public GraphClient(@Client("${oracle.graph-server.url}") HttpClient httpClient) {
61-
this.httpClient = httpClient;
62-
}
63-
64-
public String query(String query) throws LoginException {
65-
return query(query, false);
66-
}
67-
68-
public String query(String query, boolean isRetry) throws LoginException {
69-
if (token == null) {
70-
login();
71-
}
72-
73-
log.info("POST /v2/runQuery");
74-
log.info("query {}", query);
75-
76-
List<String> statements = new ArrayList<>();
77-
statements.add(query);
78-
79-
Map<String, Object> payload = new HashMap<>();
80-
payload.put("driver", "PGQL_IN_DATABASE");
81-
payload.put("formatter", "GVT");
82-
payload.put("statements", statements);
83-
log.info("payload: {}", payload);
84-
85-
CharSequence token = this.token;
40+
private static final String anonymousFunction;
8641

42+
static {
8743
try {
88-
HttpResponse<String> response = httpClient.toBlocking().exchange(
89-
POST("/v2/runQuery", payload)
90-
.contentType(MediaType.APPLICATION_JSON)
91-
.accept(MediaType.APPLICATION_JSON)
92-
.bearerAuth(token),
93-
String.class);
94-
95-
log.info("received response - reading body");
96-
97-
String responseBody = response.getBody()
98-
.orElseThrow(() -> new InternalError("response body could not be converted to string"));
99-
log.info("response body: {}", responseBody);
100-
101-
Response json = objectMapper.readValue(responseBody, Response.class);
102-
Result result = json.getResults().get(0);
103-
104-
boolean responseIsSuccess = (boolean) result.getSuccess();
105-
106-
if (!responseIsSuccess) {
107-
String errorMsg = new StringBuilder("query failed. Response = ").append(result.getError())
108-
.toString();
109-
log.warn(errorMsg);
110-
throw new InternalError("could not get response from server");
111-
}
112-
113-
return (String) result.getResult();
114-
} catch (HttpClientResponseException e) {
115-
Object body = e.getResponse().body();
116-
if (e.getStatus() == HttpStatus.FORBIDDEN || e.getStatus() == HttpStatus.UNAUTHORIZED) {
117-
if (isRetry) {
118-
log.warn("authentication failed while trying to run query. Response = {}", body);
119-
throw new LoginException("authentication failed");
120-
}
121-
log.info("hit 401, refreshing token and retrying");
122-
refreshToken();
123-
return query(query, true);
124-
}
125-
if (e.getStatus() == HttpStatus.BAD_REQUEST) {
126-
throw new IllegalArgumentException(body != null ? body.toString() : "bad request");
127-
}
128-
log.warn("query failed. Response = {}", body);
129-
throw new InternalError("query failed");
130-
} catch (JsonProcessingException e) {
131-
log.warn("could not parse response", e);
132-
throw new InternalError("failed to parse response");
133-
}
134-
}
135-
136-
private void login() throws LoginException {
137-
log.info("POST /auth/token");
138-
log.info("login {}", username);
139-
140-
Map<String, Object> payload = new HashMap<>();
141-
payload.put("username", username);
142-
payload.put("password", password);
143-
payload.put("createSession", false);
144-
145-
try {
146-
HttpResponse<String> response = httpClient.toBlocking().exchange(
147-
POST("/auth/token", payload)
148-
.contentType(MediaType.APPLICATION_JSON)
149-
.accept(MediaType.APPLICATION_JSON),
150-
String.class);
151-
152-
TokenResponse json = objectMapper.readValue(
153-
response.getBody().orElseThrow(() -> new InternalError("response body could not be converted to string")),
154-
TokenResponse.class);
155-
token = json.getAccessToken();
156-
log.info("obtained token: {}", token);
157-
} catch (HttpClientResponseException e) {
158-
log.warn("login failed with status {}", e.getStatus(), e);
159-
throw new LoginException("login failed");
160-
} catch (JsonProcessingException e) {
161-
log.warn("could not parse server response", e);
162-
throw new LoginException("token refresh failed");
44+
anonymousFunction = IOUtils.toString(
45+
GraphClient.class.getResourceAsStream("/my_sqlgraph_json.sql"),
46+
StandardCharsets.UTF_8);
47+
} catch (Exception e) {
48+
log.error("Cannot locate anonymous function file");
49+
throw new RuntimeException(e);
16350
}
16451
}
16552

166-
private void refreshToken() throws LoginException {
167-
log.info("refreshToken {}", username);
168-
169-
Map<String, Object> payload = new HashMap<>();
170-
payload.put("token", token);
171-
payload.put("createSession", false);
172-
173-
try {
174-
HttpResponse<String> response = httpClient.toBlocking().exchange(
175-
PUT("/auth/token", payload)
176-
.contentType(MediaType.APPLICATION_JSON)
177-
.accept(MediaType.APPLICATION_JSON),
178-
String.class);
179-
180-
TokenResponse json = objectMapper.readValue(
181-
response.getBody().orElseThrow(() -> new InternalError("response body could not be converted to string")),
182-
TokenResponse.class);
183-
token = json.getAccessToken();
184-
log.info("obtained token: {}", token);
185-
} catch (HttpClientResponseException e) {
186-
log.warn("token refresh failed with status {}", e.getStatus(), e);
187-
throw new LoginException("token refresh failed");
188-
} catch (JsonProcessingException e) {
189-
log.warn("could not parse server response", e);
190-
throw new LoginException("token refresh failed");
53+
public String query(String query, String parameter) throws Exception {
54+
55+
String wrapQuery = anonymousFunction + "SELECT MY_SQLGRAPH_JSON('" + query + "') as result from dual";
56+
String resultString = null;
57+
try (Connection conn = DriverManager.getConnection(jdbcUrl, username, password)) {
58+
try (PreparedStatement ps = conn.prepareStatement(wrapQuery)) {
59+
ps.setString(1, parameter);
60+
log.info("running: {}", wrapQuery);
61+
log.info("replaced parameter: {}", parameter);
62+
try (ResultSet resultSet = ps.executeQuery()) {
63+
if (resultSet.next()) {
64+
resultString = resultSet.getString(1);
65+
}
66+
}
67+
}
19168
}
69+
return resultString;
19270
}
19371
}

graphviz-demo/src/main/java/com/oracle/example/HRController.java

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
package com.oracle.example;
22

3-
import javax.security.auth.login.LoginException;
4-
53
import java.util.Arrays;
64
import java.util.stream.Collectors;
75

@@ -32,19 +30,26 @@ public HRController(GraphClient graphClient) {
3230

3331
@Get("/directs")
3432
@Produces(MediaType.APPLICATION_JSON)
35-
public String getDirects(@QueryValue String email) throws LoginException {
36-
String pgql = "select e from match ()-[e]->(x:employees) on MYHR where x.email = '" + email + "'";
37-
log.info("running {}", pgql);
38-
return graphClient.query(pgql);
33+
public String getDirects(@QueryValue String email) throws Exception {
34+
String query = "SELECT * FROM GRAPH_TABLE(\n"
35+
+ "MYHR\n"
36+
+ "MATCH (m)-[e]->(n IS employees WHERE n.email =''' || ? ||''')\n"
37+
+ "COLUMNS (vertex_id(m) as m_id, edge_id(e) as e_id, vertex_id(n) as n_id)\n"
38+
+ ")";
39+
return graphClient.query(query, email);
3940
}
4041

4142
@Get("/neighbors")
4243
@Produces(MediaType.APPLICATION_JSON)
43-
public String getNeighbors(@QueryValue String ids) throws LoginException {
44+
public String getNeighbors(@QueryValue String ids) throws Exception {
4445
ids = Arrays.stream(ids.split(",")).map(String::trim).collect(Collectors.joining("','"));
45-
String pgql = "select e from match (x) -[e]-> () on MYHR where id(x) in ('" + ids + "')";
46-
log.info("running {}", pgql);
47-
return graphClient.query(pgql);
46+
String query = "SELECT * FROM GRAPH_TABLE(\n"
47+
+ "MYHR\n"
48+
+ "MATCH (m)-[e]->(n)\n"
49+
+ "WHERE JSON_value(vertex_id(m), ''$.ELEM_TABLE'') || json_query(vertex_id(m), ''$.KEY_VALUE'' returning varchar2) in (''' || ? || ''') \n"
50+
+ "COLUMNS (vertex_id(m) as m_id, edge_id(e) as e_id, vertex_id(n) as n_id)\n"
51+
+ ")";
52+
return graphClient.query(query, ids);
4853
}
4954

5055
@Error(global = true)

0 commit comments

Comments
 (0)