Skip to content

Commit 363013c

Browse files
committed
Add user profile management classes and update version to 3.1.0-SNAPSHOT
1 parent 1c5597e commit 363013c

File tree

5 files changed

+305
-1
lines changed

5 files changed

+305
-1
lines changed

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
version=3.0.1-SNAPSHOT
1+
version=3.1.0-SNAPSHOT
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package com.digitalsanctuary.spring.user.profile;
2+
3+
import java.time.LocalDateTime;
4+
import com.digitalsanctuary.spring.user.persistence.model.User;
5+
import jakarta.persistence.Column;
6+
import jakarta.persistence.Id;
7+
import jakarta.persistence.JoinColumn;
8+
import jakarta.persistence.MappedSuperclass;
9+
import jakarta.persistence.MapsId;
10+
import jakarta.persistence.OneToOne;
11+
import lombok.Data;
12+
13+
/**
14+
* Base class for user profile entities that extend the core {@link User} functionality. This class provides the foundation for creating
15+
* application-specific user profiles with shared common attributes.
16+
*
17+
* <p>
18+
* This class uses {@code @MappedSuperclass} to allow inheritance of JPA mappings, enabling extending classes to add additional fields while
19+
* maintaining a consistent database structure. The profile shares its primary key with the associated {@link User} entity through the {@code @MapsId}
20+
* annotation.
21+
* </p>
22+
*
23+
* Example implementation: {@code @Entity
24+
*
25+
* @Table(name = "customer_profile") public class CustomerProfile extends BaseUserProfile { private String customerType; private String
26+
* shippingPreference; private List<Order> orders; } }
27+
*
28+
* Database Structure: - id/user_id (PK/FK to user_account table) - last_accessed (timestamp) - preferred_locale (varchar)
29+
*
30+
* @see User
31+
* @see MappedSuperclass
32+
*/
33+
@Data
34+
@MappedSuperclass
35+
public abstract class BaseUserProfile {
36+
37+
/**
38+
* The primary key for the profile, shared with the associated User entity. This is automatically populated through the {@code @MapsId} annotation
39+
* when the profile is persisted.
40+
*/
41+
@Id
42+
private Long id;
43+
44+
/**
45+
* The associated User entity. This establishes a one-to-one relationship with shared primary key through the {@code @MapsId} annotation. The
46+
* foreign key column will be named "user_id".
47+
*/
48+
@OneToOne
49+
@MapsId
50+
@JoinColumn(name = "user_id")
51+
private User user;
52+
53+
/**
54+
* Timestamp of the last time this profile was accessed. This can be used for tracking user activity, session management, or implementing timeout
55+
* functionality.
56+
*/
57+
@Column(name = "last_accessed")
58+
private LocalDateTime lastAccessed;
59+
60+
/**
61+
* The user's preferred locale for internationalization purposes. This should contain a valid locale code (e.g., "en_US", "fr_FR"). Applications
62+
* can use this to provide localized content and formatting.
63+
*/
64+
@Column(name = "preferred_locale")
65+
private String locale;
66+
67+
// Note: Getters and setters are provided by Lombok @Data annotation
68+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package com.digitalsanctuary.spring.user.profile;
2+
3+
import com.digitalsanctuary.spring.user.persistence.model.User;
4+
5+
/**
6+
* Service interface for managing user profiles. This interface defines the core operations for retrieving, creating, and updating user profiles that
7+
* extend the base profile functionality.
8+
*
9+
* <p>
10+
* Implementations of this interface handle the persistence and business logic for user profiles, providing a standardized way to manage extended user
11+
* data beyond the core {@link User} entity.
12+
* </p>
13+
*
14+
* Example implementation: {@code @Service public class CustomUserProfileService implements UserProfileService<CustomUserProfile> { private final
15+
* CustomUserProfileRepository profileRepository;
16+
*
17+
* @Override public CustomUserProfile getOrCreateProfile(User user) { return profileRepository.findByUserId(user.getId()) .orElseGet(() -> {
18+
* CustomUserProfile profile = new CustomUserProfile(); profile.setUser(user); return profileRepository.save(profile); }); }
19+
*
20+
* @Override public CustomUserProfile updateProfile(CustomUserProfile profile) { return profileRepository.save(profile); } } }
21+
*
22+
* @param <T> the type of user profile to manage, must extend BaseUserProfile
23+
* @see BaseUserProfile
24+
* @see User
25+
*/
26+
public interface UserProfileService<T extends BaseUserProfile> {
27+
28+
/**
29+
* Retrieves an existing profile for the given user or creates a new one if none exists. This method ensures that every user has an associated
30+
* profile.
31+
*
32+
* <p>
33+
* Implementations should:
34+
* </p>
35+
* <ul>
36+
* <li>Check if a profile exists for the user</li>
37+
* <li>Create a new profile if none exists</li>
38+
* <li>Initialize any required default values for new profiles</li>
39+
* <li>Persist the profile if newly created</li>
40+
* </ul>
41+
*
42+
* @param user the user to get or create a profile for
43+
* @return the existing or newly created profile
44+
* @throws IllegalArgumentException if user is null
45+
* @throws RuntimeException if profile creation or retrieval fails
46+
*/
47+
T getOrCreateProfile(User user);
48+
49+
/**
50+
* Updates an existing user profile with new information.
51+
*
52+
* <p>
53+
* Implementations should:
54+
* </p>
55+
* <ul>
56+
* <li>Validate the profile data before updating</li>
57+
* <li>Persist the changes to the data store</li>
58+
* <li>Return the updated profile instance</li>
59+
* </ul>
60+
*
61+
* @param profile the profile to update
62+
* @return the updated profile
63+
* @throws IllegalArgumentException if profile is null or invalid
64+
* @throws RuntimeException if profile update fails
65+
*/
66+
T updateProfile(T profile);
67+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package com.digitalsanctuary.spring.user.profile.session;
2+
3+
import org.springframework.context.ApplicationListener;
4+
import org.springframework.security.authentication.event.InteractiveAuthenticationSuccessEvent;
5+
import org.springframework.stereotype.Component;
6+
import com.digitalsanctuary.spring.user.profile.BaseUserProfile;
7+
import com.digitalsanctuary.spring.user.profile.UserProfileService;
8+
import com.digitalsanctuary.spring.user.service.DSUserDetails;
9+
10+
/**
11+
* Base authentication listener that handles successful user authentication events by loading or creating the appropriate user profile and storing it
12+
* in the session. This class provides the core functionality for maintaining user profile state across the application session.
13+
*
14+
* <p>
15+
* This listener automatically responds to successful interactive authentication events (like form login) by retrieving or creating a user profile via
16+
* the {@link UserProfileService} and storing it in the session-scoped {@link BaseSessionProfile}.
17+
* </p>
18+
*
19+
* <p>
20+
* Example implementation:
21+
* </p>
22+
*
23+
* <pre>
24+
* {@code
25+
* @Component
26+
* public class CustomAuthenticationListener extends BaseAuthenticationListener<CustomUserProfile> {
27+
* public CustomAuthenticationListener(CustomSessionProfile sessionProfile, CustomUserProfileService profileService) {
28+
* super(sessionProfile, profileService);
29+
* }
30+
* }
31+
* }</pre>
32+
*
33+
* @param <T> the type of user profile, must extend BaseUserProfile
34+
*
35+
* @see BaseSessionProfile
36+
* @see UserProfileService
37+
* @see InteractiveAuthenticationSuccessEvent
38+
* @see DSUserDetails
39+
*/
40+
@Component
41+
public abstract class BaseAuthenticationListener<T extends BaseUserProfile> implements ApplicationListener<InteractiveAuthenticationSuccessEvent> {
42+
43+
/** The session profile manager for storing user profile data. */
44+
private final BaseSessionProfile<T> sessionProfile;
45+
46+
/** The service for retrieving or creating user profiles. */
47+
private final UserProfileService<T> profileService;
48+
49+
/**
50+
* Constructs a new BaseAuthenticationListener with the specified session profile and profile service.
51+
*
52+
* @param sessionProfile the session-scoped profile manager
53+
* @param profileService the service for managing user profiles
54+
* @throws IllegalArgumentException if either parameter is null
55+
*/
56+
protected BaseAuthenticationListener(BaseSessionProfile<T> sessionProfile, UserProfileService<T> profileService) {
57+
if (sessionProfile == null || profileService == null) {
58+
throw new IllegalArgumentException("Session profile and profile service must not be null");
59+
}
60+
this.sessionProfile = sessionProfile;
61+
this.profileService = profileService;
62+
}
63+
64+
/**
65+
* Handles successful authentication events by loading or creating the user's profile and storing it in the session.
66+
*
67+
* <p>
68+
* This method is automatically called by Spring's event system when a user successfully authenticates. It checks if the authentication principal
69+
* is a {@link DSUserDetails} instance, and if so, retrieves or creates the associated profile and stores it in the session.
70+
* </p>
71+
*
72+
* @param event the authentication success event
73+
* @throws IllegalStateException if the authentication details are invalid or missing
74+
*/
75+
@Override
76+
public void onApplicationEvent(InteractiveAuthenticationSuccessEvent event) {
77+
if (event.getAuthentication().getPrincipal() instanceof DSUserDetails) {
78+
DSUserDetails userDetails = (DSUserDetails) event.getAuthentication().getPrincipal();
79+
T profile = profileService.getOrCreateProfile(userDetails.getUser());
80+
sessionProfile.setUserProfile(profile);
81+
}
82+
}
83+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package com.digitalsanctuary.spring.user.profile.session;
2+
3+
import java.io.Serializable;
4+
import java.time.LocalDateTime;
5+
import org.springframework.context.annotation.Scope;
6+
import org.springframework.context.annotation.ScopedProxyMode;
7+
import org.springframework.stereotype.Component;
8+
import org.springframework.web.context.WebApplicationContext;
9+
import com.digitalsanctuary.spring.user.persistence.model.User;
10+
import com.digitalsanctuary.spring.user.profile.BaseUserProfile;
11+
import lombok.Data;
12+
13+
/**
14+
* Base class for session-scoped user profile management. This class provides the foundation for maintaining user profile data within the session
15+
* context of a web application. It is designed to be extended by applications to add custom profile management functionality.
16+
*
17+
* <p>
18+
* This class is session-scoped and uses proxy mode TARGET_CLASS to ensure proper session management in a web environment. It maintains a reference to
19+
* the user's profile and tracks when it was last updated.
20+
* </p>
21+
*
22+
* <p>
23+
* Example usage:
24+
* </p>
25+
*
26+
* <pre>
27+
* {@code
28+
* @Component
29+
* public class CustomSessionProfile extends BaseSessionProfile<CustomUserProfile> {
30+
* // Add custom methods for your application
31+
* public boolean hasSpecificPermission() {
32+
* return getUserProfile().getPermissions().contains("SPECIFIC_PERMISSION");
33+
* }
34+
* }
35+
* }</pre>
36+
*
37+
* @param <T> the type of user profile, must extend BaseUserProfile
38+
*
39+
* @see BaseUserProfile
40+
* @see WebApplicationContext
41+
* @see Serializable
42+
*/
43+
@Data
44+
@Component
45+
@Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)
46+
public abstract class BaseSessionProfile<T extends BaseUserProfile> implements Serializable {
47+
48+
/** Serialization version ID. */
49+
private static final long serialVersionUID = 1L;
50+
51+
/** The current user's profile. */
52+
private T userProfile;
53+
54+
/** Timestamp of when the profile was last updated. */
55+
private LocalDateTime lastUpdated;
56+
57+
/**
58+
* Retrieves the current user's profile.
59+
*
60+
* @return the user profile of type T, or null if no profile is set
61+
*/
62+
public T getUserProfile() {
63+
return userProfile;
64+
}
65+
66+
/**
67+
* Sets the user's profile and updates the lastUpdated timestamp. This method is typically called during authentication or when the profile data
68+
* is modified.
69+
*
70+
* @param userProfile the user profile to set
71+
*/
72+
public void setUserProfile(T userProfile) {
73+
this.userProfile = userProfile;
74+
this.lastUpdated = LocalDateTime.now();
75+
}
76+
77+
/**
78+
* Convenience method to get the core User entity associated with the profile.
79+
*
80+
* @return the User entity if a profile is set, null otherwise
81+
* @see User
82+
*/
83+
public User getUser() {
84+
return userProfile != null ? userProfile.getUser() : null;
85+
}
86+
}

0 commit comments

Comments
 (0)