diff --git a/ccm-core/src/main/java/org/libreccm/admin/ui/usersgroupsroles/GroupDetails.java b/ccm-core/src/main/java/org/libreccm/admin/ui/usersgroupsroles/GroupDetails.java
new file mode 100644
index 000000000..ba342ce02
--- /dev/null
+++ b/ccm-core/src/main/java/org/libreccm/admin/ui/usersgroupsroles/GroupDetails.java
@@ -0,0 +1,302 @@
+/*
+ * Copyright (C) 2017 LibreCCM Foundation.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301 USA
+ */
+package org.libreccm.admin.ui.usersgroupsroles;
+
+import com.arsdigita.ui.admin.AdminUiConstants;
+
+import com.vaadin.data.HasValue;
+import com.vaadin.icons.VaadinIcons;
+import com.vaadin.server.Page;
+import com.vaadin.server.UserError;
+import com.vaadin.ui.Button;
+import com.vaadin.ui.FormLayout;
+import com.vaadin.ui.Grid;
+import com.vaadin.ui.HorizontalLayout;
+import com.vaadin.ui.Label;
+import com.vaadin.ui.Notification;
+import com.vaadin.ui.Panel;
+import com.vaadin.ui.TabSheet;
+import com.vaadin.ui.TextField;
+import com.vaadin.ui.UI;
+import com.vaadin.ui.VerticalLayout;
+import com.vaadin.ui.Window;
+import com.vaadin.ui.components.grid.HeaderCell;
+import com.vaadin.ui.components.grid.HeaderRow;
+import com.vaadin.ui.renderers.ButtonRenderer;
+import com.vaadin.ui.themes.ValoTheme;
+import org.libreccm.admin.ui.ConfirmDiscardDialog;
+import org.libreccm.cdi.utils.CdiUtil;
+import org.libreccm.security.Group;
+import org.libreccm.security.GroupManager;
+import org.libreccm.security.GroupRepository;
+import org.libreccm.security.User;
+import org.libreccm.security.UserRepository;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.ResourceBundle;
+import java.util.stream.Collectors;
+
+/**
+ *
+ * @author Jens Pelzetter
+ */
+public class GroupDetails extends Window {
+
+ private static final long serialVersionUID = 4252189590984878037L;
+
+ private static final String COL_USER_NAME = "username";
+ private static final String COL_GIVEN_NAME = "given_name";
+ private static final String COL_FAMILY_NAME = "family_name";
+ private static final String COL_EMAIL = "email";
+ private static final String COL_REMOVE = "remove";
+
+ private final UsersGroupsRoles usersGroupsRoles;
+ private final Group group;
+ private final GroupRepository groupRepo;
+ private final GroupManager groupManager;
+
+ private boolean dataHasChanged = false;
+
+ private Label groupNameDisplay;
+ private TextField groupNameField;
+ private Button editButton;
+ private HorizontalLayout saveCancelButtons;
+
+ public GroupDetails(final Group group,
+ final UsersGroupsRoles usersGroupsRoles,
+ final GroupRepository groupRepo,
+ final GroupManager groupManager) {
+
+ super(String.format("Edit group %s", group.getName()));
+
+ this.group = group;
+ this.usersGroupsRoles = usersGroupsRoles;
+ this.groupRepo = groupRepo;
+ this.groupManager = groupManager;
+
+ addWidgets();
+ }
+
+ private void addWidgets() {
+
+ final ResourceBundle bundle = ResourceBundle
+ .getBundle(AdminUiConstants.ADMIN_BUNDLE,
+ UI.getCurrent().getLocale());
+
+ final GroupDetails.DataHasChangedListener dataHasChangedListener
+ = new GroupDetails.DataHasChangedListener();
+
+ groupNameDisplay = new Label();
+ groupNameDisplay.setCaption(bundle
+ .getString("ui.admin.group_edit.groupname.label"));
+
+ groupNameField = new TextField(bundle
+ .getString("ui.admin.group_edit.groupname.label"));
+ groupNameField.setRequiredIndicatorVisible(true);
+
+ editButton = new Button("ui.admin.group.edit");
+ editButton.addClickListener(event -> {
+ groupNameDisplay.setVisible(false);
+ groupNameField.setVisible(true);
+ editButton.setVisible(false);
+ saveCancelButtons.setVisible(true);
+ });
+
+ final Button submit = new Button();
+ submit.setCaption(bundle.getString("ui.admin.save"));
+ submit.addClickListener(event -> saveGroup());
+
+ final Button cancel = new Button(bundle.getString("ui.admin.cancel"));
+ cancel.addClickListener(event -> {
+ groupNameField.setValue(group.getName());
+ groupNameField.setVisible(false);
+ saveCancelButtons.setVisible(false);
+ editButton.setVisible(true);
+ groupNameDisplay.setVisible(true);
+ });
+
+ saveCancelButtons = new HorizontalLayout(submit, cancel);
+
+ final FormLayout formLayout = new FormLayout(groupNameDisplay,
+ groupNameField);
+ groupNameField.setVisible(false);
+
+ groupNameField.addValueChangeListener(dataHasChangedListener);
+
+ final VerticalLayout layout = new VerticalLayout(formLayout,
+ editButton,
+ saveCancelButtons);
+ saveCancelButtons.setVisible(false);
+
+ final Panel propertiesPanel = new Panel(layout);
+ propertiesPanel.setCaption(bundle
+ .getString("ui.admin.group_details.edit"));
+
+ groupNameDisplay.setValue(group.getName());
+ groupNameField.setValue(group.getName());
+
+ dataHasChanged = false;
+
+ final CdiUtil cdiUtil = CdiUtil.createCdiUtil();
+ final GroupMembersController controller = cdiUtil
+ .findBean(GroupMembersController.class);
+
+ final Grid membersGrid = new Grid<>();
+ membersGrid.addColumn(User::getName)
+ .setId(COL_USER_NAME)
+ .setCaption("User Name");
+ membersGrid.addColumn(User::getGivenName)
+ .setId(COL_GIVEN_NAME)
+ .setCaption("Given name");
+ membersGrid.addColumn(User::getFamilyName)
+ .setId(COL_FAMILY_NAME)
+ .setCaption("Family name");
+ membersGrid
+ .addColumn(user -> user.getPrimaryEmailAddress().getAddress())
+ .setId(COL_EMAIL)
+ .setCaption("E-Mail");
+ membersGrid.addColumn(user -> bundle.getString(
+ "ui.groups.members.remove"),
+ new ButtonRenderer<>(event -> {
+ controller
+ .removeMemberFromGroup(event.getItem(),
+ group);
+ membersGrid.getDataProvider().refreshAll();
+ }))
+ .setId(COL_REMOVE);
+
+ membersGrid.setWidth("100%");
+
+ final UserRepository userRepo = cdiUtil.findBean(UserRepository.class);
+
+ final HeaderRow membersGridHeader = membersGrid.prependHeaderRow();
+ final Button addMemberButton = new Button("Add member");
+ addMemberButton.setIcon(VaadinIcons.PLUS);
+ addMemberButton.setStyleName(ValoTheme.BUTTON_TINY);
+ addMemberButton.addClickListener(event -> {
+ final UserSelector userSelector = new UserSelector(
+ "Select users to add to group",
+ "Add selected users to group",
+ usersGroupsRoles,
+ userRepo.findByGroup(group),
+ (selectedUsers -> {
+ selectedUsers.forEach(user -> {
+ controller.addMembersToGroup(selectedUsers, group);
+ membersGrid.getDataProvider().refreshAll();
+ });
+ }));
+ userSelector.center();
+ UI.getCurrent().addWindow(userSelector);
+ });
+ final HeaderCell membersGridHeaderCell = membersGridHeader
+ .join(COL_USER_NAME,
+ COL_GIVEN_NAME,
+ COL_FAMILY_NAME,
+ COL_EMAIL,
+ COL_REMOVE);
+ membersGridHeaderCell
+ .setComponent(new HorizontalLayout(addMemberButton));
+
+ final GroupMembersTableDataProvider dataProvider = cdiUtil
+ .findBean(GroupMembersTableDataProvider.class);
+ dataProvider.setGroup(group);
+ membersGrid.setDataProvider(dataProvider);
+
+ final TabSheet tabs = new TabSheet();
+ tabs.addTab(membersGrid, "Members");
+ tabs.addTab(new Label("Roles Placeholder"), "Roles");
+
+// final Panel membersPanel = new Panel("Members");
+// membersPanel.setContent(membersGrid);
+// final VerticalLayout windowLayout = new VerticalLayout(propertiesPanel,
+// membersPanel);
+ final VerticalLayout windowLayout = new VerticalLayout(propertiesPanel,
+ tabs);
+
+ setContent(windowLayout);
+ }
+
+ @Override
+ public void close() {
+
+ if (dataHasChanged) {
+ final ResourceBundle bundle = ResourceBundle
+ .getBundle(AdminUiConstants.ADMIN_BUNDLE,
+ UI.getCurrent().getLocale());
+
+ final ConfirmDiscardDialog dialog = new ConfirmDiscardDialog(
+ this,
+ bundle.getString("ui.admin.group_edit.discard_confirm"));
+ dialog.setModal(true);
+ UI.getCurrent().addWindow(dialog);
+ } else {
+ super.close();
+ }
+ }
+
+ protected void saveGroup() {
+
+ final ResourceBundle bundle = ResourceBundle
+ .getBundle(AdminUiConstants.ADMIN_BUNDLE,
+ UI.getCurrent().getLocale());
+
+ boolean valid = true;
+
+ if (groupNameField.getValue() == null
+ || groupNameField.getValue().trim().isEmpty()) {
+
+ groupNameField.setComponentError(new UserError(
+ bundle.getString("ui.admin.group_edit.groupname.error.notempty")));
+ valid = false;
+ }
+
+ if (!valid) {
+ return;
+ }
+
+ final String notificationText;
+ group.setName(groupNameField.getValue());
+ notificationText = String.format("Saved changes to group %s",
+ group.getName());
+
+ groupRepo.save(group);
+
+ dataHasChanged = false;
+ if (usersGroupsRoles != null) {
+ usersGroupsRoles.refreshGroups();
+ }
+ close();
+ new Notification(notificationText, Notification.Type.TRAY_NOTIFICATION)
+ .show(Page.getCurrent());
+ }
+
+ private class DataHasChangedListener
+ implements HasValue.ValueChangeListener {
+
+ private static final long serialVersionUID = -1410903365203533072L;
+
+ @Override
+ public void valueChange(final HasValue.ValueChangeEvent event) {
+ dataHasChanged = true;
+ }
+
+ }
+
+}
diff --git a/ccm-core/src/main/java/org/libreccm/admin/ui/usersgroupsroles/GroupMembersController.java b/ccm-core/src/main/java/org/libreccm/admin/ui/usersgroupsroles/GroupMembersController.java
new file mode 100644
index 000000000..02040f310
--- /dev/null
+++ b/ccm-core/src/main/java/org/libreccm/admin/ui/usersgroupsroles/GroupMembersController.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2017 LibreCCM Foundation.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301 USA
+ */
+package org.libreccm.admin.ui.usersgroupsroles;
+
+import org.libreccm.security.Group;
+import org.libreccm.security.GroupManager;
+import org.libreccm.security.GroupRepository;
+import org.libreccm.security.User;
+import org.libreccm.security.UserRepository;
+
+import java.util.Set;
+
+import javax.enterprise.context.RequestScoped;
+import javax.inject.Inject;
+import javax.transaction.Transactional;
+
+/**
+ *
+ * @author Jens Pelzetter
+ */
+@RequestScoped
+class GroupMembersController {
+
+ @Inject
+ private UserRepository userRepo;
+
+ @Inject
+ private GroupRepository groupRepo;
+
+ @Inject
+ private GroupManager groupManager;
+
+ @Transactional(Transactional.TxType.REQUIRED)
+ public void addMembersToGroup(final Set users, final Group group) {
+
+ users.forEach(user -> addMemberToGroup(user, group));
+ }
+
+ @Transactional(Transactional.TxType.REQUIRED)
+ public void addMemberToGroup(final User user, final Group group) {
+
+ final User theUser = userRepo
+ .findById(user.getPartyId())
+ .orElseThrow(() -> new IllegalArgumentException(String
+ .format("No user with id %d in the database. "
+ + "Where did that ID come from?",
+ user.getPartyId())));
+
+ final Group theGroup = groupRepo
+ .findById(group.getPartyId())
+ .orElseThrow(() -> new IllegalArgumentException(String
+ .format("No group with id %d in the database. "
+ + "Where did that ID come from?",
+ group.getPartyId())));
+
+ groupManager.addMemberToGroup(theUser, theGroup);
+ }
+
+ @Transactional(Transactional.TxType.REQUIRED)
+ public void removeMemberFromGroup(final User member, final Group group) {
+
+ final User theMember = userRepo
+ .findById(member.getPartyId())
+ .orElseThrow(() -> new IllegalArgumentException(String
+ .format("No user with id %d in the database. "
+ + "Where did that ID come from?",
+ member.getPartyId())));
+
+ final Group theGroup = groupRepo
+ .findById(group.getPartyId())
+ .orElseThrow(() -> new IllegalArgumentException(String
+ .format("No group with id %d in the database. "
+ + "Where did that ID come from?",
+ group.getPartyId())));
+
+ groupManager.removeMemberFromGroup(theMember, theGroup);
+ }
+
+
+}
diff --git a/ccm-core/src/main/java/org/libreccm/admin/ui/usersgroupsroles/GroupMembersTableDataProvider.java b/ccm-core/src/main/java/org/libreccm/admin/ui/usersgroupsroles/GroupMembersTableDataProvider.java
new file mode 100644
index 000000000..62311fc09
--- /dev/null
+++ b/ccm-core/src/main/java/org/libreccm/admin/ui/usersgroupsroles/GroupMembersTableDataProvider.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2017 LibreCCM Foundation.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301 USA
+ */
+package org.libreccm.admin.ui.usersgroupsroles;
+
+import com.vaadin.cdi.ViewScoped;
+import com.vaadin.data.provider.AbstractDataProvider;
+import com.vaadin.data.provider.Query;
+import org.libreccm.security.Group;
+import org.libreccm.security.GroupMembership;
+import org.libreccm.security.User;
+
+import java.util.Objects;
+import java.util.stream.Stream;
+
+import javax.inject.Inject;
+import javax.persistence.EntityManager;
+import javax.persistence.criteria.CriteriaBuilder;
+import javax.persistence.criteria.CriteriaQuery;
+import javax.persistence.criteria.Join;
+import javax.persistence.criteria.Root;
+import javax.transaction.Transactional;
+
+/**
+ *
+ * @author Jens Pelzetter
+ */
+@ViewScoped
+public class GroupMembersTableDataProvider
+ extends AbstractDataProvider {
+
+ private static final long serialVersionUID = -1924910843845830008L;
+
+ @Inject
+ private EntityManager entityManager;
+
+ private Group group;
+
+ @Override
+ public boolean isInMemory() {
+ return false;
+ }
+
+ @Override
+ public int size(Query query) {
+
+ Objects.requireNonNull(group,
+ "This data provider needs to be initalized "
+ + "by calling setGroup(Group) before calling "
+ + "the count method.");
+
+ final CriteriaBuilder builder = entityManager.getCriteriaBuilder();
+ CriteriaQuery criteriaQuery = builder.createQuery(Long.class);
+// final Root from = criteriaQuery.from(User.class);
+// final Join, ?> memberships = from.join("groups");
+//
+// criteriaQuery = criteriaQuery.select(builder.count(from));
+//
+// criteriaQuery.where(builder.equal(memberships.get("group"),
+// group));
+// criteriaQuery
+// .where(builder
+// .equal(builder.treat(from.get("groups"),
+// GroupMembership.class).get("group"),
+// group));
+
+ final Root from = criteriaQuery
+ .from(GroupMembership.class);
+
+ criteriaQuery = criteriaQuery.select(builder.count(from));
+ criteriaQuery.where(builder.equal(from.get("group"), group));
+
+ return entityManager
+ .createQuery(criteriaQuery)
+ .getSingleResult()
+ .intValue();
+ }
+
+ @Transactional(Transactional.TxType.REQUIRED)
+ @Override
+ public Stream fetch(final Query query) {
+
+ Objects.requireNonNull(group,
+ "This data provider needs to be initalized "
+ + "by calling setGroup(Group) before calling "
+ + "the fetch method.");
+
+ final CriteriaBuilder builder = entityManager.getCriteriaBuilder();
+// final CriteriaQuery criteriaQuery = builder
+// .createQuery(User.class);
+// final Root from = criteriaQuery.from(User.class);
+// final Join memberships = from
+// .join("memberships");
+//
+// criteriaQuery.where(builder.equal(memberships.get("group"),
+// group));
+//
+// return entityManager
+// .createQuery(criteriaQuery)
+// .setMaxResults(query.getLimit())
+// .setFirstResult(query.getOffset())
+// .getResultList()
+// .stream();
+
+ final CriteriaQuery criteriaQuery = builder
+ .createQuery(GroupMembership.class);
+ final Root from = criteriaQuery
+ .from(GroupMembership.class);
+ final Join, ?> join = from.join("member");
+ criteriaQuery
+ .where(builder.equal(from.get("group"), group))
+ .orderBy(builder.asc(join.get("name")));
+
+ return entityManager
+ .createQuery(criteriaQuery)
+ .setMaxResults(query.getLimit())
+ .setFirstResult(query.getOffset())
+ .getResultList()
+ .stream()
+ .map(membership -> membership.getMember());
+ }
+
+ public void setGroup(final Group group) {
+ Objects.requireNonNull(group);
+ this.group = group;
+ }
+
+}
diff --git a/ccm-core/src/main/java/org/libreccm/admin/ui/usersgroupsroles/RoleDetails.java b/ccm-core/src/main/java/org/libreccm/admin/ui/usersgroupsroles/RoleDetails.java
new file mode 100644
index 000000000..f962c0718
--- /dev/null
+++ b/ccm-core/src/main/java/org/libreccm/admin/ui/usersgroupsroles/RoleDetails.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2017 LibreCCM Foundation.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301 USA
+ */
+package org.libreccm.admin.ui.usersgroupsroles;
+
+import com.vaadin.ui.Window;
+
+/**
+ *
+ * @author Jens Pelzetter
+ */
+public class RoleDetails extends Window {
+
+ private static final long serialVersionUID = 8109931561947913438L;
+
+
+
+}
diff --git a/ccm-core/src/main/java/org/libreccm/admin/ui/usersgroupsroles/UserSelectionAction.java b/ccm-core/src/main/java/org/libreccm/admin/ui/usersgroupsroles/UserSelectionAction.java
new file mode 100644
index 000000000..126975e9e
--- /dev/null
+++ b/ccm-core/src/main/java/org/libreccm/admin/ui/usersgroupsroles/UserSelectionAction.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2017 LibreCCM Foundation.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301 USA
+ */
+package org.libreccm.admin.ui.usersgroupsroles;
+
+import org.libreccm.security.User;
+
+import java.util.Set;
+
+/**
+ *
+ * @author Jens Pelzetter
+ */
+@FunctionalInterface
+public interface UserSelectionAction {
+
+ void action(Set selectedUsers);
+
+}
diff --git a/ccm-core/src/main/java/org/libreccm/admin/ui/usersgroupsroles/UserSelector.java b/ccm-core/src/main/java/org/libreccm/admin/ui/usersgroupsroles/UserSelector.java
new file mode 100644
index 000000000..7c1def8e0
--- /dev/null
+++ b/ccm-core/src/main/java/org/libreccm/admin/ui/usersgroupsroles/UserSelector.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2017 LibreCCM Foundation.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301 USA
+ */
+package org.libreccm.admin.ui.usersgroupsroles;
+
+import com.arsdigita.ui.admin.AdminUiConstants;
+
+import com.vaadin.icons.VaadinIcons;
+import com.vaadin.ui.Button;
+import com.vaadin.ui.Grid;
+import com.vaadin.ui.HorizontalLayout;
+import com.vaadin.ui.UI;
+import com.vaadin.ui.Window;
+import com.vaadin.ui.components.grid.HeaderCell;
+import com.vaadin.ui.components.grid.HeaderRow;
+import com.vaadin.ui.themes.ValoTheme;
+import org.libreccm.cdi.utils.CdiUtil;
+import org.libreccm.security.User;
+import org.libreccm.security.UserRepository;
+
+import java.util.List;
+import java.util.ResourceBundle;
+
+/**
+ *
+ * @author Jens Pelzetter
+ */
+public class UserSelector extends Window {
+
+ private static final long serialVersionUID = -6227551833159691370L;
+
+ private static final String COL_USER_NAME = "username";
+ private static final String COL_GIVEN_NAME = "given_name";
+ private static final String COL_FAMILY_NAME = "family_name";
+ private static final String COL_EMAIL = "email";
+
+ private final UserRepository userRepo;
+
+ private final UserSelectionAction groupSelectionAction;
+
+ public UserSelector(final String caption,
+ final String actionLabel,
+ final UsersGroupsRoles usersGroupsRoles,
+ final List excludedUsers,
+ final UserSelectionAction action) {
+
+ final CdiUtil cdiUtil = CdiUtil.createCdiUtil();
+ userRepo = cdiUtil.findBean(UserRepository.class);
+ this.groupSelectionAction = action;
+
+ addWidgets(caption, actionLabel, excludedUsers, action);
+ }
+
+ private void addWidgets(final String caption,
+ final String actionLabel,
+ final List excludedUsers,
+ final UserSelectionAction action) {
+
+ setCaption(caption);
+
+ final ResourceBundle bundle = ResourceBundle
+ .getBundle(AdminUiConstants.ADMIN_BUNDLE,
+ UI.getCurrent().getLocale());
+
+ final CdiUtil cdiUtil = CdiUtil.createCdiUtil();
+
+ final Grid usersGrid = new Grid<>();
+ usersGrid.addColumn(User::getName)
+ .setId(COL_USER_NAME)
+ .setCaption("User Name");
+ usersGrid.addColumn(User::getGivenName)
+ .setId(COL_GIVEN_NAME)
+ .setCaption("Given name");
+ usersGrid.addColumn(User::getFamilyName)
+ .setId(COL_FAMILY_NAME)
+ .setCaption("Family name");
+ usersGrid
+ .addColumn(user -> user.getPrimaryEmailAddress().getAddress())
+ .setId(COL_EMAIL)
+ .setCaption("E-Mail");
+
+ usersGrid.setSelectionMode(Grid.SelectionMode.MULTI);
+ usersGrid.setWidth("100%");
+
+ final Button actionButton = new Button(actionLabel);
+ actionButton.addClickListener(event -> {
+ action.action(usersGrid.getSelectedItems());
+ close();
+ });
+ actionButton.setIcon(VaadinIcons.PLUS_CIRCLE_O);
+ actionButton.setStyleName(ValoTheme.BUTTON_TINY);
+
+ final Button clearButton = new Button("Clear selection");
+ clearButton.addClickListener(event -> {
+ usersGrid.getSelectionModel().deselectAll();
+ });
+ clearButton.setIcon(VaadinIcons.BACKSPACE);
+ clearButton.setStyleName(ValoTheme.BUTTON_TINY);
+
+ final HeaderRow actions = usersGrid.prependHeaderRow();
+ final HeaderCell actionsCell = actions.join(COL_USER_NAME,
+ COL_GIVEN_NAME,
+ COL_FAMILY_NAME,
+ COL_EMAIL);
+ actionsCell.setComponent(new HorizontalLayout(actionButton,
+ clearButton));
+
+ final UserSelectorDataProvider dataProvider = cdiUtil
+ .findBean(UserSelectorDataProvider.class);
+
+ dataProvider.setExcludedUsers(excludedUsers);
+
+ usersGrid.setDataProvider(dataProvider);
+
+ setContent(usersGrid);
+
+ }
+
+}
diff --git a/ccm-core/src/main/java/org/libreccm/admin/ui/usersgroupsroles/UserSelectorDataProvider.java b/ccm-core/src/main/java/org/libreccm/admin/ui/usersgroupsroles/UserSelectorDataProvider.java
new file mode 100644
index 000000000..550789e40
--- /dev/null
+++ b/ccm-core/src/main/java/org/libreccm/admin/ui/usersgroupsroles/UserSelectorDataProvider.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2017 LibreCCM Foundation.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301 USA
+ */
+package org.libreccm.admin.ui.usersgroupsroles;
+
+import com.vaadin.cdi.ViewScoped;
+import com.vaadin.data.provider.AbstractDataProvider;
+import com.vaadin.data.provider.Query;
+import org.libreccm.security.User;
+
+import java.util.List;
+import java.util.stream.Stream;
+
+import javax.inject.Inject;
+import javax.persistence.EntityManager;
+import javax.persistence.criteria.CriteriaBuilder;
+import javax.persistence.criteria.CriteriaQuery;
+import javax.persistence.criteria.Expression;
+import javax.persistence.criteria.Root;
+import javax.transaction.Transactional;
+
+/**
+ *
+ * @author Jens Pelzetter
+ */
+@ViewScoped
+public class UserSelectorDataProvider extends AbstractDataProvider {
+
+ private static final long serialVersionUID = -9019928941945017145L;
+
+ @Inject
+ private EntityManager entityManager;
+
+ private String userNameFilter;
+
+ private List excludedUsers;
+
+ @Override
+ public boolean isInMemory() {
+ return false;
+ }
+
+ @Transactional(Transactional.TxType.REQUIRED)
+ @Override
+ public int size(final Query query) {
+ final CriteriaBuilder builder = entityManager.getCriteriaBuilder();
+ CriteriaQuery criteriaQuery = builder.createQuery(Long.class);
+ final Root from = criteriaQuery.from(User.class);
+
+ criteriaQuery = criteriaQuery.select(builder.count(from));
+
+ if (userNameFilter != null && !userNameFilter.trim().isEmpty()) {
+ criteriaQuery
+ .where(builder.like(builder.lower(from.get("name")),
+ String.format("%s%%", userNameFilter)));
+ }
+
+ if (excludedUsers != null && !excludedUsers.isEmpty()) {
+ criteriaQuery.where(builder.not(from.in(excludedUsers)));
+ }
+
+ return entityManager
+ .createQuery(criteriaQuery)
+ .getSingleResult()
+ .intValue();
+ }
+
+ @Transactional
+ @Override
+ public Stream fetch(final Query query) {
+ final CriteriaBuilder builder = entityManager.getCriteriaBuilder();
+ final CriteriaQuery criteriaQuery = builder
+ .createQuery(User.class);
+ final Root from = criteriaQuery.from(User.class);
+
+ if (userNameFilter != null && !userNameFilter.trim().isEmpty()) {
+ criteriaQuery
+ .where(builder.like(builder.lower(from.get("name")),
+ String.format("%s%%", userNameFilter)));
+ }
+
+ if (excludedUsers != null && !excludedUsers.isEmpty()) {
+ criteriaQuery.where(builder.not(from.in(excludedUsers)));
+ }
+
+ criteriaQuery.orderBy(builder.asc(from.get("name")));
+
+ return entityManager
+ .createQuery(criteriaQuery)
+ .setMaxResults(query.getLimit())
+ .setFirstResult(query.getOffset())
+ .getResultList()
+ .stream();
+ }
+
+ public void setUserNameFilter(final String userNameFilter) {
+ this.userNameFilter = userNameFilter;
+ refreshAll();
+ }
+
+ public void setExcludedUsers(final List excludedUsers) {
+ this.excludedUsers = excludedUsers;
+ refreshAll();
+ }
+
+}