• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Nanopublication / nanopub-java / 22763596638

06 Mar 2026 12:31PM UTC coverage: 51.142% (-0.08%) from 51.223%
22763596638

push

github

Ziroli
feat: support SPARQL CONSTRUCT query responses (issue #61)

- Broaden Accept header in QueryCall to include text/turtle and application/ld+json
- Add rdfContent field to ApiResponse for holding RDF model results
- Update QueryAccess.call() to detect Content-Type and dispatch to CSV or RDF parsing
- Add processRdfContent() hook to QueryAccess (default no-op, backward compatible)
- Update RunQuery to print Turtle when response is RDF

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

1020 of 2986 branches covered (34.16%)

Branch coverage included in aggregate %.

5228 of 9231 relevant lines covered (56.64%)

7.96 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

48.21
src/main/java/org/nanopub/extra/services/QueryAccess.java
1
package org.nanopub.extra.services;
2

3
import com.opencsv.CSVReader;
4
import com.opencsv.CSVWriterBuilder;
5
import com.opencsv.ICSVWriter;
6
import com.opencsv.exceptions.CsvValidationException;
7
import org.apache.http.HttpResponse;
8
import org.eclipse.rdf4j.model.Model;
9
import org.eclipse.rdf4j.rio.RDFFormat;
10
import org.eclipse.rdf4j.rio.Rio;
11

12
import java.io.BufferedReader;
13
import java.io.IOException;
14
import java.io.InputStreamReader;
15
import java.io.Writer;
16

17
/**
18
 * Second-generation query API access
19
 */
20
public abstract class QueryAccess {
9✔
21

22
    /**
23
     * Process the header.
24
     *
25
     * @param line the header line from the CSV response
26
     */
27
    protected abstract void processHeader(String[] line);
28

29
    /**
30
     * Process a line of data from the CSV response.
31
     *
32
     * @param line the line of data from the CSV response
33
     */
34
    protected abstract void processLine(String[] line);
35

36
    /**
37
     * Process the RDF content from a SPARQL CONSTRUCT query response.
38
     * Override this method to handle RDF responses. Default implementation is a no-op.
39
     *
40
     * @param model the parsed RDF model
41
     */
42
    protected void processRdfContent(Model model) {
43
    }
×
44

45
    /**
46
     * Call a query with the given queryId and parameters.
47
     *
48
     * @param queryRef the query reference
49
     * @throws FailedApiCallException         if the API call fails
50
     * @throws APINotReachableException       if the API is not reachable
51
     * @throws NotEnoughAPIInstancesException if there are not enough API instances available
52
     */
53
    public void call(QueryRef queryRef) throws FailedApiCallException, APINotReachableException, NotEnoughAPIInstancesException {
54
        HttpResponse resp = QueryCall.run(queryRef);
9✔
55
        String contentType = resp.getFirstHeader("Content-Type") != null
12!
56
                ? resp.getFirstHeader("Content-Type").getValue() : "";
18✔
57
        String mimeType = contentType.contains(";") ? contentType.split(";")[0].trim() : contentType.trim();
36!
58
        if (mimeType.equals("text/csv") || mimeType.isEmpty()) {
12!
59
            try (CSVReader csvReader = new CSVReader(new BufferedReader(new InputStreamReader(resp.getEntity().getContent())))) {
39✔
60
                String[] line = null;
6✔
61
                int n = 0;
6✔
62
                while ((line = csvReader.readNext()) != null) {
15✔
63
                    n++;
3✔
64
                    if (n == 1) {
9!
65
                        processHeader(line);
12✔
66
                    } else {
67
                        processLine(line);
×
68
                    }
69
                }
70
            } catch (IOException | CsvValidationException ex) {
×
71
                throw new FailedApiCallException(ex);
×
72
            }
3✔
73
        } else {
74
            RDFFormat format = Rio.getParserFormatForMIMEType(mimeType).orElse(RDFFormat.TURTLE);
×
75
            try {
76
                Model model = Rio.parse(resp.getEntity().getContent(), format);
×
77
                processRdfContent(model);
×
78
            } catch (IOException ex) {
×
79
                throw new FailedApiCallException(ex);
×
80
            }
×
81
        }
82
    }
3✔
83

84
    /**
85
     * Print the response of a query in CSV format to the given writer.
86
     *
87
     * @param queryRef the query reference
88
     * @param writer   the writer to print the CSV response to
89
     * @throws FailedApiCallException         if the API call fails
90
     * @throws APINotReachableException       if the API is not reachable
91
     * @throws NotEnoughAPIInstancesException if there are not enough API instances available
92
     */
93
    public static void printCvsResponse(QueryRef queryRef, Writer writer) throws FailedApiCallException, APINotReachableException, NotEnoughAPIInstancesException {
94
        ICSVWriter icsvWriter = new CSVWriterBuilder(writer).withSeparator(',').build();
×
95
        QueryAccess a = new QueryAccess() {
×
96

97
            @Override
98
            protected void processHeader(String[] line) {
99
                icsvWriter.writeNext(line);
×
100
            }
×
101

102
            @Override
103
            protected void processLine(String[] line) {
104
                icsvWriter.writeNext(line);
×
105
            }
×
106

107
        };
108
        a.call(queryRef);
×
109
        icsvWriter.flushQuietly();
×
110
    }
×
111

112
    /**
113
     * Get the response of a query as an ApiResponse object.
114
     *
115
     * @param queryRef the query reference
116
     * @return an ApiResponse object containing the response data
117
     * @throws FailedApiCallException         if the API call fails
118
     * @throws APINotReachableException       if the API is not reachable
119
     * @throws NotEnoughAPIInstancesException if there are not enough API instances available
120
     */
121
    public static ApiResponse get(QueryRef queryRef) throws FailedApiCallException, APINotReachableException, NotEnoughAPIInstancesException {
122
        final ApiResponse response = new ApiResponse();
12✔
123
        QueryAccess a = new QueryAccess() {
33✔
124

125
            @Override
126
            protected void processHeader(String[] line) {
127
                response.setHeader(line);
12✔
128
            }
3✔
129

130
            @Override
131
            protected void processLine(String[] line) {
132
                response.add(line);
×
133
            }
×
134

135
            @Override
136
            protected void processRdfContent(Model model) {
137
                response.setRdfContent(model);
×
138
            }
×
139

140
        };
141
        a.call(queryRef);
9✔
142
        return response;
6✔
143
    }
144

145
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc