Skip to content

Conversation

@thomaslow
Copy link
Collaborator

@thomaslow thomaslow commented Nov 25, 2025

This PR refactors all forms on the "users" page from @SessionScoped to @ViewScoped. In addition, a few minor cleanup improvements are made.

Related to: #6456

Updated forms:

  • UserForm
  • RoleForm
  • AuthorityForm
  • ClientForm
  • LdapGroupForm
  • LdapServerForm

Changes:

  • @SessionScoped to @ViewScoped
  • Splitting form classes into list view and edit view
  • Splitting user edit form into multiple classes, one for each tab view
  • Column selection dialog only shows columns of the currently active tab
  • Various view parameters are encoded as URL query parameters and updated via Javascript
    • tabIndex - the currently active tab
    • sortField - the field that is used for sorting
    • sortOrder - the order of sorting
    • firstRow - the first data row shown in the data table (corresponds to a page)
    • filter - the active list filter
    • referrerListOptions - the list options sortField, sortOrder, firstRow and filter encoded in a single parameter such they can be restored when navigating back to the list after editing a single entity
  • Various cleanups and refactoring, e.g.
    • moving database retrieval calls from getter to bean init methods
    • removing unused methods
    • adding Javadoc
  • Fixes Newly saved filters are sometimes missing in the list of saved filters #6826

Due to the replacement of UserForm.java, RoleForm.java, ..., merging with the following PR needs to be done more carefully such that any improvements are not lost:

@thomaslow thomaslow force-pushed the refactor-bean-scopes-user-form branch from 1e46493 to f12ff8e Compare December 9, 2025 16:05
@thomaslow thomaslow force-pushed the refactor-bean-scopes-user-form branch from 1f63b41 to a5000d2 Compare December 16, 2025 11:56
…n be restored after navigating back from an edit view.
@thomaslow
Copy link
Collaborator Author

thomaslow commented Dec 17, 2025

@solth After refactoring the user list view from @SessionScoped to @ViewScoped I noticed that various state information is missing because of that, in particular:

  • the current active tab
  • the current page in the list
  • sorting criteria for a list
  • any search filter

I moved these state information into the URL as query parameters, which seems to work fine. I even keep these state information available by forwarding them to the respective edit views and reloading them after navigating back to the source list view (via another query parameter referrerListOptions similar to keepPagination).

In general, I think this approach works fine and now would allow to, e.g., create bookmarks with existing sorting and filter criteria.

However, I see a problem for the task and process list, and their respective filter options. When navigating to the task or process list, the current page and sorting options are reset to their defaults, but the filter option is loaded from the session scope. You can navigate anywhere in Kitodo.Production and still have the same filter option active when returning to the task or process list.

I am not entirely sure, but I think this is most certainly desired behavior.

Anyway, this long-term information about the current active filter in the task and process list cannot be modeled as query parameters (alone). We could try cookies? What do you think?

@thomaslow thomaslow marked this pull request as ready for review December 17, 2025 17:17
@BartChris
Copy link
Collaborator

Anyway, this long-term information about the current active filter in the task and process list cannot be modeled as query parameters (alone). We could try cookies? What do you think?

Would it be an option to store only the filter-related information in a small, dedicated @SessionScoped bean and inject it into the @ViewScoped process and task list views?

@thomaslow
Copy link
Collaborator Author

thomaslow commented Dec 18, 2025

Would it be an option to store only the filter-related information in a small, dedicated @SessionScoped bean and inject it into the @ViewScoped process and task list views?

Yes, of course this would be possible. Other options would be, storing it:

  • in the database in a field lastProcessListFilter and lastTaskListFilter
  • in a @SessionScoped bean
  • in a Cookie with some lifetime (session, one day, one week, forever), or the browser's LocalStorage (without a lifetime)

In any case I would suggest storing all relevant list view options (including the filter) in URL query parameters as well (overwriting any long-term preferences), such that you can easily save various filters via browser bookmarks.

@solth solth mentioned this pull request Jan 5, 2026
Copy link
Member

