Skip to content

Commit 793fffa

Browse files
Nested views
Signed-off-by: Anders Swanson <anders.swanson@oracle.com>
1 parent 976b801 commit 793fffa

22 files changed

+711
-158
lines changed

database/starters/oracle-spring-boot-json-relational-duality-views/pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,5 +88,11 @@
8888
<artifactId>spring-boot-testcontainers</artifactId>
8989
<scope>test</scope>
9090
</dependency>
91+
92+
<dependency>
93+
<groupId>org.projectlombok</groupId>
94+
<artifactId>lombok</artifactId>
95+
<scope>test</scope>
96+
</dependency>
9197
</dependencies>
9298
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.oracle.spring.json.duality.annotation;
2+
3+
import java.lang.annotation.Retention;
4+
import java.lang.annotation.RetentionPolicy;
5+
import java.lang.annotation.Target;
6+
7+
@Target({})
8+
@Retention(RetentionPolicy.RUNTIME)
9+
public @interface AccessMode {
10+
boolean insert() default false;
11+
boolean update() default false;
12+
boolean delete() default false;
13+
}
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package com.oracle.spring.json.duality.builder;
1+
package com.oracle.spring.json.duality.annotation;
22

33
import java.lang.annotation.Documented;
44
import java.lang.annotation.ElementType;
@@ -11,4 +11,6 @@
1111
@Retention(RetentionPolicy.RUNTIME)
1212
public @interface JsonRelationalDualityView {
1313
String name() default "";
14+
15+
AccessMode accessMode() default @AccessMode();
1416
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package com.oracle.spring.json.duality.annotation;
2+
3+
import java.lang.annotation.Documented;
4+
import java.lang.annotation.ElementType;
5+
import java.lang.annotation.Retention;
6+
import java.lang.annotation.RetentionPolicy;
7+
import java.lang.annotation.Target;
8+
9+
@Documented
10+
@Target({ElementType.FIELD})
11+
@Retention(RetentionPolicy.RUNTIME)
12+
public @interface JsonRelationalDualityViewEntity {
13+
String name() default "";
14+
Class<?> entity();
15+
16+
AccessMode accessMode() default @AccessMode();
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
package com.oracle.spring.json.duality.builder;
2+
3+
import java.lang.annotation.Annotation;
4+
import java.lang.reflect.Field;
5+
import java.util.Set;
6+
7+
import com.oracle.spring.json.duality.annotation.AccessMode;
8+
import com.oracle.spring.json.duality.annotation.JsonRelationalDualityView;
9+
import com.oracle.spring.json.duality.annotation.JsonRelationalDualityViewEntity;
10+
import jakarta.json.bind.annotation.JsonbProperty;
11+
import jakarta.persistence.Column;
12+
import jakarta.persistence.JoinTable;
13+
import jakarta.persistence.ManyToMany;
14+
import jakarta.persistence.ManyToOne;
15+
import jakarta.persistence.OneToMany;
16+
import jakarta.persistence.OneToOne;
17+
import jakarta.persistence.Table;
18+
import org.hibernate.mapping.Join;
19+
import org.springframework.util.StringUtils;
20+
21+
public final class Annotations {
22+
public static final String _ID_FIELD = "_id";
23+
24+
static final Set<Class<? extends Annotation>> RELATIONAL_ANNOTATIONS = Set.of(
25+
OneToMany.class,
26+
ManyToOne.class,
27+
OneToOne.class,
28+
ManyToMany.class
29+
);
30+
31+
static JoinTable getJoinTableAnnotation( Field f, ManyToMany manyToMany, Class<?> mappedType) {
32+
JoinTable annotation = f.getAnnotation(JoinTable.class);
33+
if (annotation != null) {
34+
return annotation;
35+
}
36+
37+
String mappedFieldName = manyToMany.mappedBy();
38+
if (!StringUtils.hasText(mappedFieldName)) {
39+
throw new IllegalArgumentException("Mapped field name is required for inverse join on field " + f.getName());
40+
}
41+
42+
for (Field field : mappedType.getDeclaredFields()) {
43+
if (field.getName().equals(mappedFieldName)) {
44+
JoinTable mappedJoinTable = field.getAnnotation(JoinTable.class);
45+
if (mappedJoinTable == null) {
46+
throw new IllegalArgumentException("Mapped field %s does has no JoinTable annotation".formatted(
47+
field.getName()
48+
));
49+
}
50+
return mappedJoinTable;
51+
}
52+
}
53+
throw new IllegalArgumentException("No JoinTable found for field " + f.getName());
54+
}
55+
56+
static String getViewEntityName(Class<?> javaType,
57+
JsonRelationalDualityViewEntity viewEntityAnnotation,
58+
Table tableAnnotation) {
59+
if (viewEntityAnnotation != null && StringUtils.hasText(viewEntityAnnotation.name())) {
60+
return viewEntityAnnotation.name().toLowerCase();
61+
}
62+
return getTableName(javaType, tableAnnotation).toLowerCase();
63+
}
64+
65+
static String getViewName(Class<?> javaType, JsonRelationalDualityView dvAnnotation) {
66+
Table tableAnnotation = javaType.getAnnotation(Table.class);
67+
final String suffix = "_dv";
68+
if (dvAnnotation != null && StringUtils.hasText(dvAnnotation.name())) {
69+
return dvAnnotation.name().toLowerCase();
70+
}
71+
if (tableAnnotation != null && StringUtils.hasText(tableAnnotation.name())) {
72+
return tableAnnotation.name().toLowerCase() + suffix;
73+
}
74+
return javaType.getName().toLowerCase() + suffix;
75+
}
76+
77+
static String getTableName(Class<?> javaType, Table tableAnnotation) {
78+
if (tableAnnotation != null && StringUtils.hasText(tableAnnotation.name())) {
79+
return tableAnnotation.name().toLowerCase();
80+
}
81+
return javaType.getName().toLowerCase();
82+
}
83+
84+
static boolean isRelationalEntity(Field f) {
85+
Annotation[] annotations = f.getAnnotations();
86+
for (Annotation annotation : annotations) {
87+
if (RELATIONAL_ANNOTATIONS.contains(annotation.annotationType())) {
88+
return true;
89+
}
90+
}
91+
92+
return false;
93+
}
94+
95+
96+
static String getJsonbPropertyName(Field f) {
97+
JsonbProperty jsonbProperty = f.getAnnotation(JsonbProperty.class);
98+
if (jsonbProperty == null || !StringUtils.hasText(jsonbProperty.value())) {
99+
return f.getName();
100+
}
101+
return jsonbProperty.value();
102+
}
103+
104+
static String getDatabaseColumnName(Field f) {
105+
Column column = f.getAnnotation(Column.class);
106+
if (column != null && StringUtils.hasText(column.name())) {
107+
return column.name();
108+
}
109+
return f.getName();
110+
}
111+
112+
static String getAccessModeStr(AccessMode accessMode) {
113+
if (accessMode == null) {
114+
return "";
115+
}
116+
117+
StringBuilder sb = new StringBuilder();
118+
if (accessMode.insert()) {
119+
sb.append("@insert ");
120+
}
121+
if (accessMode.update()) {
122+
sb.append("@update ");
123+
}
124+
if (accessMode.delete()) {
125+
sb.append("@delete ");
126+
}
127+
return sb.toString();
128+
}
129+
}

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

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,22 @@
55
import java.sql.Statement;
66
import java.util.ArrayList;
77
import java.util.List;
8+
import java.util.Optional;
89

10+
import com.oracle.spring.json.duality.annotation.JsonRelationalDualityView;
911
import javax.sql.DataSource;
1012
import org.springframework.beans.factory.DisposableBean;
1113
import org.springframework.boot.autoconfigure.orm.jpa.HibernateProperties;
1214
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
1315
import org.springframework.stereotype.Component;
1416

17+
import static com.oracle.spring.json.duality.builder.Annotations.getAccessModeStr;
18+
import static com.oracle.spring.json.duality.builder.Annotations.getViewName;
19+
1520
@Component
1621
public final class DualityViewBuilder implements DisposableBean {
22+
private static final String PREFIX = "JSON Relational Duality Views: ";
23+
1724
private final DataSource dataSource;
1825
private final boolean isShowSql;
1926
private final RootSnippet rootSnippet;
@@ -29,22 +36,40 @@ public DualityViewBuilder(DataSource dataSource,
2936
);
3037
}
3138

32-
void build(Class<?> javaType, JsonRelationalDualityView dvAnnotation) {
39+
void apply(Class<?> javaType) {
3340
if (rootSnippet.equals(RootSnippet.NONE)) {
3441
return;
3542
}
36-
ViewEntity ve = new ViewEntity(javaType, new StringBuilder(), rootSnippet, 0);
37-
String ddl = ve.build().toString();
43+
String ddl = build(javaType);
3844
if (isShowSql) {
39-
// TODO: log sql statement
45+
System.out.println(PREFIX + ddl);
4046
}
4147
if (rootSnippet.equals(RootSnippet.VALIDATE)) {
42-
// TODO: handle duality view validation
48+
// TODO: Handle view validation.
4349
return;
4450
}
51+
4552
runDDL(ddl);
4653
}
4754

55+
String build(Class<?> javaType) {
56+
JsonRelationalDualityView dvAnnotation = javaType.getAnnotation(JsonRelationalDualityView.class);
57+
if (dvAnnotation == null) {
58+
throw new IllegalArgumentException("%s not found for type %s".formatted(
59+
JsonRelationalDualityView.class.getSimpleName(), javaType.getName())
60+
);
61+
}
62+
String viewName = getViewName(javaType, dvAnnotation);
63+
String accessMode = getAccessModeStr(dvAnnotation.accessMode());
64+
ViewEntity ve = new ViewEntity(javaType,
65+
new StringBuilder(),
66+
rootSnippet,
67+
accessMode,
68+
viewName,
69+
0);
70+
return ve.build().toString();
71+
}
72+
4873
private void runDDL(String ddl) {
4974
try (Connection conn = dataSource.getConnection();
5075
Statement stmt = conn.createStatement()) {

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@
22

33
import java.util.Set;
44

5+
import com.oracle.spring.json.duality.annotation.JsonRelationalDualityView;
56
import jakarta.annotation.PostConstruct;
67
import jakarta.persistence.EntityManager;
78
import jakarta.persistence.metamodel.EntityType;
9+
import org.springframework.boot.context.event.ApplicationReadyEvent;
10+
import org.springframework.context.event.EventListener;
811
import org.springframework.stereotype.Component;
912

1013
@Component
@@ -17,16 +20,15 @@ public DualityViewScanner(DualityViewBuilder dualityViewBuilder, EntityManager e
1720
this.entityManager = entityManager;
1821
}
1922

20-
@PostConstruct
23+
@EventListener(ApplicationReadyEvent.class)
2124
public void scan() {
2225
Set<EntityType<?>> entities = entityManager.getMetamodel().getEntities();
2326
for (EntityType<?> entityType : entities) {
2427
Class<?> javaType = entityType.getJavaType();
2528
JsonRelationalDualityView dvAnnotation = javaType.getAnnotation(JsonRelationalDualityView.class);
2629
if (dvAnnotation != null) {
27-
dualityViewBuilder.build(javaType, dvAnnotation);
30+
dualityViewBuilder.apply(javaType);
2831
}
2932
}
30-
3133
}
3234
}

0 commit comments

Comments
 (0)