Skip to content

Commit 7ef216d

Browse files
authored
Fix runtime errors in jpv3 cli (#273)
* Fix broken tests, missed when running native profile * Fix logger so its config is actually picked up and used * Make host arg required; it's needed for the CSV update * Refactor the jpv3 cli utilities * Set CSV parser to not fail on unknown column fields * Fix native-image reflection config (runtime errors)
1 parent 40ce1f7 commit 7ef216d

File tree

12 files changed

+209
-147
lines changed

12 files changed

+209
-147
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,4 @@ jiiify-presentation-v3.iml
3434

3535
# Output of native builds
3636
output.zip
37+
output.csv.zip

pom.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -446,6 +446,9 @@
446446
<goals>
447447
<goal>testCompile</goal>
448448
</goals>
449+
<configuration>
450+
<testExcludes combine.self="override"/>
451+
</configuration>
449452
</execution>
450453
</executions>
451454
</plugin>
@@ -475,6 +478,7 @@
475478
<arg>--exact-reachability-metadata</arg>
476479
<arg>--enable-native-access=ALL-UNNAMED</arg>
477480
<arg>--enable-url-protocols=https</arg>
481+
<arg>-H:-ReduceImplicitExceptionStackTraceInformation</arg>
478482
</buildArgs>
479483
<metadataRepository>
480484
<enabled>true</enabled>

src/main/java/info/freelibrary/iiif/presentation/v3/utils/cmdline/JPv3.java

Lines changed: 6 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,7 @@
33

44
import static info.freelibrary.util.Constants.COLON;
55

6-
import com.fasterxml.jackson.core.JsonFactory;
7-
import com.fasterxml.jackson.core.JsonParser;
8-
import com.fasterxml.jackson.core.JsonToken;
9-
import info.freelibrary.iiif.presentation.v3.Annotation;
10-
import info.freelibrary.iiif.presentation.v3.AnnotationCollection;
11-
import info.freelibrary.iiif.presentation.v3.AnnotationPage;
12-
import info.freelibrary.iiif.presentation.v3.Collection;
13-
import info.freelibrary.iiif.presentation.v3.Manifest;
14-
import info.freelibrary.iiif.presentation.v3.ResourceTypes;
156
import info.freelibrary.iiif.presentation.v3.properties.MediaType;
16-
import info.freelibrary.iiif.presentation.v3.utils.JSON;
177
import info.freelibrary.iiif.presentation.v3.utils.MessageCodes;
188
import info.freelibrary.iiif.presentation.v3.utils.csv.Mapper;
199
import info.freelibrary.iiif.presentation.v3.utils.csv.MappingException;
@@ -32,18 +22,15 @@
3222
import java.net.http.HttpRequest;
3323
import java.net.http.HttpResponse;
3424
import java.nio.charset.StandardCharsets;
35-
import java.nio.file.Files;
3625
import java.nio.file.Path;
3726
import java.util.Base64;
38-
import java.util.Optional;
3927
import java.util.concurrent.Callable;
4028
import java.util.stream.Stream;
4129

4230
/** A jpv3 executable. */
4331
@CommandLine.Command(name = "jpv3", mixinStandardHelpOptions = true, version = "jpv3 0.0.1-SNAPSHOT",
4432
description = { "", "A tool for working with IIIF manifests and collection documents:", "" },
4533
usageHelpWidth = 120)
46-
@SuppressWarnings({ PMD.EXCESSIVE_IMPORTS })
4734
public final class JPv3 implements Callable<Integer> {
4835

4936
/** The logger for the executable. */
@@ -73,9 +60,9 @@ public final class JPv3 implements Callable<Integer> {
7360
paramLabel = "PASSWORD", defaultValue = "${env:JPV3_PASSWORD}")
7461
private String myPassword;
7562

76-
/** The host to which the ZIP file is being uploaded. This is only needed for uploads. */
77-
@CommandLine.Option(names = { "-H", "--host" }, description = "The host to which the ZIP file is being uploaded",
78-
paramLabel = "HOST", defaultValue = "${env:JPV3_HOST}")
63+
/** The IIIF manifests and collection documents server. */
64+
@CommandLine.Option(names = { "-H", "--host" }, description = "The IIIF manifests and collection documents server",
65+
paramLabel = "HOST", defaultValue = "${env:JPV3_HOST}", required = true)
7966
private URI myHost;
8067

8168
/** The help flag. */
@@ -91,33 +78,10 @@ public final class JPv3 implements Callable<Integer> {
9178
private boolean myLogsAreVerbose;
9279

9380
/** Creates a new JPv3 instance. */
94-
public JPv3() {
81+
private JPv3() {
9582
// This is intentionally empty
9683
}
9784

98-
/**
99-
* Quickly finds a JSON property value.
100-
*
101-
* @param aFilePath A path to a JSON file
102-
* @param aKey A JSON property key
103-
* @return The optional property value, which will be empty if no value was found
104-
* @throws IOException If there is trouble parsing the JSON in the supplied file
105-
*/
106-
public static Optional<String> findValue(final Path aFilePath, final String aKey) throws IOException {
107-
final JsonFactory factory = new JsonFactory();
108-
109-
try (JsonParser parser = factory.createParser(aFilePath.toFile())) {
110-
while (!parser.isClosed()) {
111-
if (JsonToken.FIELD_NAME == parser.nextToken() && aKey.equals(parser.currentName())) {
112-
parser.nextToken(); // Increment the parser to the property value token
113-
return Optional.ofNullable(parser.getValueAsString());
114-
}
115-
}
116-
}
117-
118-
return Optional.empty();
119-
}
120-
12185
/**
12286
* Runs the jpv3 executable. This is just a toy at the moment. Expect it to fail.
12387
*
@@ -129,34 +93,13 @@ public static void main(final String[] anArgsArray) throws IOException {
12993
System.exit(new CommandLine(new JPv3()).execute(anArgsArray));
13094
}
13195

132-
/**
133-
* Reads a particular IIIF Presentation JSON file of the supplied type.
134-
*
135-
* @param aPath A path to a JSON IIIF Presentation file
136-
* @param aType A type of IIIF Presentation file
137-
* @return The contents of the supplied file
138-
* @throws IOException If there is trouble reading the JSON source file
139-
*/
140-
@SuppressWarnings(PMD.UNUSED_PRIVATE_METHOD)
141-
private static String read(final Path aPath, final String aType) throws IOException {
142-
final String content = Files.readString(aPath);
143-
return switch (aType) {
144-
case ResourceTypes.MANIFEST -> JSON.readValue(content, Manifest.class).toString();
145-
case ResourceTypes.COLLECTION -> JSON.readValue(content, Collection.class).toString();
146-
case ResourceTypes.ANNOTATION -> JSON.readValue(content, Annotation.class).toString();
147-
case ResourceTypes.ANNOTATION_COLLECTION -> JSON.readValue(content, AnnotationCollection.class).toString();
148-
case ResourceTypes.ANNOTATION_PAGE -> JSON.readValue(content, AnnotationPage.class).toString();
149-
default -> LOGGER.getMessage(LOGGER.getMessage(MessageCodes.JPA_159, aType));
150-
};
151-
}
152-
15396
/** Runs the application. */
15497
@Override
15598
@SuppressWarnings({ PMD.CYCLOMATIC_COMPLEXITY, PMD.COGNITIVE_COMPLEXITY })
15699
public Integer call() throws Exception {
157100
// Make sure we have a username and password if we're uploading the resulting ZIP file
158-
if ((myAction.myUploadFlag || myAction.myPatchFlag) && (StringUtils.trimToNull(myUsername) == null ||
159-
StringUtils.trimToNull(myPassword) == null || myHost == null)) {
101+
if ((myAction.myUploadFlag || myAction.myPatchFlag) &&
102+
(StringUtils.trimToNull(myUsername) == null || StringUtils.trimToNull(myPassword) == null)) {
160103
throw new CommandLine.ParameterException(new CommandLine(this),
161104
LOGGER.getMessage(MessageCodes.JPA_174, Constants.EOL));
162105
}

src/main/java/info/freelibrary/iiif/presentation/v3/utils/cmdline/JPv3FileDetector.java

Lines changed: 0 additions & 71 deletions
This file was deleted.
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
2+
package info.freelibrary.iiif.presentation.v3.utils.cmdline;
3+
4+
import com.fasterxml.jackson.core.JsonFactory;
5+
import com.fasterxml.jackson.core.JsonParser;
6+
import com.fasterxml.jackson.core.JsonToken;
7+
import info.freelibrary.iiif.presentation.v3.Annotation;
8+
import info.freelibrary.iiif.presentation.v3.AnnotationCollection;
9+
import info.freelibrary.iiif.presentation.v3.AnnotationPage;
10+
import info.freelibrary.iiif.presentation.v3.Collection;
11+
import info.freelibrary.iiif.presentation.v3.Manifest;
12+
import info.freelibrary.iiif.presentation.v3.ResourceTypes;
13+
import info.freelibrary.iiif.presentation.v3.properties.MediaType;
14+
import info.freelibrary.iiif.presentation.v3.utils.JSON;
15+
import info.freelibrary.iiif.presentation.v3.utils.MessageCodes;
16+
import info.freelibrary.util.Logger;
17+
import info.freelibrary.util.LoggerFactory;
18+
import info.freelibrary.util.warnings.Checkstyle;
19+
import info.freelibrary.util.warnings.PMD;
20+
21+
import java.io.BufferedReader;
22+
import java.io.IOException;
23+
import java.io.InputStream;
24+
import java.nio.charset.StandardCharsets;
25+
import java.nio.file.Files;
26+
import java.nio.file.Path;
27+
import java.nio.file.spi.FileTypeDetector;
28+
import java.util.Optional;
29+
30+
/**
31+
* Utility class for working with IIIF Presentation JSON files.
32+
*/
33+
public final class JPv3Utils {
34+
35+
/** A logger for the JPv3Utils class. */
36+
private static final Logger LOGGER = LoggerFactory.getLogger(JPv3Utils.class, MessageCodes.BUNDLE);
37+
38+
/**
39+
* Creates a new JPv3Utils.
40+
*/
41+
private JPv3Utils() {
42+
// This is intentionally left empty
43+
}
44+
45+
/**
46+
* Quickly finds a JSON property value.
47+
*
48+
* @param aFilePath A path to a JSON file
49+
* @param aKey A JSON property key
50+
* @return The optional property value, which will be empty if no value was found
51+
* @throws IOException If there is trouble parsing the JSON in the supplied file
52+
*/
53+
public static Optional<String> findValue(final Path aFilePath, final String aKey) throws IOException {
54+
final JsonFactory factory = new JsonFactory();
55+
56+
try (JsonParser parser = factory.createParser(aFilePath.toFile())) {
57+
while (!parser.isClosed()) {
58+
if (JsonToken.FIELD_NAME == parser.nextToken() && aKey.equals(parser.currentName())) {
59+
parser.nextToken(); // Increment the parser to the property value token
60+
return Optional.ofNullable(parser.getValueAsString());
61+
}
62+
}
63+
}
64+
65+
return Optional.empty();
66+
}
67+
68+
/**
69+
* Reads a particular IIIF Presentation JSON file of the supplied type.
70+
*
71+
* @param aPath A path to a JSON IIIF Presentation file
72+
* @param aType A type of IIIF Presentation file
73+
* @return The contents of the supplied file
74+
* @throws IOException If there is trouble reading the JSON source file
75+
*/
76+
public static String read(final Path aPath, final String aType) throws IOException {
77+
final String content = Files.readString(aPath);
78+
return switch (aType) {
79+
case ResourceTypes.MANIFEST -> JSON.readValue(content, Manifest.class).toString();
80+
case ResourceTypes.COLLECTION -> JSON.readValue(content, Collection.class).toString();
81+
case ResourceTypes.ANNOTATION -> JSON.readValue(content, Annotation.class).toString();
82+
case ResourceTypes.ANNOTATION_COLLECTION -> JSON.readValue(content, AnnotationCollection.class).toString();
83+
case ResourceTypes.ANNOTATION_PAGE -> JSON.readValue(content, AnnotationPage.class).toString();
84+
default -> LOGGER.getMessage(LOGGER.getMessage(MessageCodes.JPA_159, aType));
85+
};
86+
}
87+
88+
/**
89+
* A small file type detector that recognizes ZIP archives, CSV files, and other file types.
90+
*/
91+
public static class JPv3FileDetector extends FileTypeDetector {
92+
93+
/** The ZIP magic number. */
94+
private static final int ZIP_MAGIC = 0x504B0304;
95+
96+
/** The header size. */
97+
private static final int HEADER_SIZE = 4;
98+
99+
@Override
100+
@SuppressWarnings({ PMD.CYCLOMATIC_COMPLEXITY, Checkstyle.BOOLEAN_EXPR_COMPLEXITY,
101+
"BooleanExpressionComplexity" })
102+
public final String probeContentType(final Path aPath) throws IOException {
103+
// Check the file at the supplied path for the ZIP magic number
104+
try (InputStream inStream = Files.newInputStream(aPath)) {
105+
final byte[] header = new byte[4];
106+
final int bytesRead = inStream.read(header);
107+
108+
if (bytesRead == HEADER_SIZE) {
109+
final int magicNum = ((header[0] & 0xFF) << 24) | ((header[1] & 0xFF) << 16) |
110+
((header[2] & 0xFF) << 8) | (header[3] & 0xFF);
111+
112+
if (magicNum == ZIP_MAGIC) {
113+
return MediaType.APPLICATION_ZIP.toString();
114+
}
115+
}
116+
}
117+
118+
try (BufferedReader reader = Files.newBufferedReader(aPath, StandardCharsets.UTF_8)) {
119+
int linesChecked = 0;
120+
String line;
121+
122+
while ((line = reader.readLine()) != null && linesChecked < 10) {
123+
// Skip empty lines at the top of the file
124+
if ((line = line.trim()).isEmpty()) {
125+
continue;
126+
}
127+
128+
linesChecked++;
129+
130+
// Common CSV delimiters
131+
if (line.contains(",") || line.contains("\t") || line.contains(";") || line.contains("|")) {
132+
return MediaType.TEXT_CSV.toString();
133+
}
134+
135+
// If we encounter non‑printable characters, bail out -- it's likely binary.
136+
if (!line.chars().allMatch(ch -> ch >= 32 && ch <= 126 || ch == '\r' || ch == '\n')) {
137+
break;
138+
}
139+
}
140+
}
141+
142+
return null;
143+
}
144+
}
145+
}

0 commit comments

Comments
 (0)