Skip to content

Commit

Permalink
Merge pull request #181 from georchestra/javadocs
Browse files Browse the repository at this point in the history
docs: improve Javadocs across multiple classes
  • Loading branch information
groldan authored Feb 13, 2025
2 parents 8c07159 + 052a1da commit 2f86428
Show file tree
Hide file tree
Showing 99 changed files with 5,248 additions and 1,215 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,18 +29,64 @@
import lombok.NonNull;
import lombok.RequiredArgsConstructor;

/**
* Abstract implementation of {@link AccountManager} providing common account
* management logic.
* <p>
* This class ensures thread-safe user retrieval and creation by using a
* {@link ReadWriteLock}. Implementations must define specific storage
* operations for finding and creating users.
* </p>
*
* <p>
* When a new user is created, an {@link AccountCreated} event is published
* using the {@link ApplicationEventPublisher} to notify the system of the new
* account.
* </p>
*
* @see AccountManager
* @see org.georchestra.gateway.security.exceptions.DuplicatedEmailFoundException
* @see org.georchestra.security.model.GeorchestraUser
*/
@RequiredArgsConstructor
public abstract class AbstractAccountsManager implements AccountManager {

private final @NonNull ApplicationEventPublisher eventPublisher;

protected final ReadWriteLock lock = new ReentrantReadWriteLock();

/**
* Retrieves an existing stored user corresponding to {@code mappedUser} or
* creates a new one if not found.
* <p>
* This method ensures thread safety by acquiring a read lock when searching for
* the user and a write lock when creating a new user.
* </p>
* <p>
* If a new user is created, an {@link AccountCreated} event is published.
* </p>
*
* @param mappedUser the user to find or create
* @return the existing or newly created {@link GeorchestraUser}
* @throws DuplicatedEmailFoundException if a user with the same email already
* exists
*/
@Override
public GeorchestraUser getOrCreate(@NonNull GeorchestraUser mappedUser) throws DuplicatedEmailFoundException {
return find(mappedUser).orElseGet(() -> createIfMissing(mappedUser));
}

/**
* Retrieves the stored user corresponding to {@code mappedUser}, if it exists.
* <p>
* This method is thread-safe and acquires a read lock to ensure consistent
* reads.
* </p>
*
* @param mappedUser the user to search for
* @return an {@link Optional} containing the found user, or an empty
* {@link Optional} if not found
*/
public Optional<GeorchestraUser> find(GeorchestraUser mappedUser) {
lock.readLock().lock();
try {
Expand All @@ -50,34 +96,86 @@ public Optional<GeorchestraUser> find(GeorchestraUser mappedUser) {
}
}

/**
* Internal method to search for a user based on OAuth2 credentials or username.
* <p>
* This method is called within {@link #find(GeorchestraUser)} and does not
* apply any locking.
* </p>
*
* @param mappedUser the user to search for
* @return an {@link Optional} containing the found user, or an empty
* {@link Optional} if not found
*/
protected Optional<GeorchestraUser> findInternal(GeorchestraUser mappedUser) {
if ((null != mappedUser.getOAuth2Provider()) && (null != mappedUser.getOAuth2Uid())) {
if (mappedUser.getOAuth2Provider() != null && mappedUser.getOAuth2Uid() != null) {
return findByOAuth2Uid(mappedUser.getOAuth2Provider(), mappedUser.getOAuth2Uid());
}
return findByUsername(mappedUser.getUsername());
}

/**
* Creates a user if it does not already exist in the repository.
* <p>
* This method acquires a write lock to ensure only one thread creates a user at
* a time. If a user is created, an {@link AccountCreated} event is published.
* </p>
*
* @param mapped the user to create if missing
* @return the existing or newly created {@link GeorchestraUser}
* @throws DuplicatedEmailFoundException if a user with the same email already
* exists
*/
protected GeorchestraUser createIfMissing(GeorchestraUser mapped) throws DuplicatedEmailFoundException {
lock.writeLock().lock();
try {
GeorchestraUser existing = findInternal(mapped).orElse(null);
if (null == existing) {
if (existing == null) {
createInternal(mapped);
existing = findInternal(mapped).orElseThrow(() -> new IllegalStateException(
"User " + mapped.getUsername() + " not found right after creation"));
"User " + mapped.getUsername() + " not found immediately after creation"));
eventPublisher.publishEvent(new AccountCreated(existing));
}
return existing;

} finally {
lock.writeLock().unlock();
}
}

/**
* Finds a user by their OAuth2 provider and unique identifier.
* <p>
* Implementations must provide a concrete method for retrieving users from
* storage.
* </p>
*
* @param oauth2Provider the OAuth2 provider (e.g., Google, GitHub)
* @param oauth2Uid the unique identifier assigned by the OAuth2 provider
* @return an {@link Optional} containing the found user, or an empty
* {@link Optional} if not found
*/
protected abstract Optional<GeorchestraUser> findByOAuth2Uid(String oauth2Provider, String oauth2Uid);

/**
* Finds a user by their username.
* <p>
* Implementations must provide a concrete method for retrieving users from
* storage.
* </p>
*
* @param username the username to search for
* @return an {@link Optional} containing the found user, or an empty
* {@link Optional} if not found
*/
protected abstract Optional<GeorchestraUser> findByUsername(String username);

/**
* Creates a new user in the repository.
* <p>
* Implementations must define how users are persisted in the storage system.
* </p>
*
* @param mapped the user to create
*/
protected abstract void createInternal(GeorchestraUser mapped);

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,23 @@
import lombok.Value;

/**
* Application event published when a new account has been created
* Event published when a new {@link GeorchestraUser} account is created.
* <p>
* This event is triggered whenever a new user is successfully registered in the
* system. It can be used to listen for account creation and trigger additional
* actions such as logging, notifications, or audits.
* </p>
*
* <p>
* This class is immutable and thread-safe, though the attached
* {@link GeorchestraUser} is mutable, so make sure not to modify it.
* </p>
*
* @see GeorchestraUser
*/
@Value
public class AccountCreated {

/** The newly created user account. */
private @NonNull GeorchestraUser user;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,35 +27,48 @@
import org.springframework.security.core.Authentication;

/**
* Manages the retrieval and creation of stored {@link GeorchestraUser
* Georchestra users}.
* <p>
* This interface provides methods to look up an existing user or create a new
* one if it does not exist. Implementations of this interface should ensure
* that user accounts are correctly managed within the system and that necessary
* events are published when a new user is created.
* </p>
*
* @see CreateAccountUserCustomizer
* @see ResolveGeorchestraUserGlobalFilter
*/
public interface AccountManager {

/**
* Finds the stored user that belongs to the {@code mappedUser} if it exists
* Retrieves the stored user corresponding to the given {@code mappedUser}, if
* it exists.
*
* @param mappedUser the user {@link ResolveGeorchestraUserGlobalFilter}
* resolved by calling
* @param mappedUser the user resolved by
* {@link ResolveGeorchestraUserGlobalFilter}, obtained
* through a call to
* {@link GeorchestraUserMapper#resolve(Authentication)}
* @return the stored version of the user if it exists, otherwise an empty
* Optional
* @return an {@link Optional} containing the stored version of the user if
* found; otherwise, an empty {@link Optional}
*/
Optional<GeorchestraUser> find(GeorchestraUser mappedUser);

/**
* Finds the stored user that belongs to the {@code mappedUser} or creates it if
* it doesn't exist in the users repository.
* Retrieves the stored user corresponding to the given {@code mappedUser}, or
* creates a new user if one does not already exist in the repository.
* <p>
* When a user is created, an {@link AccountCreated} event must be published to
* the {@link ApplicationEventPublisher}.
*
* @param mappedUser the user {@link ResolveGeorchestraUserGlobalFilter}
* resolved by calling
* If a new user is created, an {@link AccountCreated} event must be published
* via the {@link ApplicationEventPublisher} to notify the system of the new
* account.
* </p>
*
* @param mappedUser the user resolved by
* {@link ResolveGeorchestraUserGlobalFilter}, obtained
* through a call to
* {@link GeorchestraUserMapper#resolve(Authentication)}
* @return the stored version of the user, whether it existed or was created as
* the result of calling this method.
* @return the stored version of the user, either previously existing or newly
* created
*/
GeorchestraUser getOrCreate(GeorchestraUser mappedUser);

}
}
Loading

0 comments on commit 2f86428

Please sign in to comment.