Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
22067cb
refactor(spi): redesign `ExportDataContext` with an immutable builder…
poikilotherm Jan 30, 2026
d45126c
refactor(spi): change `ExportException` to extend `RuntimeException` …
poikilotherm Jan 30, 2026
d18d8e1
refactor(spi): enhance `ExportDataProvider` with context-aware data r…
poikilotherm Jan 30, 2026
0947cc0
chore(deps): add JUnit Jupiter dependency for unit testing
poikilotherm Jan 30, 2026
ece68ae
feat(core): introduce plugin loader, core-provider and plugin contracts
poikilotherm Feb 27, 2026
9d81d16
chore(build): add `jdk.version` POM property for Java 17 compilation …
poikilotherm Feb 27, 2026
0c8a945
refactor(spi): add `@Override` to `getMediaType` in `XMLExporter` for…
poikilotherm Feb 27, 2026
d734ac5
style(export): rename ExportDataContext to DatasetExportQuery, better…
poikilotherm Mar 2, 2026
7359acc
feat(export): add export query predicates and builders for dataset an…
poikilotherm Mar 2, 2026
b211872
feat(export): add `PageRequest` for pagination support with validation
poikilotherm Mar 2, 2026
ef777fe
refactor(export): replace `ExportDataContext` with `DatasetExportQuer…
poikilotherm Mar 2, 2026
d269680
refactor(export): remove `getTabularDataDetails` method in `ExportDat…
poikilotherm Mar 2, 2026
6fb4228
refactor(export): restructure package and move `export` module to ded…
poikilotherm Mar 10, 2026
b42a780
refactor(meta): move `CoreProvider` and `Plugin` interfaces to "meta"…
poikilotherm Mar 10, 2026
5454e92
feat(meta): enhance `PluginContractProcessor` for stricter validation…
poikilotherm Mar 10, 2026
da8a7de
style(meta): improve error messages in `PluginContractProcessor` for …
poikilotherm Mar 10, 2026
1c4fb1a
feat(meta): enforce `@PluginContract` usage restriction to interfaces…
poikilotherm Mar 10, 2026
67cb4fc
feat(meta): enforce validation against direct implementation of base …
poikilotherm Mar 10, 2026
531366d
feat(meta): add `PluginDescriptor` and `PluginDescriptorFormat` for p…
poikilotherm Mar 11, 2026
9e3aa61
refactor(meta): rename `kind` to `role` in `@PluginContract` for impr…
poikilotherm Mar 13, 2026
b5e9f2d
refactor(meta): rename `PluginDescriptor` to `Descriptor` for consist…
poikilotherm Mar 27, 2026
e79804f
refactor(meta): remove unused `DESCRIPTOR_DIRECTORY` constant and enh…
poikilotherm Mar 27, 2026
d19f1fc
test(meta): add unit test for invalid `@PluginContract` usage on non-…
poikilotherm Mar 27, 2026
41873d6
refactor(meta): extract `transformClassName` to centralize class name…
poikilotherm Mar 27, 2026
9e65bd1
feat(meta): introduce `PluginDescriptor` and `SourcedDescriptor` for …
poikilotherm Mar 27, 2026
fdcc7f1
feat(meta): add `DescriptorScanner` for scanning plugin descriptors i…
poikilotherm Mar 27, 2026
4b3f117
feat(core): move PluginLoader infrastructure to new Core Maven Module
poikilotherm Mar 27, 2026
2bc5750
chore(meta,export): comment out version definitions and add notes on …
poikilotherm Mar 28, 2026
9ab95d2
feat(core): introduce `LoaderHelper`, `LoaderConfiguration`, and `Des…
poikilotherm Mar 28, 2026
0358a12
chore(core): add Maven POM file for core module with initial dependen…
poikilotherm Mar 28, 2026
cd6b88a
feat(core): enhance `PluginLoader` with validation and configuration …
poikilotherm Mar 28, 2026
c905de0
chore(api): introduce Dataverse SPI package module
poikilotherm Mar 28, 2026
f345c94
chore: update parent POM at Git root to manage modules and dependencies
poikilotherm Mar 28, 2026
a3eda7e
refactor(meta): centralize processor constants into `ProcessorConstants`
poikilotherm Mar 30, 2026
9747e21
docs(meta): document SPI annotations for plugin contracts and impleme…
poikilotherm Mar 31, 2026
7ef5f68
feat(meta): enforce contract graph validation in `PluginContractProce…
poikilotherm Mar 31, 2026
61fa9ae
test(meta): restructure and expand test cases for `PluginContractProc…
poikilotherm Mar 31, 2026
547f61d
chore(api): use Maven Shade Plugin in API module to build Uber-JAR
poikilotherm Mar 31, 2026
b243299
test(meta): add tests for intermediate interface compatibility in `Pl…
poikilotherm Mar 31, 2026
7cc308b
feat(export): introduce `@PluginContract` annotations and `API_LEVEL`…
poikilotherm Mar 31, 2026
fb20e30
feat(export): add default `identity()` method to `Exporter` interface
poikilotherm Mar 31, 2026
84e28a1
test(export): add integration tests for descriptor and service file g…
poikilotherm Mar 31, 2026
1e6626a
feat(docs): add Maven site documentation and resources
poikilotherm Apr 1, 2026
62fc22a
feat(docs): parameterize site base URL for flexible Maven site deploy…
poikilotherm Apr 1, 2026
05251eb
test(core): improve test coverage and update interface validation sce…
poikilotherm Apr 1, 2026
b696c0d
fix(meta): add validation for non-public interfaces in `PluginContrac…
poikilotherm Apr 1, 2026
a6aebde
fix(meta): improve directory/jar scanning logic in `DescriptorScanner`
poikilotherm Apr 1, 2026
5417734
test(core): add infrastructure for dynamic plugin compilation and plu…
poikilotherm Apr 1, 2026
e8f131d
test(core): parameterize API level test cases in `PluginLoaderIntegra…
poikilotherm Apr 2, 2026
52e750a
feat(core,meta): add SPI record validation and corresponding tests
poikilotherm Apr 2, 2026
d3bfa9b
fix(meta): validate plugin and provider levels to prevent negative va…
poikilotherm Apr 2, 2026
1cae9a5
feat(core): extend LoaderHelper for determination of provider API levels
poikilotherm Apr 2, 2026
a60d249
feat(core): add provider API level preloading validation in PluginLoader
poikilotherm Apr 2, 2026
bf8d9a4
refactor(core): simplify `LoaderConfiguration` API and update usages
poikilotherm Apr 2, 2026
fb05a21
ci: re-enable site workflow for `main` branch only
poikilotherm Apr 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions .github/workflows/site.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
name: Publish Maven Site

