Skip to content

Developer Guide

This section covers the data model, configuration reference, and extension points available to developers integrating User Profile into an application.

Data Model

User Profile stores one record per user. The entity is intentionally simple: a flat table keyed by a generated integer identifier and associated to its owner by username.

---
config:
  layout: elk
---
erDiagram
    up_user_profile {
        integer id PK
        varchar username UK
        varchar first_name
        varchar last_name
        varchar phone_number
        varchar address
        varchar email
        blob avatar
        timestamp creation_time
    }
Hold "Alt" / "Option" to enable pan & zoom

Database Schema

User Profile manages a single table. The schema below represents the DDL generated by Hibernate for a standard relational database. Column names follow Hibernate's default snake_case naming strategy.

CREATE SEQUENCE up_user_profile_seq
    START WITH 1
    INCREMENT BY 50;

CREATE TABLE up_user_profile (
    id             INTEGER        NOT NULL,
    username       VARCHAR(255)   NOT NULL UNIQUE,
    first_name     VARCHAR(255),
    last_name      VARCHAR(255),
    phone_number   VARCHAR(255),
    address        VARCHAR(255),
    email          VARCHAR(255),
    avatar         BLOB,
    creation_time  TIMESTAMP(6),
    CONSTRAINT pk_up_user_profile PRIMARY KEY (id)
);

The avatar column stores raw binary image data. Its exact SQL type depends on the database dialect: BLOB on MySQL and H2, BYTEA on PostgreSQL.

The up_user_profile_seq sequence is created by Hibernate when GenerationType.AUTO resolves to a sequence-based strategy, which is the default behaviour in Hibernate 7.

The username column carries a NOT NULL UNIQUE constraint, so the database itself guarantees that a profile belongs to exactly one user. An application upgrading from a version that allowed duplicates must remove them before the constraint can be created.

Module Overview

User Profile is structured as six Maven modules following the AppJars layered architecture:

Module Artifact ID Description
Model appjars-user-profile-model DTOs, filter and sort classes, auto-configuration
Business API appjars-user-profile-business UserProfileService interface
Business Impl appjars-user-profile-business-impl Service implementation with @Transactional operations
Data API appjars-user-profile-data UserProfileDao interface
Data Impl appjars-user-profile-data-impl JPA entity and DAO implementation
Flow UI appjars-user-profile-flow Vaadin views, forms, and route configuration

A monolithic application includes the three implementation modules (-business-impl, -data-impl, -flow). The API modules (-business, -data) are pulled in transitively.

Note

Version 2.0 standardises explicit serialVersionUID values on the serializable DTO and Vaadin state types. Sessions, caches, or messages serialized by an earlier version are not compatible and must not be carried across the upgrade.

Spring Auto-Configuration

User Profile registers itself through Spring Boot's auto-configuration mechanism. The entry point is UserProfileAutoConfiguration, declared in:

META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

This class scans the com.appjars.userprofile package for all Spring components and JPA entities, so no additional @EntityScan or @ComponentScan declarations are required in the host application beyond those described in the Getting Started guide.

Configuration Properties

The following properties can be set in application.properties to customise the behaviour of User Profile:

Property Default Description
com.appjars.userprofile.url.profile up/profile URL path of the personal profile view
com.appjars.userprofile.url.profile-list up/profile-list URL path of the administrator profiles list
com.appjars.userprofile.feat.redirect true When true, users without a profile are automatically redirected to the profile view on their first login
spring.servlet.multipart.max-file-size 1MB Maximum size of individual avatar image uploads. Set to 10MB to match the limit the profile form itself enforces
spring.servlet.multipart.max-request-size 10MB Maximum size of multipart requests. Should match max-file-size

The profile form rejects any image above 10 MB before it reaches the server, so raising the multipart limits beyond 10MB has no effect.

Service API

UserProfileService is the primary programmatic interface to User Profile. It extends CrudService and ValidationSupport<UserProfileDto>, and exposes the following methods specific to the module:

// Find a profile by its exact, case-sensitive username
Optional<UserProfileDto> findByUsername(String username);

// Retrieve a paginated, filtered, and sorted stream of profiles
Stream<UserProfileDto> getProfiles(int offset, int limit, ProfileFilter filter, List<ProfileSort> sortOrder);

// Count profiles matching a filter
Integer countProfiles(int offset, int limit, ProfileFilter filter);

findByUsername matches the username exactly and case-sensitively; it does not treat the value as a pattern, so _ and % carry no special meaning. It returns an empty Optional for a null username.

