diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 25541a8..af99780 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -61,7 +61,14 @@ jobs: # distributionManagement points at GitHub Packages, so we override # the deploy target to the local staging directory configured in the # JReleaser block (target/staging-deploy). - run: ./mvnw -B -Pfull-build clean deploy -DaltDeploymentRepository=local::file:./target/staging-deploy + # + # The path is ABSOLUTE (github.workspace) on purpose: in the multi-module + # reactor a relative ./target/staging-deploy would resolve per-module and + # scatter each module's artifacts under its own target/, so JReleaser + # (running once at the root) would not find them. An absolute path stages + # every module — core, the feature modules, spring-services-all and the BOM — + # into the single root staging directory JReleaser reads. + run: ./mvnw -B -Pfull-build clean deploy -DaltDeploymentRepository=local::file:${{ github.workspace }}/target/staging-deploy - name: Publish with JReleaser run: ./mvnw -B -Pdeploy-release jreleaser:full-release -N diff --git a/README.md b/README.md index 6e8ad9b..7ec65ab 100644 --- a/README.md +++ b/README.md @@ -16,14 +16,52 @@ into the full stack with a single import or pick the parts they need. Releases are published to Maven Central; SNAPSHOTs are published to the Sonatype Central Portal snapshot repository on every push to `main`. +`spring-services` is a **Maven multi-module reactor**. The bare `com.open-elements:spring-services` +coordinate is the reactor parent (a `pom`, no classes) — depend on one of the modules instead. + +**Everything (drop-in):** `spring-services-all` bundles the core plus every optional feature module. + ```xml com.open-elements - spring-services + spring-services-all ``` +**À la carte:** import the BOM once, then declare `spring-services-core` plus only the feature +modules you need (`spring-services-slack`, `spring-services-mcp`, `spring-services-email`, +`spring-services-search`, `spring-services-dbbackup`) without versions: + +```xml + + + + com.open-elements + spring-services-bom + + pom + import + + + + + + + com.open-elements + spring-services-core + + + com.open-elements + spring-services-slack + + +``` + +Each feature module self-activates when present on the classpath, so consumers pull only the +dependencies they use. Migrating from the pre-split `spring-services` artifact? See +[`docs/releases/upgrade-to-1.4.md`](docs/releases/upgrade-to-1.4.md). + To consume a `-SNAPSHOT` build, also declare the Central Portal snapshot repository (releases still come from Maven Central): @@ -272,23 +310,33 @@ later release). Consumers must run one Flyway migration when upgrading. ## Project Layout +A Maven reactor: `spring-services` (parent, `pom`) aggregates the modules below. All classes stay +under `com.openelements.spring.base.*` regardless of the module that ships them. + ``` -src/main/java/com/openelements/spring/base/ -├── FullSpringServiceConfig.java — Aggregate @Import of every feature config -├── data/ — Generic CRUD abstractions -├── events/ — Lifecycle events -├── security/ — Filter chain, JWT converter, AuthService -│ ├── apikey/ — X-API-Key authentication filter -│ └── user/ — Local user mirror -├── services/ -│ ├── apikey/ — API key data layer -│ ├── search/ — Meilisearch client, startup reindex, highlighter -│ ├── settings/ — Key/value settings -│ ├── tag/ — Tag CRUD + PreTagDeleteEvent -│ └── webhook/ — Outbound webhook delivery -└── tenant/ — Multi-tenancy primitives +spring-services/ — reactor parent (packaging=pom) +├── spring-services-core — all 7 @Entity + single persistence unit, the core +│ auto-configuration (SpringServicesCoreAutoConfiguration) and +│ the always-present features: +│ data/ — generic CRUD abstractions +│ events/ — lifecycle events +│ security/ — filter chains, JWT converter, AuthService +│ apikey/, user/ +│ services/ — apikey, settings, tag, comment, audit, +│ webhook, translation, system +│ tenant/, apitoken/ +├── spring-services-slack — Slack messaging (→ slack-api-client) +├── spring-services-mcp — MCP server (→ MCP SDK) +├── spring-services-email — SMTP email (→ spring-boot-starter-mail) +├── spring-services-search — Meilisearch full-text search (RestClient, no extra dep) +├── spring-services-dbbackup — db-backup sidecar client (RestClient, no extra dep) +├── spring-services-all — everything bundle (depends on all modules; no config of its own) +└── spring-services-bom — bill of materials for lockstep versioning ``` +Each optional feature module ships its own `@AutoConfiguration` guarded by `@ConditionalOnClass`, so +it self-activates when present and never pulls its heavy dependency into a consumer that skips it. + ## Release Process ### SNAPSHOT Publishing (automatic) diff --git a/docs/releases/upgrade-to-1.4.md b/docs/releases/upgrade-to-1.4.md new file mode 100644 index 0000000..d7d54a4 --- /dev/null +++ b/docs/releases/upgrade-to-1.4.md @@ -0,0 +1,138 @@ +# Upgrade prompt: `com.open-elements:spring-services` 1.3.0 → 1.4.0 + +`spring-services` 1.4.0 restructures the library into a **Maven multi-module reactor**. This is a +**breaking coordinate change** — but **not** a Java-API or database change: + +- The `com.open-elements:spring-services` coordinate is now the **reactor parent** (`packaging=pom`). + It no longer resolves to a jar with classes, so a consumer that declares it as a dependency will + fail to compile. +- The library is split into `spring-services-core` (all seven entities, single persistence unit) plus + optional feature modules — `spring-services-slack`, `spring-services-mcp`, `spring-services-email`, + `spring-services-search`, `spring-services-dbbackup` — an **everything** bundle + `spring-services-all`, and a BOM `spring-services-bom`. +- Consumers now depend only on the features they use; a consumer that used everything switches to + `spring-services-all` (drop-in). Heavy/rare libraries (`slack-api-client`, the MCP SDK, + `spring-boot-starter-mail`) are no longer pulled unless the matching module is present. + +There are **no public Java API changes** (method signatures, DTOs) and **no schema change** — the +`oe_spring_services` schema introduced in 1.3.0 is unchanged. The one internal rename: the starter +auto-configuration `com.openelements.spring.base.SpringServicesAutoConfiguration` is now +`SpringServicesCoreAutoConfiguration` (only relevant if you referenced it in an +`spring.autoconfigure.exclude` list). + +> If you are upgrading from a version **older than 1.3.0**, apply the 1.3.0 database migration first +> (see `upgrade-to-1.3.md`: move the tables into `oe_spring_services`), then this coordinate swap. + +This file is a self-contained prompt for an agent (Claude Code, etc.) to run inside a consumer repo. +Paste it verbatim. + +--- + +## Prompt + +You are working inside a Spring Boot service that depends on `com.open-elements:spring-services`. +Goal: upgrade from `1.3.0` to `1.4.0`. The library became a Maven reactor; you must change the +dependency coordinate. No application code, schema, or configuration changes are required. + +### What changed in 1.4.0 + +#### Dependency coordinate (required change) + +The old single coordinate no longer ships classes. Choose one of two migration paths. + +**Path A — everything (drop-in).** Replace `spring-services` with `spring-services-all`: + +```xml + + + com.open-elements + spring-services + 1.3.0 + + + + + com.open-elements + spring-services-all + 1.4.0 + +``` + +`spring-services-all` bundles core plus every feature module, reproducing the pre-split behaviour. + +**Path B — à-la-carte (only what you use).** Import the BOM once, then declare `spring-services-core` +plus only the feature modules you need, without versions: + +```xml + + + + com.open-elements + spring-services-bom + 1.4.0 + pom + import + + + + + + + com.open-elements + spring-services-core + + + + com.open-elements + spring-services-slack + + +``` + +Feature module artifact ids: `spring-services-slack`, `spring-services-mcp`, +`spring-services-email`, `spring-services-search`, `spring-services-dbbackup`. + +#### Self-activation + +Each feature module self-activates when it is on the classpath (via its own auto-configuration), +gated by the same enable properties as before (e.g. `openelements.mcp.enabled`, +`openelements.meilisearch.enabled`, `openelements.db-backup.enabled`). Slack and email activate when +their module is present. You do not need `@Import` to wire a feature — adding the module is enough. + +#### Auto-configuration rename (only if you excluded it) + +If your application excluded the starter auto-configuration by name, rename it: + +``` +com.openelements.spring.base.SpringServicesAutoConfiguration + -> +com.openelements.spring.base.SpringServicesCoreAutoConfiguration +``` + +Most applications never reference this class and have nothing to do here. + +#### No API, schema, or configuration changes + +No public Java types or signatures changed. The database schema (`oe_spring_services`, from 1.3.0) is +unchanged — no migration. Property names are unchanged. + +### Steps + +1. Find the consumer's build file that declares `com.open-elements:spring-services`. +2. Replace it per **Path A** (switch to `spring-services-all`) or **Path B** (BOM + `-core` + chosen + feature modules). Bump the version to `1.4.0`. +3. If you kept a hand-written `spring.autoconfigure.exclude` for the starter, rename it to + `SpringServicesCoreAutoConfiguration`. +4. Resolve dependencies (`mvn -U dependency:resolve` or `./gradlew --refresh-dependencies`). +5. Compile and run the consumer's test suite to verify the build is green. +6. Commit the coordinate change. + +### Guard rails / Don't do this + +- Do **not** keep depending on the bare `com.open-elements:spring-services` coordinate — it is now a + parent pom and carries no classes. +- Do **not** re-add `slack-api-client`, the MCP SDK, or `spring-boot-starter-mail` by hand — pull the + corresponding feature module instead, which brings the dependency transitively. +- Do **not** change the database or any `@Table(schema = ...)` — there is no schema change in 1.4.0. +- Do **not** mix explicit module versions with the BOM import; let the BOM manage them. +- Do **not** bundle unrelated upgrades into this coordinate change. diff --git a/docs/specs/INDEX.md b/docs/specs/INDEX.md index 8695d4f..c10fdb2 100644 --- a/docs/specs/INDEX.md +++ b/docs/specs/INDEX.md @@ -15,6 +15,6 @@ | 011 | 011-security-config-hygiene | Security config hygiene | backend, security, architecture | Tighten Spring Boot auto-configuration annotations, drop synchronized from UserService hot path, centralise JSON AuthenticationEntryPoint, use SecurityContextHolderStrategy, replace UserInformation sentinel with Optional, lock Roles utility class | — | done | | 012 | 012-scim-foundation-user-model | SCIM foundation user model | backend, security, database | Relax UserEntity.sub, add externalId / userName / active, JIT-login correlation and backfill, AccessDeniedException gate for deactivated users — prepares the data model for the SCIM-Provider spec without introducing SCIM endpoints | — | done | | 013 | 013-dedicated-schema-and-starter | Dedicated schema + starter | backend, database, architecture, build | Move all 7 library tables into a fixed `oe_spring_services` schema (single persistence unit) and ship a real Spring Boot starter that registers the library package additively via AutoConfigurationPackages — zero-config consumption. Migrations stay consumer-managed; the library ships ready-to-apply per-release SQL in the upgrade doc. Includes the app→lib cross-entity reference contract | #25 | done | -| 014 | 014-multi-module-restructuring | Multi-module restructuring | build, architecture, infrastructure | Restructure the single artifact into a Maven reactor: `spring-services-core` (all 7 entities, single persistence unit) + optional feature modules (`slack`, `mcp`, `email`, `search`, `dbbackup`) + `spring-services-all` aggregator jar + `spring-services-bom`. Lockstep versioning; per-module `@AutoConfiguration` guarded by `@ConditionalOnClass`; builds on spec 013 (released first). Breaking change: `spring-services` coordinate becomes the reactor parent, consumers migrate to `spring-services-all` | #27 | in progress | +| 014 | 014-multi-module-restructuring | Multi-module restructuring | build, architecture, infrastructure | Restructure the single artifact into a Maven reactor: `spring-services-core` (all 7 entities, single persistence unit) + optional feature modules (`slack`, `mcp`, `email`, `search`, `dbbackup`) + `spring-services-all` aggregator jar + `spring-services-bom`. Lockstep versioning; per-module `@AutoConfiguration` guarded by `@ConditionalOnClass`; builds on spec 013 (released first). Breaking change: `spring-services` coordinate becomes the reactor parent, consumers migrate to `spring-services-all` | #27 | done | | 015 | 015-scim-users-provider | SCIM Users provider | backend, security, api, database | SCIM 2.0 service-provider first slice: an isolated `/scim/v2/**` bearer-token filter chain (static `openelements.scim.token`), discovery endpoints (ServiceProviderConfig/ResourceTypes/Schemas), and the Users resource (list/filter, POST, GET, PUT full-replace, soft DELETE) mapped onto spec 012's `UserEntity`. `POST` collision → `409 uniqueness`; DELETE is soft (`deleted`/`deleted_at`); writes audited under a reserved SCIM service principal. Groups+membership and Group→Role mapping deferred to follow-up issues; grounded in real Authentik 2026.5.4 traffic (#21) with a pre-merge verification test plan. | #21 | open | diff --git a/pom.xml b/pom.xml index 656c891..277ec95 100644 --- a/pom.xml +++ b/pom.xml @@ -40,6 +40,13 @@ spring-services-core + spring-services-slack + spring-services-search + spring-services-dbbackup + spring-services-email + spring-services-mcp + spring-services-all + spring-services-bom diff --git a/release.sh b/release.sh index 6d9780c..f075b39 100755 --- a/release.sh +++ b/release.sh @@ -53,7 +53,9 @@ generate_release_doc() { } echo "Releasing version $NEW_VERSION" -./mvnw versions:set -DnewVersion=$NEW_VERSION +# -DprocessAllModules keeps the reactor lockstep: it bumps the parent and every +# module (each references the parent version explicitly, not ${revision}). +./mvnw versions:set -DnewVersion=$NEW_VERSION -DprocessAllModules # Build and test locally so we never push a tag that fails CI. # @@ -96,6 +98,6 @@ git tag "v$NEW_VERSION" git push origin "v$NEW_VERSION" echo "Setting version to $NEXT_VERSION" -./mvnw versions:set -DnewVersion=$NEXT_VERSION +./mvnw versions:set -DnewVersion=$NEXT_VERSION -DprocessAllModules git commit -am "Version $NEXT_VERSION" git push diff --git a/spring-services-all/pom.xml b/spring-services-all/pom.xml new file mode 100644 index 0000000..ebbc833 --- /dev/null +++ b/spring-services-all/pom.xml @@ -0,0 +1,83 @@ + + 4.0.0 + + + com.open-elements + spring-services + 1.3.0-SNAPSHOT + + + spring-services-all + + Spring Services All + Everything bundle: depends on core plus every optional feature module. Drop-in + replacement for the pre-split spring-services artifact. Ships no configuration of its own — + each module self-activates via its own auto-configuration. + + + + com.open-elements + spring-services-core + ${project.version} + + + com.open-elements + spring-services-slack + ${project.version} + + + com.open-elements + spring-services-search + ${project.version} + + + com.open-elements + spring-services-dbbackup + ${project.version} + + + com.open-elements + spring-services-email + ${project.version} + + + com.open-elements + spring-services-mcp + ${project.version} + + + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework.boot + spring-boot-testcontainers + test + + + org.testcontainers + testcontainers + test + + + org.testcontainers + testcontainers-postgresql + test + + + org.testcontainers + testcontainers-junit-jupiter + test + + + org.postgresql + postgresql + test + + + + diff --git a/spring-services-all/src/test/java/com/example/aggregate/AggregateApp.java b/spring-services-all/src/test/java/com/example/aggregate/AggregateApp.java new file mode 100644 index 0000000..3b1f337 --- /dev/null +++ b/spring-services-all/src/test/java/com/example/aggregate/AggregateApp.java @@ -0,0 +1,13 @@ +package com.example.aggregate; + +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration; + +/** + * A zero-config consumer application with the full reactor on the classpath + * ({@code spring-services-all}). It declares its own entity/repository ({@link Order}) and + * no {@code @Import}, {@code @EntityScan}, or {@code @EnableJpaRepositories} — every + * library module self-activates through its own auto-configuration. + */ +@SpringBootApplication(exclude = OAuth2ResourceServerAutoConfiguration.class) +public class AggregateApp {} diff --git a/spring-services-all/src/test/java/com/example/aggregate/AggregateStarterIntegrationTest.java b/spring-services-all/src/test/java/com/example/aggregate/AggregateStarterIntegrationTest.java new file mode 100644 index 0000000..f615d17 --- /dev/null +++ b/spring-services-all/src/test/java/com/example/aggregate/AggregateStarterIntegrationTest.java @@ -0,0 +1,72 @@ +package com.example.aggregate; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.openelements.spring.base.mcp.McpProperties; +import com.openelements.spring.base.services.email.EmailService; +import com.openelements.spring.base.services.slack.SlackService; +import com.openelements.spring.base.services.user.SystemUser; +import com.openelements.spring.base.services.user.UserRepository; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Import; +import org.springframework.test.context.ActiveProfiles; +import org.testcontainers.junit.jupiter.Testcontainers; + +/** + * Aggregate / zero-config starter integration test (spec 013 gate 1, relocated here per spec 014). + * + *