on:
push:
branches:
- main
workflow_dispatch:

permissions:
contents: write

concurrency:
group: publish-maven-site-${{ github.ref }}
cancel-in-progress: true

jobs:
publish-site:
runs-on: ubuntu-latest

steps:
- name: Check out source
uses: actions/checkout@v6

- name: Set up Java 17
uses: actions/setup-java@v5
with:
distribution: temurin
java-version: '17'
cache: maven

# TODO: Later on, we need to extend this mechanism to provide both version and snapshot,
# as well as a different case the stable version and "latest".
- name: Build and verify project site
# By default, docs.site.base is / for local development. For deployment, this needs to be adapted.
# IMPORTANT: make sure the base ends with "/"!
run: mvn -B verify site -Ddocs.site.base=/dataverse-spi/snapshot/

- name: Add .nojekyll
run: touch target/site/.nojekyll

- name: Deploy to gh-pages
uses: JamesIves/github-pages-deploy-action@v4
with:
branch: gh-pages
folder: target/site
target-folder: snapshot
clean: true
single-commit: true
63 changes: 63 additions & 0 deletions api/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>io.gdcc.spi</groupId>
<artifactId>parent</artifactId>
<version>2.1.0-SNAPSHOT</version>
</parent>

<groupId>io.gdcc</groupId>
<artifactId>dataverse-spi</artifactId>
<!-- For now, keep all the module versions in lockstep. Can be diversified later. -->
<!-- <version>x.y.z.-SNAPSHOT</version> -->

