Skip to content

Commit 7a8b840

Browse files
Duality View builder
Signed-off-by: Anders Swanson <anders.swanson@oracle.com>
1 parent 7850d52 commit 7a8b840

File tree

16 files changed

+208
-108
lines changed

16 files changed

+208
-108
lines changed

database/starters/oracle-spring-boot-json-relational-duality-views/src/main/java/com/oracle/spring/json/duality/builder/Annotations.java

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,9 @@ static String getNestedViewName(Class<?> javaType,
4747
JsonRelationalDualityView dvAnnotation,
4848
Table tableAnnotation) {
4949
if (dvAnnotation != null && StringUtils.hasText(dvAnnotation.name())) {
50-
return dvAnnotation.name().toLowerCase();
50+
return dvAnnotation.name();
5151
}
52-
return getTableName(javaType, tableAnnotation).toLowerCase();
52+
return getTableName(javaType, tableAnnotation);
5353
}
5454

5555
public static String getViewName(Class<?> javaType, JsonRelationalDualityView dvAnnotation) {
@@ -92,21 +92,23 @@ static String getDatabaseColumnName(Field f) {
9292
return f.getName();
9393
}
9494

95-
static String getAccessModeStr(AccessMode accessMode) {
96-
if (accessMode == null) {
97-
return "";
98-
}
99-
95+
static String getAccessModeStr(AccessMode accessMode, ManyToMany manyToMany) {
10096
StringBuilder sb = new StringBuilder();
101-
if (accessMode.insert()) {
102-
sb.append("@insert ");
103-
}
104-
if (accessMode.update()) {
105-
sb.append("@update ");
97+
if (manyToMany != null) {
98+
sb.append("@unnest ");
10699
}
107-
if (accessMode.delete()) {
108-
sb.append("@delete ");
100+
if (accessMode != null) {
101+
if (accessMode.insert()) {
102+
sb.append("@insert ");
103+
}
104+
if (accessMode.update()) {
105+
sb.append("@update ");
106+
}
107+
if (accessMode.delete()) {
108+
sb.append("@delete ");
109+
}
109110
}
111+
110112
return sb.toString();
111113
}
112114
}

database/starters/oracle-spring-boot-json-relational-duality-views/src/main/java/com/oracle/spring/json/duality/builder/DualityViewBuilder.java

Lines changed: 59 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,11 @@
66
import java.sql.Connection;
77
import java.sql.SQLException;
88
import java.sql.Statement;
9-
import java.util.ArrayList;
10-
import java.util.List;
9+
import java.util.HashMap;
10+
import java.util.Map;
1111

1212
import com.oracle.spring.json.duality.annotation.JsonRelationalDualityView;
13+
import jakarta.annotation.PostConstruct;
1314
import javax.sql.DataSource;
1415
import org.springframework.beans.factory.DisposableBean;
1516
import org.springframework.boot.autoconfigure.orm.jpa.HibernateProperties;
@@ -22,11 +23,12 @@
2223
@Component
2324
public final class DualityViewBuilder implements DisposableBean {
2425
private static final String PREFIX = "JSON Relational Duality Views: ";
26+
private static final int TABLE_OR_VIEW_DOES_NOT_EXIST = 942;
2527

2628
private final DataSource dataSource;
2729
private final boolean isShowSql;
2830
private final RootSnippet rootSnippet;
29-
private final List<String> dualityViews = new ArrayList<>();
31+
private final Map<String, String> dualityViews = new HashMap<>();
3032

3133
public DualityViewBuilder(DataSource dataSource,
3234
JpaProperties jpaProperties,
@@ -38,38 +40,51 @@ public DualityViewBuilder(DataSource dataSource,
3840
);
3941
}
4042

41-
void apply(Class<?> javaType) {
42-
if (rootSnippet.equals(RootSnippet.NONE)) {
43-
return;
44-
}
45-
String ddl = build(javaType);
46-
if (isShowSql) {
47-
System.out.println(PREFIX + ddl);
48-
}
49-
if (rootSnippet.equals(RootSnippet.VALIDATE)) {
50-
// TODO: Handle view validation.
51-
return;
43+
void apply() {
44+
switch (rootSnippet) {
45+
case NONE -> {
46+
return;
47+
}
48+
case CREATE_DROP -> {
49+
try {
50+
createDrop();
51+
} catch (SQLException e) {
52+
throw new RuntimeException(e);
53+
}
54+
}
5255
}
5356

54-
runDDL(ddl);
57+
for (String ddl : dualityViews.values()) {
58+
if (isShowSql) {
59+
System.out.println(PREFIX + ddl);
60+
}
61+
if (rootSnippet.equals(RootSnippet.VALIDATE)) {
62+
// TODO: Handle view validation.
63+
return;
64+
}
65+
66+
runDDL(ddl);
67+
}
5568
}
5669

57-
String build(Class<?> javaType) {
70+
public String build(Class<?> javaType) {
5871
JsonRelationalDualityView dvAnnotation = javaType.getAnnotation(JsonRelationalDualityView.class);
5972
if (dvAnnotation == null) {
6073
throw new IllegalArgumentException("%s not found for type %s".formatted(
6174
JsonRelationalDualityView.class.getSimpleName(), javaType.getName())
6275
);
6376
}
6477
String viewName = getViewName(javaType, dvAnnotation);
65-
String accessMode = getAccessModeStr(dvAnnotation.accessMode());
78+
String accessMode = getAccessModeStr(dvAnnotation.accessMode(), null);
6679
ViewEntity ve = new ViewEntity(javaType,
6780
new StringBuilder(),
6881
rootSnippet,
6982
accessMode,
7083
viewName,
7184
0);
72-
return ve.build().toString();
85+
String ddl = ve.build().toString();
86+
dualityViews.put(viewName, ddl);
87+
return ddl;
7388
}
7489

7590
private void runDDL(String ddl) {
@@ -81,17 +96,38 @@ private void runDDL(String ddl) {
8196
}
8297
}
8398

99+
@PostConstruct
100+
public void init() throws SQLException {
101+
createDrop();
102+
}
84103

85104
@Override
86105
public void destroy() throws Exception {
106+
createDrop();
107+
}
108+
109+
private void createDrop() throws SQLException {
87110
if (rootSnippet.equals(RootSnippet.CREATE_DROP) && !dualityViews.isEmpty()) {
88-
final String dropView = """
89-
drop view %s
90-
""";
91111
try (Connection conn = dataSource.getConnection();
92112
Statement stmt = conn.createStatement()) {
93-
for (String view : dualityViews) {
94-
stmt.execute(dropView.formatted(view));
113+
dropViews(stmt);
114+
}
115+
}
116+
}
117+
118+
private void dropViews(Statement stmt) {
119+
final String dropView = "drop view %s";
120+
121+
for (String view : dualityViews.keySet()) {
122+
String dropStatement = dropView.formatted(view);
123+
if (isShowSql) {
124+
System.out.println(PREFIX + dropStatement);
125+
}
126+
try {
127+
stmt.execute(dropStatement);
128+
} catch (SQLException e) {
129+
if (e.getErrorCode() != TABLE_OR_VIEW_DOES_NOT_EXIST) {
130+
throw new RuntimeException(e);
95131
}
96132
}
97133
}

database/starters/oracle-spring-boot-json-relational-duality-views/src/main/java/com/oracle/spring/json/duality/builder/DualityViewScanner.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,21 +52,22 @@ private void applyDvScan(JsonRelationalDualityViewScan dvScan) {
5252
}
5353
}
5454
for (Class<?> javaType : dvScan.basePackageClasses()) {
55-
applyClass(javaType);
55+
addClass(javaType);
5656
}
57+
dualityViewBuilder.apply();
5758
}
5859

5960
private void scanPackage(String packageName) {
6061
Set<Class<?>> types = scanner.findTypes(packageName);
6162
for (Class<?> type : types) {
62-
applyClass(type);
63+
addClass(type);
6364
}
6465
}
6566

66-
private void applyClass(Class<?> javaType) {
67+
private void addClass(Class<?> javaType) {
6768
JsonRelationalDualityView dvAnnotation = javaType.getAnnotation(JsonRelationalDualityView.class);
6869
if (dvAnnotation != null) {
69-
dualityViewBuilder.apply(javaType);
70+
dualityViewBuilder.build(javaType);
7071
}
7172
}
7273
}

database/starters/oracle-spring-boot-json-relational-duality-views/src/main/java/com/oracle/spring/json/duality/builder/RootSnippet.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public static RootSnippet fromDdlAuto(String ddlAuto) {
2828
case "none" -> NONE;
2929
case "validate" -> VALIDATE;
3030
case "create" -> CREATE;
31-
case "create_drop" -> CREATE_DROP;
31+
case "create-drop" -> CREATE_DROP;
3232
case "update" -> UPDATE;
3333
default -> throw new IllegalStateException("Unexpected value: " + ddlAuto);
3434
};

database/starters/oracle-spring-boot-json-relational-duality-views/src/main/java/com/oracle/spring/json/duality/builder/ViewEntity.java

Lines changed: 37 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@
1212
import com.oracle.spring.json.duality.annotation.JsonRelationalDualityView;
1313
import jakarta.json.bind.annotation.JsonbProperty;
1414
import jakarta.persistence.Id;
15-
import jakarta.persistence.JoinColumn;
1615
import jakarta.persistence.JoinTable;
1716
import jakarta.persistence.ManyToMany;
1817
import jakarta.persistence.Table;
18+
import org.springframework.util.StringUtils;
1919

2020
import static com.oracle.spring.json.duality.builder.Annotations._ID_FIELD;
2121
import static com.oracle.spring.json.duality.builder.Annotations.getAccessModeStr;
@@ -27,10 +27,10 @@
2727
import static com.oracle.spring.json.duality.builder.Annotations.isFieldIncluded;
2828

2929
final class ViewEntity {
30-
3130
private static final String SEPARATOR = " : ";
3231
private static final String END_ENTITY = "}";
33-
private static final String BEGIN_ENTITY = " {\n";
32+
private static final String END_ARRAY_ENTITY = "} ]";
33+
private static final String BEGIN_ARRAY_ENTITY = "[ {\n";
3434
private static final int TAB_WIDTH = 2;
3535

3636
private final Class<?> javaType;
@@ -91,8 +91,11 @@ private String getStatementPrefix(Table tableAnnotation) {
9191

9292
private String getNestedEntityPrefix(Table tableAnnotation) {
9393
String tableName = getTableName(javaType, tableAnnotation);
94-
return "%s : %s %s{\n".formatted(
95-
viewName, tableName, accessMode
94+
if (tableName.equals(viewName)) {
95+
return "%s %s{\n".formatted(tableName, accessMode);
96+
}
97+
return "%s%s%s %s{\n".formatted(
98+
viewName, SEPARATOR, tableName, accessMode
9699
);
97100
}
98101

@@ -136,13 +139,13 @@ private void parseRelationalEntity(Field f, JsonRelationalDualityView dvAnnotati
136139
// Add join table if present.
137140
ManyToMany manyToMany = f.getAnnotation(ManyToMany.class);
138141
if (manyToMany != null) {
139-
parseManyToMany(manyToMany, f, entityJavaType);
142+
parseManyToMany(manyToMany, dvAnnotation, f, entityJavaType);
140143
}
141144
// Add nested entity.
142-
parseNestedEntity(entityJavaType, dvAnnotation);
145+
parseNestedEntity(entityJavaType, dvAnnotation, manyToMany);
143146
// Additional trailer for join table if present.
144147
if (manyToMany != null) {
145-
addTrailer(true);
148+
addTrailer(true, END_ARRAY_ENTITY);
146149
}
147150
}
148151

@@ -158,10 +161,10 @@ private Class<?> getGenericFieldType(Field f) {
158161
return f.getType();
159162
}
160163

161-
private void parseNestedEntity(Class<?> entityJavaType, JsonRelationalDualityView dvAnnotation) {
164+
private void parseNestedEntity(Class<?> entityJavaType, JsonRelationalDualityView dvAnnotation, ManyToMany manyToMany) {
162165
Table tableAnnotation = entityJavaType.getAnnotation(Table.class);
163-
String viewEntityName = getNestedViewName(entityJavaType, dvAnnotation, tableAnnotation);
164-
String accessMode = getAccessModeStr(dvAnnotation.accessMode());
166+
String viewEntityName = getNestedViewName(entityJavaType, manyToMany == null ? dvAnnotation : null, tableAnnotation);
167+
String accessMode = getAccessModeStr(dvAnnotation.accessMode(), manyToMany);
165168
ViewEntity ve = new ViewEntity(entityJavaType,
166169
new StringBuilder(),
167170
accessMode,
@@ -176,23 +179,19 @@ private void parseColumn(Field f) {
176179
addProperty(getJsonbPropertyName(f), getDatabaseColumnName(f));
177180
}
178181

179-
private void parseManyToMany(ManyToMany manyToMany, Field f, Class<?> entityJavaType) {
182+
private void parseManyToMany(ManyToMany manyToMany, JsonRelationalDualityView dvAnnotation, Field f, Class<?> entityJavaType) {
180183
JoinTable joinTable = getJoinTableAnnotation(f, manyToMany, entityJavaType);
181-
sb.append(getPadding());
182-
sb.append(joinTable.name());
183-
sb.append(BEGIN_ENTITY);
184-
incNesting();
185-
addJoinColumns(joinTable.joinColumns());
186-
addJoinColumns(joinTable.inverseJoinColumns());
187-
}
188-
189-
private void addJoinColumns(JoinColumn[] joinColumns) {
190-
for (JoinColumn joinColumn : joinColumns) {
191-
addProperty(joinColumn.name(), joinColumn.name());
184+
String propertyName = dvAnnotation.name();
185+
if (!StringUtils.hasText(propertyName)) {
186+
propertyName = getJsonbPropertyName(f);
192187
}
188+
addProperty(propertyName, joinTable.name(), false);
189+
sb.append(" ").append(getAccessModeStr(dvAnnotation.accessMode(), null));
190+
sb.append(BEGIN_ARRAY_ENTITY);
191+
incNesting();
193192
}
194193

195-
private void addProperty(String jsonbPropertyName, String databaseColumnName) {
194+
private void addProperty(String jsonbPropertyName, String databaseColumnName, boolean addNewLine) {
196195
sb.append(getPadding());
197196
if (jsonbPropertyName.equals(databaseColumnName)) {
198197
sb.append(jsonbPropertyName);
@@ -201,16 +200,26 @@ private void addProperty(String jsonbPropertyName, String databaseColumnName) {
201200
.append(SEPARATOR)
202201
.append(databaseColumnName);
203202
}
204-
sb.append("\n");
203+
if (addNewLine) {
204+
sb.append("\n");
205+
}
206+
}
207+
208+
private void addProperty(String jsonbPropertyName, String databaseColumnName) {
209+
addProperty(jsonbPropertyName, databaseColumnName, true);
210+
}
211+
212+
private void addTrailer(boolean addNewLine) {
213+
addTrailer(addNewLine, END_ENTITY);
205214
}
206215

207-
private void addTrailer(boolean addNewline) {
216+
private void addTrailer(boolean addNewLine, String terminal) {
208217
decNesting();
209218
if (nesting > 0) {
210219
sb.append(getPadding());
211220
}
212-
sb.append(END_ENTITY);
213-
if (addNewline) {
221+
sb.append(terminal);
222+
if (addNewLine) {
214223
sb.append("\n");
215224
}
216225
}

database/starters/oracle-spring-boot-json-relational-duality-views/src/test/java/com/oracle/spring/json/duality/JsonRelationalDualityClient.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public JsonRelationalDualityClient(JdbcClient jdbcClient, JSONB jsonb) {
2727
public <T> int save(T entity, Class<T> entityJavaType) {
2828
String viewName = getViewName(entityJavaType, entityJavaType.getAnnotation(JsonRelationalDualityView.class));
2929
final String sql = """
30-
insert into %s (data) values(?)
30+
insert into %s (data) values (?)
3131
""".formatted(viewName);
3232

3333
byte[] oson = jsonb.toOSON(entity);

0 commit comments

Comments
 (0)