The signature of the method AbstractEntityService.convertDtoToEntity
has been extended to include an Optional<OUT> currentEntityOpt
, which contains the already persistent entity in the case of an update request. This can be used to carry out any necessary transfers of values from the database or validations.
public abstract class AbstractEntityService<
OUT extends AbstractBaseModel<ID>, ID extends Serializable, IN>
extends AbstractCrudService<OUT, ID, IN> {
/*...*/
// Before migration;
protected abstract <E extends IN> OUT convertDtoToEntity(@NotNull final E entity);
// after migration:
protected abstract <E extends IN> OUT convertDtoToEntity(@NotNull final E dto, Optional<OUT> currentEntityOpt);
/*...*/
}
You have to adjust the implementation of the method in your service class accordingly.
-
The
findById
method inAbstractUserController
does not takeID id
parameter anymore. Any calls to theAbstractUserController.findById
should be adjusted to omit the paramter. -
The
updateObject
method in theAbstractUserController
class has been renamed toupdate
. Every call to theupdateObject(ID, USERDTO, SPEC)
method of theAbstractUserController
should be replaced withupdate(ID, USERDTO, SPEC)
.
Example:
userController.updateObject(id, userDTO, spec);
to
userController.update(id, userDTO, spec);
- All implementations of the
AbstractUserController
should be checked to see whether one of the methodsfindById
,updateObject
,update
,delete
or,terminate
have been overwritten. If this is the case, the method signature must be adapted to the use of specifications.
Example:
/* old implementation */
@Override
@GetMapping("/{id}")
@Secured(BasicApplicationRight.Authority.USER_READ)
@Operation(summary = "Retrieve a user by her id")
public UserRepresentation findById(@PathVariable("id") @NotNull Long id) {
// do something
super.findById(id);
}
/* new implementation */
@Override
@GetMapping("/{id}")
@Secured(BasicApplicationRight.Authority.USER_READ)
@Operation(summary = "Retrieve a user by her id")
public UserRepresentation findById(@PathVariable("id") @NotNull Long id,
@Parameter(hidden = true) @Spec(path = "id", pathVars = "id", spec = Equal.class) UserSpec spec) {
// do something
super.findById(id, spec);
}
The used super-constructor in the implementation of the AbstractUserService
in your application has a new added
parameter AdminRightRoleCache adminRightRoleCache
. You have to change it from
protected UserService(
@NotNull UserRepository userRepository,
@NotNull PasswordEncoder passwordEncoder,
@NotNull UserMailService userMailService,
@NotNull RoleService roleService,
@NotNull JwtTokenService jwtTokenService) {
super(userRepository, passwordEncoder, userMailService, roleService, jwtTokenService);
}
to
protected UserService(
@NotNull UserRepository userRepository,
@NotNull PasswordEncoder passwordEncoder,
@NotNull UserMailService userMailService,
@NotNull RoleService roleService,
@NotNull AdminRightRoleCache adminRightRoleCache,
@NotNull JwtTokenService jwtTokenService) {
super(
userRepository,
passwordEncoder,
userMailService,
roleService,
adminRightRoleCache,
jwtTokenService);
}
If you have your own implementation of DefaultRoleInitializer
in your application you need to change the
super-constructor call from
public RoleInitializer(
InitProperties initProperties,
RoleRepository roleRepository,
RoleService roleService,
RightService rightService) {
super(initProperties, roleRepository, roleService, rightService);
}
to
public RoleInitializer(
InitProperties initProperties,
RoleRepository roleRepository,
RoleService roleService,
RightService rightService,
AdminRightRoleCache adminRightRoleCache) {
super(initProperties, roleRepository, roleService, rightService, adminRightRoleCache);
}
A new environment variableAPP_AUTH_LDAP_GROUP_SEARCH_SUBTREE
respectively app.auth.ldap.group-search-subtree
has
been introduced. If you want to enable the group subtree search, you have to set this variable to true
. You may alter
your application.yaml
(or application-ldap.yaml
) as follows:
app:
auth:
ldap:
enabled: true
allow-signup: true
update-role: true
url: ldap://ldap.localhost:389/dc=user,dc=example,dc=de
user-search-base: ou=users
user-search-filter: (mail={0})
group-search-base: ou=groups
group-search-filter: (member={0})
group-search-subtree: false # new, default value `false`
manager-dn: uid=example,ou=services,ou=users,dc=user,dc=example,dc=de
manager-password: changeme
user-firstname-attr: givenName
user-lastname-attr: sn
# a mapping from the above ldap attribute's value to an essencium user role
# if multiple mapping match, the first match takes precedence
roles:
- src: 'cn=admins,ou=groups,dc=user,dc=frachtwerk,dc=de'
dst: 'ADMIN'
- If your Application has its own
RightInitializer
extendingDefaultRightInitializer
you have to change the constructor from
public MyRightInitializer(RightService rightService, RoleService roleService) {
super(rightService, roleRepository);
}
to
public RightInitializer(RightService rightService, RoleRepository roleRepository) {
super(rightService, roleRepository);
}
If @Validated
is used, parameter annotations such as @Email
or @NotNull
are evaluated and violations result in a
HandlerMethodValidationException
. Since Spring Boot 3.2.2, only the error message "Validation failure"
is output by
default. Essencium therefore implements its own handler that outputs the embedded violations as an error message.
Assuming that the parameter eMail
is incorrect and the mandatory parameter lastName
is not transmitted at all, the
resulting error message is as follows:
{
"status": 400,
"error": "400 BAD_REQUEST \"Validation failure\"",
"message": [
"email must be a correctly formatted email address",
"lastName must not be empty"
],
"timestamp": "2024-01-24T09:47:14.443917986",
"path": "/v1/users"
}
The messages are always composed according to the scheme " ".
The annotation @RestrictAccessToOwnedEntities
can be used to restrict access to entities. This makes it possible to
restrict access to entities based on the ownership of the entity. You may use it in combination with the
@OwnershipSpec
annotation to specify the ownership of the entity. It can be used on controller class level, controller
method level or on the entity class level. If used on the entity class level, the controller has to be annotated with
@ExposesEntity
to tell the application which Class has to be scanned for OwnershipSpecs. In previous versions, the
annotation @ExposesResourceFor
was used for this purpose. Since this annotation was derived from Spring HATEOAS, it is
no longer possible to use it for this purpose. Use @ExposesEntity
in your Application instead.
From this version onwards, database dependencies must be defined and integrated at application level. Developers must
therefore define one (or more) of the following dependencies in the pom.xml
:
<dependencies>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>${postgresql.version}</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>${h2.version}</version>
</dependency>
<dependency>
<groupId>org.mariadb.jdbc</groupId>
<artifactId>mariadb-java-client</artifactId>
<version>${mariadb.version}</version>
</dependency>
<dependency>
<groupId>com.microsoft.sqlserver</groupId>
<artifactId>mssql-jdbc</artifactId>
<version>${mssql.version}</version>
</dependency>
</dependencies>
List is not exhaustive.
Right: Defines a singular attribute that can be checked, for example, for access control in controllers (@Secured()
).
Role: Defines a set of rights that can be assigned to users.
User: Defines a user with a set of roles. The rights of the user result from the sum of the rights of the assigned
roles.
Roles and Users can now be created via environment variables:
essencium:
init:
roles:
- name: ADMIN
description: Administrator
rights: [ ]
protected: true
- name: USER
description: User
rights:
- READ
protected: true
default-role: true
users:
- first-name: Admin
last-name: User
username: devnull@frachtwerk.de
password: adminAdminAdmin
roles:
- ADMIN
- USER
- first-name: User
last-name: User
username: user@frachtwerk.de
password: userUserUser
roles:
- USER
If no roles or users are defined, the default role ADMIN
having all BasicApplicationRights assigned and no users are
created.
Please note that an existing database must be migrated according to the old schema (one role per user)! For postgresql, the following migration script can be used:
ALTER TABLE IF EXISTS "FW_ROLE"
ADD COLUMN IF NOT EXISTS "is_default_role" BOOLEAN NOT NULL DEFAULT FALSE;
ALTER TABLE IF EXISTS "FW_ROLE"
ADD COLUMN IF NOT EXISTS "is_system_role" BOOLEAN NOT NULL DEFAULT FALSE;
CREATE TABLE IF NOT EXISTS "FW_USER_ROLES"
(
"user_id"
bigint
NOT
NULL,
"roles_name"
character
varying
(
255
) NOT NULL,
CONSTRAINT "FW_USER_ROLES_pkey" PRIMARY KEY
(
user_id,
roles_name
),
CONSTRAINT "FK5x6ca7enc8g15hxhty2s9iikp" FOREIGN KEY
(
roles_name
)
REFERENCES "FW_ROLE"
(
name
) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE NO ACTION,
CONSTRAINT "FKh0k8otxier8qii1l14gvc87wi" FOREIGN KEY
(
user_id
)
REFERENCES "FW_USER"
(
id
) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE NO ACTION
);
INSERT INTO "FW_USER_ROLES" ("user_id", "roles_name")
SELECT "id", "role_name"
FROM "FW_USER";
ALTER TABLE "FW_USER" DROP COLUMN "role_name";
This SQL script requires that the database schema corresponds at least to the schema of Essencium version 2.3.0 (Flyway Migration 2.3.2). For the migration of previous Essencium versions, see essencium-backend-development/src/main/resources/migrations.
With regard to the environment variables, the previous root element 'essencium-backend' has been renamed to 'essencium'.
For example, essencium-backend.jpa.table-prefix: "FW_"
will now be essencium.jpa.table-prefix: "FW_"
.
As a result, the following previous variables are completely omitted:
essencium-backend.overrides.*
essencium-backend.initial-admin.*
Reverting the changes made in 2.4.7
to the environment variable APP_URL
and the corresponding property app.url
a
new environment variable called APP_DOMAIN
is introduced. This variable is by default used to build the app.url
property.
environment variable | property | default value | description |
---|---|---|---|
APP_DOMAIN | app.domain | localhost |
The domain of the application without any protocol or port. APP_DOMAIN is used to set the domain of the cookies (refresh token). |
APP_URL | app.url | http://${app.domain}:8098 , http://localhost:8098 |
The URL of the application. APP_URL is used for branding and redirects |
- If you have extendend
AbstractUserService
, you fave to update your Constructor addingJwtTokenService
.
@Service
public class UserService extends AbstractUserService<User, Long, UserInput> {
protected UserService(
@NotNull UserRepository userRepository,
@NotNull PasswordEncoder passwordEncoder,
@NotNull UserMailService userMailService,
@NotNull RoleService roleService,
@NotNull JwtTokenService jwtTokenService) {
super(userRepository, passwordEncoder, userMailService, roleService, jwtTokenService);
}
// ...
}
- If you have added the Annotation
@EnableAsync
somehow, you have to remove it. In Essencium Asyncroneous execution is enabled by default. - If you use Flyway in your application, please note that in addition to the core library, the specific implementation
for your DBMS must also be inserted in the
pom.xml
. Example for PostgreSQL databases:
<!-- Flyway Database Migration Dependencies -->
<!-- https://mvnrepository.com/artifact/org.flywaydb/flyway-core -->
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
<version>10.1.0</version>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-database-postgresql</artifactId>
<version>10.1.0</version>
</dependency>
- You'll have to add following properties in your
application.yml
(Please note that this can also apply to unit and integration tests):
mail:
# ... previous values for host, port, username, password, default-sender, smtp, branding, new-user-mail, reset-token-mail, contact-mail
new-login-mail:
subject-key: mail.new-login.subject
template: NewLoginMessage.ftl
- JWT-Processing has been restructured. Please add the following parameters in your
application.yaml
:
app:
auth:
jwt:
access-token-expiration: 86400 # 24 hours
refresh-token-expiration: 2592000 # 30 days
issuer: Frachtwerk GmbH
cleanup-interval: 3600 # 1 hour
-
The parameter
app.url
has to be given without any pre- or suffix. (e.g.app.url: localhost
) -
In Dockerfiles, the previous ENTRYPOINT must be changed from:
ENTRYPOINT [ "sh", "-c", "java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom org.springframework.boot.loader.JarLauncher" ]
to:
ENTRYPOINT [ "sh", "-c", "java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom org.springframework.boot.loader.launch.JarLauncher" ]
See https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-3.2-Release-Notes for details.
- If you are using
essencium-backend
(not one of the model implementations) you have to update your UserService and UserRepresentationAssembler implementations.UserDto
as input brings a new boolean-field namedloginDisabled
which has to be handled in the implementations. Same goes for theUserRepresentation
which should have a new boolean-field namedloginDisabled
as well.
- Replace all
spring-starter-*
dependencies with the correspondingessencium-backend-*
dependencies. - Replace all
de.frachtwerk.starter.backend.*
imports with the correspondingde.frachtwerk.essencium.backend.*
imports.
- Either choose one of the implementation libraries or build directly on
essencium-backend
. - When you build on
essencium-backend
, you need to implement theUser
entity, as well as all associated services (UserService
,UserRepository
,UserRepresentation
,UserRepresentationAssembler
andUserController
) in your application. For a more comfortable development result it is recommended to implement the classesAbstractModel
,ModelSpec
,AssemblingService
andDefaultAssemblingEntityService
as well and build on them afterwards. Example implementations can be found below. - Adjust the import paths for
User
,Role
andRight
according to the new implementations. - Fix all other code issues that arise from the new implementation. ;-)
- Due to the changed role rights model, existing databases have to be migrated. An example migration script (which also works on empty databases and can be used with e.g. Flyway) can be found below.
All examples are based on the SequenceID model. For the other variants it is recommended to use the corresponding implementation libraries as a template.
@Entity
public class User extends AbstractBaseUser<Long> {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "hibernate_sequence")
@SequenceGenerator(
name = "hibernate_sequence",
sequenceName = "hibernate_sequence",
allocationSize = 1)
private Long id;
}
@Repository
public interface UserRepository extends BaseUserRepository<User, Long> {
}
@Service
public class UserService extends AbstractUserService<User, Long, UserDto<Long>> {
protected UserService(
@NotNull UserRepository userRepository,
@NotNull PasswordEncoder passwordEncoder,
@NotNull UserMailService userMailService,
@NotNull RoleService roleService) {
super(userRepository, passwordEncoder, userMailService, roleService);
}
@Override
protected @NotNull <E extends UserDto<Long>> User convertDtoToEntity(@NotNull E entity) {
Role role = roleService.getById(entity.getRole());
return User.builder()
.email(entity.getEmail())
.enabled(entity.isEnabled())
.role(role)
.firstName(entity.getFirstName())
.lastName(entity.getLastName())
.locale(entity.getLocale())
.mobile(entity.getMobile())
.phone(entity.getPhone())
.source(entity.getSource())
.id(entity.getId())
.build();
}
@Override
public UserDto<Long> getNewUser() {
return new UserDto<>();
}
}
@RestController
@RequestMapping("/v1/users")
public class UserController
extends AbstractUserController<
User, UserRepresentation, AppUserDto, BaseUserSpec<User, Long>, Long> {
protected UserController(UserService userService, UserAssembler assembler) {
super(userService, assembler);
}
}
public class UserRepresentation {
private Long id;
private String createdBy;
private String updatedBy;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
private String firstName;
private String lastName;
private String phone;
private String mobile;
private String email;
private Locale locale;
private Role role;
}
@Primary
@Component
public class UserAssembler extends AbstractRepresentationAssembler<User, UserRepresentation> {
@Override
public @NonNull UserRepresentation toModel(@NonNull User entity) {
return UserRepresentation.builder()
.id(entity.getId())
.createdBy(entity.getCreatedBy())
.createdAt(entity.getCreatedAt())
.updatedBy(entity.getUpdatedBy())
.updatedAt(entity.getUpdatedAt())
.firstName(entity.getFirstName())
.lastName(entity.getLastName())
.phone(entity.getPhone())
.mobile(entity.getMobile())
.email(entity.getEmail())
.locale(entity.getLocale())
.role(entity.getRole())
.build();
}
}
public abstract class AbstractModel extends SequenceIdModel {
}
public interface ModelSpec<T extends SequenceIdModel> extends BaseModelSpec<T, Long> {
}
public interface AssemblingService<M extends AbstractBaseModel, R> {
AbstractRepresentationAssembler<M, R> getAssembler();
default R toOutput(M entity) {
return getAssembler().toModel(entity);
}
default Page<R> toOutput(Page<M> page) {
if (page == null) {
return null;
}
return page.map(this::toOutput);
}
}
public abstract class DefaultAssemblingEntityService<M extends SequenceIdModel, IN, OUT>
extends AbstractEntityService<M, Long, IN> implements AssemblingService<M, OUT> {
@Getter
private final AbstractRepresentationAssembler<M, OUT> assembler;
protected DefaultAssemblingEntityService(
final AbstractRepository<M> repository,
final AbstractRepresentationAssembler<M, OUT> assembler) {
super(repository);
this.assembler = assembler;
}
}
-- create potentially non-existing sequences
CREATE SEQUENCE IF NOT EXISTS hibernate_sequence
INCREMENT 1
START 1
MINVALUE 1
MAXVALUE 9223372036854775807
CACHE 1;
-- create potentially non-existing tables
CREATE TABLE IF NOT EXISTS "FW_USER"
(
id
bigint
NOT
NULL,
created_at
timestamp
(
6
) without time zone,
created_by character varying
(
255
),
updated_at timestamp
(
6
)
without time zone,
updated_by character varying
(
255
),
email character varying
(
150
),
enabled boolean NOT NULL,
failed_login_attempts integer NOT NULL DEFAULT 0,
first_name character varying
(
255
),
last_name character varying
(
255
),
locale character varying
(
255
) NOT NULL,
login_disabled boolean NOT NULL,
mobile character varying
(
255
),
nonce character varying
(
255
),
password character varying
(
255
),
password_reset_token character varying
(
255
),
phone character varying
(
255
),
source character varying
(
255
),
role_id bigint NOT NULL,
CONSTRAINT "FW_USER_pkey" PRIMARY KEY
(
id
),
CONSTRAINT uk_o5gwjnjfosht4tf5lq48rxfoj UNIQUE
(
email
)
);
CREATE TABLE IF NOT EXISTS "FW_RIGHT"
(
id
bigint
NOT
NULL,
created_at
timestamp
(
6
) without time zone,
created_by character varying
(
255
),
updated_at timestamp
(
6
)
without time zone,
updated_by character varying
(
255
),
description character varying
(
512
),
name character varying
(
255
) NOT NULL,
CONSTRAINT "FW_RIGHT_pkey" PRIMARY KEY
(
id
),
CONSTRAINT uk_jep1itavphekmnphj0vp38s9u UNIQUE
(
name
)
);
CREATE TABLE IF NOT EXISTS "FW_ROLE"
(
id
bigint
NOT
NULL,
created_at
timestamp
(
6
) without time zone,
created_by character varying
(
255
),
updated_at timestamp
(
6
)
without time zone,
updated_by character varying
(
255
),
description character varying
(
255
),
is_protected boolean NOT NULL,
name character varying
(
255
) NOT NULL,
CONSTRAINT "FW_ROLE_pkey" PRIMARY KEY
(
id
)
);
CREATE TABLE IF NOT EXISTS "FW_ROLE_RIGHTS"
(
role_id
bigint
NOT
NULL,
rights_id
bigint
NOT
NULL,
CONSTRAINT
"FW_ROLE_RIGHTS_pkey"
PRIMARY
KEY
(
role_id,
rights_id
)
);
-- USER -> ROLE
ALTER TABLE IF EXISTS "FW_USER"
ADD COLUMN "role_name" VARCHAR (255);
UPDATE "FW_USER"
SET role_name = role.name FROM "FW_ROLE" role
WHERE role.id = "FW_USER".role_id;
ALTER TABLE IF EXISTS "FW_USER"
DROP
CONSTRAINT IF EXISTS "FKnpftnul0ve9guxtoqakx201de";
ALTER TABLE IF EXISTS "FW_USER"
DROP
COLUMN IF EXISTS role_id;
-- ROLE -> RIGHT
ALTER TABLE IF EXISTS "FW_ROLE_RIGHTS"
ADD COLUMN "role_name" VARCHAR (255);
UPDATE "FW_ROLE_RIGHTS"
SET role_name = role.name FROM "FW_ROLE" role
WHERE role.id = "FW_ROLE_RIGHTS".role_id;
-- RIGHT --> ROLE
ALTER TABLE IF EXISTS "FW_ROLE_RIGHTS"
ADD COLUMN "rights_authority" VARCHAR (255);
ALTER TABLE IF EXISTS "FW_RIGHT"
RENAME name TO authority;
UPDATE "FW_ROLE_RIGHTS"
SET rights_authority = appright.authority FROM "FW_RIGHT" appright
WHERE appright.id = "FW_ROLE_RIGHTS".rights_id;
-- Remove old Constraints
ALTER TABLE IF EXISTS "FW_ROLE_RIGHTS"
DROP
CONSTRAINT IF EXISTS "FK4akiafdy6sibodflxw662bf0x";
ALTER TABLE IF EXISTS "FW_ROLE_RIGHTS"
DROP
CONSTRAINT IF EXISTS "FKc28mpb53220tvxffq0buv1sc6";
-- Remove old Primary Keys
ALTER TABLE IF EXISTS "FW_ROLE_RIGHTS"
DROP
CONSTRAINT IF EXISTS "FW_ROLE_RIGHTS_pkey";
ALTER TABLE IF EXISTS "FW_ROLE"
DROP
COLUMN IF EXISTS id;
ALTER TABLE IF EXISTS "FW_RIGHT"
DROP
COLUMN IF EXISTS id;
-- Remove old Columns
ALTER TABLE IF EXISTS "FW_ROLE_RIGHTS"
DROP
COLUMN "role_id";
ALTER TABLE IF EXISTS "FW_ROLE_RIGHTS"
DROP
COLUMN "rights_id";
-- Add new Primary Keys
ALTER TABLE IF EXISTS "FW_ROLE"
ADD CONSTRAINT "FW_ROLE_pkey" PRIMARY KEY ("name");
ALTER TABLE IF EXISTS "FW_RIGHT"
ADD CONSTRAINT "FW_RIGHT_pkey" PRIMARY KEY ("authority");
ALTER TABLE IF EXISTS "FW_ROLE_RIGHTS"
ADD CONSTRAINT "FW_ROLE_RIGHT_pkey" PRIMARY KEY ("role_name", "rights_authority");
-- Add new Constraints
ALTER TABLE IF EXISTS "FW_USER"
ADD CONSTRAINT "FK8xvm8eci4kcyn46nr2xd4axx9" FOREIGN KEY ("role_name") REFERENCES "FW_ROLE" ("name") MATCH SIMPLE
ON
UPDATE NO ACTION
ON
DELETE
NO ACTION;
ALTER TABLE IF EXISTS "FW_ROLE_RIGHTS"
ADD CONSTRAINT "FKhqod6jll49rbgohaml3pi5ofi" FOREIGN KEY ("rights_authority")
REFERENCES "FW_RIGHT" ("authority") MATCH SIMPLE
ON
UPDATE NO ACTION
ON
DELETE
NO ACTION;
ALTER TABLE IF EXISTS "FW_ROLE_RIGHTS"
ADD CONSTRAINT "FKillb2aaughbvyxj9j8sa9835g" FOREIGN KEY ("role_name")
REFERENCES "FW_ROLE" ("name") MATCH SIMPLE
ON
UPDATE NO ACTION
ON
DELETE
NO ACTION;
-- Delete obsolete Columns
ALTER TABLE IF EXISTS "FW_RIGHT"
DROP
COLUMN IF EXISTS created_at;
ALTER TABLE IF EXISTS "FW_RIGHT"
DROP
COLUMN IF EXISTS created_by;
ALTER TABLE IF EXISTS "FW_RIGHT"
DROP
COLUMN IF EXISTS updated_at;
ALTER TABLE IF EXISTS "FW_RIGHT"
DROP
COLUMN IF EXISTS updated_by;
ALTER TABLE IF EXISTS "FW_ROLE"
DROP
COLUMN IF EXISTS created_at;
ALTER TABLE IF EXISTS "FW_ROLE"
DROP
COLUMN IF EXISTS created_by;
ALTER TABLE IF EXISTS "FW_ROLE"
DROP
COLUMN IF EXISTS updated_at;
ALTER TABLE IF EXISTS "FW_ROLE"
DROP
COLUMN IF EXISTS updated_by;
Projects overriding UserController may now use AbstractUserController with 4 parameters:
U extends User
as handled EntityR
as RepresentationT extends UserDto
as Input-EntityS extends UserSpec<U>
as Specification
By Default the AbstractUserController is implemented as follows:
@RestController
@RequestMapping("/v1/users")
public class UserController extends AbstractUserController<User, User, UserDto, UserSpec<User>> {
public UserController(UserService userService, UserRepresentationDefaultAssembler assembler) {
super(userService, assembler);
}
}
If you wish to use an own Representation, you have to do three steps:
- Write your own controller
- Write your own Representation
- Write your own Assembler
@RestController
@RequestMapping("/v1/users")
public class MyUserController extends AbstractUserController<MyUser, MyUserRepresentation, UserDto, UserSpec<User>> {
public UserController(UserService userService, MyUserRepresentationAssembler assembler) {
super(userService, assembler);
}
}
public class MyUserRepresentation {
private Long id;
private String firstName;
private String lastName;
}
@Primary // <- to disable the UserRepresentationDefaultAssembler
@Component
public class MyUserRepresentationAssembler
extends AbstractRepresentationAssembler<User, MyUserRepresentation> {
@Override
public @NonNull MyUserRepresentation toModel(@NonNull User entity) {
return MyUserRepresentation.builder()
.id(entity.getId())
.firstName(entity.getFirstName())
.lastName(entity.getLastName())
.build();
}
}
- Configure the following Sentry properties in the application.yaml:
- api_url
- organization
- project
environment
,token
anddsn
should be set in the Container Stacks- You can find other Configurations in the
application.yaml
. Like Version trackingrelease: @project.version@
- New
/sentry/feedback
Post-Endpoint- Input:
{ "eventId": "c3e0aefba2cd43c1b4570f96d14e14dc", "name": "Test User", "email": "test@mail.de", "comments": "Comment to Issue" }
- The eventId refers to the Sentry Issue Id.
- Input:
- Replace
org.jetbrains.annotations.NotNull
withjakarta.validation.constraints.NotNull
in all files - Replace
org.jetbrains.annotations.Nullable
withjakarta.annotation.Nullable
in all files - If you have overridden or extended the
DefaultRoleInitializer
you have to change the constructor frompublic MyRoleInitializer(RightService rightService, RoleService roleService)
topublic MyRoleInitializer(RightService rightService, RoleService roleService, DefaultRoleProperties defaultRoleProperties)
- If you have accessed
USER_ROLE_NAME
orUSER_ROLE_DESCRIPTION
provided byDefaultRoleInitializer
you have to change it todefaultRoleProperties.getName()
anddefaultRoleProperties.getDescription()
respectively.
LDAP group sync
- remove
userRoleAttr
from any configuration - the following parameters have to be set:
groupSearchBase
groupSearchFilter
defaultRole
- if you know what you are doing even
groupRoleAttribute
can be set.
The complete list of ldap associated env variables:
app:
auth:
ldap:
enabled: true
allow-signup: true
update-role: true
user-search-base: ou=users
user-search-filter: (mail={0})
group-search-base: ou=groups
group-search-filter: (member={0})
# group-role-attribute: spring.security.ldap.dn # default value
# default-role: USER # default value
user-firstname-attr: givenName # default = "notSet"
user-lastname-attr: sn # default = "notSet
url: ldap://ldap.admin.frachtwerk.de:389/dc=user,dc=frachtwerk,dc=de
manager-dn: uid=service.user,ou=services,ou=users,dc=user,dc=frachtwerk,dc=de
manager-password: serviceuserpassword
roles:
- src: 'cn=admins,ou=groups,dc=user,dc=frachtwerk,dc=de'
dst: 'ADMIN'
- src: 'cn=users,ou=groups,dc=user,dc=frachtwerk,dc=de'
dst: 'USER'
# if 'group-role-attribute' is set to 'cn' roles can be mapped like this:
# - src: 'intern'
# dst: 'USER'
Breaking Changes concerning openApi documentation
- If there are any project specific swagger-configurations, delete them.
- Follow this (https://springdoc.org/v2/#migrating-from-springfox) migration guide to update your api-documentation
- The file
.mvn/jvm.config
with the following content must be created in the project root directory. This is necessary because due to an upgrade of the formatter maven has to be configured in advance.
--add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED
- All Java-based images in the pipeline, as well as the Dockerfiles, must be changed to Java-17-based images.
- The property
java.version
must be changed to17
- The version of
org.springframework.boot.spring-boot-starter-parent
must be changed to3.0.4
- The version of
de.frachtwerk.spring-essencium-backend
must be changed to2.0.0
- The dependency
spring-boot-properties-migrator
has to be added
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-properties-migrator -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-properties-migrator</artifactId>
</dependency>
- The dependency
com.github.tomakehurst.wiremock-jre8
has to be replaced bycom.github.tomakehurst.wiremock
using version3.0.0-beta-4
(or higher) - The Version of
com.h2database.h2
must be changed to2.1.214
- The Version of
org.postgresql.postgresql
must be changed to42.5.4
- The Version of
net.kaczmarzyk.specification-arg-resolver
must be changed to3.0.1
- If used
org.springframework.security.spring-security-oauth2-client
andorg.springframework.security.spring-security-oauth2-jose
have to be replaced byorg.springframework.boot.spring-boot-starter-oauth2-resource-server
andorg.springframework.boot.spring-boot-starter-oauth2-client
javax
-packages have to be migrated to correspondingjakarta
-packages, exceptjavax.servlet-api
(there is no corresponding jakarta-package)- Every
fasterxml
-dependency has to be removed. -
Use
The Dependency
com.cosium.code.maven-git-code-format
has to be replaced bycom.cosium.code.git-code-format-maven-plugin
using version4.2
(or higher)mvn org.codehaus.mojo:build-helper-maven-plugin:add-test-source@add-integration-test-sources git-code-format:format-code
to format your code completely (including Integration Tests, see README)
org.hibernate.dialect.PostgreSQL10Dialect
has to be replaced byorg.hibernate.dialect.PostgreSQLDialect
in every profile-yaml using it.- jwt-secrets have to be at least 32 char
Model
andNativeIdModel
have been deprecated. Consider usingIdentityIdModel
respectivelySequenceIdModel
instead.⚠️ be aware of the sequence names in existing Databases- Projects using this old code snippet have to change to the new one since
GenerationType.AUTO
isn't supported by Hibernate 6.1.x anymore:
// old code
public class NativeIdModel extends AbstractModel {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "native")
@SequenceGenerator(name = "native", sequenceName = "native", allocationSize = 1)
private Long id;
}
// new code
public abstract class SequenceIdModel extends AbstractModel {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "hibernate_sequence")
@SequenceGenerator(
name = "hibernate_sequence",
sequenceName = "hibernate_sequence",
allocationSize = 1)
private Long id;
}
// If you have used `sequenceName = "native"` in your Project before, you should implement your own id using a SequenceGenerator with `sequenceName = "native"`.
- Projects using this old code snippet have to change to the new one since
"org.hibernate.type.TextType
isn't supported by Hibernate 6.1.x anymore. Additionally@Lob
is currently converted into CLOB by Hibernate which is currently unsupported by PostgreSQL.
// old
public class Example extends NativeIdModel {
@Lob
@Type(type = "org.hibernate.type.TextType")
private String string;
}
// new
public class Example extends SequenceIdModel {
@JdbcTypeCode(Types.LONGVARCHAR)
private String string;
}
javax
has to be replaced byjakarta
- Pipeline and docker images have to be upgraded
- To keep the
FW_
table prefix, the following has to be added inapplication.yaml
:
essencium-backend:
jpa:
table-prefix: "FW_"
- The default table naming strategy of hibernate and spring boot has changed. The new style uses camelCase column names.
To activate the previously used
under_score
-style the following has to be added inapplication.yaml
:
essencium-backend:
jpa:
camel-case-to-underscore: true
- Change Spring Version to
2.7.5
- ResourceProperties (spring.resources) is deprecated. If you need to access resources information, you need to inject
WebProperties and access its getResources accessor.
- Change
Resources resources
toWebProperties webproperties
and access the Information withwebproperties.getResources()
- Change
- Major Update in H2 Database:
- The maximum length of CHARACTER, CHARACTER VARYING and VARCHAR_IGNORECASE, columns, BINARY, BINARY VARYING, JAVA_OBJECT, GEOMETRY and JSON values is 1_000_000_000 characters.
NEXTVAL
is replaced byNEXT VALUE FOR
- Only Compatible in Legacy Mode - use the database URL jdbc:h2:~/test;MODE=LEGACY or the SQL statement SET MODE LEGACY
TranslationController#updateTranslation
now accepts aTranslationDto
as first and only parameter instead of aTranslation
. If you override this method in your application it needs to be adapted accordingly.
Version 1.18.4
switches from 4-digit locales to 2-digit locales. Therefore, all translation files and mail translation
files
have to be renamed from ...translation-xx_XX.properties
to ...translation-xx.properties
. Same goes for all e-mail
templates, located under src/main/resources/templates
. Also, all occurrences of de_DE
, en_US
, ... in config
files (application-*.yaml
) need to be replaced by their language-only equivalent.
DataInitializationConfiguration
in your application, make sure to
include DataMigrationInitializer
in the return result of getInitializers()
, otherwise the application won't start.
- Spring's
ErrorController
interface has changed, see here. If your project overrides this class, you need to adapt it accordingly (i.e. removegetErrorPath()
method).
- Constructor of
WebSecurityConfig
has changed. If your project overrides this class, you need to change it accordingly. See here.
For controller method access management the annotation @RestrictToOwnedEntities
is now deprecated.
Use @RestrictAccessToOwnedEntities
and @OwnershipSpec
instead.
Email templates are now split in header, footer and main sections. Different languages of emails are now supported too. Therefore, the freemarker templates and your application.yaml files need to be adapted.
See this commit.
One constructor of the User object expects a User object now instead of the UserDto. If you used this constructor in your application you have to adapt it. Also, the frontend needs to be adapted.
No backend-side changes needed. However, frontend needs to be adapted.
Version 1.15.0
switches from plain text mail to HTML mails. Therefore, the following files were removed:
resources/templates/ContactMessage.tmplt
resources/templates/NewUserMessage.tmplt
resources/templates/ResetToken.tmplt
These files are replaced with:
resources/templates/ContactMessage.ftl
resources/templates/NewUserMessage.ftl
resources/templates/ResetTokenMessage.ftl
When upgrading to 1.15.0
you need to copy the new files in your corresponding resources/templates/
folder.
You can change the templates to fit your needs. Multiple parameters (like logo, colors or url of the service) can be
changed
using properties (e.g. with changing your profile or setting environment variables). You can even override the template
path using the corresponding properties, e.g. for the welcome mail for new users mail.new-user-mail.template
.
Please make sure, that you use the corresponding properties before manually changing the mail templates.