Skip to content

Feature/spring boot project generator - #607

Merged
ArmenSl merged 17 commits into
BESSER-PEARL:developmentfrom
miloss01:feature/spring-boot-project-generator
Sep 22, 2026
Merged

ArmenSl merged 17 commits into
BESSER-PEARL:developmentfrom
miloss01:feature/spring-boot-project-generator

Conversation

@miloss01

@miloss01 miloss01 commented Sep 21, 2026 •

Copy link
Copy Markdown
Contributor

This generator generates Spring Boot project. It consists of several generators:

SpringBackendGenerator - Combining work of all other generators (instanties them and calls theirs generate methods). Aside from that that it generates fixed files (files that are not based on structural model). For example: files needed for mvn, pom.xml etc.
SpringEntityGenerator - Generates entity classes (and enumeration classes). It generates simple fields (and sets default value if provided), fields made by relationships (with needed anotations), empty and parametrized constructor, methods, getters and setters.
SpringRepositoryGenerator - Generates Spring Data JPA repositories. For every entity, one repository is generated. Repositories include findAllBy... methods based on entity's fields.
SpringServiceGenerator - Generates service interface and implementation. Interface includes same custom methods based on fields as repository and, also, some basic methods that are implicitly generated by JPA repository (findAll, findById, save, delete). Implemetation of service generates all methods including theirs bodies which call the same named methods from generated repository.
SpringControllerGenerator - Generates basic endpoints (getAll, getById, create, update, deleteById) and theirs bodies. Endpoints for updating and creating have checks if passed ID already exists and if not returns 404 exception.
SpringHttpGenerator - Generates .http files that are used for quick endpoint testing.

# Conflicts:
#	besser/utilities/web_modeling_editor/backend/backend.py
… that compiles

The generator could not be instantiated by the web editor at all:
SpringBackendGenerator required spring_boot_version, java_version and app_name
positionally before output_dir, while the router calls every generator as
generator_class(model, output_dir=...). Those are now keyword arguments with
defaults, matching the rest of besser/generators.

The generated project also did not compile. Four copies of the B-UML to Java
type map had drifted apart: TimeType mapped to LocalTime in the entity and
controller generators but to LocalDateTime in the repository and service
generators, so an entity field and the methods taking it disagreed on type.
The map now lives once in java_types.py, and the findAllBy signature builder
is shared so the two sides cannot drift again.

Association ownership compared a Multiplicity object against the integer 1,
which is always unequal, so every association resolved to the same end as
owner and mappedBy was placed arbitrarily. Ownership is now decided from
multiplicity.max against UNLIMITED_MAX_MULTIPLICITY, which also fixes
self-associations emitting one end twice instead of both.

B-UML names reached the filesystem unsanitized. NamedElement.name rejects
spaces and hyphens but permits "..", "/" and "\", so a class named ../../evil
was a valid model that wrote outside output_dir. Class, field, package and
file names now go through Java identifier sanitization, and the new
_generate_spring router branch resolves the project directory with the
existing _safe_path helper rather than a raw os.path.join.

The Maven wrapper shipped as three .j2 templates containing no Jinja tags at
all, rendered into .mvn/ where Maven does not look for them. They are static
files under resources/ now, copied to the project root with the executable bit
and fixed line endings, and declared as package data so they ship in the wheel.

Also: the generator is registered in SUPPORTED_GENERATORS and dispatched with
its configuration rather than falling through to the generic path that ignored
it; unmapped types fall back to Object instead of raising KeyError; a class
with no identifier reports which class rather than a bare StopIteration; the
controller uses the resolved identifier accessor instead of a hardcoded
setId; enumeration literals and associations are sorted so output is
deterministic; and example.py, a 206-line scratch script no other generator
ships, is removed.

Adds tests/generators/spring with 24 tests covering structure, content,
determinism, name sanitization and config pass-through. Verified end to end by
running mvnw compile on a generated project.
@ArmenSl

ArmenSl commented Sep 22, 2026

Copy link
Copy Markdown
Collaborator

Hi @miloss01 — thanks for opening this, and congratulations again on the thesis. I've brought it up to current development and finished the parts you said you wouldn't have time for, so it ships in v7.17.0 today. Summary of what changed and why, so nothing is a surprise:

It couldn't be called by the editor. SpringBackendGenerator required spring_boot_version, java_version and app_name positionally before output_dir, but the backend invokes every generator as generator_class(model, output_dir=...). Those are keyword arguments with defaults now.

The generated project didn't compile. There were four copies of the B-UML→Java type map, and they had drifted: TimeType mapped to LocalTime in the entity and controller generators but LocalDateTime in the repository and service generators — so an entity field and the methods taking it disagreed on type. One map now, in java_types.py, and the findAllBy signature builder is shared so the two sides can't drift apart again.

Association ownership was effectively random. _get_relation_owner_map compared a Multiplicity object against the integer 1, which is always unequal, so every association picked the same end as owner and mappedBy landed arbitrarily. Now decided from multiplicity.max against UNLIMITED_MAX_MULTIPLICITY — which also fixed self-associations emitting one end twice.

Path traversal. NamedElement.name rejects spaces and hyphens but permits .., / and \, so a class named ../../evil was a valid model that wrote outside output_dir. Class, field, package and file names go through Java identifier sanitization now (including keyword collisions), and the router resolves the project directory with the existing _safe_path helper.

The Maven wrapper. mvnw, mvnw.cmd and maven-wrapper.properties shipped as .j2 templates containing no Jinja tags at all, and were written into .mvn/ where Maven doesn't look for them, so ./mvnw never resolved. They're static files under resources/ now, copied to the project root with the executable bit and fixed line endings, and declared as package data so they ship in the wheel.

Your backend.py changes are dropped, and that's good news — the backend was refactored into routers since your base, and the generic dispatch means registering in config/generators.py is now the entire integration. Your registration entry merged as-is. I added a _generate_spring branch only so the editor's config dialog actually reaches the generator.

Smaller things: unmapped types fall back to Object instead of raising KeyError; a class with no identifier reports which class instead of a bare StopIteration; the controller uses the resolved id accessor rather than a hardcoded setId; enum literals and associations are sorted so output is deterministic; @GeneratedValue is restricted to Integer/Long ids since Hibernate can't auto-generate a String one; .http files moved out of the compiled source tree; and example.py is removed (no other generator ships one — the model it built became the test fixture).

Added tests/generators/spring (24 tests: structure, content, determinism, sanitization, config pass-through) and docs/source/generators/spring.rst. Verified end to end by running mvnw compile on a generated project — it exits 0 and produces class files.

The structure you chose held up well: the split into entity/repository/service/controller/http generators, the inheritance handling via @MappedSuperclass, and the annotation selection per multiplicity were all sound. Most of the above was integration drift from the 600-commit gap rather than design problems. You're credited in the release notes.

The traversal test asserted evil.java while the generator correctly writes
Evil.java, since a class name is capitalized on the way to a Java identifier.
Windows matched it case-insensitively so the assertion passed locally and
failed on the Linux CI runners.
@ArmenSl
ArmenSl merged commit 86deb6f into BESSER-PEARL:development Sep 22, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants