Skip to content

Commit 471f865

Browse files
Merge pull request #999 from ie3-institute/ms/#954-CsvDataSource-should-throw-exceptions-on-error
CsvDataSource should throw exceptions on error.
2 parents 49dfa19 + 09c51c2 commit 471f865

21 files changed

+288
-202
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3333
- Refactored and abstracted `EntitySource`s and `EntityData` creation [#969](https://github.com/ie3-institute/PowerSystemDataModel/issues/969)
3434
- Updated contributing.md [#737](https://github.com/ie3-institute/PowerSystemDataModel/issues/737)
3535
- Don't throw exceptions for not yet implemented validations [#879](https://github.com/ie3-institute/PowerSystemDataModel/issues/879)
36+
- `CsvDataSource` throws exceptions on error [#954](https://github.com/ie3-institute/PowerSystemDataModel/issues/954)
3637

3738
## [4.1.0] - 2023-11-02
3839

src/main/java/edu/ie3/datamodel/io/connectors/CsvFileConnector.java

Lines changed: 1 addition & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -159,28 +159,7 @@ public synchronized <C extends UniqueEntity> void closeEntityWriter(Class<C> clz
159159
}
160160

161161
/**
162-
* Initializes a file reader for the given class that should be read in. The expected file name is
163-
* determined based on {@link FileNamingStrategy} of the this {@link CsvFileConnector} instance
164-
*
165-
* @param clz the class of the entity that should be read
166-
* @return the reader that contains information about the file to be read in
167-
* @throws FileNotFoundException If the matching file cannot be found
168-
*/
169-
public BufferedReader initReader(Class<? extends UniqueEntity> clz)
170-
throws FileNotFoundException, ConnectorException {
171-
Path filePath =
172-
fileNamingStrategy
173-
.getFilePath(clz)
174-
.orElseThrow(
175-
() ->
176-
new ConnectorException(
177-
"Cannot find a naming strategy for class '" + clz.getSimpleName() + "'."));
178-
return initReader(filePath);
179-
}
180-
181-
/**
182-
* Initializes a file reader for the given file name. Use {@link
183-
* CsvFileConnector#initReader(Class)} for files that actually correspond to concrete entities.
162+
* Initializes a file reader for the given file name.
184163
*
185164
* @param filePath path of file starting from base folder, including file name but not file
186165
* extension
@@ -247,18 +226,6 @@ private Set<Path> getIndividualTimeSeriesFilePaths() {
247226
}
248227
}
249228

250-
/**
251-
* Initialises a reader to get grip on the file that contains mapping information between
252-
* coordinate id and actual coordinate
253-
*
254-
* @return A {@link BufferedReader}
255-
* @throws FileNotFoundException If the file is not present
256-
*/
257-
public BufferedReader initIdCoordinateReader() throws FileNotFoundException {
258-
Path filePath = Path.of(fileNamingStrategy.getIdCoordinateEntityName());
259-
return initReader(filePath);
260-
}
261-
262229
/**
263230
* Builds a new file definition consisting of file name and head line elements
264231
*

src/main/java/edu/ie3/datamodel/io/source/DataSource.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,6 @@ Optional<Set<String>> getSourceFields(Class<? extends UniqueEntity> entityClass)
2323
throws SourceException;
2424

2525
/** Creates a stream of maps that represent the rows in the database */
26-
Stream<Map<String, String>> getSourceData(Class<? extends UniqueEntity> entityClass);
26+
Stream<Map<String, String>> getSourceData(Class<? extends UniqueEntity> entityClass)
27+
throws SourceException;
2728
}

src/main/java/edu/ie3/datamodel/io/source/EntitySource.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -384,9 +384,14 @@ protected static Stream<Try<AssetInputEntityData, SourceException>> assetInputEn
384384
*/
385385
protected Stream<Try<EntityData, SourceException>> buildEntityData(
386386
Class<? extends UniqueEntity> entityClass) {
387-
return dataSource
388-
.getSourceData(entityClass)
389-
.map(fieldsToAttributes -> new Success<>(new EntityData(fieldsToAttributes, entityClass)));
387+
388+
return Try.of(() -> dataSource.getSourceData(entityClass), SourceException.class)
389+
.convert(
390+
data ->
391+
data.map(
392+
fieldsToAttributes ->
393+
new Success<>(new EntityData(fieldsToAttributes, entityClass))),
394+
exception -> Stream.of(Failure.of(exception)));
390395
}
391396

392397
protected static <S extends UniqueEntity> Map<UUID, S> unpackMap(

src/main/java/edu/ie3/datamodel/io/source/IdCoordinateSource.java

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,9 @@ public interface IdCoordinateSource {
2323
/**
2424
* Method to retrieve the fields found in the source.
2525
*
26-
* @param entityClass class of the source
2726
* @return an option for the found fields
2827
*/
29-
Optional<Set<String>> getSourceFields(Class<?> entityClass) throws SourceException;
28+
Optional<Set<String>> getSourceFields() throws SourceException;
3029

3130
/**
3231
* Get the matching coordinate for the given ID
@@ -135,7 +134,14 @@ default List<CoordinateDistance> restrictToBoundingBox(
135134
Point point = distance.getCoordinateB();
136135

137136
// check for bounding box
138-
if (!topLeft && (point.getX() < coordinate.getX() && point.getY() > coordinate.getY())) {
137+
if (coordinate.equalsExact(point, 1e-6)) {
138+
// if current point is matching the given coordinate, we need to return only the current
139+
// point
140+
resultingDistances.clear();
141+
resultingDistances.add(distance);
142+
return resultingDistances;
143+
} else if (!topLeft
144+
&& (point.getX() < coordinate.getX() && point.getY() > coordinate.getY())) {
139145
resultingDistances.add(distance);
140146
topLeft = true;
141147
} else if (!topRight
@@ -150,13 +156,6 @@ default List<CoordinateDistance> restrictToBoundingBox(
150156
&& (point.getX() > coordinate.getX() && point.getY() < coordinate.getY())) {
151157
resultingDistances.add(distance);
152158
bottomRight = true;
153-
} else if (coordinate.equalsExact(point, 1e-6)) {
154-
// if current point is matching the given coordinate, we need to return only the current
155-
// point
156-
157-
resultingDistances.clear();
158-
resultingDistances.add(distance);
159-
return resultingDistances;
160159
} else {
161160
other.add(distance);
162161
}

src/main/java/edu/ie3/datamodel/io/source/TimeSeriesMappingSource.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ protected TimeSeriesMappingSource() {
3333
*
3434
* @return That mapping
3535
*/
36-
public Map<UUID, UUID> getMapping() {
36+
public Map<UUID, UUID> getMapping() throws SourceException {
3737
return getMappingSourceData()
3838
.map(this::createMappingEntry)
3939
.filter(Try::isSuccess)
@@ -48,7 +48,7 @@ public Map<UUID, UUID> getMapping() {
4848
* @param modelIdentifier Identifier of the model
4949
* @return An {@link Optional} to the time series identifier
5050
*/
51-
public Optional<UUID> getTimeSeriesUuid(UUID modelIdentifier) {
51+
public Optional<UUID> getTimeSeriesUuid(UUID modelIdentifier) throws SourceException {
5252
return Optional.ofNullable(getMapping().get(modelIdentifier));
5353
}
5454

@@ -57,7 +57,7 @@ public Optional<UUID> getTimeSeriesUuid(UUID modelIdentifier) {
5757
*
5858
* @return Stream of maps
5959
*/
60-
public abstract Stream<Map<String, String>> getMappingSourceData();
60+
public abstract Stream<Map<String, String>> getMappingSourceData() throws SourceException;
6161

6262
/** Returns the option for fields found in the source */
6363
public abstract Optional<Set<String>> getSourceFields() throws SourceException;

src/main/java/edu/ie3/datamodel/io/source/csv/CsvDataSource.java

Lines changed: 73 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@
55
*/
66
package edu.ie3.datamodel.io.source.csv;
77

8-
import edu.ie3.datamodel.exceptions.ConnectorException;
98
import edu.ie3.datamodel.exceptions.SourceException;
109
import edu.ie3.datamodel.io.connectors.CsvFileConnector;
1110
import edu.ie3.datamodel.io.naming.FileNamingStrategy;
1211
import edu.ie3.datamodel.io.source.DataSource;
1312
import edu.ie3.datamodel.models.UniqueEntity;
13+
import edu.ie3.datamodel.utils.Try;
14+
import edu.ie3.datamodel.utils.Try.Failure;
15+
import edu.ie3.datamodel.utils.Try.Success;
1416
import edu.ie3.datamodel.utils.validation.ValidationUtils;
1517
import edu.ie3.util.StringUtils;
1618
import java.io.BufferedReader;
@@ -46,6 +48,8 @@ public class CsvDataSource implements DataSource {
4648
protected final String csvSep;
4749
protected final CsvFileConnector connector;
4850

51+
private final FileNamingStrategy fileNamingStrategy;
52+
4953
/**
5054
* @deprecated ensures downward compatibility with old csv data format. Can be removed when
5155
* support for old csv format is removed. *
@@ -56,41 +60,38 @@ public class CsvDataSource implements DataSource {
5660
public CsvDataSource(String csvSep, Path folderPath, FileNamingStrategy fileNamingStrategy) {
5761
this.csvSep = csvSep;
5862
this.connector = new CsvFileConnector(folderPath, fileNamingStrategy);
63+
this.fileNamingStrategy = fileNamingStrategy;
5964
}
6065

6166
@Override
6267
public Optional<Set<String>> getSourceFields(Class<? extends UniqueEntity> entityClass)
6368
throws SourceException {
64-
return getSourceFields(() -> connector.initReader(entityClass));
69+
return getSourceFields(getFilePath(entityClass).getOrThrow());
6570
}
6671

67-
public Optional<Set<String>> getSourceFields(ReaderSupplier readerSupplier)
68-
throws SourceException {
69-
try (BufferedReader reader = readerSupplier.get()) {
72+
/**
73+
* @param filePath path of file starting from base folder, including file name but not file
74+
* extension
75+
* @return The source field names as a set, if file exists
76+
* @throws SourceException on error while reading the source file
77+
*/
78+
public Optional<Set<String>> getSourceFields(Path filePath) throws SourceException {
79+
try (BufferedReader reader = connector.initReader(filePath)) {
7080
return Optional.of(
7181
Arrays.stream(parseCsvRow(reader.readLine(), csvSep)).collect(Collectors.toSet()));
7282
} catch (FileNotFoundException e) {
7383
// A file not existing can be acceptable in many cases, and is handled elsewhere.
7484
log.debug("The source for the given entity couldn't be found! Cause: {}", e.getMessage());
7585
return Optional.empty();
76-
} catch (ConnectorException | IOException e) {
86+
} catch (IOException e) {
7787
throw new SourceException("Error while trying to read source", e);
7888
}
7989
}
8090

81-
public interface ReaderSupplier {
82-
BufferedReader get() throws FileNotFoundException, ConnectorException;
83-
}
84-
8591
@Override
86-
public Stream<Map<String, String>> getSourceData(Class<? extends UniqueEntity> entityClass) {
87-
return buildStreamWithFieldsToAttributesMap(entityClass, connector);
88-
}
89-
90-
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
91-
92-
public BufferedReader createReader(Path filePath) throws FileNotFoundException {
93-
return connector.initReader(filePath);
92+
public Stream<Map<String, String>> getSourceData(Class<? extends UniqueEntity> entityClass)
93+
throws SourceException {
94+
return buildStreamWithFieldsToAttributesMap(entityClass, true).getOrThrow();
9495
}
9596

9697
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
@@ -279,25 +280,24 @@ protected <T extends UniqueEntity> Predicate<Optional<T>> isPresentCollectIfNot(
279280
};
280281
}
281282

283+
public FileNamingStrategy getNamingStrategy() {
284+
return fileNamingStrategy;
285+
}
286+
282287
/**
283-
* Tries to open a file reader from the connector based on the provided entity class and hands it
284-
* over for further processing.
288+
* Tries to open a file reader based on the provided entity class and hands it over for further
289+
* processing.
285290
*
286291
* @param entityClass the entity class that should be build and that is used to get the
287292
* corresponding reader
288-
* @param connector the connector that should be used to get the reader from
289293
* @return a parallel stream of maps, where each map represents one row of the csv file with the
290294
* mapping (fieldName to fieldValue)
291295
*/
292-
protected Stream<Map<String, String>> buildStreamWithFieldsToAttributesMap(
293-
Class<? extends UniqueEntity> entityClass, CsvFileConnector connector) {
294-
try {
295-
return buildStreamWithFieldsToAttributesMap(entityClass, connector.initReader(entityClass));
296-
} catch (FileNotFoundException | ConnectorException e) {
297-
log.warn(
298-
"Unable to find file for entity '{}': {}", entityClass.getSimpleName(), e.getMessage());
299-
}
300-
return Stream.empty();
296+
protected Try<Stream<Map<String, String>>, SourceException> buildStreamWithFieldsToAttributesMap(
297+
Class<? extends UniqueEntity> entityClass, boolean allowFileNotExisting) {
298+
return getFilePath(entityClass)
299+
.flatMap(
300+
path -> buildStreamWithFieldsToAttributesMap(entityClass, path, allowFileNotExisting));
301301
}
302302

303303
/**
@@ -306,13 +306,13 @@ protected Stream<Map<String, String>> buildStreamWithFieldsToAttributesMap(
306306
* the returning stream is a parallel stream, the order of the elements cannot be guaranteed.
307307
*
308308
* @param entityClass the entity class that should be build
309-
* @param bufferedReader the reader to use
310-
* @return a parallel stream of maps, where each map represents one row of the csv file with the
311-
* mapping (fieldName to fieldValue)
309+
* @param filePath the path of the file to read
310+
* @return a try containing either a parallel stream of maps, where each map represents one row of
311+
* the csv file with the mapping (fieldName to fieldValue) or an exception
312312
*/
313-
protected Stream<Map<String, String>> buildStreamWithFieldsToAttributesMap(
314-
Class<? extends UniqueEntity> entityClass, BufferedReader bufferedReader) {
315-
try (BufferedReader reader = bufferedReader) {
313+
protected Try<Stream<Map<String, String>>, SourceException> buildStreamWithFieldsToAttributesMap(
314+
Class<? extends UniqueEntity> entityClass, Path filePath, boolean allowFileNotExisting) {
315+
try (BufferedReader reader = connector.initReader(filePath)) {
316316
final String[] headline = parseCsvRow(reader.readLine(), csvSep);
317317

318318
// by default try-with-resources closes the reader directly when we leave this method (which
@@ -322,14 +322,31 @@ protected Stream<Map<String, String>> buildStreamWithFieldsToAttributesMap(
322322
Collection<Map<String, String>> allRows = csvRowFieldValueMapping(reader, headline);
323323

324324
return distinctRowsWithLog(
325-
allRows, fieldToValues -> fieldToValues.get("uuid"), entityClass.getSimpleName(), "UUID")
326-
.parallelStream();
325+
allRows,
326+
fieldToValues -> fieldToValues.get("uuid"),
327+
entityClass.getSimpleName(),
328+
"UUID")
329+
.map(Set::parallelStream);
330+
} catch (FileNotFoundException e) {
331+
if (allowFileNotExisting) {
332+
log.warn("Unable to find file '{}': {}", filePath, e.getMessage());
333+
return Success.of(Stream.empty());
334+
} else {
335+
return Failure.of(new SourceException("Unable to find file '" + filePath + "'.", e));
336+
}
327337
} catch (IOException e) {
328-
log.warn(
329-
"Cannot read file to build entity '{}': {}", entityClass.getSimpleName(), e.getMessage());
338+
return Failure.of(
339+
new SourceException(
340+
"Cannot read file to build entity '" + entityClass.getSimpleName() + "'", e));
330341
}
342+
}
331343

332-
return Stream.empty();
344+
private Try<Path, SourceException> getFilePath(Class<? extends UniqueEntity> entityClass) {
345+
return Try.from(
346+
fileNamingStrategy.getFilePath(entityClass),
347+
() ->
348+
new SourceException(
349+
"Cannot find a naming strategy for class '" + entityClass.getSimpleName() + "'."));
333350
}
334351

335352
protected List<Map<String, String>> csvRowFieldValueMapping(
@@ -358,10 +375,10 @@ protected List<Map<String, String>> csvRowFieldValueMapping(
358375
* debug String)
359376
* @param keyDescriptor Colloquial descriptor of the key, that is meant to be unique (for debug
360377
* String)
361-
* @return either a set containing only unique rows or an empty set if at least two rows with the
362-
* same UUID but different field values exist
378+
* @return a try of either a set containing only unique rows or an exception if at least two rows
379+
* with the same UUID but different field values exist
363380
*/
364-
protected Set<Map<String, String>> distinctRowsWithLog(
381+
protected Try<Set<Map<String, String>>, SourceException> distinctRowsWithLog(
365382
Collection<Map<String, String>> allRows,
366383
final Function<Map<String, String>, String> keyExtractor,
367384
String entityDescriptor,
@@ -385,18 +402,19 @@ protected Set<Map<String, String>> distinctRowsWithLog(
385402
allRowsSet.removeAll(distinctIdSet);
386403
String affectedCoordinateIds =
387404
allRowsSet.stream().map(keyExtractor).collect(Collectors.joining(",\n"));
388-
log.error(
389-
"""
390-
'{}' entities with duplicated {} key, but different field values found! Please review the corresponding input file!
391-
Affected primary keys:
392-
{}""",
393-
entityDescriptor,
394-
keyDescriptor,
395-
affectedCoordinateIds);
396-
// if this happens, we return an empty set to prevent further processing
397-
return new HashSet<>();
405+
406+
// if this happens, we return a failure
407+
return Failure.of(
408+
new SourceException(
409+
"'"
410+
+ entityDescriptor
411+
+ "' entities with duplicated "
412+
+ keyDescriptor
413+
+ " key, but different field "
414+
+ "values found! Please review the corresponding input file! Affected primary keys: "
415+
+ affectedCoordinateIds));
398416
}
399417

400-
return allRowsSet;
418+
return Success.of(allRowsSet);
401419
}
402420
}

0 commit comments

Comments
 (0)