Boots {@link AggregateApp} — a zero-config consumer with the full reactor + * ({@code spring-services-all}) on the classpath — and asserts that: + * + *

+ */ +@SpringBootTest(classes = AggregateApp.class) +@Import(AggregateTestConfig.class) +@Testcontainers +@ActiveProfiles("testcontainers") +class AggregateStarterIntegrationTest { + + @Autowired private ApplicationContext context; + + @Autowired private OrderRepository orderRepository; + + @Autowired private UserRepository userRepository; + + @Test + @DisplayName("Application and core-library persistence both resolve under the full classpath") + void applicationAndCorePersistenceResolve() { + final Order order = new Order(); + order.setDescription("aggregate order"); + final Order saved = orderRepository.save(order); + + assertThat(orderRepository.findById(saved.getId())).isPresent(); + assertThat(userRepository.findById(SystemUser.ID)) + .as("System User must be bootstrapped in the dedicated schema") + .isPresent(); + } + + @Test + @DisplayName("Representative beans from the slack, email and mcp feature modules are all present") + void optionalFeatureModulesSelfActivate() { + assertThat(context.getBeanNamesForType(SlackService.class)) + .as("slack module must self-activate") + .isNotEmpty(); + assertThat(context.getBeanNamesForType(EmailService.class)) + .as("email module must self-activate") + .isNotEmpty(); + assertThat(context.getBeanNamesForType(McpProperties.class)) + .as("mcp module must self-activate (properties bound unconditionally)") + .isNotEmpty(); + } +} diff --git a/spring-services-all/src/test/java/com/example/aggregate/AggregateTestConfig.java b/spring-services-all/src/test/java/com/example/aggregate/AggregateTestConfig.java new file mode 100644 index 0000000..a5c8358 --- /dev/null +++ b/spring-services-all/src/test/java/com/example/aggregate/AggregateTestConfig.java @@ -0,0 +1,29 @@ +package com.example.aggregate; + +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; +import org.springframework.context.annotation.Bean; +import org.springframework.security.oauth2.jwt.JwtDecoder; +import org.testcontainers.containers.PostgreSQLContainer; + +/** + * Test infrastructure for the aggregate context: a PostgreSQL Testcontainer and a stub + * {@link JwtDecoder} required by the core JWT security chain (the aggregate test does not exercise + * real JWT validation). + */ +@TestConfiguration(proxyBeanMethods = false) +public class AggregateTestConfig { + + @Bean + @ServiceConnection + public PostgreSQLContainer postgresContainer() { + return new PostgreSQLContainer<>("postgres:16-alpine"); + } + + @Bean + public JwtDecoder testJwtDecoder() { + return token -> { + throw new UnsupportedOperationException("JWT validation is disabled in the aggregate test"); + }; + } +} diff --git a/spring-services-all/src/test/java/com/example/aggregate/Order.java b/spring-services-all/src/test/java/com/example/aggregate/Order.java new file mode 100644 index 0000000..2588415 --- /dev/null +++ b/spring-services-all/src/test/java/com/example/aggregate/Order.java @@ -0,0 +1,42 @@ +package com.example.aggregate; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import java.util.UUID; + +/** + * A consumer application's own entity (default schema, not {@code oe_spring_services}), used by the + * aggregate test to prove the application's own persistence resolves alongside the full library. + */ +@Entity +@Table(name = "orders") +public class Order { + + @Id + @GeneratedValue(strategy = GenerationType.UUID) + @Column(name = "id", updatable = false, nullable = false) + private UUID id; + + @Column(name = "description") + private String description; + + public UUID getId() { + return id; + } + + public void setId(final UUID id) { + this.id = id; + } + + public String getDescription() { + return description; + } + + public void setDescription(final String description) { + this.description = description; + } +} diff --git a/spring-services-all/src/test/java/com/example/aggregate/OrderRepository.java b/spring-services-all/src/test/java/com/example/aggregate/OrderRepository.java new file mode 100644 index 0000000..75e6b95 --- /dev/null +++ b/spring-services-all/src/test/java/com/example/aggregate/OrderRepository.java @@ -0,0 +1,7 @@ +package com.example.aggregate; + +import java.util.UUID; +import org.springframework.data.jpa.repository.JpaRepository; + +/** The aggregate test application's own repository. */ +public interface OrderRepository extends JpaRepository {} diff --git a/spring-services-all/src/test/resources/application-testcontainers.properties b/spring-services-all/src/test/resources/application-testcontainers.properties new file mode 100644 index 0000000..31464e6 --- /dev/null +++ b/spring-services-all/src/test/resources/application-testcontainers.properties @@ -0,0 +1,4 @@ +spring.jpa.hibernate.ddl-auto=create-drop +# Provision the dedicated oe_spring_services schema for the schema-qualified library entities. +spring.jpa.properties.hibernate.hbm2ddl.create_namespaces=true +spring.main.allow-bean-definition-overriding=true diff --git a/spring-services-bom/pom.xml b/spring-services-bom/pom.xml new file mode 100644 index 0000000..6c3295d --- /dev/null +++ b/spring-services-bom/pom.xml @@ -0,0 +1,58 @@ + + 4.0.0 + + + com.open-elements + spring-services + 1.3.0-SNAPSHOT + + + spring-services-bom + pom + + Spring Services BOM + Bill of materials: import once to manage every spring-services module at a single + lockstep version, then declare individual modules without versions. + + + + + com.open-elements + spring-services-core + ${project.version} + + + com.open-elements + spring-services-slack + ${project.version} + + + com.open-elements + spring-services-search + ${project.version} + + + com.open-elements + spring-services-dbbackup + ${project.version} + + + com.open-elements + spring-services-email + ${project.version} + + + com.open-elements + spring-services-mcp + ${project.version} + + + com.open-elements + spring-services-all + ${project.version} + + + + + diff --git a/spring-services-core/pom.xml b/spring-services-core/pom.xml index dbb881c..ad01b21 100644 --- a/spring-services-core/pom.xml +++ b/spring-services-core/pom.xml @@ -34,10 +34,6 @@ org.springframework.boot spring-boot-starter-oauth2-resource-server - - org.springframework.boot - spring-boot-starter-mail - io.swagger.core.v3 swagger-annotations-jakarta @@ -49,31 +45,6 @@ ${jspecify.version} compile - - com.slack.api - slack-api-client - ${slack-api-client.version} - - - - - io.modelcontextprotocol.sdk - mcp-spring-webmvc - ${mcp-sdk.version} - - - - - io.modelcontextprotocol.sdk - mcp-json-jackson2 - ${mcp-sdk.version} - - org.springframework.boot @@ -123,4 +94,38 @@ + + + + + org.apache.maven.plugins + maven-enforcer-plugin + + + ban-optional-feature-dependencies + + enforce + + + + + + com.slack.api:slack-api-client + io.modelcontextprotocol.sdk:* + org.springframework.boot:spring-boot-starter-mail + + spring-services-core must not depend on optional feature + libraries (slack-api-client, the MCP SDK, or + spring-boot-starter-mail). Those belong to the feature + modules. + + + + + + + + + diff --git a/spring-services-core/src/main/java/com/openelements/spring/base/FullSpringServiceConfig.java b/spring-services-core/src/main/java/com/openelements/spring/base/FullSpringServiceConfig.java index 5c623a6..5a7c3ce 100644 --- a/spring-services-core/src/main/java/com/openelements/spring/base/FullSpringServiceConfig.java +++ b/spring-services-core/src/main/java/com/openelements/spring/base/FullSpringServiceConfig.java @@ -4,11 +4,7 @@ import com.openelements.spring.base.services.apikey.ApiKeyConfig; import com.openelements.spring.base.services.audit.AuditConfig; import com.openelements.spring.base.services.comment.CommentConfig; -import com.openelements.spring.base.services.dbbackup.DbBackupConfig; -import com.openelements.spring.base.services.email.EmailConfig; -import com.openelements.spring.base.services.search.SearchConfig; import com.openelements.spring.base.services.settings.SettingsConfig; -import com.openelements.spring.base.services.slack.SlackConfig; import com.openelements.spring.base.services.tag.TagConfig; import com.openelements.spring.base.services.translation.TranslationConfig; import com.openelements.spring.base.services.webhook.WebhookConfig; @@ -39,13 +35,7 @@ *
  • {@link SettingsConfig} — generic key/value settings store. *
  • {@link TagConfig} — taggable-entity support. *
  • {@link WebhookConfig} — outbound webhook subscriptions and dispatching. - *
  • {@link SlackConfig} — Slack messaging service for posting plain-text messages to channels. - *
  • {@link EmailConfig} — Email sending service for delivering plain-text emails via SMTP. *
  • {@link AuditConfig} — Audit log of every data lifecycle event with user attribution. - *
  • {@link SearchConfig} — Meilisearch-backed full-text search: startup reindex, scoped keys, - * and per-index settings. - *
  • {@link DbBackupConfig} — Client for the db-backup-service sidecar: trigger backups, poll - * job status, list and stream backup artefacts. * */ @Import({ @@ -55,13 +45,9 @@ SettingsConfig.class, TagConfig.class, WebhookConfig.class, - SlackConfig.class, - EmailConfig.class, AuditConfig.class, CommentConfig.class, - TranslationConfig.class, - SearchConfig.class, - DbBackupConfig.class + TranslationConfig.class }) public class FullSpringServiceConfig { } diff --git a/spring-services-core/src/main/java/com/openelements/spring/base/SpringServicesAutoConfiguration.java b/spring-services-core/src/main/java/com/openelements/spring/base/SpringServicesCoreAutoConfiguration.java similarity index 87% rename from spring-services-core/src/main/java/com/openelements/spring/base/SpringServicesAutoConfiguration.java rename to spring-services-core/src/main/java/com/openelements/spring/base/SpringServicesCoreAutoConfiguration.java index a017c88..4717cf7 100644 --- a/spring-services-core/src/main/java/com/openelements/spring/base/SpringServicesAutoConfiguration.java +++ b/spring-services-core/src/main/java/com/openelements/spring/base/SpringServicesCoreAutoConfiguration.java @@ -13,7 +13,11 @@ import org.springframework.core.type.AnnotationMetadata; /** - * Auto-configuration that turns spring-services into a real Spring Boot starter. + * Core auto-configuration that turns {@code spring-services-core} into a real Spring Boot starter. + * + *

    Each optional feature module ({@code -slack}, {@code -search}, {@code -dbbackup}, {@code -email}, + * {@code -mcp}) ships its own {@code @AutoConfiguration} guarded by {@code @ConditionalOnClass}; this + * class configures only the always-present core features. * *

    Registered through {@code META-INF/spring/ * org.springframework.boot.autoconfigure.AutoConfiguration.imports}, so a consuming application only @@ -43,8 +47,8 @@ @AutoConfiguration @ConditionalOnClass(EntityManagerFactory.class) @AutoConfigureBefore({HibernateJpaAutoConfiguration.class, JpaRepositoriesAutoConfiguration.class}) -@Import({FullSpringServiceConfig.class, SpringServicesAutoConfiguration.PackageRegistrar.class}) -public class SpringServicesAutoConfiguration { +@Import({FullSpringServiceConfig.class, SpringServicesCoreAutoConfiguration.PackageRegistrar.class}) +public class SpringServicesCoreAutoConfiguration { /** Root package of the library — scanned for both entities and Spring Data repositories. */ static final String LIBRARY_ROOT_PACKAGE = "com.openelements.spring.base"; diff --git a/spring-services-core/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/spring-services-core/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index 6186b5b..4cb3c76 100644 --- a/spring-services-core/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/spring-services-core/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -1 +1 @@ -com.openelements.spring.base.SpringServicesAutoConfiguration +com.openelements.spring.base.SpringServicesCoreAutoConfiguration diff --git a/spring-services-core/src/test/java/com/example/app/StarterTestApp.java b/spring-services-core/src/test/java/com/example/app/StarterTestApp.java index 87adb9b..741475e 100644 --- a/spring-services-core/src/test/java/com/example/app/StarterTestApp.java +++ b/spring-services-core/src/test/java/com/example/app/StarterTestApp.java @@ -9,7 +9,7 @@ * {@code @EntityScan}, and no {@code @EnableJpaRepositories}, and it does not point * {@code scanBasePackages} at the library. The default {@code @SpringBootApplication} behaviour * registers only this package ({@code com.example.app}) via {@code @AutoConfigurationPackage}. The - * library's {@code SpringServicesAutoConfiguration} must additively register + * library's {@code SpringServicesCoreAutoConfiguration} must additively register * {@code com.openelements.spring.base} so that both the application's and the library's entities and * repositories are discovered — which is exactly what {@link StarterAutoConfigurationIntegrationTest} * asserts. diff --git a/spring-services-core/src/test/java/com/example/explicitapp/ExplicitImportTestApp.java b/spring-services-core/src/test/java/com/example/explicitapp/ExplicitImportTestApp.java index 055a00b..51ff757 100644 --- a/spring-services-core/src/test/java/com/example/explicitapp/ExplicitImportTestApp.java +++ b/spring-services-core/src/test/java/com/example/explicitapp/ExplicitImportTestApp.java @@ -1,7 +1,7 @@ package com.example.explicitapp; import com.openelements.spring.base.FullSpringServiceConfig; -import com.openelements.spring.base.SpringServicesAutoConfiguration; +import com.openelements.spring.base.SpringServicesCoreAutoConfiguration; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.domain.EntityScan; import org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration; @@ -10,7 +10,7 @@ /** * A consumer application that wires the library the old, explicit way: the Spring Boot - * starter ({@link SpringServicesAutoConfiguration}) is excluded, and the library is brought in via + * starter ({@link SpringServicesCoreAutoConfiguration}) is excluded, and the library is brought in via * {@code @Import(FullSpringServiceConfig.class)} plus explicit {@code @EntityScan} / * {@code @EnableJpaRepositories} over the library root. This proves the pre-starter integration * contract still works and produces no duplicate-bean errors. @@ -18,7 +18,7 @@ @SpringBootApplication( exclude = { OAuth2ResourceServerAutoConfiguration.class, - SpringServicesAutoConfiguration.class + SpringServicesCoreAutoConfiguration.class }) @Import(FullSpringServiceConfig.class) @EntityScan("com.openelements.spring.base") diff --git a/spring-services-core/src/test/java/com/openelements/spring/base/SpringServicesAutoConfigurationTest.java b/spring-services-core/src/test/java/com/openelements/spring/base/SpringServicesCoreAutoConfigurationTest.java similarity index 83% rename from spring-services-core/src/test/java/com/openelements/spring/base/SpringServicesAutoConfigurationTest.java rename to spring-services-core/src/test/java/com/openelements/spring/base/SpringServicesCoreAutoConfigurationTest.java index 5859ee5..a0ec620 100644 --- a/spring-services-core/src/test/java/com/openelements/spring/base/SpringServicesAutoConfigurationTest.java +++ b/spring-services-core/src/test/java/com/openelements/spring/base/SpringServicesCoreAutoConfigurationTest.java @@ -10,7 +10,7 @@ import org.springframework.boot.test.context.runner.ApplicationContextRunner; /** - * Activation-guard test for {@link SpringServicesAutoConfiguration}. + * Activation-guard test for {@link SpringServicesCoreAutoConfiguration}. * *

    Verifies the {@code @ConditionalOnClass(EntityManagerFactory.class)} guard: in an application * without JPA on the classpath the starter must stay inert rather than fail trying to configure @@ -20,12 +20,12 @@ *

    The positive case (the auto-configuration activating and wiring the full platform when JPA is * present) is covered end-to-end by {@code com.example.app.StarterAutoConfigurationIntegrationTest}. */ -@DisplayName("SpringServicesAutoConfiguration — activation guard") -class SpringServicesAutoConfigurationTest { +@DisplayName("SpringServicesCoreAutoConfiguration — activation guard") +class SpringServicesCoreAutoConfigurationTest { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(SpringServicesAutoConfiguration.class)); + .withConfiguration(AutoConfigurations.of(SpringServicesCoreAutoConfiguration.class)); @Test @DisplayName("Backs off (does not activate) when EntityManagerFactory is absent from the classpath") @@ -36,6 +36,6 @@ void backsOffWithoutJpa() { context -> assertThat(context) .hasNotFailed() - .doesNotHaveBean(SpringServicesAutoConfiguration.class)); + .doesNotHaveBean(SpringServicesCoreAutoConfiguration.class)); } } diff --git a/spring-services-core/src/test/java/com/openelements/spring/base/data/Tests.java b/spring-services-core/src/test/java/com/openelements/spring/base/data/Tests.java index 38db89c..39fbc7a 100644 --- a/spring-services-core/src/test/java/com/openelements/spring/base/data/Tests.java +++ b/spring-services-core/src/test/java/com/openelements/spring/base/data/Tests.java @@ -13,7 +13,7 @@ import org.springframework.data.domain.Pageable; // This is a focused slice test of AbstractDbBackedDataService using the in-package ForTestConfig -// fixture. The library's real starter (SpringServicesAutoConfiguration) is excluded: because +// fixture. The library's real starter (SpringServicesCoreAutoConfiguration) is excluded: because // ForTestConfig lives inside the library root package, the starter's additive // AutoConfigurationPackages.register("com.openelements.spring.base") would overlap ForTestConfig's // own (.data) package registration — registering ForTestRepository twice — and would also pull the @@ -22,7 +22,7 @@ @SpringBootTest( classes = ForTestConfig.class, properties = - "spring.autoconfigure.exclude=com.openelements.spring.base.SpringServicesAutoConfiguration") + "spring.autoconfigure.exclude=com.openelements.spring.base.SpringServicesCoreAutoConfiguration") public class Tests { @Autowired private ForTestDataService forTestDataService; diff --git a/spring-services-dbbackup/pom.xml b/spring-services-dbbackup/pom.xml new file mode 100644 index 0000000..bf6771c --- /dev/null +++ b/spring-services-dbbackup/pom.xml @@ -0,0 +1,37 @@ + + 4.0.0 + + + com.open-elements + spring-services + 1.3.0-SNAPSHOT + + + spring-services-dbbackup + + Spring Services DB-Backup + Optional db-backup-service sidecar client feature module for spring-services + + + + com.open-elements + spring-services-core + ${project.version} + + + + + org.springframework.boot + spring-boot-starter-test + test + + + org.wiremock + wiremock-standalone + ${wiremock.version} + test + + + + diff --git a/spring-services-dbbackup/src/main/java/com/openelements/spring/base/dbbackup/DbBackupAutoConfiguration.java b/spring-services-dbbackup/src/main/java/com/openelements/spring/base/dbbackup/DbBackupAutoConfiguration.java new file mode 100644 index 0000000..ee8930c --- /dev/null +++ b/spring-services-dbbackup/src/main/java/com/openelements/spring/base/dbbackup/DbBackupAutoConfiguration.java @@ -0,0 +1,23 @@ +package com.openelements.spring.base.dbbackup; + +import com.openelements.spring.base.services.dbbackup.DbBackupConfig; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.context.annotation.Import; + +/** + * Auto-configuration for the optional db-backup-service sidecar client feature. + * + *

    Registered through this module's + * {@code META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports}, so the + * feature self-activates when {@code spring-services-dbbackup} is on the classpath. The module + * carries no extra third-party dependency (it talks to the sidecar over Spring's {@code RestClient} + * via core), so there is no heavy-dependency class to guard on; runtime opt-in stays governed by + * {@link DbBackupConfig}'s existing {@code @ConditionalOnProperty(openelements.db-backup.enabled)}. + * + *

    Lives outside the {@code services.dbbackup} package on purpose: {@link DbBackupConfig} declares + * a {@code @ComponentScan} over its own package, and an auto-configuration co-located there would be + * swept up by that scan and registered twice. + */ +@AutoConfiguration +@Import(DbBackupConfig.class) +public class DbBackupAutoConfiguration {} diff --git a/spring-services-core/src/main/java/com/openelements/spring/base/services/dbbackup/BackupDownload.java b/spring-services-dbbackup/src/main/java/com/openelements/spring/base/services/dbbackup/BackupDownload.java similarity index 100% rename from spring-services-core/src/main/java/com/openelements/spring/base/services/dbbackup/BackupDownload.java rename to spring-services-dbbackup/src/main/java/com/openelements/spring/base/services/dbbackup/BackupDownload.java diff --git a/spring-services-core/src/main/java/com/openelements/spring/base/services/dbbackup/BackupJob.java b/spring-services-dbbackup/src/main/java/com/openelements/spring/base/services/dbbackup/BackupJob.java similarity index 100% rename from spring-services-core/src/main/java/com/openelements/spring/base/services/dbbackup/BackupJob.java rename to spring-services-dbbackup/src/main/java/com/openelements/spring/base/services/dbbackup/BackupJob.java diff --git a/spring-services-core/src/main/java/com/openelements/spring/base/services/dbbackup/BackupJobStatus.java b/spring-services-dbbackup/src/main/java/com/openelements/spring/base/services/dbbackup/BackupJobStatus.java similarity index 100% rename from spring-services-core/src/main/java/com/openelements/spring/base/services/dbbackup/BackupJobStatus.java rename to spring-services-dbbackup/src/main/java/com/openelements/spring/base/services/dbbackup/BackupJobStatus.java diff --git a/spring-services-core/src/main/java/com/openelements/spring/base/services/dbbackup/BackupMetadata.java b/spring-services-dbbackup/src/main/java/com/openelements/spring/base/services/dbbackup/BackupMetadata.java similarity index 100% rename from spring-services-core/src/main/java/com/openelements/spring/base/services/dbbackup/BackupMetadata.java rename to spring-services-dbbackup/src/main/java/com/openelements/spring/base/services/dbbackup/BackupMetadata.java diff --git a/spring-services-core/src/main/java/com/openelements/spring/base/services/dbbackup/BackupServiceInfo.java b/spring-services-dbbackup/src/main/java/com/openelements/spring/base/services/dbbackup/BackupServiceInfo.java similarity index 100% rename from spring-services-core/src/main/java/com/openelements/spring/base/services/dbbackup/BackupServiceInfo.java rename to spring-services-dbbackup/src/main/java/com/openelements/spring/base/services/dbbackup/BackupServiceInfo.java diff --git a/spring-services-core/src/main/java/com/openelements/spring/base/services/dbbackup/BackupTrigger.java b/spring-services-dbbackup/src/main/java/com/openelements/spring/base/services/dbbackup/BackupTrigger.java similarity index 100% rename from spring-services-core/src/main/java/com/openelements/spring/base/services/dbbackup/BackupTrigger.java rename to spring-services-dbbackup/src/main/java/com/openelements/spring/base/services/dbbackup/BackupTrigger.java diff --git a/spring-services-core/src/main/java/com/openelements/spring/base/services/dbbackup/BackupTriggerResult.java b/spring-services-dbbackup/src/main/java/com/openelements/spring/base/services/dbbackup/BackupTriggerResult.java similarity index 100% rename from spring-services-core/src/main/java/com/openelements/spring/base/services/dbbackup/BackupTriggerResult.java rename to spring-services-dbbackup/src/main/java/com/openelements/spring/base/services/dbbackup/BackupTriggerResult.java diff --git a/spring-services-core/src/main/java/com/openelements/spring/base/services/dbbackup/DbBackupClient.java b/spring-services-dbbackup/src/main/java/com/openelements/spring/base/services/dbbackup/DbBackupClient.java similarity index 100% rename from spring-services-core/src/main/java/com/openelements/spring/base/services/dbbackup/DbBackupClient.java rename to spring-services-dbbackup/src/main/java/com/openelements/spring/base/services/dbbackup/DbBackupClient.java diff --git a/spring-services-core/src/main/java/com/openelements/spring/base/services/dbbackup/DbBackupConfig.java b/spring-services-dbbackup/src/main/java/com/openelements/spring/base/services/dbbackup/DbBackupConfig.java similarity index 85% rename from spring-services-core/src/main/java/com/openelements/spring/base/services/dbbackup/DbBackupConfig.java rename to spring-services-dbbackup/src/main/java/com/openelements/spring/base/services/dbbackup/DbBackupConfig.java index a64d5e9..6239702 100644 --- a/spring-services-core/src/main/java/com/openelements/spring/base/services/dbbackup/DbBackupConfig.java +++ b/spring-services-dbbackup/src/main/java/com/openelements/spring/base/services/dbbackup/DbBackupConfig.java @@ -13,9 +13,9 @@ * part of the full platform setup. * *

    This is a plain {@code @Configuration} (not {@code @AutoConfiguration}) for the same reason as - * {@link com.openelements.spring.base.services.search.SearchConfig}: a host application's - * {@code @ComponentScan} reliably picks up plain configurations but excludes auto-configurations, - * so the properties are always registered alongside the client. + * the {@code SearchConfig} of the search module: a host application's {@code @ComponentScan} + * reliably picks up plain configurations but excludes auto-configurations, so the properties are + * always registered alongside the client. * *

    The feature is opt-in and disabled by default: none of these beans are created * unless {@code openelements.db-backup.enabled=true}. This avoids forcing a db-backup configuration diff --git a/spring-services-core/src/main/java/com/openelements/spring/base/services/dbbackup/DbBackupException.java b/spring-services-dbbackup/src/main/java/com/openelements/spring/base/services/dbbackup/DbBackupException.java similarity index 100% rename from spring-services-core/src/main/java/com/openelements/spring/base/services/dbbackup/DbBackupException.java rename to spring-services-dbbackup/src/main/java/com/openelements/spring/base/services/dbbackup/DbBackupException.java diff --git a/spring-services-core/src/main/java/com/openelements/spring/base/services/dbbackup/DbBackupProperties.java b/spring-services-dbbackup/src/main/java/com/openelements/spring/base/services/dbbackup/DbBackupProperties.java similarity index 100% rename from spring-services-core/src/main/java/com/openelements/spring/base/services/dbbackup/DbBackupProperties.java rename to spring-services-dbbackup/src/main/java/com/openelements/spring/base/services/dbbackup/DbBackupProperties.java diff --git a/spring-services-core/src/main/java/com/openelements/spring/base/services/dbbackup/package-info.java b/spring-services-dbbackup/src/main/java/com/openelements/spring/base/services/dbbackup/package-info.java similarity index 100% rename from spring-services-core/src/main/java/com/openelements/spring/base/services/dbbackup/package-info.java rename to spring-services-dbbackup/src/main/java/com/openelements/spring/base/services/dbbackup/package-info.java diff --git a/spring-services-dbbackup/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/spring-services-dbbackup/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..6db5f17 --- /dev/null +++ b/spring-services-dbbackup/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +com.openelements.spring.base.dbbackup.DbBackupAutoConfiguration diff --git a/spring-services-core/src/test/java/com/openelements/spring/base/services/dbbackup/DbBackupClientTest.java b/spring-services-dbbackup/src/test/java/com/openelements/spring/base/services/dbbackup/DbBackupClientTest.java similarity index 100% rename from spring-services-core/src/test/java/com/openelements/spring/base/services/dbbackup/DbBackupClientTest.java rename to spring-services-dbbackup/src/test/java/com/openelements/spring/base/services/dbbackup/DbBackupClientTest.java diff --git a/spring-services-email/pom.xml b/spring-services-email/pom.xml new file mode 100644 index 0000000..6760b3f --- /dev/null +++ b/spring-services-email/pom.xml @@ -0,0 +1,35 @@ + + 4.0.0 + + + com.open-elements + spring-services + 1.3.0-SNAPSHOT + + + spring-services-email + + Spring Services Email + Optional SMTP email sending feature module for spring-services + + + + com.open-elements + spring-services-core + ${project.version} + + + org.springframework.boot + spring-boot-starter-mail + + + + + org.springframework.boot + spring-boot-starter-test + test + + + + diff --git a/spring-services-email/src/main/java/com/openelements/spring/base/email/EmailAutoConfiguration.java b/spring-services-email/src/main/java/com/openelements/spring/base/email/EmailAutoConfiguration.java new file mode 100644 index 0000000..6140c71 --- /dev/null +++ b/spring-services-email/src/main/java/com/openelements/spring/base/email/EmailAutoConfiguration.java @@ -0,0 +1,27 @@ +package com.openelements.spring.base.email; + +import com.openelements.spring.base.services.email.EmailConfig; +import jakarta.mail.internet.MimeMessage; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.context.annotation.Import; + +/** + * Auto-configuration for the optional SMTP email feature. + * + *

    Registered through this module's + * {@code META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports}, so the + * feature self-activates when {@code spring-services-email} is on the classpath. + * {@link ConditionalOnClass @ConditionalOnClass(MimeMessage.class)} keeps it inert if the + * {@code spring-boot-starter-mail} dependency is not present, so a bundle that ships this class + * without the mail library causes no {@code NoClassDefFoundError}. + * + *

    Lives outside the {@code services.email} package on purpose: {@link EmailConfig} declares a + * {@code @ComponentScan} over its own package, and an auto-configuration co-located there would be + * swept up by that scan and registered twice. {@code EmailService} still degrades gracefully when no + * {@code JavaMailSender} is configured (no {@code spring.mail.host}). + */ +@AutoConfiguration +@ConditionalOnClass(MimeMessage.class) +@Import(EmailConfig.class) +public class EmailAutoConfiguration {} diff --git a/spring-services-core/src/main/java/com/openelements/spring/base/services/email/EmailConfig.java b/spring-services-email/src/main/java/com/openelements/spring/base/services/email/EmailConfig.java similarity index 100% rename from spring-services-core/src/main/java/com/openelements/spring/base/services/email/EmailConfig.java rename to spring-services-email/src/main/java/com/openelements/spring/base/services/email/EmailConfig.java diff --git a/spring-services-core/src/main/java/com/openelements/spring/base/services/email/EmailException.java b/spring-services-email/src/main/java/com/openelements/spring/base/services/email/EmailException.java similarity index 100% rename from spring-services-core/src/main/java/com/openelements/spring/base/services/email/EmailException.java rename to spring-services-email/src/main/java/com/openelements/spring/base/services/email/EmailException.java diff --git a/spring-services-core/src/main/java/com/openelements/spring/base/services/email/EmailProperties.java b/spring-services-email/src/main/java/com/openelements/spring/base/services/email/EmailProperties.java similarity index 100% rename from spring-services-core/src/main/java/com/openelements/spring/base/services/email/EmailProperties.java rename to spring-services-email/src/main/java/com/openelements/spring/base/services/email/EmailProperties.java diff --git a/spring-services-core/src/main/java/com/openelements/spring/base/services/email/EmailService.java b/spring-services-email/src/main/java/com/openelements/spring/base/services/email/EmailService.java similarity index 100% rename from spring-services-core/src/main/java/com/openelements/spring/base/services/email/EmailService.java rename to spring-services-email/src/main/java/com/openelements/spring/base/services/email/EmailService.java diff --git a/spring-services-core/src/main/java/com/openelements/spring/base/services/email/package-info.java b/spring-services-email/src/main/java/com/openelements/spring/base/services/email/package-info.java similarity index 100% rename from spring-services-core/src/main/java/com/openelements/spring/base/services/email/package-info.java rename to spring-services-email/src/main/java/com/openelements/spring/base/services/email/package-info.java diff --git a/spring-services-email/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/spring-services-email/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..c3b99d8 --- /dev/null +++ b/spring-services-email/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +com.openelements.spring.base.email.EmailAutoConfiguration diff --git a/spring-services-core/src/test/java/com/openelements/spring/base/services/email/EmailConfigTest.java b/spring-services-email/src/test/java/com/openelements/spring/base/services/email/EmailConfigTest.java similarity index 100% rename from spring-services-core/src/test/java/com/openelements/spring/base/services/email/EmailConfigTest.java rename to spring-services-email/src/test/java/com/openelements/spring/base/services/email/EmailConfigTest.java diff --git a/spring-services-core/src/test/java/com/openelements/spring/base/services/email/EmailServiceTest.java b/spring-services-email/src/test/java/com/openelements/spring/base/services/email/EmailServiceTest.java similarity index 97% rename from spring-services-core/src/test/java/com/openelements/spring/base/services/email/EmailServiceTest.java rename to spring-services-email/src/test/java/com/openelements/spring/base/services/email/EmailServiceTest.java index 680b21a..f7e6edc 100644 --- a/spring-services-core/src/test/java/com/openelements/spring/base/services/email/EmailServiceTest.java +++ b/spring-services-email/src/test/java/com/openelements/spring/base/services/email/EmailServiceTest.java @@ -133,10 +133,9 @@ void plainAddressWhenNoDisplayName() { } /** - * Symmetric to the WARN-on-blank-token check in {@link - * com.openelements.spring.base.services.slack.SlackConfigTest}: an environment that exports - * {@code OPEN_ELEMENTS_EMAIL_FROM_NAME=" "} must not emit an RFC header containing only - * whitespace as the display name. + * Symmetric to the WARN-on-blank-token check in {@code SlackConfigTest} (spring-services-slack): + * an environment that exports {@code OPEN_ELEMENTS_EMAIL_FROM_NAME=" "} must not emit an RFC + * header containing only whitespace as the display name. */ @Test @DisplayName("A whitespace-only from-name is treated as missing — the From header is the bare address.") diff --git a/spring-services-mcp/pom.xml b/spring-services-mcp/pom.xml new file mode 100644 index 0000000..25ffd06 --- /dev/null +++ b/spring-services-mcp/pom.xml @@ -0,0 +1,50 @@ + + 4.0.0 + + + com.open-elements + spring-services + 1.3.0-SNAPSHOT + + + spring-services-mcp + + Spring Services MCP + Optional Model Context Protocol (MCP) server feature module for spring-services + + + + com.open-elements + spring-services-core + ${project.version} + + + + + io.modelcontextprotocol.sdk + mcp-spring-webmvc + ${mcp-sdk.version} + + + + + io.modelcontextprotocol.sdk + mcp-json-jackson2 + ${mcp-sdk.version} + + + + + org.springframework.boot + spring-boot-starter-test + test + + + + diff --git a/spring-services-core/src/main/java/com/openelements/spring/base/mcp/McpActorLabel.java b/spring-services-mcp/src/main/java/com/openelements/spring/base/mcp/McpActorLabel.java similarity index 100% rename from spring-services-core/src/main/java/com/openelements/spring/base/mcp/McpActorLabel.java rename to spring-services-mcp/src/main/java/com/openelements/spring/base/mcp/McpActorLabel.java diff --git a/spring-services-mcp/src/main/java/com/openelements/spring/base/mcp/McpAutoConfiguration.java b/spring-services-mcp/src/main/java/com/openelements/spring/base/mcp/McpAutoConfiguration.java new file mode 100644 index 0000000..f825e4f --- /dev/null +++ b/spring-services-mcp/src/main/java/com/openelements/spring/base/mcp/McpAutoConfiguration.java @@ -0,0 +1,27 @@ +package com.openelements.spring.base.mcp; + +import io.modelcontextprotocol.server.McpServer; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.context.annotation.Import; + +/** + * Auto-configuration for the optional Model Context Protocol (MCP) server feature. + * + *

    Registered through this module's + * {@code META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports}, so the + * feature self-activates when {@code spring-services-mcp} is on the classpath — the endpoint and its + * dedicated security filter chain can no longer be accidentally present in an application that never + * added the module. {@link ConditionalOnClass @ConditionalOnClass(McpServer.class)} keeps it inert + * if the MCP SDK is absent, so a bundle shipping this class without the SDK causes no + * {@code NoClassDefFoundError}. + * + *

    Imports {@link McpConfiguration}, the feature entry point, which binds {@link McpProperties} + * unconditionally and wires the server/security beans only when {@code openelements.mcp.enabled=true} + * (unchanged {@code @ConditionalOnProperty} behaviour). {@code @Import(McpConfiguration.class)} + * therefore remains valid for consumers who prefer explicit wiring. + */ +@AutoConfiguration +@ConditionalOnClass(McpServer.class) +@Import(McpConfiguration.class) +public class McpAutoConfiguration {} diff --git a/spring-services-core/src/main/java/com/openelements/spring/base/mcp/McpConfiguration.java b/spring-services-mcp/src/main/java/com/openelements/spring/base/mcp/McpConfiguration.java similarity index 100% rename from spring-services-core/src/main/java/com/openelements/spring/base/mcp/McpConfiguration.java rename to spring-services-mcp/src/main/java/com/openelements/spring/base/mcp/McpConfiguration.java diff --git a/spring-services-core/src/main/java/com/openelements/spring/base/mcp/McpPage.java b/spring-services-mcp/src/main/java/com/openelements/spring/base/mcp/McpPage.java similarity index 100% rename from spring-services-core/src/main/java/com/openelements/spring/base/mcp/McpPage.java rename to spring-services-mcp/src/main/java/com/openelements/spring/base/mcp/McpPage.java diff --git a/spring-services-core/src/main/java/com/openelements/spring/base/mcp/McpPaging.java b/spring-services-mcp/src/main/java/com/openelements/spring/base/mcp/McpPaging.java similarity index 100% rename from spring-services-core/src/main/java/com/openelements/spring/base/mcp/McpPaging.java rename to spring-services-mcp/src/main/java/com/openelements/spring/base/mcp/McpPaging.java diff --git a/spring-services-core/src/main/java/com/openelements/spring/base/mcp/McpProperties.java b/spring-services-mcp/src/main/java/com/openelements/spring/base/mcp/McpProperties.java similarity index 100% rename from spring-services-core/src/main/java/com/openelements/spring/base/mcp/McpProperties.java rename to spring-services-mcp/src/main/java/com/openelements/spring/base/mcp/McpProperties.java diff --git a/spring-services-core/src/main/java/com/openelements/spring/base/mcp/McpSecurityConfig.java b/spring-services-mcp/src/main/java/com/openelements/spring/base/mcp/McpSecurityConfig.java similarity index 100% rename from spring-services-core/src/main/java/com/openelements/spring/base/mcp/McpSecurityConfig.java rename to spring-services-mcp/src/main/java/com/openelements/spring/base/mcp/McpSecurityConfig.java diff --git a/spring-services-core/src/main/java/com/openelements/spring/base/mcp/McpServerConfig.java b/spring-services-mcp/src/main/java/com/openelements/spring/base/mcp/McpServerConfig.java similarity index 100% rename from spring-services-core/src/main/java/com/openelements/spring/base/mcp/McpServerConfig.java rename to spring-services-mcp/src/main/java/com/openelements/spring/base/mcp/McpServerConfig.java diff --git a/spring-services-core/src/main/java/com/openelements/spring/base/mcp/McpToolLogic.java b/spring-services-mcp/src/main/java/com/openelements/spring/base/mcp/McpToolLogic.java similarity index 100% rename from spring-services-core/src/main/java/com/openelements/spring/base/mcp/McpToolLogic.java rename to spring-services-mcp/src/main/java/com/openelements/spring/base/mcp/McpToolLogic.java diff --git a/spring-services-core/src/main/java/com/openelements/spring/base/mcp/McpToolProvider.java b/spring-services-mcp/src/main/java/com/openelements/spring/base/mcp/McpToolProvider.java similarity index 100% rename from spring-services-core/src/main/java/com/openelements/spring/base/mcp/McpToolProvider.java rename to spring-services-mcp/src/main/java/com/openelements/spring/base/mcp/McpToolProvider.java diff --git a/spring-services-core/src/main/java/com/openelements/spring/base/mcp/McpToolSupport.java b/spring-services-mcp/src/main/java/com/openelements/spring/base/mcp/McpToolSupport.java similarity index 100% rename from spring-services-core/src/main/java/com/openelements/spring/base/mcp/McpToolSupport.java rename to spring-services-mcp/src/main/java/com/openelements/spring/base/mcp/McpToolSupport.java diff --git a/spring-services-core/src/main/java/com/openelements/spring/base/mcp/McpTools.java b/spring-services-mcp/src/main/java/com/openelements/spring/base/mcp/McpTools.java similarity index 100% rename from spring-services-core/src/main/java/com/openelements/spring/base/mcp/McpTools.java rename to spring-services-mcp/src/main/java/com/openelements/spring/base/mcp/McpTools.java diff --git a/spring-services-core/src/main/java/com/openelements/spring/base/mcp/McpUnavailableException.java b/spring-services-mcp/src/main/java/com/openelements/spring/base/mcp/McpUnavailableException.java similarity index 100% rename from spring-services-core/src/main/java/com/openelements/spring/base/mcp/McpUnavailableException.java rename to spring-services-mcp/src/main/java/com/openelements/spring/base/mcp/McpUnavailableException.java diff --git a/spring-services-mcp/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/spring-services-mcp/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..3fc34fe --- /dev/null +++ b/spring-services-mcp/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +com.openelements.spring.base.mcp.McpAutoConfiguration diff --git a/spring-services-core/src/test/java/com/openelements/spring/base/mcp/McpActorLabelTest.java b/spring-services-mcp/src/test/java/com/openelements/spring/base/mcp/McpActorLabelTest.java similarity index 100% rename from spring-services-core/src/test/java/com/openelements/spring/base/mcp/McpActorLabelTest.java rename to spring-services-mcp/src/test/java/com/openelements/spring/base/mcp/McpActorLabelTest.java diff --git a/spring-services-mcp/src/test/java/com/openelements/spring/base/mcp/McpAutoConfigurationTest.java b/spring-services-mcp/src/test/java/com/openelements/spring/base/mcp/McpAutoConfigurationTest.java new file mode 100644 index 0000000..b39150c --- /dev/null +++ b/spring-services-mcp/src/test/java/com/openelements/spring/base/mcp/McpAutoConfigurationTest.java @@ -0,0 +1,36 @@ +package com.openelements.spring.base.mcp; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.modelcontextprotocol.server.McpServer; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.FilteredClassLoader; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; + +/** + * Activation-guard test for {@link McpAutoConfiguration}: proves the {@code @ConditionalOnClass} + * guard keeps the feature inert when the MCP SDK is not on the classpath, so a bundle that ships the + * auto-configuration class without the SDK causes no failure. + */ +@DisplayName("McpAutoConfiguration — activation guard") +class McpAutoConfigurationTest { + + private final ApplicationContextRunner contextRunner = + new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(McpAutoConfiguration.class)); + + @Test + @DisplayName("Stays inert (no MCP beans) when the MCP SDK type McpServer is absent") + void inertWhenSdkAbsent() { + contextRunner + .withClassLoader(new FilteredClassLoader(McpServer.class)) + .run( + context -> + assertThat(context) + .hasNotFailed() + .doesNotHaveBean(McpAutoConfiguration.class) + .doesNotHaveBean(McpProperties.class)); + } +} diff --git a/spring-services-core/src/test/java/com/openelements/spring/base/mcp/McpPagingTest.java b/spring-services-mcp/src/test/java/com/openelements/spring/base/mcp/McpPagingTest.java similarity index 100% rename from spring-services-core/src/test/java/com/openelements/spring/base/mcp/McpPagingTest.java rename to spring-services-mcp/src/test/java/com/openelements/spring/base/mcp/McpPagingTest.java diff --git a/spring-services-core/src/test/java/com/openelements/spring/base/mcp/McpPropertiesTest.java b/spring-services-mcp/src/test/java/com/openelements/spring/base/mcp/McpPropertiesTest.java similarity index 100% rename from spring-services-core/src/test/java/com/openelements/spring/base/mcp/McpPropertiesTest.java rename to spring-services-mcp/src/test/java/com/openelements/spring/base/mcp/McpPropertiesTest.java diff --git a/spring-services-core/src/test/java/com/openelements/spring/base/mcp/McpToolSupportTest.java b/spring-services-mcp/src/test/java/com/openelements/spring/base/mcp/McpToolSupportTest.java similarity index 100% rename from spring-services-core/src/test/java/com/openelements/spring/base/mcp/McpToolSupportTest.java rename to spring-services-mcp/src/test/java/com/openelements/spring/base/mcp/McpToolSupportTest.java diff --git a/spring-services-core/src/test/java/com/openelements/spring/base/mcp/McpToolsTest.java b/spring-services-mcp/src/test/java/com/openelements/spring/base/mcp/McpToolsTest.java similarity index 100% rename from spring-services-core/src/test/java/com/openelements/spring/base/mcp/McpToolsTest.java rename to spring-services-mcp/src/test/java/com/openelements/spring/base/mcp/McpToolsTest.java diff --git a/spring-services-search/pom.xml b/spring-services-search/pom.xml new file mode 100644 index 0000000..8a6bfb5 --- /dev/null +++ b/spring-services-search/pom.xml @@ -0,0 +1,37 @@ + + 4.0.0 + + + com.open-elements + spring-services + 1.3.0-SNAPSHOT + + + spring-services-search + + Spring Services Search + Optional Meilisearch-backed full-text search feature module for spring-services + + + + com.open-elements + spring-services-core + ${project.version} + + + + + org.springframework.boot + spring-boot-starter-test + test + + + org.wiremock + wiremock-standalone + ${wiremock.version} + test + + + + diff --git a/spring-services-search/src/main/java/com/openelements/spring/base/search/SearchAutoConfiguration.java b/spring-services-search/src/main/java/com/openelements/spring/base/search/SearchAutoConfiguration.java new file mode 100644 index 0000000..92b98a7 --- /dev/null +++ b/spring-services-search/src/main/java/com/openelements/spring/base/search/SearchAutoConfiguration.java @@ -0,0 +1,23 @@ +package com.openelements.spring.base.search; + +import com.openelements.spring.base.services.search.SearchConfig; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.context.annotation.Import; + +/** + * Auto-configuration for the optional Meilisearch-backed search feature. + * + *

    Registered through this module's + * {@code META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports}, so the + * feature self-activates when {@code spring-services-search} is on the classpath. The module carries + * no extra third-party dependency (it talks to Meilisearch over Spring's {@code RestClient}), so + * there is no heavy-dependency class to guard on; runtime opt-in is governed by + * {@link SearchConfig}'s existing {@code @ConditionalOnProperty(openelements.meilisearch.enabled)}. + * + *

    Lives outside the {@code services.search} package on purpose: {@link SearchConfig} declares a + * {@code @ComponentScan} over its own package, and an auto-configuration co-located there would be + * swept up by that scan and registered twice. + */ +@AutoConfiguration +@Import(SearchConfig.class) +public class SearchAutoConfiguration {} diff --git a/spring-services-core/src/main/java/com/openelements/spring/base/services/search/BatchWriter.java b/spring-services-search/src/main/java/com/openelements/spring/base/services/search/BatchWriter.java similarity index 100% rename from spring-services-core/src/main/java/com/openelements/spring/base/services/search/BatchWriter.java rename to spring-services-search/src/main/java/com/openelements/spring/base/services/search/BatchWriter.java diff --git a/spring-services-core/src/main/java/com/openelements/spring/base/services/search/BootstrapInvoker.java b/spring-services-search/src/main/java/com/openelements/spring/base/services/search/BootstrapInvoker.java similarity index 100% rename from spring-services-core/src/main/java/com/openelements/spring/base/services/search/BootstrapInvoker.java rename to spring-services-search/src/main/java/com/openelements/spring/base/services/search/BootstrapInvoker.java diff --git a/spring-services-core/src/main/java/com/openelements/spring/base/services/search/Highlighter.java b/spring-services-search/src/main/java/com/openelements/spring/base/services/search/Highlighter.java similarity index 100% rename from spring-services-core/src/main/java/com/openelements/spring/base/services/search/Highlighter.java rename to spring-services-search/src/main/java/com/openelements/spring/base/services/search/Highlighter.java diff --git a/spring-services-core/src/main/java/com/openelements/spring/base/services/search/IndexSettings.java b/spring-services-search/src/main/java/com/openelements/spring/base/services/search/IndexSettings.java similarity index 100% rename from spring-services-core/src/main/java/com/openelements/spring/base/services/search/IndexSettings.java rename to spring-services-search/src/main/java/com/openelements/spring/base/services/search/IndexSettings.java diff --git a/spring-services-core/src/main/java/com/openelements/spring/base/services/search/MeilisearchBootstrapRunner.java b/spring-services-search/src/main/java/com/openelements/spring/base/services/search/MeilisearchBootstrapRunner.java similarity index 100% rename from spring-services-core/src/main/java/com/openelements/spring/base/services/search/MeilisearchBootstrapRunner.java rename to spring-services-search/src/main/java/com/openelements/spring/base/services/search/MeilisearchBootstrapRunner.java diff --git a/spring-services-core/src/main/java/com/openelements/spring/base/services/search/MeilisearchClient.java b/spring-services-search/src/main/java/com/openelements/spring/base/services/search/MeilisearchClient.java similarity index 100% rename from spring-services-core/src/main/java/com/openelements/spring/base/services/search/MeilisearchClient.java rename to spring-services-search/src/main/java/com/openelements/spring/base/services/search/MeilisearchClient.java diff --git a/spring-services-core/src/main/java/com/openelements/spring/base/services/search/MeilisearchException.java b/spring-services-search/src/main/java/com/openelements/spring/base/services/search/MeilisearchException.java similarity index 100% rename from spring-services-core/src/main/java/com/openelements/spring/base/services/search/MeilisearchException.java rename to spring-services-search/src/main/java/com/openelements/spring/base/services/search/MeilisearchException.java diff --git a/spring-services-core/src/main/java/com/openelements/spring/base/services/search/MeilisearchIndexSettingsInitializer.java b/spring-services-search/src/main/java/com/openelements/spring/base/services/search/MeilisearchIndexSettingsInitializer.java similarity index 100% rename from spring-services-core/src/main/java/com/openelements/spring/base/services/search/MeilisearchIndexSettingsInitializer.java rename to spring-services-search/src/main/java/com/openelements/spring/base/services/search/MeilisearchIndexSettingsInitializer.java diff --git a/spring-services-core/src/main/java/com/openelements/spring/base/services/search/MeilisearchProperties.java b/spring-services-search/src/main/java/com/openelements/spring/base/services/search/MeilisearchProperties.java similarity index 100% rename from spring-services-core/src/main/java/com/openelements/spring/base/services/search/MeilisearchProperties.java rename to spring-services-search/src/main/java/com/openelements/spring/base/services/search/MeilisearchProperties.java diff --git a/spring-services-core/src/main/java/com/openelements/spring/base/services/search/MeilisearchScopedKeyInitializer.java b/spring-services-search/src/main/java/com/openelements/spring/base/services/search/MeilisearchScopedKeyInitializer.java similarity index 100% rename from spring-services-core/src/main/java/com/openelements/spring/base/services/search/MeilisearchScopedKeyInitializer.java rename to spring-services-search/src/main/java/com/openelements/spring/base/services/search/MeilisearchScopedKeyInitializer.java diff --git a/spring-services-core/src/main/java/com/openelements/spring/base/services/search/ScopedKeySpec.java b/spring-services-search/src/main/java/com/openelements/spring/base/services/search/ScopedKeySpec.java similarity index 100% rename from spring-services-core/src/main/java/com/openelements/spring/base/services/search/ScopedKeySpec.java rename to spring-services-search/src/main/java/com/openelements/spring/base/services/search/ScopedKeySpec.java diff --git a/spring-services-core/src/main/java/com/openelements/spring/base/services/search/SearchConfig.java b/spring-services-search/src/main/java/com/openelements/spring/base/services/search/SearchConfig.java similarity index 100% rename from spring-services-core/src/main/java/com/openelements/spring/base/services/search/SearchConfig.java rename to spring-services-search/src/main/java/com/openelements/spring/base/services/search/SearchConfig.java diff --git a/spring-services-core/src/main/java/com/openelements/spring/base/services/search/SearchIndexBootstrapStep.java b/spring-services-search/src/main/java/com/openelements/spring/base/services/search/SearchIndexBootstrapStep.java similarity index 100% rename from spring-services-core/src/main/java/com/openelements/spring/base/services/search/SearchIndexBootstrapStep.java rename to spring-services-search/src/main/java/com/openelements/spring/base/services/search/SearchIndexBootstrapStep.java diff --git a/spring-services-core/src/main/java/com/openelements/spring/base/services/search/SearchReadinessState.java b/spring-services-search/src/main/java/com/openelements/spring/base/services/search/SearchReadinessState.java similarity index 100% rename from spring-services-core/src/main/java/com/openelements/spring/base/services/search/SearchReadinessState.java rename to spring-services-search/src/main/java/com/openelements/spring/base/services/search/SearchReadinessState.java diff --git a/spring-services-core/src/main/java/com/openelements/spring/base/services/search/TaskOutcome.java b/spring-services-search/src/main/java/com/openelements/spring/base/services/search/TaskOutcome.java similarity index 100% rename from spring-services-core/src/main/java/com/openelements/spring/base/services/search/TaskOutcome.java rename to spring-services-search/src/main/java/com/openelements/spring/base/services/search/TaskOutcome.java diff --git a/spring-services-core/src/main/java/com/openelements/spring/base/services/search/package-info.java b/spring-services-search/src/main/java/com/openelements/spring/base/services/search/package-info.java similarity index 100% rename from spring-services-core/src/main/java/com/openelements/spring/base/services/search/package-info.java rename to spring-services-search/src/main/java/com/openelements/spring/base/services/search/package-info.java diff --git a/spring-services-search/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/spring-services-search/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..d16f8df --- /dev/null +++ b/spring-services-search/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +com.openelements.spring.base.search.SearchAutoConfiguration diff --git a/spring-services-core/src/test/java/com/openelements/spring/base/services/search/HighlighterTest.java b/spring-services-search/src/test/java/com/openelements/spring/base/services/search/HighlighterTest.java similarity index 100% rename from spring-services-core/src/test/java/com/openelements/spring/base/services/search/HighlighterTest.java rename to spring-services-search/src/test/java/com/openelements/spring/base/services/search/HighlighterTest.java diff --git a/spring-services-core/src/test/java/com/openelements/spring/base/services/search/MeilisearchBootstrapRunnerTest.java b/spring-services-search/src/test/java/com/openelements/spring/base/services/search/MeilisearchBootstrapRunnerTest.java similarity index 100% rename from spring-services-core/src/test/java/com/openelements/spring/base/services/search/MeilisearchBootstrapRunnerTest.java rename to spring-services-search/src/test/java/com/openelements/spring/base/services/search/MeilisearchBootstrapRunnerTest.java diff --git a/spring-services-core/src/test/java/com/openelements/spring/base/services/search/MeilisearchClientTest.java b/spring-services-search/src/test/java/com/openelements/spring/base/services/search/MeilisearchClientTest.java similarity index 100% rename from spring-services-core/src/test/java/com/openelements/spring/base/services/search/MeilisearchClientTest.java rename to spring-services-search/src/test/java/com/openelements/spring/base/services/search/MeilisearchClientTest.java diff --git a/spring-services-core/src/test/java/com/openelements/spring/base/services/search/MeilisearchIndexSettingsInitializerTest.java b/spring-services-search/src/test/java/com/openelements/spring/base/services/search/MeilisearchIndexSettingsInitializerTest.java similarity index 100% rename from spring-services-core/src/test/java/com/openelements/spring/base/services/search/MeilisearchIndexSettingsInitializerTest.java rename to spring-services-search/src/test/java/com/openelements/spring/base/services/search/MeilisearchIndexSettingsInitializerTest.java diff --git a/spring-services-core/src/test/java/com/openelements/spring/base/services/search/MeilisearchPropertiesBindingTest.java b/spring-services-search/src/test/java/com/openelements/spring/base/services/search/MeilisearchPropertiesBindingTest.java similarity index 100% rename from spring-services-core/src/test/java/com/openelements/spring/base/services/search/MeilisearchPropertiesBindingTest.java rename to spring-services-search/src/test/java/com/openelements/spring/base/services/search/MeilisearchPropertiesBindingTest.java diff --git a/spring-services-core/src/test/java/com/openelements/spring/base/services/search/MeilisearchScopedKeyInitializerTest.java b/spring-services-search/src/test/java/com/openelements/spring/base/services/search/MeilisearchScopedKeyInitializerTest.java similarity index 100% rename from spring-services-core/src/test/java/com/openelements/spring/base/services/search/MeilisearchScopedKeyInitializerTest.java rename to spring-services-search/src/test/java/com/openelements/spring/base/services/search/MeilisearchScopedKeyInitializerTest.java diff --git a/spring-services-slack/pom.xml b/spring-services-slack/pom.xml new file mode 100644 index 0000000..fd0ed66 --- /dev/null +++ b/spring-services-slack/pom.xml @@ -0,0 +1,36 @@ + + 4.0.0 + + + com.open-elements + spring-services + 1.3.0-SNAPSHOT + + + spring-services-slack + + Spring Services Slack + Optional Slack messaging feature module for spring-services + + + + com.open-elements + spring-services-core + ${project.version} + + + com.slack.api + slack-api-client + ${slack-api-client.version} + + + + + org.springframework.boot + spring-boot-starter-test + test + + + + diff --git a/spring-services-core/src/main/java/com/openelements/spring/base/services/slack/SlackConfig.java b/spring-services-slack/src/main/java/com/openelements/spring/base/services/slack/SlackConfig.java similarity index 100% rename from spring-services-core/src/main/java/com/openelements/spring/base/services/slack/SlackConfig.java rename to spring-services-slack/src/main/java/com/openelements/spring/base/services/slack/SlackConfig.java diff --git a/spring-services-core/src/main/java/com/openelements/spring/base/services/slack/SlackException.java b/spring-services-slack/src/main/java/com/openelements/spring/base/services/slack/SlackException.java similarity index 100% rename from spring-services-core/src/main/java/com/openelements/spring/base/services/slack/SlackException.java rename to spring-services-slack/src/main/java/com/openelements/spring/base/services/slack/SlackException.java diff --git a/spring-services-core/src/main/java/com/openelements/spring/base/services/slack/SlackProperties.java b/spring-services-slack/src/main/java/com/openelements/spring/base/services/slack/SlackProperties.java similarity index 100% rename from spring-services-core/src/main/java/com/openelements/spring/base/services/slack/SlackProperties.java rename to spring-services-slack/src/main/java/com/openelements/spring/base/services/slack/SlackProperties.java diff --git a/spring-services-core/src/main/java/com/openelements/spring/base/services/slack/SlackService.java b/spring-services-slack/src/main/java/com/openelements/spring/base/services/slack/SlackService.java similarity index 100% rename from spring-services-core/src/main/java/com/openelements/spring/base/services/slack/SlackService.java rename to spring-services-slack/src/main/java/com/openelements/spring/base/services/slack/SlackService.java diff --git a/spring-services-core/src/main/java/com/openelements/spring/base/services/slack/package-info.java b/spring-services-slack/src/main/java/com/openelements/spring/base/services/slack/package-info.java similarity index 100% rename from spring-services-core/src/main/java/com/openelements/spring/base/services/slack/package-info.java rename to spring-services-slack/src/main/java/com/openelements/spring/base/services/slack/package-info.java diff --git a/spring-services-slack/src/main/java/com/openelements/spring/base/slack/SlackAutoConfiguration.java b/spring-services-slack/src/main/java/com/openelements/spring/base/slack/SlackAutoConfiguration.java new file mode 100644 index 0000000..cac1c98 --- /dev/null +++ b/spring-services-slack/src/main/java/com/openelements/spring/base/slack/SlackAutoConfiguration.java @@ -0,0 +1,28 @@ +package com.openelements.spring.base.slack; + +import com.openelements.spring.base.services.slack.SlackConfig; +import com.slack.api.Slack; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.context.annotation.Import; + +/** + * Auto-configuration for the optional Slack messaging feature. + * + *

    Registered through this module's + * {@code META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports}, so the + * feature self-activates when {@code spring-services-slack} is on the classpath and stays absent + * otherwise. {@link ConditionalOnClass @ConditionalOnClass(Slack.class)} keeps it inert if the + * {@code slack-api-client} dependency is not present, so a bundle that ships this class without the + * SDK causes no {@code NoClassDefFoundError}. + * + *

    Lives outside the {@code services.slack} package on purpose: {@link SlackConfig} declares a + * {@code @ComponentScan} over its own package, and an auto-configuration co-located there would be + * swept up by that scan and registered twice. Delegates the actual bean wiring to + * {@link SlackConfig}, which remains a plain {@code @Configuration} consumers can still + * {@code @Import} explicitly. + */ +@AutoConfiguration +@ConditionalOnClass(Slack.class) +@Import(SlackConfig.class) +public class SlackAutoConfiguration {} diff --git a/spring-services-slack/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/spring-services-slack/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..80dee58 --- /dev/null +++ b/spring-services-slack/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +com.openelements.spring.base.slack.SlackAutoConfiguration diff --git a/spring-services-core/src/test/java/com/openelements/spring/base/services/slack/SlackConfigTest.java b/spring-services-slack/src/test/java/com/openelements/spring/base/services/slack/SlackConfigTest.java similarity index 100% rename from spring-services-core/src/test/java/com/openelements/spring/base/services/slack/SlackConfigTest.java rename to spring-services-slack/src/test/java/com/openelements/spring/base/services/slack/SlackConfigTest.java diff --git a/spring-services-core/src/test/java/com/openelements/spring/base/services/slack/SlackServiceTest.java b/spring-services-slack/src/test/java/com/openelements/spring/base/services/slack/SlackServiceTest.java similarity index 100% rename from spring-services-core/src/test/java/com/openelements/spring/base/services/slack/SlackServiceTest.java rename to spring-services-slack/src/test/java/com/openelements/spring/base/services/slack/SlackServiceTest.java