<!--
Even with no source code, still package as JAR!
As a POM-only artifact, it only acts like a BOM/parent, not roping in dependencies for users!
-->
<packaging>jar</packaging>

<!--
This module aggregates all the artifacts to form a single distribution package.
Users use the transitive dependencies to build Dataverse plugins.
-->
<dependencies>
<dependency>
<groupId>io.gdcc.spi</groupId>
<artifactId>meta</artifactId>
</dependency>
<dependency>
<groupId>io.gdcc.spi</groupId>
<artifactId>core</artifactId>
</dependency>
<dependency>
<groupId>io.gdcc.spi</groupId>
<artifactId>export</artifactId>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.6.2</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<shadedArtifactAttached>false</shadedArtifactAttached>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>

</project>
34 changes: 34 additions & 0 deletions core/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>io.gdcc.spi</groupId>
<artifactId>parent</artifactId>
<version>2.1.0-SNAPSHOT</version>
</parent>

<artifactId>core</artifactId>
<!-- For now, keep all the module versions in lockstep. Can be diversified later. -->
<!-- <version>x.y.z.-SNAPSHOT</version> -->

<dependencies>
<dependency>
<groupId>io.gdcc.spi</groupId>
<artifactId>meta</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

</project>
196 changes: 196 additions & 0 deletions core/src/main/java/io/gdcc/spi/core/loader/LoaderConfiguration.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
package io.gdcc.spi.core.loader;


