mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-05 01:50:27 +08:00
Refactor code structure for improved readability and maintainability
This commit is contained in:
@@ -0,0 +1,183 @@
|
||||
# User Management System (Java)
|
||||
|
||||
A comprehensive user management system built in Java for testing Code Index MCP's analysis capabilities.
|
||||
|
||||
## Features
|
||||
|
||||
- **User Management**: Create, update, delete, and search users
|
||||
- **Authentication**: BCrypt password hashing and verification
|
||||
- **Authorization**: Role-based access control (Admin, User, Guest)
|
||||
- **Data Validation**: Input validation and sanitization
|
||||
- **Export/Import**: JSON and CSV export capabilities
|
||||
- **Persistence**: File-based storage with JSON serialization
|
||||
- **Logging**: SLF4J logging with Logback
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
src/main/java/com/example/usermanagement/
|
||||
├── models/
|
||||
│ ├── Person.java # Base person model
|
||||
│ ├── User.java # User model with auth features
|
||||
│ ├── UserRole.java # User role enumeration
|
||||
│ └── UserStatus.java # User status enumeration
|
||||
├── services/
|
||||
│ └── UserManager.java # User management service
|
||||
├── utils/
|
||||
│ ├── ValidationUtils.java # Validation utilities
|
||||
│ ├── UserNotFoundException.java # Custom exception
|
||||
│ └── DuplicateUserException.java # Custom exception
|
||||
└── Main.java # Main demo application
|
||||
```
|
||||
|
||||
## Technologies Used
|
||||
|
||||
- **Java 11**: Modern Java features and APIs
|
||||
- **Jackson**: JSON processing and serialization
|
||||
- **BCrypt**: Secure password hashing
|
||||
- **Apache Commons**: Utility libraries (Lang3, CSV)
|
||||
- **SLF4J + Logback**: Logging framework
|
||||
- **Maven**: Build and dependency management
|
||||
- **JUnit 5**: Testing framework
|
||||
|
||||
## Build and Run
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Java 11 or higher
|
||||
- Maven 3.6+
|
||||
|
||||
### Build
|
||||
|
||||
```bash
|
||||
mvn clean compile
|
||||
```
|
||||
|
||||
### Run
|
||||
|
||||
```bash
|
||||
mvn exec:java -Dexec.mainClass="com.example.usermanagement.Main"
|
||||
```
|
||||
|
||||
### Test
|
||||
|
||||
```bash
|
||||
mvn test
|
||||
```
|
||||
|
||||
### Package
|
||||
|
||||
```bash
|
||||
mvn package
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Creating Users
|
||||
|
||||
```java
|
||||
UserManager userManager = new UserManager();
|
||||
|
||||
// Create a basic user
|
||||
User user = userManager.createUser("John Doe", 30, "john_doe", "john@example.com");
|
||||
user.setPassword("SecurePass123!");
|
||||
|
||||
// Create an admin user
|
||||
User admin = userManager.createUser("Jane Smith", 35, "jane_admin",
|
||||
"jane@example.com", UserRole.ADMIN);
|
||||
admin.setPassword("AdminPass123!");
|
||||
admin.addPermission("user_management");
|
||||
```
|
||||
|
||||
### User Authentication
|
||||
|
||||
```java
|
||||
// Verify password
|
||||
boolean isValid = user.verifyPassword("SecurePass123!");
|
||||
|
||||
// Login
|
||||
if (user.login()) {
|
||||
System.out.println("Login successful!");
|
||||
System.out.println("Last login: " + user.getLastLogin());
|
||||
}
|
||||
```
|
||||
|
||||
### User Management
|
||||
|
||||
```java
|
||||
// Search users
|
||||
List<User> results = userManager.searchUsers("john");
|
||||
|
||||
// Filter users
|
||||
List<User> activeUsers = userManager.getActiveUsers();
|
||||
List<User> adminUsers = userManager.getUsersByRole(UserRole.ADMIN);
|
||||
List<User> olderUsers = userManager.getUsersOlderThan(25);
|
||||
|
||||
// Update user
|
||||
Map<String, Object> updates = Map.of("age", 31, "email", "newemail@example.com");
|
||||
userManager.updateUser("john_doe", updates);
|
||||
|
||||
// Export users
|
||||
String jsonData = userManager.exportUsers("json");
|
||||
String csvData = userManager.exportUsers("csv");
|
||||
```
|
||||
|
||||
## Testing Features
|
||||
|
||||
This project tests the following Java language features:
|
||||
|
||||
### Core Language Features
|
||||
- **Classes and Inheritance**: Person and User class hierarchy
|
||||
- **Enums**: UserRole and UserStatus with methods
|
||||
- **Interfaces**: Custom exceptions and validation
|
||||
- **Generics**: Collections with type safety
|
||||
- **Annotations**: Jackson JSON annotations
|
||||
- **Exception Handling**: Custom exceptions and try-catch blocks
|
||||
|
||||
### Modern Java Features
|
||||
- **Streams API**: Filtering, mapping, and collecting
|
||||
- **Lambda Expressions**: Functional programming
|
||||
- **Method References**: Stream operations
|
||||
- **Optional**: Null-safe operations
|
||||
- **Time API**: LocalDateTime usage
|
||||
|
||||
### Advanced Features
|
||||
- **Concurrent Collections**: ConcurrentHashMap
|
||||
- **Reflection**: Jackson serialization
|
||||
- **File I/O**: NIO.2 Path and Files
|
||||
- **Logging**: SLF4J with parameterized messages
|
||||
- **Validation**: Input validation and sanitization
|
||||
|
||||
### Framework Integration
|
||||
- **Maven**: Build lifecycle and dependency management
|
||||
- **Jackson**: JSON serialization/deserialization
|
||||
- **BCrypt**: Password hashing
|
||||
- **Apache Commons**: Utility libraries
|
||||
- **SLF4J**: Structured logging
|
||||
|
||||
### Design Patterns
|
||||
- **Builder Pattern**: Object construction
|
||||
- **Factory Pattern**: User creation
|
||||
- **Repository Pattern**: Data access
|
||||
- **Service Layer**: Business logic separation
|
||||
|
||||
## Dependencies
|
||||
|
||||
### Core Dependencies
|
||||
- **Jackson Databind**: JSON processing
|
||||
- **Jackson JSR310**: Java 8 time support
|
||||
- **BCrypt**: Password hashing
|
||||
- **Apache Commons Lang3**: Utilities
|
||||
- **Apache Commons CSV**: CSV processing
|
||||
|
||||
### Logging
|
||||
- **SLF4J API**: Logging facade
|
||||
- **Logback Classic**: Logging implementation
|
||||
|
||||
### Testing
|
||||
- **JUnit 5**: Testing framework
|
||||
- **Mockito**: Mocking framework
|
||||
|
||||
## License
|
||||
|
||||
MIT License - This is a sample project for testing purposes.
|
||||
@@ -0,0 +1,117 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
|
||||
http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>com.example</groupId>
|
||||
<artifactId>user-management</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>User Management System</name>
|
||||
<description>A sample user management system for testing Code Index MCP</description>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>11</maven.compiler.source>
|
||||
<maven.compiler.target>11</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<junit.version>5.9.2</junit.version>
|
||||
<jackson.version>2.15.2</jackson.version>
|
||||
<slf4j.version>2.0.7</slf4j.version>
|
||||
<logback.version>1.4.7</logback.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<!-- Jackson for JSON processing -->
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<version>${jackson.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||||
<artifactId>jackson-datatype-jsr310</artifactId>
|
||||
<version>${jackson.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Logging -->
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
<version>${slf4j.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
<version>${logback.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Apache Commons -->
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
<version>3.12.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-csv</artifactId>
|
||||
<version>1.9.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- BCrypt for password hashing -->
|
||||
<dependency>
|
||||
<groupId>org.mindrot</groupId>
|
||||
<artifactId>jbcrypt</artifactId>
|
||||
<version>0.4</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Test Dependencies -->
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
<version>${junit.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
<version>5.3.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.11.0</version>
|
||||
<configuration>
|
||||
<source>11</source>
|
||||
<target>11</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>3.1.2</version>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>exec-maven-plugin</artifactId>
|
||||
<version>3.1.0</version>
|
||||
<configuration>
|
||||
<mainClass>com.example.usermanagement.Main</mainClass>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,220 @@
|
||||
package com.example.usermanagement;
|
||||
|
||||
import com.example.usermanagement.models.User;
|
||||
import com.example.usermanagement.models.UserRole;
|
||||
import com.example.usermanagement.services.UserManager;
|
||||
import com.example.usermanagement.utils.UserNotFoundException;
|
||||
import com.example.usermanagement.utils.DuplicateUserException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Main class demonstrating the User Management System.
|
||||
*/
|
||||
public class Main {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(Main.class);
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.out.println("=".repeat(50));
|
||||
System.out.println("User Management System Demo (Java)");
|
||||
System.out.println("=".repeat(50));
|
||||
|
||||
// Create user manager
|
||||
UserManager userManager = new UserManager();
|
||||
|
||||
// Create sample users
|
||||
System.out.println("\n1. Creating sample users...");
|
||||
createSampleUsers(userManager);
|
||||
|
||||
// Display all users
|
||||
System.out.println("\n2. Listing all users...");
|
||||
listAllUsers(userManager);
|
||||
|
||||
// Test user retrieval
|
||||
System.out.println("\n3. Testing user retrieval...");
|
||||
testUserRetrieval(userManager);
|
||||
|
||||
// Test user search
|
||||
System.out.println("\n4. Testing user search...");
|
||||
testUserSearch(userManager);
|
||||
|
||||
// Test user filtering
|
||||
System.out.println("\n5. Testing user filtering...");
|
||||
testUserFiltering(userManager);
|
||||
|
||||
// Test user updates
|
||||
System.out.println("\n6. Testing user updates...");
|
||||
testUserUpdates(userManager);
|
||||
|
||||
// Test authentication
|
||||
System.out.println("\n7. Testing authentication...");
|
||||
testAuthentication(userManager);
|
||||
|
||||
// Display statistics
|
||||
System.out.println("\n8. User statistics...");
|
||||
displayStatistics(userManager);
|
||||
|
||||
// Test export functionality
|
||||
System.out.println("\n9. Testing export functionality...");
|
||||
testExport(userManager);
|
||||
|
||||
// Test user permissions
|
||||
System.out.println("\n10. Testing user permissions...");
|
||||
testPermissions(userManager);
|
||||
|
||||
System.out.println("\n" + "=".repeat(50));
|
||||
System.out.println("Demo completed successfully!");
|
||||
System.out.println("=".repeat(50));
|
||||
}
|
||||
|
||||
private static void createSampleUsers(UserManager userManager) {
|
||||
try {
|
||||
// Create admin user
|
||||
User admin = userManager.createUser("Alice Johnson", 30, "alice_admin",
|
||||
"alice@example.com", UserRole.ADMIN);
|
||||
admin.setPassword("AdminPass123!");
|
||||
admin.addPermission("user_management");
|
||||
admin.addPermission("system_admin");
|
||||
|
||||
// Create regular users
|
||||
User user1 = userManager.createUser("Bob Smith", 25, "bob_user", "bob@example.com");
|
||||
user1.setPassword("UserPass123!");
|
||||
|
||||
User user2 = userManager.createUser("Charlie Brown", 35, "charlie", "charlie@example.com");
|
||||
user2.setPassword("CharliePass123!");
|
||||
|
||||
User user3 = userManager.createUser("Diana Prince", 28, "diana", "diana@example.com");
|
||||
user3.setPassword("DianaPass123!");
|
||||
|
||||
System.out.println("✓ Created " + userManager.getUserCount() + " users");
|
||||
|
||||
} catch (DuplicateUserException e) {
|
||||
System.out.println("✗ Error creating users: " + e.getMessage());
|
||||
} catch (Exception e) {
|
||||
System.out.println("✗ Unexpected error: " + e.getMessage());
|
||||
logger.error("Error creating sample users", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void listAllUsers(UserManager userManager) {
|
||||
List<User> users = userManager.getAllUsers();
|
||||
|
||||
System.out.println("Found " + users.size() + " users:");
|
||||
users.forEach(user ->
|
||||
System.out.println(" • " + user.getUsername() + " (" + user.getName() +
|
||||
") - " + user.getRole().getDisplayName() +
|
||||
" [" + user.getStatus().getDisplayName() + "]")
|
||||
);
|
||||
}
|
||||
|
||||
private static void testUserRetrieval(UserManager userManager) {
|
||||
try {
|
||||
User user = userManager.getUser("alice_admin");
|
||||
System.out.println("✓ Retrieved user: " + user.getUsername() + " (" + user.getName() + ")");
|
||||
|
||||
User userByEmail = userManager.getUserByEmail("bob@example.com");
|
||||
if (userByEmail != null) {
|
||||
System.out.println("✓ Found user by email: " + userByEmail.getUsername());
|
||||
}
|
||||
|
||||
} catch (UserNotFoundException e) {
|
||||
System.out.println("✗ User retrieval failed: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private static void testUserSearch(UserManager userManager) {
|
||||
List<User> searchResults = userManager.searchUsers("alice");
|
||||
System.out.println("Search results for 'alice': " + searchResults.size() + " users found");
|
||||
|
||||
searchResults.forEach(user ->
|
||||
System.out.println(" • " + user.getUsername() + " (" + user.getName() + ")")
|
||||
);
|
||||
}
|
||||
|
||||
private static void testUserFiltering(UserManager userManager) {
|
||||
List<User> olderUsers = userManager.getUsersOlderThan(30);
|
||||
System.out.println("Users older than 30: " + olderUsers.size() + " users");
|
||||
|
||||
olderUsers.forEach(user ->
|
||||
System.out.println(" • " + user.getUsername() + " (" + user.getName() + ") - age " + user.getAge())
|
||||
);
|
||||
|
||||
List<User> adminUsers = userManager.getUsersByRole(UserRole.ADMIN);
|
||||
System.out.println("Admin users: " + adminUsers.size() + " users");
|
||||
}
|
||||
|
||||
private static void testUserUpdates(UserManager userManager) {
|
||||
try {
|
||||
Map<String, Object> updates = Map.of("age", 26);
|
||||
User updatedUser = userManager.updateUser("bob_user", updates);
|
||||
System.out.println("✓ Updated " + updatedUser.getUsername() + "'s age to " + updatedUser.getAge());
|
||||
|
||||
} catch (UserNotFoundException e) {
|
||||
System.out.println("✗ Update failed: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private static void testAuthentication(UserManager userManager) {
|
||||
try {
|
||||
User user = userManager.getUser("alice_admin");
|
||||
|
||||
// Test password verification
|
||||
boolean isValid = user.verifyPassword("AdminPass123!");
|
||||
System.out.println("✓ Password verification: " + (isValid ? "SUCCESS" : "FAILED"));
|
||||
|
||||
// Test login
|
||||
boolean loginSuccess = user.login();
|
||||
System.out.println("✓ Login attempt: " + (loginSuccess ? "SUCCESS" : "FAILED"));
|
||||
|
||||
if (loginSuccess) {
|
||||
System.out.println("✓ Last login: " + user.getLastLogin());
|
||||
}
|
||||
|
||||
} catch (UserNotFoundException e) {
|
||||
System.out.println("✗ Authentication test failed: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private static void displayStatistics(UserManager userManager) {
|
||||
Map<String, Integer> stats = userManager.getUserStats();
|
||||
|
||||
stats.forEach((key, value) ->
|
||||
System.out.println(" " + key.replace("_", " ").toUpperCase() + ": " + value)
|
||||
);
|
||||
}
|
||||
|
||||
private static void testExport(UserManager userManager) {
|
||||
try {
|
||||
String jsonExport = userManager.exportUsers("json");
|
||||
System.out.println("✓ JSON export: " + jsonExport.length() + " characters");
|
||||
|
||||
String csvExport = userManager.exportUsers("csv");
|
||||
System.out.println("✓ CSV export: " + csvExport.split("\n").length + " lines");
|
||||
|
||||
} catch (Exception e) {
|
||||
System.out.println("✗ Export failed: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private static void testPermissions(UserManager userManager) {
|
||||
try {
|
||||
User admin = userManager.getUser("alice_admin");
|
||||
|
||||
System.out.println("Admin permissions: " + admin.getPermissions());
|
||||
System.out.println("Has user_management permission: " + admin.hasPermission("user_management"));
|
||||
System.out.println("Is admin: " + admin.isAdmin());
|
||||
|
||||
// Test role privileges
|
||||
System.out.println("Admin role can act on USER role: " +
|
||||
admin.getRole().canActOn(UserRole.USER));
|
||||
|
||||
} catch (UserNotFoundException e) {
|
||||
System.out.println("✗ Permission test failed: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,284 @@
|
||||
package com.example.usermanagement.models;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Represents a person with basic information.
|
||||
* This class serves as the base class for more specific person types.
|
||||
*/
|
||||
public class Person {
|
||||
|
||||
@JsonProperty("name")
|
||||
private String name;
|
||||
|
||||
@JsonProperty("age")
|
||||
private int age;
|
||||
|
||||
@JsonProperty("email")
|
||||
private String email;
|
||||
|
||||
@JsonProperty("created_at")
|
||||
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss")
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
@JsonProperty("metadata")
|
||||
private Map<String, Object> metadata;
|
||||
|
||||
/**
|
||||
* Default constructor for Jackson deserialization.
|
||||
*/
|
||||
public Person() {
|
||||
this.createdAt = LocalDateTime.now();
|
||||
this.metadata = new HashMap<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor with name and age.
|
||||
*
|
||||
* @param name The person's name
|
||||
* @param age The person's age
|
||||
* @throws IllegalArgumentException if validation fails
|
||||
*/
|
||||
public Person(String name, int age) {
|
||||
this();
|
||||
setName(name);
|
||||
setAge(age);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor with name, age, and email.
|
||||
*
|
||||
* @param name The person's name
|
||||
* @param age The person's age
|
||||
* @param email The person's email address
|
||||
* @throws IllegalArgumentException if validation fails
|
||||
*/
|
||||
public Person(String name, int age, String email) {
|
||||
this(name, age);
|
||||
setEmail(email);
|
||||
}
|
||||
|
||||
// Getters and Setters
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
if (StringUtils.isBlank(name)) {
|
||||
throw new IllegalArgumentException("Name cannot be null or empty");
|
||||
}
|
||||
if (name.length() > 100) {
|
||||
throw new IllegalArgumentException("Name cannot exceed 100 characters");
|
||||
}
|
||||
this.name = name.trim();
|
||||
}
|
||||
|
||||
public int getAge() {
|
||||
return age;
|
||||
}
|
||||
|
||||
public void setAge(int age) {
|
||||
if (age < 0) {
|
||||
throw new IllegalArgumentException("Age cannot be negative");
|
||||
}
|
||||
if (age > 150) {
|
||||
throw new IllegalArgumentException("Age cannot exceed 150");
|
||||
}
|
||||
this.age = age;
|
||||
}
|
||||
|
||||
public String getEmail() {
|
||||
return email;
|
||||
}
|
||||
|
||||
public void setEmail(String email) {
|
||||
if (StringUtils.isNotBlank(email) && !isValidEmail(email)) {
|
||||
throw new IllegalArgumentException("Invalid email format");
|
||||
}
|
||||
this.email = StringUtils.isBlank(email) ? null : email.trim();
|
||||
}
|
||||
|
||||
public LocalDateTime getCreatedAt() {
|
||||
return createdAt;
|
||||
}
|
||||
|
||||
public void setCreatedAt(LocalDateTime createdAt) {
|
||||
this.createdAt = createdAt;
|
||||
}
|
||||
|
||||
public Map<String, Object> getMetadata() {
|
||||
return new HashMap<>(metadata);
|
||||
}
|
||||
|
||||
public void setMetadata(Map<String, Object> metadata) {
|
||||
this.metadata = metadata == null ? new HashMap<>() : new HashMap<>(metadata);
|
||||
}
|
||||
|
||||
// Business methods
|
||||
|
||||
/**
|
||||
* Returns a greeting message for the person.
|
||||
*
|
||||
* @return A personalized greeting
|
||||
*/
|
||||
public String greet() {
|
||||
return String.format("Hello, I'm %s and I'm %d years old.", name, age);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the person has an email address.
|
||||
*
|
||||
* @return true if email is present and not empty
|
||||
*/
|
||||
public boolean hasEmail() {
|
||||
return StringUtils.isNotBlank(email);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the person's email address.
|
||||
*
|
||||
* @param newEmail The new email address
|
||||
* @throws IllegalArgumentException if email format is invalid
|
||||
*/
|
||||
public void updateEmail(String newEmail) {
|
||||
setEmail(newEmail);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds metadata to the person.
|
||||
*
|
||||
* @param key The metadata key
|
||||
* @param value The metadata value
|
||||
*/
|
||||
public void addMetadata(String key, Object value) {
|
||||
if (StringUtils.isNotBlank(key)) {
|
||||
metadata.put(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets metadata value by key.
|
||||
*
|
||||
* @param key The metadata key
|
||||
* @return The metadata value or null if not found
|
||||
*/
|
||||
public Object getMetadata(String key) {
|
||||
return metadata.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets metadata value by key with default value.
|
||||
*
|
||||
* @param key The metadata key
|
||||
* @param defaultValue The default value if key is not found
|
||||
* @return The metadata value or default value
|
||||
*/
|
||||
public Object getMetadata(String key, Object defaultValue) {
|
||||
return metadata.getOrDefault(key, defaultValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes metadata by key.
|
||||
*
|
||||
* @param key The metadata key to remove
|
||||
* @return The removed value or null if not found
|
||||
*/
|
||||
public Object removeMetadata(String key) {
|
||||
return metadata.remove(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all metadata.
|
||||
*/
|
||||
public void clearMetadata() {
|
||||
metadata.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates email format using a simple regex.
|
||||
*
|
||||
* @param email The email to validate
|
||||
* @return true if email format is valid
|
||||
*/
|
||||
private boolean isValidEmail(String email) {
|
||||
String emailPattern = "^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$";
|
||||
return email.matches(emailPattern);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Person instance from a map of data.
|
||||
*
|
||||
* @param data The data map
|
||||
* @return A new Person instance
|
||||
*/
|
||||
public static Person fromMap(Map<String, Object> data) {
|
||||
Person person = new Person();
|
||||
|
||||
if (data.containsKey("name")) {
|
||||
person.setName((String) data.get("name"));
|
||||
}
|
||||
|
||||
if (data.containsKey("age")) {
|
||||
person.setAge((Integer) data.get("age"));
|
||||
}
|
||||
|
||||
if (data.containsKey("email")) {
|
||||
person.setEmail((String) data.get("email"));
|
||||
}
|
||||
|
||||
if (data.containsKey("metadata")) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> metadata = (Map<String, Object>) data.get("metadata");
|
||||
person.setMetadata(metadata);
|
||||
}
|
||||
|
||||
return person;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the person to a map representation.
|
||||
*
|
||||
* @return A map containing person data
|
||||
*/
|
||||
public Map<String, Object> toMap() {
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("name", name);
|
||||
map.put("age", age);
|
||||
map.put("email", email);
|
||||
map.put("created_at", createdAt);
|
||||
map.put("metadata", new HashMap<>(metadata));
|
||||
return map;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (obj == null || getClass() != obj.getClass()) return false;
|
||||
|
||||
Person person = (Person) obj;
|
||||
return age == person.age &&
|
||||
Objects.equals(name, person.name) &&
|
||||
Objects.equals(email, person.email) &&
|
||||
Objects.equals(createdAt, person.createdAt) &&
|
||||
Objects.equals(metadata, person.metadata);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(name, age, email, createdAt, metadata);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("Person{name='%s', age=%d, email='%s', createdAt=%s}",
|
||||
name, age, email, createdAt);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,363 @@
|
||||
package com.example.usermanagement.models;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.mindrot.jbcrypt.BCrypt;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* User class extending Person with authentication and authorization features.
|
||||
*/
|
||||
public class User extends Person {
|
||||
|
||||
@JsonProperty("username")
|
||||
private String username;
|
||||
|
||||
@JsonProperty("password_hash")
|
||||
private String passwordHash;
|
||||
|
||||
@JsonProperty("role")
|
||||
private UserRole role;
|
||||
|
||||
@JsonProperty("status")
|
||||
private UserStatus status;
|
||||
|
||||
@JsonProperty("last_login")
|
||||
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss")
|
||||
private LocalDateTime lastLogin;
|
||||
|
||||
@JsonProperty("login_attempts")
|
||||
private int loginAttempts;
|
||||
|
||||
@JsonProperty("permissions")
|
||||
private Set<String> permissions;
|
||||
|
||||
/**
|
||||
* Default constructor for Jackson deserialization.
|
||||
*/
|
||||
public User() {
|
||||
super();
|
||||
this.role = UserRole.USER;
|
||||
this.status = UserStatus.ACTIVE;
|
||||
this.loginAttempts = 0;
|
||||
this.permissions = new HashSet<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor with basic information.
|
||||
*
|
||||
* @param name The user's name
|
||||
* @param age The user's age
|
||||
* @param username The username
|
||||
*/
|
||||
public User(String name, int age, String username) {
|
||||
super(name, age);
|
||||
setUsername(username);
|
||||
this.role = UserRole.USER;
|
||||
this.status = UserStatus.ACTIVE;
|
||||
this.loginAttempts = 0;
|
||||
this.permissions = new HashSet<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor with email.
|
||||
*
|
||||
* @param name The user's name
|
||||
* @param age The user's age
|
||||
* @param username The username
|
||||
* @param email The email address
|
||||
*/
|
||||
public User(String name, int age, String username, String email) {
|
||||
super(name, age, email);
|
||||
setUsername(username);
|
||||
this.role = UserRole.USER;
|
||||
this.status = UserStatus.ACTIVE;
|
||||
this.loginAttempts = 0;
|
||||
this.permissions = new HashSet<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor with role.
|
||||
*
|
||||
* @param name The user's name
|
||||
* @param age The user's age
|
||||
* @param username The username
|
||||
* @param email The email address
|
||||
* @param role The user role
|
||||
*/
|
||||
public User(String name, int age, String username, String email, UserRole role) {
|
||||
this(name, age, username, email);
|
||||
this.role = role;
|
||||
}
|
||||
|
||||
// Getters and Setters
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
if (StringUtils.isBlank(username)) {
|
||||
throw new IllegalArgumentException("Username cannot be null or empty");
|
||||
}
|
||||
if (username.length() < 3 || username.length() > 20) {
|
||||
throw new IllegalArgumentException("Username must be between 3 and 20 characters");
|
||||
}
|
||||
if (!username.matches("^[a-zA-Z0-9_]+$")) {
|
||||
throw new IllegalArgumentException("Username can only contain letters, numbers, and underscores");
|
||||
}
|
||||
this.username = username.trim();
|
||||
}
|
||||
|
||||
public String getPasswordHash() {
|
||||
return passwordHash;
|
||||
}
|
||||
|
||||
public void setPasswordHash(String passwordHash) {
|
||||
this.passwordHash = passwordHash;
|
||||
}
|
||||
|
||||
public UserRole getRole() {
|
||||
return role;
|
||||
}
|
||||
|
||||
public void setRole(UserRole role) {
|
||||
this.role = role != null ? role : UserRole.USER;
|
||||
}
|
||||
|
||||
public UserStatus getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(UserStatus status) {
|
||||
this.status = status != null ? status : UserStatus.ACTIVE;
|
||||
}
|
||||
|
||||
public LocalDateTime getLastLogin() {
|
||||
return lastLogin;
|
||||
}
|
||||
|
||||
public void setLastLogin(LocalDateTime lastLogin) {
|
||||
this.lastLogin = lastLogin;
|
||||
}
|
||||
|
||||
public int getLoginAttempts() {
|
||||
return loginAttempts;
|
||||
}
|
||||
|
||||
public void setLoginAttempts(int loginAttempts) {
|
||||
this.loginAttempts = Math.max(0, loginAttempts);
|
||||
}
|
||||
|
||||
public Set<String> getPermissions() {
|
||||
return new HashSet<>(permissions);
|
||||
}
|
||||
|
||||
public void setPermissions(Set<String> permissions) {
|
||||
this.permissions = permissions != null ? new HashSet<>(permissions) : new HashSet<>();
|
||||
}
|
||||
|
||||
// Authentication methods
|
||||
|
||||
/**
|
||||
* Sets the user's password using BCrypt hashing.
|
||||
*
|
||||
* @param password The plain text password
|
||||
* @throws IllegalArgumentException if password is invalid
|
||||
*/
|
||||
public void setPassword(String password) {
|
||||
if (StringUtils.isBlank(password)) {
|
||||
throw new IllegalArgumentException("Password cannot be null or empty");
|
||||
}
|
||||
if (password.length() < 8) {
|
||||
throw new IllegalArgumentException("Password must be at least 8 characters long");
|
||||
}
|
||||
|
||||
// Hash the password with BCrypt
|
||||
this.passwordHash = BCrypt.hashpw(password, BCrypt.gensalt());
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies a password against the stored hash.
|
||||
*
|
||||
* @param password The plain text password to verify
|
||||
* @return true if password matches
|
||||
*/
|
||||
public boolean verifyPassword(String password) {
|
||||
if (StringUtils.isBlank(password) || StringUtils.isBlank(passwordHash)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
return BCrypt.checkpw(password, passwordHash);
|
||||
} catch (IllegalArgumentException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Permission methods
|
||||
|
||||
/**
|
||||
* Adds a permission to the user.
|
||||
*
|
||||
* @param permission The permission to add
|
||||
*/
|
||||
public void addPermission(String permission) {
|
||||
if (StringUtils.isNotBlank(permission)) {
|
||||
permissions.add(permission.trim());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a permission from the user.
|
||||
*
|
||||
* @param permission The permission to remove
|
||||
*/
|
||||
public void removePermission(String permission) {
|
||||
permissions.remove(permission);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the user has a specific permission.
|
||||
*
|
||||
* @param permission The permission to check
|
||||
* @return true if user has the permission
|
||||
*/
|
||||
public boolean hasPermission(String permission) {
|
||||
return permissions.contains(permission);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all permissions.
|
||||
*/
|
||||
public void clearPermissions() {
|
||||
permissions.clear();
|
||||
}
|
||||
|
||||
// Status and role methods
|
||||
|
||||
/**
|
||||
* Checks if the user is an admin.
|
||||
*
|
||||
* @return true if user is admin
|
||||
*/
|
||||
public boolean isAdmin() {
|
||||
return role == UserRole.ADMIN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the user is active.
|
||||
*
|
||||
* @return true if user is active
|
||||
*/
|
||||
public boolean isActive() {
|
||||
return status == UserStatus.ACTIVE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the user is locked due to too many failed login attempts.
|
||||
*
|
||||
* @return true if user is locked
|
||||
*/
|
||||
public boolean isLocked() {
|
||||
return status == UserStatus.SUSPENDED || loginAttempts >= 5;
|
||||
}
|
||||
|
||||
// Login methods
|
||||
|
||||
/**
|
||||
* Records a successful login.
|
||||
*
|
||||
* @return true if login was successful
|
||||
*/
|
||||
public boolean login() {
|
||||
if (!isActive() || isLocked()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.lastLogin = LocalDateTime.now();
|
||||
this.loginAttempts = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Records a failed login attempt.
|
||||
*/
|
||||
public void failedLoginAttempt() {
|
||||
this.loginAttempts++;
|
||||
if (this.loginAttempts >= 5) {
|
||||
this.status = UserStatus.SUSPENDED;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets login attempts.
|
||||
*/
|
||||
public void resetLoginAttempts() {
|
||||
this.loginAttempts = 0;
|
||||
}
|
||||
|
||||
// Status change methods
|
||||
|
||||
/**
|
||||
* Activates the user account.
|
||||
*/
|
||||
public void activate() {
|
||||
this.status = UserStatus.ACTIVE;
|
||||
this.loginAttempts = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deactivates the user account.
|
||||
*/
|
||||
public void deactivate() {
|
||||
this.status = UserStatus.INACTIVE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Suspends the user account.
|
||||
*/
|
||||
public void suspend() {
|
||||
this.status = UserStatus.SUSPENDED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks the user as deleted.
|
||||
*/
|
||||
public void delete() {
|
||||
this.status = UserStatus.DELETED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (obj == null || getClass() != obj.getClass()) return false;
|
||||
if (!super.equals(obj)) return false;
|
||||
|
||||
User user = (User) obj;
|
||||
return loginAttempts == user.loginAttempts &&
|
||||
Objects.equals(username, user.username) &&
|
||||
Objects.equals(passwordHash, user.passwordHash) &&
|
||||
role == user.role &&
|
||||
status == user.status &&
|
||||
Objects.equals(lastLogin, user.lastLogin) &&
|
||||
Objects.equals(permissions, user.permissions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(super.hashCode(), username, passwordHash, role, status,
|
||||
lastLogin, loginAttempts, permissions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("User{username='%s', name='%s', role=%s, status=%s, lastLogin=%s}",
|
||||
username, getName(), role, status, lastLogin);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
package com.example.usermanagement.models;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonValue;
|
||||
|
||||
/**
|
||||
* Enumeration for user roles in the system.
|
||||
*/
|
||||
public enum UserRole {
|
||||
|
||||
/**
|
||||
* Administrator role with full system access.
|
||||
*/
|
||||
ADMIN("admin", "Administrator", "Full system access"),
|
||||
|
||||
/**
|
||||
* Regular user role with standard permissions.
|
||||
*/
|
||||
USER("user", "User", "Standard user permissions"),
|
||||
|
||||
/**
|
||||
* Guest role with limited permissions.
|
||||
*/
|
||||
GUEST("guest", "Guest", "Limited guest permissions");
|
||||
|
||||
private final String code;
|
||||
private final String displayName;
|
||||
private final String description;
|
||||
|
||||
/**
|
||||
* Constructor for UserRole enum.
|
||||
*
|
||||
* @param code The role code
|
||||
* @param displayName The display name
|
||||
* @param description The role description
|
||||
*/
|
||||
UserRole(String code, String displayName, String description) {
|
||||
this.code = code;
|
||||
this.displayName = displayName;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the role code.
|
||||
*
|
||||
* @return The role code
|
||||
*/
|
||||
@JsonValue
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the display name.
|
||||
*
|
||||
* @return The display name
|
||||
*/
|
||||
public String getDisplayName() {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the role description.
|
||||
*
|
||||
* @return The role description
|
||||
*/
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a UserRole by its code.
|
||||
*
|
||||
* @param code The role code to search for
|
||||
* @return The UserRole or null if not found
|
||||
*/
|
||||
public static UserRole fromCode(String code) {
|
||||
if (code == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (UserRole role : values()) {
|
||||
if (role.code.equalsIgnoreCase(code)) {
|
||||
return role;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this role has higher privilege than another role.
|
||||
*
|
||||
* @param other The other role to compare with
|
||||
* @return true if this role has higher privilege
|
||||
*/
|
||||
public boolean hasHigherPrivilegeThan(UserRole other) {
|
||||
return this.ordinal() < other.ordinal();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this role has lower privilege than another role.
|
||||
*
|
||||
* @param other The other role to compare with
|
||||
* @return true if this role has lower privilege
|
||||
*/
|
||||
public boolean hasLowerPrivilegeThan(UserRole other) {
|
||||
return this.ordinal() > other.ordinal();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this role can perform actions on another role.
|
||||
*
|
||||
* @param targetRole The target role
|
||||
* @return true if this role can act on the target role
|
||||
*/
|
||||
public boolean canActOn(UserRole targetRole) {
|
||||
// Admin can act on all roles
|
||||
if (this == ADMIN) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Users can only act on guests
|
||||
if (this == USER) {
|
||||
return targetRole == GUEST;
|
||||
}
|
||||
|
||||
// Guests cannot act on anyone
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return displayName;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
package com.example.usermanagement.models;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonValue;
|
||||
|
||||
/**
|
||||
* Enumeration for user status in the system.
|
||||
*/
|
||||
public enum UserStatus {
|
||||
|
||||
/**
|
||||
* Active status - user can login and use the system.
|
||||
*/
|
||||
ACTIVE("active", "Active", "User can login and use the system"),
|
||||
|
||||
/**
|
||||
* Inactive status - user account is temporarily disabled.
|
||||
*/
|
||||
INACTIVE("inactive", "Inactive", "User account is temporarily disabled"),
|
||||
|
||||
/**
|
||||
* Suspended status - user account is suspended due to violations.
|
||||
*/
|
||||
SUSPENDED("suspended", "Suspended", "User account is suspended due to violations"),
|
||||
|
||||
/**
|
||||
* Deleted status - user account is marked for deletion.
|
||||
*/
|
||||
DELETED("deleted", "Deleted", "User account is marked for deletion");
|
||||
|
||||
private final String code;
|
||||
private final String displayName;
|
||||
private final String description;
|
||||
|
||||
/**
|
||||
* Constructor for UserStatus enum.
|
||||
*
|
||||
* @param code The status code
|
||||
* @param displayName The display name
|
||||
* @param description The status description
|
||||
*/
|
||||
UserStatus(String code, String displayName, String description) {
|
||||
this.code = code;
|
||||
this.displayName = displayName;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the status code.
|
||||
*
|
||||
* @return The status code
|
||||
*/
|
||||
@JsonValue
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the display name.
|
||||
*
|
||||
* @return The display name
|
||||
*/
|
||||
public String getDisplayName() {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the status description.
|
||||
*
|
||||
* @return The status description
|
||||
*/
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a UserStatus by its code.
|
||||
*
|
||||
* @param code The status code to search for
|
||||
* @return The UserStatus or null if not found
|
||||
*/
|
||||
public static UserStatus fromCode(String code) {
|
||||
if (code == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (UserStatus status : values()) {
|
||||
if (status.code.equalsIgnoreCase(code)) {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this status allows user login.
|
||||
*
|
||||
* @return true if user can login with this status
|
||||
*/
|
||||
public boolean allowsLogin() {
|
||||
return this == ACTIVE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this status indicates the user is disabled.
|
||||
*
|
||||
* @return true if user is disabled
|
||||
*/
|
||||
public boolean isDisabled() {
|
||||
return this == INACTIVE || this == SUSPENDED || this == DELETED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this status indicates the user is deleted.
|
||||
*
|
||||
* @return true if user is deleted
|
||||
*/
|
||||
public boolean isDeleted() {
|
||||
return this == DELETED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this status can be changed to another status.
|
||||
*
|
||||
* @param targetStatus The target status
|
||||
* @return true if status change is allowed
|
||||
*/
|
||||
public boolean canChangeTo(UserStatus targetStatus) {
|
||||
// Cannot change from deleted status
|
||||
if (this == DELETED) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Cannot change to same status
|
||||
if (this == targetStatus) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// All other changes are allowed
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return displayName;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,488 @@
|
||||
package com.example.usermanagement.services;
|
||||
|
||||
import com.example.usermanagement.models.User;
|
||||
import com.example.usermanagement.models.UserRole;
|
||||
import com.example.usermanagement.models.UserStatus;
|
||||
import com.example.usermanagement.utils.UserNotFoundException;
|
||||
import com.example.usermanagement.utils.DuplicateUserException;
|
||||
import com.example.usermanagement.utils.ValidationUtils;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
import org.apache.commons.csv.CSVFormat;
|
||||
import org.apache.commons.csv.CSVPrinter;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Service class for managing users in the system.
|
||||
* Provides CRUD operations, search functionality, and data persistence.
|
||||
*/
|
||||
public class UserManager {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(UserManager.class);
|
||||
|
||||
private final Map<String, User> users;
|
||||
private final ObjectMapper objectMapper;
|
||||
private final String storagePath;
|
||||
|
||||
/**
|
||||
* Constructor with default storage path.
|
||||
*/
|
||||
public UserManager() {
|
||||
this(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor with custom storage path.
|
||||
*
|
||||
* @param storagePath The file path for user data storage
|
||||
*/
|
||||
public UserManager(String storagePath) {
|
||||
this.users = new ConcurrentHashMap<>();
|
||||
this.objectMapper = new ObjectMapper();
|
||||
this.objectMapper.registerModule(new JavaTimeModule());
|
||||
this.storagePath = storagePath;
|
||||
|
||||
if (StringUtils.isNotBlank(storagePath)) {
|
||||
loadUsersFromFile();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new user in the system.
|
||||
*
|
||||
* @param name The user's name
|
||||
* @param age The user's age
|
||||
* @param username The username
|
||||
* @param email The email address (optional)
|
||||
* @param role The user role
|
||||
* @return The created user
|
||||
* @throws DuplicateUserException if username already exists
|
||||
* @throws IllegalArgumentException if validation fails
|
||||
*/
|
||||
public User createUser(String name, int age, String username, String email, UserRole role) {
|
||||
logger.debug("Creating user with username: {}", username);
|
||||
|
||||
if (users.containsKey(username)) {
|
||||
throw new DuplicateUserException("User with username '" + username + "' already exists");
|
||||
}
|
||||
|
||||
// Validate inputs
|
||||
ValidationUtils.validateUsername(username);
|
||||
if (StringUtils.isNotBlank(email)) {
|
||||
ValidationUtils.validateEmail(email);
|
||||
}
|
||||
|
||||
User user = new User(name, age, username, email, role);
|
||||
users.put(username, user);
|
||||
|
||||
saveUsersToFile();
|
||||
logger.info("User created successfully: {}", username);
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new user with default role.
|
||||
*
|
||||
* @param name The user's name
|
||||
* @param age The user's age
|
||||
* @param username The username
|
||||
* @param email The email address (optional)
|
||||
* @return The created user
|
||||
*/
|
||||
public User createUser(String name, int age, String username, String email) {
|
||||
return createUser(name, age, username, email, UserRole.USER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new user with minimal information.
|
||||
*
|
||||
* @param name The user's name
|
||||
* @param age The user's age
|
||||
* @param username The username
|
||||
* @return The created user
|
||||
*/
|
||||
public User createUser(String name, int age, String username) {
|
||||
return createUser(name, age, username, null, UserRole.USER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a user by username.
|
||||
*
|
||||
* @param username The username
|
||||
* @return The user
|
||||
* @throws UserNotFoundException if user is not found
|
||||
*/
|
||||
public User getUser(String username) {
|
||||
User user = users.get(username);
|
||||
if (user == null) {
|
||||
throw new UserNotFoundException("User with username '" + username + "' not found");
|
||||
}
|
||||
return user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a user by email address.
|
||||
*
|
||||
* @param email The email address
|
||||
* @return The user or null if not found
|
||||
*/
|
||||
public User getUserByEmail(String email) {
|
||||
return users.values().stream()
|
||||
.filter(user -> Objects.equals(user.getEmail(), email))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates user information.
|
||||
*
|
||||
* @param username The username
|
||||
* @param updates A map of field updates
|
||||
* @return The updated user
|
||||
* @throws UserNotFoundException if user is not found
|
||||
*/
|
||||
public User updateUser(String username, Map<String, Object> updates) {
|
||||
User user = getUser(username);
|
||||
|
||||
updates.forEach((field, value) -> {
|
||||
switch (field.toLowerCase()) {
|
||||
case "name":
|
||||
user.setName((String) value);
|
||||
break;
|
||||
case "age":
|
||||
user.setAge((Integer) value);
|
||||
break;
|
||||
case "email":
|
||||
user.setEmail((String) value);
|
||||
break;
|
||||
case "role":
|
||||
if (value instanceof UserRole) {
|
||||
user.setRole((UserRole) value);
|
||||
} else if (value instanceof String) {
|
||||
user.setRole(UserRole.fromCode((String) value));
|
||||
}
|
||||
break;
|
||||
case "status":
|
||||
if (value instanceof UserStatus) {
|
||||
user.setStatus((UserStatus) value);
|
||||
} else if (value instanceof String) {
|
||||
user.setStatus(UserStatus.fromCode((String) value));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
logger.warn("Unknown field for update: {}", field);
|
||||
}
|
||||
});
|
||||
|
||||
saveUsersToFile();
|
||||
logger.info("User updated successfully: {}", username);
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a user (soft delete).
|
||||
*
|
||||
* @param username The username
|
||||
* @return true if user was deleted
|
||||
* @throws UserNotFoundException if user is not found
|
||||
*/
|
||||
public boolean deleteUser(String username) {
|
||||
User user = getUser(username);
|
||||
user.delete();
|
||||
|
||||
saveUsersToFile();
|
||||
logger.info("User deleted successfully: {}", username);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a user completely from the system.
|
||||
*
|
||||
* @param username The username
|
||||
* @return true if user was removed
|
||||
* @throws UserNotFoundException if user is not found
|
||||
*/
|
||||
public boolean removeUser(String username) {
|
||||
if (!users.containsKey(username)) {
|
||||
throw new UserNotFoundException("User with username '" + username + "' not found");
|
||||
}
|
||||
|
||||
users.remove(username);
|
||||
saveUsersToFile();
|
||||
logger.info("User removed completely: {}", username);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all users in the system.
|
||||
*
|
||||
* @return A list of all users
|
||||
*/
|
||||
public List<User> getAllUsers() {
|
||||
return new ArrayList<>(users.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all active users.
|
||||
*
|
||||
* @return A list of active users
|
||||
*/
|
||||
public List<User> getActiveUsers() {
|
||||
return users.values().stream()
|
||||
.filter(User::isActive)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets users by role.
|
||||
*
|
||||
* @param role The user role
|
||||
* @return A list of users with the specified role
|
||||
*/
|
||||
public List<User> getUsersByRole(UserRole role) {
|
||||
return users.values().stream()
|
||||
.filter(user -> user.getRole() == role)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters users using a custom predicate.
|
||||
*
|
||||
* @param predicate The filter predicate
|
||||
* @return A list of filtered users
|
||||
*/
|
||||
public List<User> filterUsers(Predicate<User> predicate) {
|
||||
return users.values().stream()
|
||||
.filter(predicate)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches users by name or username.
|
||||
*
|
||||
* @param query The search query
|
||||
* @return A list of matching users
|
||||
*/
|
||||
public List<User> searchUsers(String query) {
|
||||
if (StringUtils.isBlank(query)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
String lowercaseQuery = query.toLowerCase();
|
||||
return users.values().stream()
|
||||
.filter(user ->
|
||||
user.getName().toLowerCase().contains(lowercaseQuery) ||
|
||||
user.getUsername().toLowerCase().contains(lowercaseQuery) ||
|
||||
(user.getEmail() != null && user.getEmail().toLowerCase().contains(lowercaseQuery)))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets users older than specified age.
|
||||
*
|
||||
* @param age The age threshold
|
||||
* @return A list of users older than the specified age
|
||||
*/
|
||||
public List<User> getUsersOlderThan(int age) {
|
||||
return filterUsers(user -> user.getAge() > age);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets users with email addresses.
|
||||
*
|
||||
* @return A list of users with email addresses
|
||||
*/
|
||||
public List<User> getUsersWithEmail() {
|
||||
return filterUsers(User::hasEmail);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets users with specific permission.
|
||||
*
|
||||
* @param permission The permission to check
|
||||
* @return A list of users with the specified permission
|
||||
*/
|
||||
public List<User> getUsersWithPermission(String permission) {
|
||||
return filterUsers(user -> user.hasPermission(permission));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the total number of users.
|
||||
*
|
||||
* @return The user count
|
||||
*/
|
||||
public int getUserCount() {
|
||||
return users.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets user statistics.
|
||||
*
|
||||
* @return A map of user statistics
|
||||
*/
|
||||
public Map<String, Integer> getUserStats() {
|
||||
Map<String, Integer> stats = new HashMap<>();
|
||||
|
||||
stats.put("total", users.size());
|
||||
stats.put("active", getActiveUsers().size());
|
||||
stats.put("admin", getUsersByRole(UserRole.ADMIN).size());
|
||||
stats.put("user", getUsersByRole(UserRole.USER).size());
|
||||
stats.put("guest", getUsersByRole(UserRole.GUEST).size());
|
||||
stats.put("with_email", getUsersWithEmail().size());
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Exports users to specified format.
|
||||
*
|
||||
* @param format The export format ("json" or "csv")
|
||||
* @return The exported data as string
|
||||
* @throws IllegalArgumentException if format is unsupported
|
||||
*/
|
||||
public String exportUsers(String format) {
|
||||
switch (format.toLowerCase()) {
|
||||
case "json":
|
||||
return exportToJson();
|
||||
case "csv":
|
||||
return exportToCsv();
|
||||
default:
|
||||
throw new IllegalArgumentException("Unsupported export format: " + format);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Exports users to JSON format.
|
||||
*
|
||||
* @return JSON string representation of users
|
||||
*/
|
||||
private String exportToJson() {
|
||||
try {
|
||||
return objectMapper.writerWithDefaultPrettyPrinter()
|
||||
.writeValueAsString(users.values());
|
||||
} catch (JsonProcessingException e) {
|
||||
logger.error("Error exporting users to JSON", e);
|
||||
return "[]";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Exports users to CSV format.
|
||||
*
|
||||
* @return CSV string representation of users
|
||||
*/
|
||||
private String exportToCsv() {
|
||||
try (StringWriter writer = new StringWriter();
|
||||
CSVPrinter printer = new CSVPrinter(writer, CSVFormat.DEFAULT.withHeader(
|
||||
"Username", "Name", "Age", "Email", "Role", "Status", "Last Login"))) {
|
||||
|
||||
for (User user : users.values()) {
|
||||
printer.printRecord(
|
||||
user.getUsername(),
|
||||
user.getName(),
|
||||
user.getAge(),
|
||||
user.getEmail(),
|
||||
user.getRole().getCode(),
|
||||
user.getStatus().getCode(),
|
||||
user.getLastLogin()
|
||||
);
|
||||
}
|
||||
|
||||
return writer.toString();
|
||||
} catch (IOException e) {
|
||||
logger.error("Error exporting users to CSV", e);
|
||||
return "Username,Name,Age,Email,Role,Status,Last Login\n";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a username exists in the system.
|
||||
*
|
||||
* @param username The username to check
|
||||
* @return true if username exists
|
||||
*/
|
||||
public boolean userExists(String username) {
|
||||
return users.containsKey(username);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all users from the system.
|
||||
*/
|
||||
public void clearAllUsers() {
|
||||
users.clear();
|
||||
saveUsersToFile();
|
||||
logger.info("All users cleared from system");
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads users from file storage.
|
||||
*/
|
||||
private void loadUsersFromFile() {
|
||||
if (StringUtils.isBlank(storagePath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
Path path = Paths.get(storagePath);
|
||||
if (!Files.exists(path)) {
|
||||
logger.debug("User storage file does not exist: {}", storagePath);
|
||||
return;
|
||||
}
|
||||
|
||||
String content = Files.readString(path);
|
||||
List<User> userList = Arrays.asList(objectMapper.readValue(content, User[].class));
|
||||
|
||||
users.clear();
|
||||
for (User user : userList) {
|
||||
users.put(user.getUsername(), user);
|
||||
}
|
||||
|
||||
logger.info("Loaded {} users from file: {}", users.size(), storagePath);
|
||||
} catch (IOException e) {
|
||||
logger.error("Error loading users from file: {}", storagePath, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves users to file storage.
|
||||
*/
|
||||
private void saveUsersToFile() {
|
||||
if (StringUtils.isBlank(storagePath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
Path path = Paths.get(storagePath);
|
||||
Files.createDirectories(path.getParent());
|
||||
|
||||
String content = objectMapper.writerWithDefaultPrettyPrinter()
|
||||
.writeValueAsString(users.values());
|
||||
Files.writeString(path, content);
|
||||
|
||||
logger.debug("Saved {} users to file: {}", users.size(), storagePath);
|
||||
} catch (IOException e) {
|
||||
logger.error("Error saving users to file: {}", storagePath, e);
|
||||
}
|
||||
}
|
||||
|
||||
// CI marker method to verify auto-reindex on change
|
||||
public String ciAddedSymbolMarker() {
|
||||
return "ci_symbol_java";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.example.usermanagement.utils;
|
||||
|
||||
/**
|
||||
* Exception thrown when attempting to create a user that already exists.
|
||||
*/
|
||||
public class DuplicateUserException extends RuntimeException {
|
||||
|
||||
/**
|
||||
* Constructs a new DuplicateUserException with the specified detail message.
|
||||
*
|
||||
* @param message the detail message
|
||||
*/
|
||||
public DuplicateUserException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new DuplicateUserException with the specified detail message and cause.
|
||||
*
|
||||
* @param message the detail message
|
||||
* @param cause the cause
|
||||
*/
|
||||
public DuplicateUserException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.example.usermanagement.utils;
|
||||
|
||||
/**
|
||||
* Exception thrown when a user is not found in the system.
|
||||
*/
|
||||
public class UserNotFoundException extends RuntimeException {
|
||||
|
||||
/**
|
||||
* Constructs a new UserNotFoundException with the specified detail message.
|
||||
*
|
||||
* @param message the detail message
|
||||
*/
|
||||
public UserNotFoundException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new UserNotFoundException with the specified detail message and cause.
|
||||
*
|
||||
* @param message the detail message
|
||||
* @param cause the cause
|
||||
*/
|
||||
public UserNotFoundException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package com.example.usermanagement.utils;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
/**
|
||||
* Utility class for validation operations.
|
||||
*/
|
||||
public final class ValidationUtils {
|
||||
|
||||
private static final String EMAIL_PATTERN = "^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$";
|
||||
private static final String USERNAME_PATTERN = "^[a-zA-Z0-9_]+$";
|
||||
|
||||
/**
|
||||
* Private constructor to prevent instantiation.
|
||||
*/
|
||||
private ValidationUtils() {
|
||||
throw new UnsupportedOperationException("Utility class cannot be instantiated");
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates email format.
|
||||
*
|
||||
* @param email The email to validate
|
||||
* @throws IllegalArgumentException if email is invalid
|
||||
*/
|
||||
public static void validateEmail(String email) {
|
||||
if (StringUtils.isBlank(email)) {
|
||||
throw new IllegalArgumentException("Email cannot be null or empty");
|
||||
}
|
||||
|
||||
if (!email.matches(EMAIL_PATTERN)) {
|
||||
throw new IllegalArgumentException("Invalid email format");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates username format.
|
||||
*
|
||||
* @param username The username to validate
|
||||
* @throws IllegalArgumentException if username is invalid
|
||||
*/
|
||||
public static void validateUsername(String username) {
|
||||
if (StringUtils.isBlank(username)) {
|
||||
throw new IllegalArgumentException("Username cannot be null or empty");
|
||||
}
|
||||
|
||||
if (username.length() < 3 || username.length() > 20) {
|
||||
throw new IllegalArgumentException("Username must be between 3 and 20 characters");
|
||||
}
|
||||
|
||||
if (!username.matches(USERNAME_PATTERN)) {
|
||||
throw new IllegalArgumentException("Username can only contain letters, numbers, and underscores");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if email format is valid.
|
||||
*
|
||||
* @param email The email to check
|
||||
* @return true if email is valid
|
||||
*/
|
||||
public static boolean isValidEmail(String email) {
|
||||
return StringUtils.isNotBlank(email) && email.matches(EMAIL_PATTERN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if username format is valid.
|
||||
*
|
||||
* @param username The username to check
|
||||
* @return true if username is valid
|
||||
*/
|
||||
public static boolean isValidUsername(String username) {
|
||||
return StringUtils.isNotBlank(username) &&
|
||||
username.length() >= 3 &&
|
||||
username.length() <= 20 &&
|
||||
username.matches(USERNAME_PATTERN);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user