Skip to content

Commit

Permalink
feat: support for XML datasets
Browse files Browse the repository at this point in the history
  • Loading branch information
laurens-runnable committed Feb 2, 2023
1 parent c98ae24 commit 228f7a3
Show file tree
Hide file tree
Showing 24 changed files with 300 additions and 90 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,6 @@ buildNumber.properties

# MacOS
.DS_Store

# Project
tmp
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@ The current implementation produces simple table output. More sophisticated exam
./mvnw spring-boot:run
```

* [http://localhost:8080/](http://localhost:8080/)
* Save `.csv` files in the [`datasets`](datasets) directory
* Reload the main page to browse the available datasets
* [http://localhost:8080/](http://localhost:8080/) to open the web interface
* Save `.csv` and `.xml` files in the [`datasets`](datasets) directory
Note that the application does not support duplicate base names. E.g. it cannot properly distinguish between
datasets `items.csv` and `items.xml`.
* Reload the web page to refresh the datasets

### Spring Developer tools

Expand Down
5 changes: 4 additions & 1 deletion app/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>net.sourceforge.htmlcleaner</groupId>
<artifactId>htmlcleaner</artifactId>
</dependency>
</dependencies>

<build>
Expand All @@ -48,7 +52,6 @@
<workingDirectory>${project.parent.basedir}</workingDirectory>
<profiles>
<profile>local</profile>
<profile>test-set</profile>
</profiles>
</configuration>
<executions>
Expand Down
17 changes: 16 additions & 1 deletion app/src/main/java/nl/runnable/dataset/Dataset.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,20 @@

import org.springframework.core.io.Resource;

public record Dataset(String name, Resource resource) {
public record Dataset(String name, Resource resource, Type type,
boolean hasHtmlFormat, boolean hasPdfFormat, boolean hasCsvFormat) {

public enum Type {
CSV,
XML,
}

public static Dataset forCsv(String name, Resource resource) {
return new Dataset(name, resource, Type.CSV, true, true, true);
}

public static Dataset forXml(String name, Resource resource, boolean hasHtmlFormat, boolean hasPdfFormat) {
return new Dataset(name, resource, Type.XML, hasHtmlFormat, hasPdfFormat, false);
}

}
3 changes: 3 additions & 0 deletions app/src/main/java/nl/runnable/dataset/DatasetRepository.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,7 @@ public interface DatasetRepository {

Stream<Dataset> findAll();

boolean stylesheetExists(@NonNull String name);


}
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@

public interface DatasetSourceFactory {

Source createIndexPageSource(Stream<Dataset> datasets);
Source createIndexPageSource(@NonNull Stream<Dataset> datasets);

Source createDatasetPageSource(@NonNull Dataset dataset);

Source createDatasetSource(@NonNull Dataset dataset);
Source createDatasetXmlSource(@NonNull Dataset dataset);

}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package nl.runnable.dataset;

import lombok.Getter;
import lombok.NonNull;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

Expand All @@ -10,12 +10,7 @@
public class XsltDatasetsProperties {

@Getter
@Setter
private String directory;

public void setDirectory(@NonNull String directory) {
while (directory.endsWith("/")) {
directory = directory.substring(0, directory.length() - 1);
}
this.directory = directory;
}
}
2 changes: 1 addition & 1 deletion app/src/main/java/nl/runnable/dataset/fop/FopConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public class FopConfig {
private Resource fopConfig;

@Bean
FopFactory fopFactory(SpringResourceResolver resourceResolver) {
FopFactory fopFactory(ResourceLoaderResourceResolver resourceResolver) {
try {
final var configurationBuilder = new DefaultConfigurationBuilder();
final var configuration = configurationBuilder.build(fopConfig.getInputStream());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

@Component
@RequiredArgsConstructor
class SpringResourceResolver implements ResourceResolver {
class ResourceLoaderResourceResolver implements ResourceResolver {

private final ResourceLoader resourceLoader;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.sax.SAXSource;
import javax.xml.transform.stream.StreamSource;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.stream.Stream;
Expand All @@ -33,6 +34,10 @@ public Source createIndexPageSource(Stream<Dataset> datasets) {
datasets.map(dataset -> {
final var element = new Element("dataset");
element.setAttribute("name", dataset.name());
element.setAttribute("type", dataset.type().name());
element.setAttribute("hasHtmlFormat", Boolean.toString(dataset.hasHtmlFormat()));
element.setAttribute("hasPdfFormat", Boolean.toString(dataset.hasPdfFormat()));
element.setAttribute("hasCsvFormat", Boolean.toString(dataset.hasCsvFormat()));
return element;
})
.forEach(datasetsElement::addContent);
Expand All @@ -47,7 +52,7 @@ public Source createIndexPageSource(Stream<Dataset> datasets) {
@Override
public Source createDatasetPageSource(@NonNull Dataset dataset) {
try {
final var source = createDatasetSource(dataset);
final var source = createDatasetXmlSource(dataset);
final var transformer = TransformerFactory.newInstance().newTransformer();
final var result = new JDOMResult();
transformer.transform(source, result);
Expand All @@ -64,12 +69,17 @@ public Source createDatasetPageSource(@NonNull Dataset dataset) {
}

@Override
public Source createDatasetSource(Dataset dataset) {
public Source createDatasetXmlSource(Dataset dataset) {
try {
final InputSource inputSource = new InputSource(new InputStreamReader(dataset.resource().getInputStream()));
final var saxSource = new SAXSource(inputSource);
saxSource.setXMLReader(new CsvXmlReader());
return saxSource;
return switch (dataset.type()) {
case CSV -> {
final InputSource inputSource = new InputSource(new InputStreamReader(dataset.resource().getInputStream()));
final var saxSource = new SAXSource(inputSource);
saxSource.setXMLReader(new CsvXmlReader());
yield saxSource;
}
case XML -> new StreamSource(dataset.resource().getInputStream());
};
} catch (IOException e) {
throw new RuntimeException(e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,41 +15,65 @@
import java.util.stream.Stream;

@Component
@RequiredArgsConstructor
@Slf4j
@RequiredArgsConstructor
class ResourceLoaderDatasetRepository implements DatasetRepository {

private final ResourcePatternResolver resourcePatternResolver;

@Value("${xslt-datasets.directory}")
private String directory;

@Value("${xslt-datasets.directory}")
public void setDirectory(String directory) {
while (directory.endsWith("/")) {
directory = directory.substring(0, directory.length() - 1);
}
this.directory = directory;
}

@Override
public Optional<Dataset> find(String name) {
final var filename = "%s/%s.csv".formatted(directory, name);
final var resource = resourcePatternResolver.getResource(filename);
if (resource.exists()) {
return Optional.of(new Dataset(resolveName(resource), resource));
} else {
return Optional.empty();
final var csv = resourcePatternResolver.getResource("%s/%s.csv".formatted(directory, name));
if (csv.exists()) {
return Optional.of(Dataset.forCsv(resolveName(csv), csv));
}

final var xml = resourcePatternResolver.getResource("%s/%s.xml".formatted(directory, name));
if (xml.exists()) {
final var hasHtmlFormat = stylesheetExists("%s.html".formatted(name));
final var hasPdfFormat = stylesheetExists("%s.fo".formatted(name));
return Optional.of(Dataset.forXml(name, xml, hasHtmlFormat, hasPdfFormat));
}

return Optional.empty();
}

@Override
public Stream<Dataset> findAll() {
try {
final var pattern = "%s/*.csv".formatted(directory);
return Stream.of(resourcePatternResolver.getResources(pattern))
.map(resource -> {
String name = resolveName(resource);
return new Dataset(name, resource);
final var csvs = Stream.of(resourcePatternResolver.getResources("%s/*.csv".formatted(directory)))
.map(csv -> Dataset.forCsv(resolveName(csv), csv));
final var xmls = Stream.of(resourcePatternResolver.getResources("%s/*.xml".formatted(directory)))
.map(xml -> {
final var name = resolveName(xml);
final var hasHtmlFormat = stylesheetExists("%s.html".formatted(name));
final var hasPdfFormat = stylesheetExists("%s.fo".formatted(name));
return Dataset.forXml(name, xml, hasHtmlFormat, hasPdfFormat);
});
return Stream.concat(csvs, xmls).sorted((a, b) -> a.name().compareToIgnoreCase(b.name()));
} catch (IOException e) {
log.warn("Error resolving resources: {}", e.getMessage());
return Stream.empty();
}
}

@Override
public boolean stylesheetExists(@NonNull String name) {
final var xslt = resourcePatternResolver.getResource("%s/%s.xslt".formatted(directory, name));
return xslt.exists();
}


@NonNull
private static String resolveName(@NonNull Resource resource) {
var name = resource.getFilename();
Expand Down
53 changes: 32 additions & 21 deletions app/src/main/java/nl/runnable/dataset/web/DatasetController.java
Original file line number Diff line number Diff line change
@@ -1,62 +1,73 @@
package nl.runnable.dataset.web;

import lombok.RequiredArgsConstructor;
import nl.runnable.dataset.Dataset;
import nl.runnable.dataset.DatasetRepository;
import nl.runnable.dataset.DatasetSourceFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;

import javax.xml.transform.Source;
import java.util.Map;
import java.util.NoSuchElementException;

import static nl.runnable.dataset.web.DatasetXsltViewResolver.datasetViewName;

@Controller
@RequestMapping("/datasets")
@RequiredArgsConstructor
public class DatasetController {

private final DatasetRepository repository;
private final DatasetRepository datasetRepository;

private final DatasetSourceFactory datasetSourceFactory;

@GetMapping
public ModelAndView index() {
final var modelAndView = new ModelAndView("dataset/index.html");
final var datasets = repository.findAll();
final var datasets = datasetRepository.findAll();
modelAndView.addObject(datasetSourceFactory.createIndexPageSource(datasets));
return modelAndView;
}

@GetMapping("/{name}.html")
public ModelAndView datasetHtml(@PathVariable(value = "name") String name) {
final var dataset = repository.find(name).orElseThrow();
final var source = datasetSourceFactory.createDatasetPageSource(dataset);

final var modelAndView = new ModelAndView("dataset/table.html");
modelAndView.addObject(source);
return modelAndView;
final var dataset = datasetRepository.find(name).orElseThrow();

var viewName = "%s.html".formatted(name);
final Source source;
if (datasetRepository.stylesheetExists(viewName)) {
viewName = datasetViewName(viewName);
source = datasetSourceFactory.createDatasetXmlSource(dataset);
} else {
viewName = "dataset/table.html";
source = datasetSourceFactory.createDatasetPageSource(dataset);
}

return new ModelAndView(viewName, Map.of("source", source));
}

@GetMapping("/{name}.pdf")
public ModelAndView datasetPdf(@PathVariable(value = "name") String name) {
final var dataset = repository.find(name).orElseThrow();
final var source = datasetSourceFactory.createDatasetSource(dataset);

final var modelAndView = new ModelAndView("dataset/table.fo");
modelAndView.addObject(source);
return modelAndView;
final var dataset = datasetRepository.find(name).orElseThrow();
final var source = datasetSourceFactory.createDatasetXmlSource(dataset);
return new ModelAndView("dataset/table.fo", Map.of("source", source));
}

@GetMapping("/{name}.xml")
public ModelAndView datasetXml(@PathVariable(value = "name") String name) {
final var dataset = repository.find(name).orElseThrow();
final var source = datasetSourceFactory.createDatasetSource(dataset);

final var modelAndView = new ModelAndView("dataset/identity.xml");
modelAndView.addObject(source);
return modelAndView;
final var dataset = datasetRepository.find(name).orElseThrow();
final var source = datasetSourceFactory.createDatasetXmlSource(dataset);
return new ModelAndView("dataset/identity.xml", Map.of("source", source));
}

@GetMapping("/{name}.csv")
public ModelAndView datasetCsv(@PathVariable(value = "name") String name) {
final var dataset = repository.find(name).orElseThrow();
final var dataset = datasetRepository.find(name).orElseThrow();
if (dataset.type() != Dataset.Type.CSV) {
throw new NoSuchElementException();
}

final var modelAndView = new ModelAndView();
modelAndView.setView(new ResourceView(dataset.resource(), "text/csv"));
Expand Down
Loading

0 comments on commit 228f7a3

Please sign in to comment.