Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ and this project uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html)

## [Unreleased]

### Changed

- A blank driver (`driver.add --shim-class`) now starts with the documents the engine and
Designer expect, as templates: a filter with User and Group and the usual attributes
synchronised both ways, shim settings with one example parameter per section (driver,
Subscriber, Publisher heartbeat), and an empty configuration-values document — instead of an
empty `<filter/>` and bare option groups.

## [0.14.0] - 2026-10-07

### Added
Expand Down
46 changes: 42 additions & 4 deletions src/main/java/com/pointblue/dirxml/dev/edit/DriverOps.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@
* artifacts the export carries are added to the tree's Library when absent
* (matched by name; an existing one is kept as is);</li>
* <li>{@code --copy-of D}: a clone of an existing driver, links re-pointed;</li>
* <li>{@code --shim-class C}: a blank driver with an empty filter.</li>
* <li>{@code --shim-class C}: a blank driver with a starting filter (User and Group, the
* usual attributes synchronised both ways), shim settings with one example parameter per
* section, and an empty configuration-values document — every document the engine and
* Designer expect, as templates to edit rather than blanks to invent.</li>
* </ul>
* Package meta travels with the copied artifacts, so the deployer treats them as
* packaged and the project writer will refuse a packaged driver.
Expand All @@ -37,6 +40,41 @@ public final class DriverOps {
private DriverOps() {
}

/** A starting filter for a blank driver: User and Group, the usual attributes, synchronised both ways. */
static final String BLANK_FILTER = "<filter>"
+ "<filter-class class-name=\"User\" publisher=\"sync\" publisher-create-homedir=\"true\" publisher-track-template-member=\"false\" subscriber=\"sync\">"
+ "<filter-attr attr-name=\"CN\" merge-authority=\"default\" publisher=\"sync\" publisher-optimize-modify=\"true\" subscriber=\"sync\"/>"
+ "<filter-attr attr-name=\"Surname\" merge-authority=\"default\" publisher=\"sync\" publisher-optimize-modify=\"true\" subscriber=\"sync\"/>"
+ "<filter-attr attr-name=\"Given Name\" merge-authority=\"default\" publisher=\"sync\" publisher-optimize-modify=\"true\" subscriber=\"sync\"/>"
+ "<filter-attr attr-name=\"Full Name\" merge-authority=\"default\" publisher=\"sync\" publisher-optimize-modify=\"true\" subscriber=\"sync\"/>"
+ "<filter-attr attr-name=\"Internet EMail Address\" merge-authority=\"default\" publisher=\"sync\" publisher-optimize-modify=\"true\" subscriber=\"sync\"/>"
+ "<filter-attr attr-name=\"Telephone Number\" merge-authority=\"default\" publisher=\"sync\" publisher-optimize-modify=\"true\" subscriber=\"sync\"/>"
+ "<filter-attr attr-name=\"Login Disabled\" merge-authority=\"default\" publisher=\"sync\" publisher-optimize-modify=\"true\" subscriber=\"sync\"/>"
+ "<filter-attr attr-name=\"Group Membership\" merge-authority=\"default\" publisher=\"sync\" publisher-optimize-modify=\"true\" subscriber=\"sync\"/>"
+ "</filter-class>"
+ "<filter-class class-name=\"Group\" publisher=\"sync\" publisher-create-homedir=\"false\" publisher-track-template-member=\"false\" subscriber=\"sync\">"
+ "<filter-attr attr-name=\"CN\" merge-authority=\"default\" publisher=\"sync\" publisher-optimize-modify=\"true\" subscriber=\"sync\"/>"
+ "<filter-attr attr-name=\"Description\" merge-authority=\"default\" publisher=\"sync\" publisher-optimize-modify=\"true\" subscriber=\"sync\"/>"
+ "<filter-attr attr-name=\"Member\" merge-authority=\"default\" publisher=\"sync\" publisher-optimize-modify=\"true\" subscriber=\"sync\"/>"
+ "</filter-class>"
+ "</filter>";

/** Shim settings for a blank driver: the three sections, each with one example parameter in the engine's definition form. */
static final String BLANK_SHIM_CONFIG = "<driver-config name=\"%NAME%\">"
+ "<driver-options><configuration-values><definitions>"
+ "<definition display-name=\"Example driver option\" name=\"example-option\" type=\"string\">"
+ "<description>A driver parameter the shim reads from its init document (replace or remove).</description><value/></definition>"
+ "</definitions></configuration-values></driver-options>"
+ "<subscriber-options><configuration-values><definitions>"
+ "<definition display-name=\"Example subscriber option\" name=\"example-sub-option\" type=\"string\">"
+ "<description>A Subscriber channel parameter (replace or remove).</description><value/></definition>"
+ "</definitions></configuration-values></subscriber-options>"
+ "<publisher-options><configuration-values><definitions>"
+ "<definition display-name=\"Publisher heartbeat interval\" name=\"pub-heartbeat-interval\" type=\"integer\">"
+ "<description>Minutes of Publisher silence after which the shim sends a status document; 0 disables it.</description><value>1</value></definition>"
+ "</definitions></configuration-values></publisher-options>"
+ "</driver-config>";

public static final class Add implements Operation {
private final String name;
private final Path export;
Expand Down Expand Up @@ -113,10 +151,10 @@ public void apply(DriverSet ds, Transaction tx) throws Refusal, IOException {
d.shimClass = shim;
d.shimAuthServer = authServer;
d.shimAuthId = authId;
d.config.put(Driver.DRIVER_FILTER, CanonicalXml.parse("<filter/>").getDocumentElement());
d.config.put(Driver.DRIVER_FILTER, CanonicalXml.parse(BLANK_FILTER).getDocumentElement());
d.config.put(Driver.SHIM_CONFIG_INFO, CanonicalXml.parse(
"<driver-config name=\"" + name.replace("&", "&amp;").replace("\"", "&quot;") + "\"><driver-options/><subscriber-options/><publisher-options/></driver-config>")
.getDocumentElement());
BLANK_SHIM_CONFIG.replace("%NAME%", name.replace("&", "&amp;").replace("\"", "&quot;"))).getDocumentElement());
d.config.put(Driver.CONFIG_VALUES, CanonicalXml.parse("<configuration-values><definitions/></configuration-values>").getDocumentElement());
}
if (ds.dn != null && !ds.dn.isBlank()) {
d.dn = "cn=" + name + "," + ds.dn;
Expand Down
17 changes: 14 additions & 3 deletions src/test/java/com/pointblue/dirxml/dev/edit/DriverOpsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,20 @@ public void blankDriver() throws IOException {
Driver d = AsCodeReader.read(tree).driver("Loop");
assertEquals("com.example.Loop", d.shimClass);
assertEquals("svc", d.shimAuthId);
assertNotNull(d.config.get(Driver.DRIVER_FILTER));
assertNotNull(d.config.get(Driver.SHIM_CONFIG_INFO));
assertTrue(r.report.ok());
// the templates: a starting filter, shim settings with an example per section, an empty configuration-values
org.w3c.dom.Element filter = d.config.get(Driver.DRIVER_FILTER);
assertNotNull(filter);
assertEquals(2, filter.getElementsByTagName("filter-class").getLength());
assertEquals("User", ((org.w3c.dom.Element) filter.getElementsByTagName("filter-class").item(0)).getAttribute("class-name"));
assertTrue(filter.getElementsByTagName("filter-attr").getLength() >= 8);
org.w3c.dom.Element shim = d.config.get(Driver.SHIM_CONFIG_INFO);
assertNotNull(shim);
assertEquals("Loop", shim.getAttribute("name"));
assertEquals(3, shim.getElementsByTagName("definition").getLength());
assertEquals(1, shim.getElementsByTagName("publisher-options").getLength());
assertNotNull(d.config.get(Driver.CONFIG_VALUES));
assertEquals("definitions", d.config.get(Driver.CONFIG_VALUES).getElementsByTagName("definitions").item(0).getNodeName());
assertTrue(r.report.text(), r.report.ok());
assertTrue(run(new DriverOps.Add("Blank2", null, null, false, null, null, null)).refusal.contains("--shim-class"));
}

Expand Down
Loading