Skip to content

Commit 0acc80a

Browse files
committed
Merge remote-tracking branch 'upstream/master' into rename_example_prefix
2 parents b8705ce + 6cbf37d commit 0acc80a

37 files changed

+656
-217
lines changed

.github/pull_request_template.md

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# <Feature Title>
22

3-
## Fixes Issue
4-
- [ ] Which issue or ticket was(will be) fixed by this PR? (capture the issue number)
3+
## Fixed Which Issue?
4+
- [ ] Which issue or ticket was(will be) fixed by this PR? (capture the issue link here)
55

66
PR Branch
77
**_#ADD LINK TO THE PR BRANCH_**
@@ -23,6 +23,15 @@ PR Branch
2323
* [ ] PR doesn't break any of the earlier features for end users
2424
* [ ] WARNING! This might break one or more earlier earlier features, hence left a comment tagging all reviewrs
2525

26+
* [ ] PR doesn't break the HTML report features directly
27+
* [ ] Yes! I've manually run it locally and seen the HTML reports are generated perfectly fine
28+
* [ ] Yes! I've opened the generated HTML reports from the `/target` folder and they look fine
29+
30+
* [ ] PR doesn't break any HTML report features indirectly
31+
* [ ] I have not added or amended any dependencies in this PR
32+
* [ ] I have double checked, the new dependency added or removed has not affected the report generation indirectly
33+
* [ ] Yes! I've seen the Sample report screenshots [here](https://github.com/authorjapps/zerocode/issues/694#issuecomment-2505958433), and HTML report of the current PR looks simillar.
34+
2635
* [ ] Branch build passed in CI
2736

2837
* [ ] No 'package.*' in the imports

.github/workflows/main.yml

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,26 +10,46 @@ on:
1010
- master
1111
jobs:
1212
build:
13+
strategy:
14+
matrix:
15+
version: [8, 11, 17, 21, 23]
16+
fail-fast: false
1317
runs-on: ubuntu-latest
1418
steps:
15-
- uses: actions/checkout@v3
19+
- uses: actions/checkout@v4
1620

17-
- name: Setting up JDK8
18-
uses: actions/setup-java@v3
21+
- name: Setting up JDK ${{ matrix.version }}
22+
uses: actions/setup-java@v4
1923
with:
20-
java-version: '8'
21-
distribution: 'adopt'
22-
23-
- name: Install Docker Compose
24-
run: |
25-
sudo apt-get update
26-
sudo apt-get install -y docker-compose
27-
24+
java-version: "${{ matrix.version }}"
25+
distribution: 'temurin'
26+
cache: 'maven'
27+
2828
- name: Running Kafka
29-
run: docker-compose -f docker/compose/kafka-schema-registry.yml up -d && sleep 10
29+
run: docker compose -f docker/compose/kafka-schema-registry.yml up -d && sleep 10
3030

3131
- name: Running PostgreSQL (to test DB SQL Executor)
32-
run: docker-compose -f docker/compose/pg_compose.yml up -d
32+
run: docker compose -f docker/compose/pg_compose.yml up -d
3333

