Refactor code structure for improved readability and maintainability

This commit is contained in:
catlog22
2025-12-12 11:19:58 +08:00
parent 77de8d857b
commit b74a90b416
169 changed files with 29206 additions and 369 deletions

View File

@@ -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.

View File

@@ -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>

View File

@@ -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());
}
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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";
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}