Inject UserProfileService in any Spring-managed component to read or write profiles programmatically:

@Autowired
private UserProfileService userProfileService;

public Optional<UserProfileDto> getCurrentUserProfile(String username) {
    return userProfileService.findByUsername(username);
}

Username Uniqueness

A username identifies exactly one profile. The rule is enforced at two levels:

  • The up_user_profile.username column carries a NOT NULL UNIQUE constraint.
  • UserProfileService contributes a creation validator and an update validator through ValidationSupport.getValidators(). Saving a profile whose username already belongs to a different profile raises a com.flowingcode.backendcore.validation.ValidationException carrying the message key appjars.userprofile.profileview.username.alreadyexists.

Both views catch the exception and show the translated message as a notification. A custom caller of the service API must handle it as well:

try {
    userProfileService.save(profile);
} catch (ValidationException e) {
    String messageKey = e.getErrors().get(0).getMessageKey();
    // present the translated message to the user
}

Events

UserProfileService publishes a ProfileUpdatedEvent Spring application event each time a profile is created or updated. Subscribe to it in any Spring component using @EventListener:

@Component
public class ProfileSyncListener {

    @EventListener
    public void onProfileUpdated(ProfileUpdatedEvent event) {
        String username  = event.username();
        String firstName = event.firstName();
        String lastName  = event.lastName();
        String email     = event.email();
        // custom logic: update search index, sync to external system, etc.
    }
}

The event is a Java record and carries the username, first name, last name, and email of the updated profile. No event is published on deletion.

Free Mode Enforcement

Without a valid User Profile license, the appjar runs in free mode and limits the application to five profiles. The limit is enforced in UserProfileServiceImpl, which throws a FreeLimitReachedException when save is called and five profiles already exist, or when update is called and the count is above five. Both views catch the exception and display an alert, so a custom caller of the service API must handle it as well.

The views also reflect the limit before the operation is attempted: while no license is present they display a restrictions bar with a Profiles usage badge, and they disable the New profile button and the Save button of the profile form once the limit is reached. In addition, AuthenticationListener suppresses the post-login redirect while the limit is reached, so that users without a profile are not sent to a view where they cannot save one.

Above the allowance the views enter a stricter state. When more than five profiles exist — the situation an application lands in when a license lapses — the Edit row action of the profiles list and the Save button of an existing profile are disabled as well, so the only operation left is deletion until the count is back within the allowance.

Customisation

Replacing Username Validation

The username entered in the profile form is validated by a UsernameValidator bean, a Vaadin Validator<String>. The default implementation, DefaultUsernameValidator, requires between 4 and 50 characters, rejects values containing spaces, and additionally applies email validation when the value contains an @ character.

The bean is registered with @ConditionalOnMissingBean, so declaring a UsernameValidator in the application replaces it:

@Bean
UsernameValidator usernameValidator() {
    return (value, context) -> value != null && value.startsWith("emp-")
        ? ValidationResult.ok()
        : ValidationResult.error("Username must start with 'emp-'");
}

Providing Profile Data from Another Source

Other AppJars obtain the first name, last name, and profile picture of a user through the UserProfileProvider interface. User Profile registers DefaultUserProfileProvider, which resolves that data through UserProfileService and maps the profile avatar to the profile picture.

The bean is registered with @ConditionalOnMissingBean, so an application that stores this information elsewhere — an LDAP directory, an external identity provider, a legacy table — can declare its own implementation:

@Bean
UserProfileProvider ldapUserProfileProvider(LdapTemplate ldapTemplate) {
    return username -> ...;
}

The interface returns the shared com.appjars.model.utils.UserProfileDto, which carries only the first name, last name, and profile picture. It is not the same class as the UserProfileDto of this appjar.

Assigning a Router Layout

By default, User Profile registers its views without a parent layout. To wrap them in the application's main layout, inject the RouteConfigurer bean and call setViewsRouterLayout in a @PostConstruct method:

@Autowired
private RouteConfigurer routeConfigurer;

@PostConstruct
public void configure() {
    routeConfigurer.setViewsRouterLayout(MainLayout.class);
}

Customising View URLs

The default URL paths can be overridden in application.properties:

com.appjars.userprofile.url.profile=myapp/my-profile
com.appjars.userprofile.url.profile-list=myapp/manage-profiles

Disabling the Profile Redirect

When com.appjars.userprofile.feat.redirect=true (the default), users who have not yet created a profile are automatically redirected to the My Profile view after login. To disable this behaviour:

com.appjars.userprofile.feat.redirect=false