3434
- name: Building and testing the changes
35-
run: mvn clean test
35+
run: mvn clean test -ntp
36+
37+
- if: always()
38+
name: Junit html report
39+
uses: javiertuya/junit-report-action@v1.3.0
40+
with:
41+
surefire-files: "**/target/surefire-reports/TEST-*.xml"
42+
report-dir: target/reports
43+
44+
- if: always()
45+
name: Interactive and granular reports
46+
run: cp core/target/*.html target/reports/ && cp core/target/*.csv target/reports/
47+
48+
- if: always()
49+
name: Publish test report files
50+
uses: actions/upload-artifact@v4.6.2
51+
with:
52+
name: "test-report-java${{ matrix.version }}"
53+
path: |
54+
target/reports
55+
!target/reports/someFileName.html

core/pom.xml

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -62,10 +62,6 @@
6262
<groupId>com.fasterxml.jackson.dataformat</groupId>
6363
<artifactId>jackson-dataformat-yaml</artifactId>
6464
</dependency>
65-
<dependency>
66-
<groupId>com.univocity</groupId>
67-
<artifactId>univocity-parsers</artifactId>
68-
</dependency>
6965
<dependency>
7066
<groupId>com.google.code.gson</groupId>
7167
<artifactId>gson</artifactId>
@@ -116,11 +112,6 @@
116112
<groupId>com.google.classpath-explorer</groupId>
117113
<artifactId>classpath-explorer</artifactId>
118114
</dependency>
119-
<dependency>
120-
<groupId>org.jukito</groupId>
121-
<artifactId>jukito</artifactId>
122-
<scope>test</scope>
123-
</dependency>
124115
<dependency>
125116
<groupId>com.google.inject</groupId>
126117
<artifactId>guice</artifactId>
@@ -150,6 +141,11 @@
150141
<artifactId>junit</artifactId>
151142
<!--<scope>test</scope>--> <!-- added with compile scope. Do not change this -->
152143
</dependency>
144+
<dependency>
145+
<groupId>org.mockito</groupId>
146+
<artifactId>mockito-core</artifactId>
147+
<scope>test</scope>
148+
</dependency>
153149
<dependency>
154150
<groupId>org.apache.httpcomponents</groupId>
155151
<artifactId>httpclient</artifactId>

core/src/main/java/org/jsmart/zerocode/core/db/DbCsvLoader.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,20 @@
1010

1111
import org.apache.commons.dbutils.QueryRunner;
1212
import org.apache.commons.lang3.StringUtils;
13+
import org.jsmart.zerocode.core.di.provider.CsvParserProvider;
1314
import org.slf4j.Logger;
1415
import org.slf4j.LoggerFactory;
1516

16-
import com.univocity.parsers.csv.CsvParser;
1717

1818
/**
1919
* Data loading in the database from a CSV external source
2020
*/
2121
class DbCsvLoader {
2222
private static final Logger LOGGER = LoggerFactory.getLogger(DbCsvLoader.class);
2323
private Connection conn;
24-
private CsvParser csvParser;
24+
private CsvParserProvider csvParser;
2525

26-
public DbCsvLoader(Connection conn, CsvParser csvParser) {
26+
public DbCsvLoader(Connection conn, CsvParserProvider csvParser) {
2727
this.conn = conn;
2828
this.csvParser = csvParser;
2929
}

core/src/main/java/org/jsmart/zerocode/core/db/DbSqlExecutor.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
import com.fasterxml.jackson.databind.ObjectMapper;
44
import com.google.inject.Inject;
55
import com.google.inject.name.Named;
6-
import com.univocity.parsers.csv.CsvParser;
76

87
import org.apache.commons.dbutils.DbUtils;
8+
import org.jsmart.zerocode.core.di.provider.CsvParserProvider;
99
import org.slf4j.Logger;
1010
import org.slf4j.LoggerFactory;
1111
import java.sql.Connection;
@@ -36,7 +36,7 @@ public class DbSqlExecutor {
3636
@Named("db.driver.password") private String password;
3737

3838
@Inject
39-
private CsvParser csvParser;
39+
private CsvParserProvider csvParser;
4040

4141
/**
4242
* The LOADCSV operation inserts the content of a CSV file into a table,

core/src/main/java/org/jsmart/zerocode/core/di/module/CsvParserModule.java

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

33
import com.google.inject.Binder;
44
import com.google.inject.Module;
5-
import com.univocity.parsers.csv.CsvParser;
65
import jakarta.inject.Singleton;
6+
import org.jsmart.zerocode.core.di.provider.CsvParserInterface;
77
import org.jsmart.zerocode.core.di.provider.CsvParserProvider;
88

99
public class CsvParserModule implements Module {
1010

1111
@Override
1212
public void configure(Binder binder) {
13-
binder.bind(CsvParser.class).toProvider(CsvParserProvider.class).in(Singleton.class);
13+
binder.bind(CsvParserInterface.class).toProvider(CsvParserProvider.class).in(Singleton.class);
1414
}
1515
}
1616

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package org.jsmart.zerocode.core.di.module;
2+
3+
import com.google.gson.TypeAdapter;
4+
import com.google.gson.stream.JsonReader;
5+
import com.google.gson.stream.JsonWriter;
6+
import java.io.IOException;
7+
import java.util.Optional;
8+
9+
public class OptionalTypeAdapter<T> extends TypeAdapter<Optional<T>> {
10+
private final TypeAdapter<T> delegate;
11+
12+
public OptionalTypeAdapter(TypeAdapter<T> delegate) {
13+
this.delegate = delegate;
14+
}
15+
16+
@Override
17+
public void write(JsonWriter out, Optional<T> value) throws IOException {
18+
if (value == null || !value.isPresent()) {
19+
out.nullValue();
20+
} else {
21+
delegate.write(out, value.get());
22+
}
23+
}
24+
25+
@Override
26+
public Optional<T> read(JsonReader in) throws IOException {
27+
if (in.peek() == com.google.gson.stream.JsonToken.NULL) {
28+
in.nextNull();
29+
return Optional.empty();
30+
} else {
31+
return Optional.ofNullable(delegate.read(in));
32+
}
33+
}
34+
35+
public static <T> TypeAdapter<Optional<T>> factory(TypeAdapter<T> delegate) {
36+
return new OptionalTypeAdapter<>(delegate);
37+
}
38+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package org.jsmart.zerocode.core.di.module;
2+
3+
import com.google.gson.Gson;
4+
import com.google.gson.TypeAdapter;
5+
import com.google.gson.TypeAdapterFactory;
6+
import com.google.gson.reflect.TypeToken;
7+
8+
import java.lang.reflect.ParameterizedType;
9+
import java.lang.reflect.Type;
10+
import java.util.Optional;
11+
12+
public class OptionalTypeAdapterFactory implements TypeAdapterFactory {
13+
14+
@Override
15+
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
16+
if (!Optional.class.isAssignableFrom(typeToken.getRawType())) {
17+
return null;
18+
}
19+
20+
Type type = typeToken.getType();
21+
if (type instanceof ParameterizedType) {
22+
Type elementType = ((ParameterizedType) type).getActualTypeArguments()[0];
23+
TypeAdapter<?> elementAdapter = gson.getAdapter(TypeToken.get(elementType));
24+
return (TypeAdapter<T>) OptionalTypeAdapter.factory(elementAdapter);
25+
}
26+
27+
return null;
28+
}
29+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package org.jsmart.zerocode.core.di.provider;
2+
3+
import com.fasterxml.jackson.core.JsonParser;
4+
import com.fasterxml.jackson.databind.DeserializationContext;
5+
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
6+
7+
import java.io.IOException;
8+
import java.util.Collections;
9+
import java.util.HashMap;
10+
import java.util.Map;
11+
import java.util.Objects;
12+
13+
/**
14+
* /
15+
* Configuration class for parsing CSV files with Jackson's CSV module,
16+
* using a custom deserializer to maintain compatibility with non-standard formats previously handled by uniVocity.
17+
*
18+
*/
19+
public class CsvParserConfig {
20+
21+
public static class CustomStringDeserializer extends StdDeserializer<String> {
22+
// Immutable map of replacement rules: pattern → replacement
23+
private static final Map<String, String> REPLACEMENTS = createReplacements();
24+
25+
/**
26+
* Creates an immutable map of replacement rules for escape patterns.
27+
* @return A map containing patterns and their replacements.
28+
*/
29+
private static Map<String, String> createReplacements() {
30+
Map<String, String> map = new HashMap<>();
31+
map.put("\\'", "'"); // Backslash-escaped single quote
32+
map.put("''", "'"); // Double single quote (single-quoted CSV)
33+
map.put("\\\\", "\\"); // Double backslash to preserve literal backslash
34+
return Collections.unmodifiableMap(map);
35+
}
36+
37+
public CustomStringDeserializer() {
38+
super(String.class);
39+
}
40+
41+
/**
42+
* Deserializes a String value from the CSV parser, applying custom escape pattern replacements.
43+
* <p>
44+
* The method processes the input string to handle non-standard escape patterns required
45+
* for the expected output (e.g., <code>["a'c", "d\"f", "x\y"]</code>). It uses a stream-based
46+
* approach to apply replacements only when the pattern is present, ensuring efficiency.
47+
* <p>
48+
* Without this deserializer, Jackson's default CSV parser may:
49+
* <ul>
50+
* <li>Strip literal backslashes (e.g., <code>x\y</code> becomes <code>xy</code>).</li>
51+
* <li>Misinterpret single-quote escaping (e.g., <code>\'</code> or <code>''</code>).</li>
52+
* </ul>
53+
* <p>
54+
* This implementation ensures compatibility with the previous CSV parsing library's behavior
55+
* and handles inputs like <code>"a'c","d""f","x\y"</code> or <code>"a\'c","d\"f","x\y"</code>.
56+
*
57+
* @return The processed string with escape patterns replaced, or null if the input is null.
58+
* @throws IOException If an I/O error occurs during parsing.
59+
*/
60+
@Override
61+
public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
62+
final String value = p.getText();
63+
if (Objects.isNull(value)) {
64+
return null;
65+
}
66+
return REPLACEMENTS.entrySet().stream()
67+
.filter(entry -> value.contains(entry.getKey()))
68+
.reduce(value, (current, entry) -> current.replace(entry.getKey(), entry.getValue()), (v1, v2) -> v1);
69+
}
70+
}
71+
72+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package org.jsmart.zerocode.core.di.provider;
2+
3+
import java.io.IOException;
4+
import java.io.InputStream;
5+
import java.util.List;
6+
7+
/**
8+
* Defines a contract for parsing a single line of CSV data into a string array.
9+
* Implementations should handle CSV lines with a specified format, such as
10+
* comma-separated fields with optional quoting and escaping.
11+
*/
12+
public interface CsvParserInterface {
13+
/**
14+
* Parses a single line of CSV data into an array of fields.
15+
*
16+
* @param line the CSV line to parse, which may include quoted fields and escaped characters
17+
* @return an array of strings representing the parsed fields; may be empty if the line is invalid
18+
* @throws IOException if an error occurs during parsing, such as malformed CSV data
19+
*/
20+
String[] parseLine(final String line) throws IOException;
21+
}

0 commit comments

Comments
 (0)