@solth solth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@thomaslow thanks a lot for this pull request. The implementation looks very solid and it's great that we will finally have properly scoped beans in Kitodo! I encountered just a few minor issues (see below). And please do not forget to rebase against/merge with the main branch (if you want to wait for #6803 to be merged that's fine by me as well)


private static final Logger logger = LogManager.getLogger(ClientListView.class);

public static final String VIEW_PATH = MessageFormat.format(REDIRECT_PATH, "users") + "&tabIndex=2";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder whether having static tab index values is a good idea here. In case the user does not have the permission to see the user list, for example, the index for all following tabs should be decreased accordingly, shouldn't it (same applies to the other views as well, of course)?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. I was thinking about changing the view parameter to something like tab=users, tab=roles (because it "looks" better), but noticed that the view parameter tabIndex was used in other parts of Kitodo.Production, so I simply reused that instead. I didn't think about users with restricted permissions that will have a different tab order. I'll change it to tab=<view_name>.


public static final String VIEW_PATH = MessageFormat.format(REDIRECT_PATH, "users") + "&tabIndex=5";

private static final Logger logger = LogManager.getLogger(LdapServerEditView.class);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
private static final Logger logger = LogManager.getLogger(LdapServerEditView.class);
private static final Logger logger = LogManager.getLogger(LdapServerListView.class);

* @param role the role to be removed
* @return true if role was removed
*/
public static boolean deleteRole(Role role) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we can move this method to the RoleService, as it contains quite a bit of business logic and isn't directly called from the template? If it stays in this class, it could probably be made private, since it is only called from within this class itself, isn't it?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, you are right. The view-independent part should be moved to the RoleService. I'll update that.

* @return true if user clients data was saved
*/
public boolean save() {
return true;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not quite sure why we need this method. It only ever returns true. Have you added this because some tabs have actual conditions that are checked before the user can be saved?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I was trying to establish a method "pattern" of common save and load methods for different edit view tabs. Maybe I should refactor this into a base class BaseEditTabView.

return Collections.emptySet();
}
KitodoPassword validPassword = new KitodoPassword(passwordToEncrypt);
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My IDE tells me that ValidatorFactor is AutoClosable and should be used with try-with-resources. Would something like

        try (ValidatorFactory factory = Validation.buildDefaultValidatorFactory()) {
            Validator validator = factory.getValidator();
            return validator.validate(validPassword);
        }

work?

Comment on lines +267 to +275
* Check whether the old password matches the password stored in the database.
*
* @return true if the old password does not match the password stored in the database
*/
private boolean isOldPasswordInvalid() {
if (!ServiceManager.getSecurityAccessService().hasAuthorityToEditUser()) {
return !Objects.equals(this.oldPassword, passwordEncoder.decrypt(this.userObject.getPassword()));
}
return false;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Despite being just a few lines, I find this whole method confusing because of the multiple negations (e.g. "invalid" instead of "valid", if(!ServicManager...), return !Objects.equals etc.). I know you just copied this method from the UserForm and didn't author it, but perhaps now would be a good opportunity to simplify the code?

/**
* Update the current browser URL to include the selected query parameter and value.
*
* @param str key the query parmaeter name
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* @param str key the query parmaeter name
* @param str key the query parameter name

id="yesButton"
value="#{msgs.yes}"
action="#{UserForm.resetTasksAndDeleteUser}"
action="#{UserListView.resetTasksAndDeleteUser(confirmResetTasksDialogUser)}"
Copy link
Member

@solth solth Jan 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
action="#{UserListView.resetTasksAndDeleteUser(confirmResetTasksDialogUser)}"
action="#{UserListView.resetTasksAndDeleteUser(UserListView.confirmResetTasksDialogUser)}"

I think this needs to reference the bean that contains user object, doesn't it? Without it I get a NullPointerException when trying to delete a user with assigned tasks:
Bildschirmfoto 2026-01-09 um 14 39 41

@thomaslow
Copy link
Collaborator Author

@solth Thank you for your feedback. I'll update this branch next week.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Newly saved filters are sometimes missing in the list of saved filters

3 participants