/**
* Immutable configuration controlling the behavior of the plugin loader.
*
* <p>Use {@link #defaults()} to start from the standard configuration and then
* adjust individual options with the fluent {@code with...} methods.</p>
*
* <p>Example:</p>
* <pre>{@code
* LoaderConfiguration configuration = LoaderConfiguration.defaults()
* .withEmitWarningsOnMultiPluginSource(true)
* .withAbortOnCompatibilityProblems(false);
* }</pre>
*/
public final class LoaderConfiguration {

private final boolean enforceSingleSourceMatchingPluginsOnly;
private final boolean emitWarningsOnMultiPluginSource;
private final boolean abortOnCompatibilityProblems;
private final boolean abortOnDuplicatedIdentities;
private final boolean enforceUnambiguousPluginIdentities;

private LoaderConfiguration(
boolean enforceSingleSourceMatchingPluginsOnly,
boolean emitWarningsOnMultiPluginSource,
boolean abortOnCompatibilityProblems,
boolean abortOnDuplicatedIdentities,
boolean enforceUnambiguousPluginIdentities
) {
this.enforceSingleSourceMatchingPluginsOnly = enforceSingleSourceMatchingPluginsOnly;
this.emitWarningsOnMultiPluginSource = emitWarningsOnMultiPluginSource;
this.abortOnCompatibilityProblems = abortOnCompatibilityProblems;
this.abortOnDuplicatedIdentities = abortOnDuplicatedIdentities;
this.enforceUnambiguousPluginIdentities = enforceUnambiguousPluginIdentities;
}

/**
* Returns the standard loader configuration (which is strictly enforcing).
*
* <ul>
* <li>{@code enforceSingleSourceMatchingPluginsOnly = false}</li>
* <li>{@code emitWarningsOnMultiPluginSource = false}</li>
* <li>{@code abortOnCompatibilityProblems = true}</li>
* <li>{@code abortOnDuplicatedIdentities = true}</li>
* <li>{@code enforceUnambiguousPluginIdentities = true}</li>
* </ul>
*/
public static LoaderConfiguration defaults() {
return new LoaderConfiguration(
true,
false,
true,
true,
true
);
}

/**
* Returns a permissive loader configuration with all strict validation features disabled.
* It has package private visibility as the only permissive usage is in a testing context.
*
* <p>
* The configuration has the following properties:
* <ul>
* <li>{@code enforceSingleSourceMatchingPluginsOnly = false}</li>
* <li>{@code emitWarningsOnMultiPluginSource = false}</li>
* <li>{@code abortOnCompatibilityProblems = false}</li>
* <li>{@code abortOnDuplicatedIdentities = false}</li>
* <li>{@code enforceUnambiguousPluginIdentities = false}</li>
* </ul>
* </p>
*
* @return a {@code LoaderConfiguration} instance with permissive settings.
*/
static LoaderConfiguration permissive() {
return new LoaderConfiguration(
false,
false,
false,
false,
false
);
}

/**
* When enabled, a source may only provide plugins for a single requested base contract.
* If any non-matching plugin is found, loading from that source is aborted entirely.
*
* <p>When disabled, non-matching plugins are ignored.</p>
*/
public boolean enforceSingleSourceMatchingPluginsOnly() {
return enforceSingleSourceMatchingPluginsOnly;
}

/**
* Returns a copy with {@link #enforceSingleSourceMatchingPluginsOnly()} updated.
*/
public LoaderConfiguration withEnforceSingleSourceMatchingPluginsOnly(boolean value) {
return new LoaderConfiguration(
value,
emitWarningsOnMultiPluginSource,
abortOnCompatibilityProblems,
abortOnDuplicatedIdentities,
enforceUnambiguousPluginIdentities
);
}

/**
* When {@link #enforceSingleSourceMatchingPluginsOnly()} is disabled, controls whether
* multi-plugin-contract sources should emit warnings.
*/
public boolean emitWarningsOnMultiPluginSource() {
return emitWarningsOnMultiPluginSource;
}

/**
* Returns a copy with {@link #emitWarningsOnMultiPluginSource()} updated.
*/
public LoaderConfiguration withEmitWarningsOnMultiPluginSource(boolean value) {
return new LoaderConfiguration(
enforceSingleSourceMatchingPluginsOnly,
value,
abortOnCompatibilityProblems,
abortOnDuplicatedIdentities,
enforceUnambiguousPluginIdentities
);
}

/**
* When enabled, plugin loading aborts on discovered compatibility problems (for example, API level mismatches).
* No classes are actually loaded, problems are detected using plugin metadata only.
*/
public boolean abortOnCompatibilityProblems() {
return abortOnCompatibilityProblems;
}

/**
* Returns a copy with {@link #abortOnCompatibilityProblems()} updated.
*/
public LoaderConfiguration withAbortOnCompatibilityProblems(boolean value) {
return new LoaderConfiguration(
enforceSingleSourceMatchingPluginsOnly,
emitWarningsOnMultiPluginSource,
value,
abortOnDuplicatedIdentities,
enforceUnambiguousPluginIdentities
);
}

/**
* When enabled, loading aborts if duplicate plugin identities are detected.
*
* <p>Note: duplicated identities make plugins undistinguishable for users.</p>
*/
public boolean abortOnDuplicatedIdentities() {
return abortOnDuplicatedIdentities;
}

/**
* Returns a copy with {@link #abortOnDuplicatedIdentities()} updated.
*/
public LoaderConfiguration withAbortOnDuplicatedIdentities(boolean value) {
return new LoaderConfiguration(
enforceSingleSourceMatchingPluginsOnly,
emitWarningsOnMultiPluginSource,
abortOnCompatibilityProblems,
value,
enforceUnambiguousPluginIdentities
);
}

/**
* When enabled, plugin identities must be unique within a source.
* Any plugin's identity that differs by case or special chars only will be seen as a duplicate.
*/
public boolean enforceUnambiguousPluginIdentities() {
return enforceUnambiguousPluginIdentities;
}

/**
* Returns a copy with {@link #enforceUnambiguousPluginIdentities()} updated.
*/
public LoaderConfiguration withEnforceUnambiguousPluginIdentities(boolean value) {
return new LoaderConfiguration(
enforceSingleSourceMatchingPluginsOnly,
emitWarningsOnMultiPluginSource,
abortOnCompatibilityProblems,
abortOnDuplicatedIdentities,
value
);
}
}


17 changes: 17 additions & 0 deletions core/src/main/java/io/gdcc/spi/core/loader/LoaderException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package io.gdcc.spi.core.loader;

import java.util.List;

public class LoaderException extends RuntimeException {

private final List<LoaderProblem> problems;

public LoaderException(List<LoaderProblem> problems) {
super("Multiple problems have been detected by the loader, accessible from getProblems().");
this.problems = List.copyOf(problems);
}

public List<LoaderProblem> getProblems() {
return problems;
}
}
Loading