From 488a904cc567a84e72ab102daef3468ba97a854b Mon Sep 17 00:00:00 2001 From: Mathieu Baudier Date: Sat, 15 Oct 2022 11:39:46 +0200 Subject: [PATCH] Eclipse E4 components depending on JCR --- Makefile | 1 + org.argeo.cms.jcr.e4/.classpath | 7 + org.argeo.cms.jcr.e4/.project | 28 + org.argeo.cms.jcr.e4/bnd.bnd | 12 + org.argeo.cms.jcr.e4/build.properties | 4 + .../argeo/cms/e4/jcr/EclipseJcrMonitor.java | 44 ++ .../argeo/cms/e4/jcr/GenericPropertyPage.java | 141 ++++ .../org/argeo/cms/e4/jcr/JcrBrowserView.java | 349 ++++++++++ .../argeo/cms/e4/jcr/JcrE4DClickListener.java | 36 ++ .../org/argeo/cms/e4/jcr/JcrNodeEditor.java | 26 + .../src/org/argeo/cms/e4/jcr/SimplePart.java | 19 + .../cms/e4/jcr/handlers/AddFolderNode.java | 67 ++ .../e4/jcr/handlers/AddRemoteRepository.java | 210 ++++++ .../cms/e4/jcr/handlers/DeleteNodes.java | 95 +++ .../argeo/cms/e4/jcr/handlers/Refresh.java | 43 ++ .../argeo/cms/e4/jcr/handlers/RenameNode.java | 59 ++ .../cms/e4/jcr/handlers/package-info.java | 2 + .../org/argeo/cms/e4/jcr/package-info.java | 2 + .../cms/e4/users/AbstractRoleEditor.java | 287 +++++++++ .../cms/e4/users/CmsWorkbenchStyles.java | 8 + .../org/argeo/cms/e4/users/GroupEditor.java | 566 ++++++++++++++++ .../org/argeo/cms/e4/users/GroupsView.java | 251 ++++++++ .../cms/e4/users/SecurityAdminImages.java | 19 + .../org/argeo/cms/e4/users/UiAdminUtils.java | 34 + .../cms/e4/users/UiUserAdminListener.java | 27 + .../argeo/cms/e4/users/UserAdminWrapper.java | 153 +++++ .../cms/e4/users/UserBatchUpdateWizard.java | 606 ++++++++++++++++++ .../org/argeo/cms/e4/users/UserEditor.java | 535 ++++++++++++++++ .../users/UserTableDefaultDClickListener.java | 39 ++ .../src/org/argeo/cms/e4/users/UsersView.java | 182 ++++++ .../cms/e4/users/handlers/DeleteGroups.java | 95 +++ .../cms/e4/users/handlers/DeleteUsers.java | 88 +++ .../argeo/cms/e4/users/handlers/NewGroup.java | 212 ++++++ .../argeo/cms/e4/users/handlers/NewUser.java | 287 +++++++++ .../cms/e4/users/handlers/package-info.java | 2 + .../org/argeo/cms/e4/users/package-info.java | 2 + .../cms/e4/users/providers/CommonNameLP.java | 21 + .../cms/e4/users/providers/DomainNameLP.java | 14 + .../argeo/cms/e4/users/providers/MailLP.java | 15 + .../cms/e4/users/providers/RoleIconLP.java | 35 + .../users/providers/UserAdminAbstractLP.java | 66 ++ .../e4/users/providers/UserDragListener.java | 40 ++ .../cms/e4/users/providers/UserFilter.java | 58 ++ .../cms/e4/users/providers/UserNameLP.java | 13 + .../cms/e4/users/providers/package-info.java | 2 + .../e4/maintenance/AbstractOsgiComposite.java | 41 ++ .../org/argeo/cms/e4/maintenance/Browse.java | 576 +++++++++++++++++ .../maintenance/ConnectivityDeploymentUi.java | 48 ++ .../cms/e4/maintenance/DataDeploymentUi.java | 139 ++++ .../e4/maintenance/DeploymentEntryPoint.java | 95 +++ .../cms/e4/maintenance/LogDeploymentUi.java | 73 +++ .../cms/e4/maintenance/MaintenanceStyles.java | 10 + .../cms/e4/maintenance/NonAdminPage.java | 30 + .../e4/maintenance/SecurityDeploymentUi.java | 85 +++ .../cms/e4/maintenance/package-info.java | 2 + 55 files changed, 5901 insertions(+) create mode 100644 org.argeo.cms.jcr.e4/.classpath create mode 100644 org.argeo.cms.jcr.e4/.project create mode 100644 org.argeo.cms.jcr.e4/bnd.bnd create mode 100644 org.argeo.cms.jcr.e4/build.properties create mode 100644 org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/jcr/EclipseJcrMonitor.java create mode 100644 org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/jcr/GenericPropertyPage.java create mode 100644 org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/jcr/JcrBrowserView.java create mode 100644 org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/jcr/JcrE4DClickListener.java create mode 100644 org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/jcr/JcrNodeEditor.java create mode 100644 org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/jcr/SimplePart.java create mode 100644 org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/jcr/handlers/AddFolderNode.java create mode 100644 org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/jcr/handlers/AddRemoteRepository.java create mode 100644 org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/jcr/handlers/DeleteNodes.java create mode 100644 org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/jcr/handlers/Refresh.java create mode 100644 org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/jcr/handlers/RenameNode.java create mode 100644 org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/jcr/handlers/package-info.java create mode 100644 org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/jcr/package-info.java create mode 100644 org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/AbstractRoleEditor.java create mode 100644 org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/CmsWorkbenchStyles.java create mode 100644 org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/GroupEditor.java create mode 100644 org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/GroupsView.java create mode 100644 org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/SecurityAdminImages.java create mode 100644 org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/UiAdminUtils.java create mode 100644 org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/UiUserAdminListener.java create mode 100644 org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/UserAdminWrapper.java create mode 100644 org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/UserBatchUpdateWizard.java create mode 100644 org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/UserEditor.java create mode 100644 org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/UserTableDefaultDClickListener.java create mode 100644 org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/UsersView.java create mode 100644 org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/handlers/DeleteGroups.java create mode 100644 org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/handlers/DeleteUsers.java create mode 100644 org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/handlers/NewGroup.java create mode 100644 org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/handlers/NewUser.java create mode 100644 org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/handlers/package-info.java create mode 100644 org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/package-info.java create mode 100644 org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/providers/CommonNameLP.java create mode 100644 org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/providers/DomainNameLP.java create mode 100644 org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/providers/MailLP.java create mode 100644 org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/providers/RoleIconLP.java create mode 100644 org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/providers/UserAdminAbstractLP.java create mode 100644 org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/providers/UserDragListener.java create mode 100644 org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/providers/UserFilter.java create mode 100644 org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/providers/UserNameLP.java create mode 100644 org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/providers/package-info.java create mode 100644 org.argeo.cms.jcr.ui/src/org/argeo/cms/e4/maintenance/AbstractOsgiComposite.java create mode 100644 org.argeo.cms.jcr.ui/src/org/argeo/cms/e4/maintenance/Browse.java create mode 100644 org.argeo.cms.jcr.ui/src/org/argeo/cms/e4/maintenance/ConnectivityDeploymentUi.java create mode 100644 org.argeo.cms.jcr.ui/src/org/argeo/cms/e4/maintenance/DataDeploymentUi.java create mode 100644 org.argeo.cms.jcr.ui/src/org/argeo/cms/e4/maintenance/DeploymentEntryPoint.java create mode 100644 org.argeo.cms.jcr.ui/src/org/argeo/cms/e4/maintenance/LogDeploymentUi.java create mode 100644 org.argeo.cms.jcr.ui/src/org/argeo/cms/e4/maintenance/MaintenanceStyles.java create mode 100644 org.argeo.cms.jcr.ui/src/org/argeo/cms/e4/maintenance/NonAdminPage.java create mode 100644 org.argeo.cms.jcr.ui/src/org/argeo/cms/e4/maintenance/SecurityDeploymentUi.java create mode 100644 org.argeo.cms.jcr.ui/src/org/argeo/cms/e4/maintenance/package-info.java diff --git a/Makefile b/Makefile index 7223d31..7f3e732 100644 --- a/Makefile +++ b/Makefile @@ -7,6 +7,7 @@ A2_CATEGORY = org.argeo.cms.jcr BUNDLES = \ org.argeo.cms.jcr \ org.argeo.cms.jcr.ui \ +org.argeo.cms.jcr.e4 \ DEP_CATEGORIES = \ org.argeo.tp \ diff --git a/org.argeo.cms.jcr.e4/.classpath b/org.argeo.cms.jcr.e4/.classpath new file mode 100644 index 0000000..81fe078 --- /dev/null +++ b/org.argeo.cms.jcr.e4/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/org.argeo.cms.jcr.e4/.project b/org.argeo.cms.jcr.e4/.project new file mode 100644 index 0000000..01f8dd2 --- /dev/null +++ b/org.argeo.cms.jcr.e4/.project @@ -0,0 +1,28 @@ + + + org.argeo.cms.jcr.e4 + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + diff --git a/org.argeo.cms.jcr.e4/bnd.bnd b/org.argeo.cms.jcr.e4/bnd.bnd new file mode 100644 index 0000000..fde6c09 --- /dev/null +++ b/org.argeo.cms.jcr.e4/bnd.bnd @@ -0,0 +1,12 @@ +Import-Package: \ +org.eclipse.swt,\ +org.eclipse.swt.widgets;version="0.0.0",\ +org.eclipse.jface.window,\ +org.eclipse.core.commands.common,\ +org.eclipse.e4.ui.model.application.ui,\ +org.eclipse.e4.ui.model.application,\ +javax.jcr.nodetype,\ +org.apache.jackrabbit.*;version="[2,3)",\ +org.argeo.cms,\ +org.argeo.jcr,\ +* \ No newline at end of file diff --git a/org.argeo.cms.jcr.e4/build.properties b/org.argeo.cms.jcr.e4/build.properties new file mode 100644 index 0000000..34d2e4d --- /dev/null +++ b/org.argeo.cms.jcr.e4/build.properties @@ -0,0 +1,4 @@ +source.. = src/ +output.. = bin/ +bin.includes = META-INF/,\ + . diff --git a/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/jcr/EclipseJcrMonitor.java b/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/jcr/EclipseJcrMonitor.java new file mode 100644 index 0000000..e10738e --- /dev/null +++ b/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/jcr/EclipseJcrMonitor.java @@ -0,0 +1,44 @@ +package org.argeo.cms.e4.jcr; + +import org.argeo.jcr.JcrMonitor; +import org.eclipse.core.runtime.IProgressMonitor; + +/** + * Wraps an Eclipse {@link IProgressMonitor} so that it can be passed to + * framework agnostic Argeo routines. + */ +public class EclipseJcrMonitor implements JcrMonitor { + private final IProgressMonitor progressMonitor; + + public EclipseJcrMonitor(IProgressMonitor progressMonitor) { + this.progressMonitor = progressMonitor; + } + + public void beginTask(String name, int totalWork) { + progressMonitor.beginTask(name, totalWork); + } + + public void done() { + progressMonitor.done(); + } + + public boolean isCanceled() { + return progressMonitor.isCanceled(); + } + + public void setCanceled(boolean value) { + progressMonitor.setCanceled(value); + } + + public void setTaskName(String name) { + progressMonitor.setTaskName(name); + } + + public void subTask(String name) { + progressMonitor.subTask(name); + } + + public void worked(int work) { + progressMonitor.worked(work); + } +} diff --git a/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/jcr/GenericPropertyPage.java b/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/jcr/GenericPropertyPage.java new file mode 100644 index 0000000..e17f17b --- /dev/null +++ b/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/jcr/GenericPropertyPage.java @@ -0,0 +1,141 @@ +package org.argeo.cms.e4.jcr; + +import java.util.ArrayList; +import java.util.List; + +import javax.jcr.Node; +import javax.jcr.Property; +import javax.jcr.PropertyIterator; +import javax.jcr.RepositoryException; + +import org.argeo.cms.ui.jcr.PropertyLabelProvider; +import org.argeo.eclipse.ui.EclipseUiException; +import org.eclipse.jface.layout.TreeColumnLayout; +import org.eclipse.jface.viewers.ColumnWeightData; +import org.eclipse.jface.viewers.IBaseLabelProvider; +import org.eclipse.jface.viewers.ITreeContentProvider; +import org.eclipse.jface.viewers.TreeViewer; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.ScrolledComposite; +import org.eclipse.swt.layout.FillLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Tree; +import org.eclipse.swt.widgets.TreeColumn; + +/** + * Generic editor property page. Lists all properties of current node as a + * complex tree. TODO: enable editing + */ +public class GenericPropertyPage { + + // Main business Objects + private Node currentNode; + + public GenericPropertyPage(Node currentNode) { + this.currentNode = currentNode; + } + + protected void createFormContent(Composite parent) { + Composite innerBox = new Composite(parent, SWT.NONE); + // Composite innerBox = new Composite(body, SWT.NO_FOCUS); + FillLayout layout = new FillLayout(); + layout.marginHeight = 5; + layout.marginWidth = 5; + innerBox.setLayout(layout); + createComplexTree(innerBox); + // TODO TreeColumnLayout triggers a scroll issue with the form: + // The inside body is always to big and a scroll bar is shown + // Composite tableCmp = new Composite(body, SWT.NO_FOCUS); + // createComplexTree(tableCmp); + } + + private TreeViewer createComplexTree(Composite parent) { + int style = SWT.BORDER | SWT.MULTI | SWT.FULL_SELECTION; + Tree tree = new Tree(parent, style); + TreeColumnLayout tableColumnLayout = new TreeColumnLayout(); + + createColumn(tree, tableColumnLayout, "Property", SWT.LEFT, 200, 30); + createColumn(tree, tableColumnLayout, "Value(s)", SWT.LEFT, 300, 60); + createColumn(tree, tableColumnLayout, "Type", SWT.LEFT, 75, 10); + createColumn(tree, tableColumnLayout, "Attributes", SWT.LEFT, 75, 0); + // Do not apply the treeColumnLayout it does not work yet + // parent.setLayout(tableColumnLayout); + + tree.setLinesVisible(true); + tree.setHeaderVisible(true); + + TreeViewer treeViewer = new TreeViewer(tree); + treeViewer.setContentProvider(new TreeContentProvider()); + treeViewer.setLabelProvider((IBaseLabelProvider) new PropertyLabelProvider()); + treeViewer.setInput(currentNode); + treeViewer.expandAll(); + return treeViewer; + } + + private static TreeColumn createColumn(Tree parent, TreeColumnLayout tableColumnLayout, String name, int style, + int width, int weight) { + TreeColumn column = new TreeColumn(parent, style); + column.setText(name); + column.setWidth(width); + column.setMoveable(true); + column.setResizable(true); + tableColumnLayout.setColumnData(column, new ColumnWeightData(weight, width, true)); + return column; + } + + private class TreeContentProvider implements ITreeContentProvider { + private static final long serialVersionUID = -6162736530019406214L; + + public Object[] getElements(Object parent) { + Object[] props = null; + try { + + if (parent instanceof Node) { + Node node = (Node) parent; + PropertyIterator pi; + pi = node.getProperties(); + List propList = new ArrayList(); + while (pi.hasNext()) { + propList.add(pi.nextProperty()); + } + props = propList.toArray(); + } + } catch (RepositoryException e) { + throw new EclipseUiException("Unexpected exception while listing node properties", e); + } + return props; + } + + public Object getParent(Object child) { + return null; + } + + public Object[] getChildren(Object parent) { + if (parent instanceof Property) { + Property prop = (Property) parent; + try { + if (prop.isMultiple()) + return prop.getValues(); + } catch (RepositoryException e) { + throw new EclipseUiException("Cannot get multi-prop values on " + prop, e); + } + } + return null; + } + + public boolean hasChildren(Object parent) { + try { + return (parent instanceof Property && ((Property) parent).isMultiple()); + } catch (RepositoryException e) { + throw new EclipseUiException("Cannot check if property is multiple for " + parent, e); + } + } + + public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { + } + + public void dispose() { + } + } +} diff --git a/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/jcr/JcrBrowserView.java b/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/jcr/JcrBrowserView.java new file mode 100644 index 0000000..0b77c07 --- /dev/null +++ b/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/jcr/JcrBrowserView.java @@ -0,0 +1,349 @@ +package org.argeo.cms.e4.jcr; + +import java.util.List; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import javax.inject.Inject; +import javax.jcr.Property; +import javax.jcr.PropertyType; +import javax.jcr.Repository; +import javax.jcr.RepositoryException; +import javax.jcr.RepositoryFactory; +import javax.jcr.Session; +import javax.jcr.Value; +import javax.jcr.observation.Event; +import javax.jcr.observation.EventListener; +import javax.jcr.observation.ObservationManager; + +import org.argeo.api.cms.CmsConstants; +import org.argeo.cms.security.CryptoKeyring; +import org.argeo.cms.security.Keyring; +import org.argeo.cms.swt.CmsException; +import org.argeo.cms.swt.CmsSwtUtils; +import org.argeo.cms.ui.jcr.JcrBrowserUtils; +import org.argeo.cms.ui.jcr.NodeContentProvider; +import org.argeo.cms.ui.jcr.NodeLabelProvider; +import org.argeo.cms.ui.jcr.OsgiRepositoryRegister; +import org.argeo.cms.ui.jcr.PropertiesContentProvider; +import org.argeo.cms.ui.jcr.model.SingleJcrNodeElem; +import org.argeo.cms.ux.widgets.TreeParent; +import org.argeo.eclipse.ui.EclipseUiException; +import org.argeo.eclipse.ui.jcr.AsyncUiEventListener; +import org.argeo.eclipse.ui.jcr.util.NodeViewerComparer; +import org.argeo.jcr.JcrUtils; +import org.eclipse.e4.core.contexts.IEclipseContext; +import org.eclipse.e4.core.di.annotations.Optional; +import org.eclipse.e4.ui.services.EMenuService; +import org.eclipse.e4.ui.workbench.modeling.EPartService; +import org.eclipse.e4.ui.workbench.modeling.ESelectionService; +import org.eclipse.jface.viewers.ColumnLabelProvider; +import org.eclipse.jface.viewers.IBaseLabelProvider; +import org.eclipse.jface.viewers.ISelectionChangedListener; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.viewers.ITreeContentProvider; +import org.eclipse.jface.viewers.SelectionChangedEvent; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.jface.viewers.TableViewer; +import org.eclipse.jface.viewers.TableViewerColumn; +import org.eclipse.jface.viewers.TreeViewer; +import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.SashForm; +import org.eclipse.swt.layout.FillLayout; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Display; + +/** + * Basic View to display a sash form to browse a JCR compliant multiple + * repository environment + */ +public class JcrBrowserView { + final static String ID = "org.argeo.cms.e4.jcrbrowser"; + final static String NODE_VIEWER_POPUP_MENU_ID = "org.argeo.cms.e4.popupmenu.nodeViewer"; + + private boolean sortChildNodes = true; + + /* DEPENDENCY INJECTION */ + @Inject + @Optional + private Keyring keyring; + @Inject + private RepositoryFactory repositoryFactory; + @Inject + private Repository nodeRepository; + + // Current user session on the home repository default workspace + private Session userSession; + + private OsgiRepositoryRegister repositoryRegister = new OsgiRepositoryRegister(); + + // This page widgets + private TreeViewer nodesViewer; + private NodeContentProvider nodeContentProvider; + private TableViewer propertiesViewer; + private EventListener resultsObserver; + + @PostConstruct + public void createPartControl(Composite parent, IEclipseContext context, EPartService partService, + ESelectionService selectionService, EMenuService menuService) { + repositoryRegister.init(); + + parent.setLayout(new FillLayout()); + SashForm sashForm = new SashForm(parent, SWT.VERTICAL); + // sashForm.setSashWidth(4); + // sashForm.setLayout(new FillLayout()); + + // Create the tree on top of the view + Composite top = new Composite(sashForm, SWT.NONE); + // GridLayout gl = new GridLayout(1, false); + top.setLayout(CmsSwtUtils.noSpaceGridLayout()); + + try { + this.userSession = this.nodeRepository.login(CmsConstants.HOME_WORKSPACE); + } catch (RepositoryException e) { + throw new CmsException("Cannot open user session", e); + } + + nodeContentProvider = new NodeContentProvider(userSession, keyring, repositoryRegister, repositoryFactory, + sortChildNodes); + + // nodes viewer + nodesViewer = createNodeViewer(top, nodeContentProvider); + + // context menu : it is completely defined in the plugin.xml file. + // MenuManager menuManager = new MenuManager(); + // Menu menu = menuManager.createContextMenu(nodesViewer.getTree()); + + // nodesViewer.getTree().setMenu(menu); + + nodesViewer.setInput(""); + + // Create the property viewer on the bottom + Composite bottom = new Composite(sashForm, SWT.NONE); + bottom.setLayout(CmsSwtUtils.noSpaceGridLayout()); + propertiesViewer = createPropertiesViewer(bottom); + + sashForm.setWeights(getWeights()); + nodesViewer.setComparer(new NodeViewerComparer()); + nodesViewer.addSelectionChangedListener(new ISelectionChangedListener() { + public void selectionChanged(SelectionChangedEvent event) { + IStructuredSelection selection = (IStructuredSelection) event.getSelection(); + selectionService.setSelection(selection.toList()); + } + }); + nodesViewer.addDoubleClickListener(new JcrE4DClickListener(nodesViewer, partService)); + menuService.registerContextMenu(nodesViewer.getControl(), NODE_VIEWER_POPUP_MENU_ID); + // getSite().registerContextMenu(menuManager, nodesViewer); + // getSite().setSelectionProvider(nodesViewer); + } + + @PreDestroy + public void dispose() { + JcrUtils.logoutQuietly(userSession); + repositoryRegister.destroy(); + } + + public void refresh(Object obj) { + // Enable full refresh from a command when no element of the tree is + // selected + if (obj == null) { + Object[] elements = nodeContentProvider.getElements(null); + for (Object el : elements) { + if (el instanceof TreeParent) + JcrBrowserUtils.forceRefreshIfNeeded((TreeParent) el); + getNodeViewer().refresh(el); + } + } else + getNodeViewer().refresh(obj); + } + + /** + * To be overridden to adapt size of form and result frames. + */ + protected int[] getWeights() { + return new int[] { 70, 30 }; + } + + protected TreeViewer createNodeViewer(Composite parent, final ITreeContentProvider nodeContentProvider) { + + final TreeViewer tmpNodeViewer = new TreeViewer(parent, SWT.MULTI); + + tmpNodeViewer.getTree().setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + + tmpNodeViewer.setContentProvider(nodeContentProvider); + tmpNodeViewer.setLabelProvider((IBaseLabelProvider) new NodeLabelProvider()); + tmpNodeViewer.addSelectionChangedListener(new ISelectionChangedListener() { + public void selectionChanged(SelectionChangedEvent event) { + if (!event.getSelection().isEmpty()) { + IStructuredSelection sel = (IStructuredSelection) event.getSelection(); + Object firstItem = sel.getFirstElement(); + if (firstItem instanceof SingleJcrNodeElem) + propertiesViewer.setInput(((SingleJcrNodeElem) firstItem).getNode()); + } else { + propertiesViewer.setInput(""); + } + } + }); + + resultsObserver = new TreeObserver(tmpNodeViewer.getTree().getDisplay()); + if (keyring != null) + try { + ObservationManager observationManager = userSession.getWorkspace().getObservationManager(); + observationManager.addEventListener(resultsObserver, Event.PROPERTY_ADDED | Event.PROPERTY_CHANGED, "/", + true, null, null, false); + } catch (RepositoryException e) { + throw new EclipseUiException("Cannot register listeners", e); + } + + // tmpNodeViewer.addDoubleClickListener(new JcrDClickListener(tmpNodeViewer)); + return tmpNodeViewer; + } + + protected TableViewer createPropertiesViewer(Composite parent) { + propertiesViewer = new TableViewer(parent, SWT.NONE); + propertiesViewer.getTable().setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + propertiesViewer.getTable().setHeaderVisible(true); + propertiesViewer.setContentProvider(new PropertiesContentProvider()); + TableViewerColumn col = new TableViewerColumn(propertiesViewer, SWT.NONE); + col.getColumn().setText("Name"); + col.getColumn().setWidth(200); + col.setLabelProvider(new ColumnLabelProvider() { + private static final long serialVersionUID = -6684361063107478595L; + + public String getText(Object element) { + try { + return ((Property) element).getName(); + } catch (RepositoryException e) { + throw new EclipseUiException("Unexpected exception in label provider", e); + } + } + }); + col = new TableViewerColumn(propertiesViewer, SWT.NONE); + col.getColumn().setText("Value"); + col.getColumn().setWidth(400); + col.setLabelProvider(new ColumnLabelProvider() { + private static final long serialVersionUID = -8201994187693336657L; + + public String getText(Object element) { + try { + Property property = (Property) element; + if (property.getType() == PropertyType.BINARY) + return ""; + else if (property.isMultiple()) { + StringBuffer buf = new StringBuffer("["); + Value[] values = property.getValues(); + for (int i = 0; i < values.length; i++) { + if (i != 0) + buf.append(", "); + buf.append(values[i].getString()); + } + buf.append(']'); + return buf.toString(); + } else + return property.getValue().getString(); + } catch (RepositoryException e) { + throw new EclipseUiException("Unexpected exception in label provider", e); + } + } + }); + col = new TableViewerColumn(propertiesViewer, SWT.NONE); + col.getColumn().setText("Type"); + col.getColumn().setWidth(200); + col.setLabelProvider(new ColumnLabelProvider() { + private static final long serialVersionUID = -6009599998150286070L; + + public String getText(Object element) { + return JcrBrowserUtils.getPropertyTypeAsString((Property) element); + } + }); + propertiesViewer.setInput(""); + return propertiesViewer; + } + + protected TreeViewer getNodeViewer() { + return nodesViewer; + } + + /** + * Resets the tree content provider + * + * @param sortChildNodes if true the content provider will use a comparer to + * sort nodes that might slow down the display + */ + public void setSortChildNodes(boolean sortChildNodes) { + this.sortChildNodes = sortChildNodes; + ((NodeContentProvider) nodesViewer.getContentProvider()).setSortChildren(sortChildNodes); + nodesViewer.setInput(""); + } + + /** Notifies the current view that a node has been added */ + public void nodeAdded(TreeParent parentNode) { + // insure that Ui objects have been correctly created: + JcrBrowserUtils.forceRefreshIfNeeded(parentNode); + getNodeViewer().refresh(parentNode); + getNodeViewer().expandToLevel(parentNode, 1); + } + + /** Notifies the current view that a node has been removed */ + public void nodeRemoved(TreeParent parentNode) { + IStructuredSelection newSel = new StructuredSelection(parentNode); + getNodeViewer().setSelection(newSel, true); + // Force refresh + IStructuredSelection tmpSel = (IStructuredSelection) getNodeViewer().getSelection(); + getNodeViewer().refresh(tmpSel.getFirstElement()); + } + + class TreeObserver extends AsyncUiEventListener { + + public TreeObserver(Display display) { + super(display); + } + + @Override + protected Boolean willProcessInUiThread(List events) throws RepositoryException { + for (Event event : events) { + if (getLog().isTraceEnabled()) + getLog().debug("Received event " + event); + String path = event.getPath(); + int index = path.lastIndexOf('/'); + String propertyName = path.substring(index + 1); + if (getLog().isTraceEnabled()) + getLog().debug("Concerned property " + propertyName); + } + return false; + } + + protected void onEventInUiThread(List events) throws RepositoryException { + if (getLog().isTraceEnabled()) + getLog().trace("Refresh result list"); + nodesViewer.refresh(); + } + + } + + public boolean getSortChildNodes() { + return sortChildNodes; + } + + public void setFocus() { + getNodeViewer().getTree().setFocus(); + } + + /* DEPENDENCY INJECTION */ + // public void setRepositoryRegister(RepositoryRegister repositoryRegister) { + // this.repositoryRegister = repositoryRegister; + // } + + public void setKeyring(CryptoKeyring keyring) { + this.keyring = keyring; + } + + public void setRepositoryFactory(RepositoryFactory repositoryFactory) { + this.repositoryFactory = repositoryFactory; + } + + public void setNodeRepository(Repository nodeRepository) { + this.nodeRepository = nodeRepository; + } +} diff --git a/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/jcr/JcrE4DClickListener.java b/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/jcr/JcrE4DClickListener.java new file mode 100644 index 0000000..f4ee2e8 --- /dev/null +++ b/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/jcr/JcrE4DClickListener.java @@ -0,0 +1,36 @@ +package org.argeo.cms.e4.jcr; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; + +import org.argeo.cms.swt.CmsException; +import org.argeo.cms.ui.jcr.JcrDClickListener; +import org.eclipse.e4.ui.model.application.ui.basic.MPart; +import org.eclipse.e4.ui.workbench.modeling.EPartService; +import org.eclipse.e4.ui.workbench.modeling.EPartService.PartState; +import org.eclipse.jface.viewers.TreeViewer; + +public class JcrE4DClickListener extends JcrDClickListener { + EPartService partService; + + public JcrE4DClickListener(TreeViewer nodeViewer, EPartService partService) { + super(nodeViewer); + this.partService = partService; + } + + @Override + protected void openNode(Node node) { + MPart part = partService.createPart(JcrNodeEditor.DESCRIPTOR_ID); + try { + part.setLabel(node.getName()); + part.getPersistedState().put("nodeWorkspace", node.getSession().getWorkspace().getName()); + part.getPersistedState().put("nodePath", node.getPath()); + } catch (RepositoryException e) { + throw new CmsException("Cannot open " + node, e); + } + + // the provided part is be shown + partService.showPart(part, PartState.ACTIVATE); + } + +} diff --git a/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/jcr/JcrNodeEditor.java b/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/jcr/JcrNodeEditor.java new file mode 100644 index 0000000..ae2b325 --- /dev/null +++ b/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/jcr/JcrNodeEditor.java @@ -0,0 +1,26 @@ +package org.argeo.cms.e4.jcr; + +import java.util.List; + +import javax.annotation.PostConstruct; +import javax.jcr.Node; + +import org.argeo.cms.ui.jcr.model.SingleJcrNodeElem; +import org.eclipse.e4.ui.model.application.ui.basic.MPart; +import org.eclipse.e4.ui.workbench.modeling.ESelectionService; +import org.eclipse.swt.layout.FillLayout; +import org.eclipse.swt.widgets.Composite; + +public class JcrNodeEditor { + final static String DESCRIPTOR_ID = "org.argeo.cms.e4.partdescriptor.nodeEditor"; + + @PostConstruct + public void createUi(Composite parent, MPart part, ESelectionService selectionService) { + parent.setLayout(new FillLayout()); + List selection = (List) selectionService.getSelection(); + Node node = ((SingleJcrNodeElem) selection.get(0)).getNode(); + GenericPropertyPage propertyPage = new GenericPropertyPage(node); + propertyPage.createFormContent(parent); + } + +} diff --git a/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/jcr/SimplePart.java b/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/jcr/SimplePart.java new file mode 100644 index 0000000..17d8d2a --- /dev/null +++ b/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/jcr/SimplePart.java @@ -0,0 +1,19 @@ +package org.argeo.cms.e4.jcr; + +import javax.annotation.PostConstruct; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Label; + +public class SimplePart { + + @PostConstruct + void init(Composite parent) { + parent.setLayout(new GridLayout()); + Label label = new Label(parent, SWT.NONE); + label.setText("Hello e4 World"); + } + +} diff --git a/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/jcr/handlers/AddFolderNode.java b/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/jcr/handlers/AddFolderNode.java new file mode 100644 index 0000000..09fa760 --- /dev/null +++ b/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/jcr/handlers/AddFolderNode.java @@ -0,0 +1,67 @@ +package org.argeo.cms.e4.jcr.handlers; + +import java.util.List; + +import javax.inject.Named; +import javax.jcr.Node; +import javax.jcr.RepositoryException; +import javax.jcr.nodetype.NodeType; + +import org.argeo.cms.e4.jcr.JcrBrowserView; +import org.argeo.cms.ui.jcr.model.SingleJcrNodeElem; +import org.argeo.cms.ui.jcr.model.WorkspaceElem; +import org.argeo.cms.ux.widgets.TreeParent; +import org.argeo.eclipse.ui.dialogs.ErrorFeedback; +import org.argeo.eclipse.ui.dialogs.SingleValue; +import org.eclipse.e4.core.di.annotations.Execute; +import org.eclipse.e4.ui.model.application.ui.basic.MPart; +import org.eclipse.e4.ui.services.IServiceConstants; +import org.eclipse.e4.ui.workbench.modeling.ESelectionService; + +/** + * Adds a node of type nt:folder, only on {@link SingleJcrNodeElem} and + * {@link WorkspaceElem} TreeObject types. + * + * This handler assumes that a selection provider is available and picks only + * first selected item. It is UI's job to enable the command only when the + * selection contains one and only one element. Thus no parameter is passed + * through the command. + */ +public class AddFolderNode { + @Execute + public void execute(@Named(IServiceConstants.ACTIVE_PART) MPart part, ESelectionService selectionService) { + List selection = (List) selectionService.getSelection(); + JcrBrowserView view = (JcrBrowserView) part.getObject(); + + if (selection != null && selection.size() == 1) { + TreeParent treeParentNode = null; + Node jcrParentNode = null; + Object obj = selection.get(0); + + if (obj instanceof SingleJcrNodeElem) { + treeParentNode = (TreeParent) obj; + jcrParentNode = ((SingleJcrNodeElem) treeParentNode).getNode(); + } else if (obj instanceof WorkspaceElem) { + treeParentNode = (TreeParent) obj; + jcrParentNode = ((WorkspaceElem) treeParentNode).getRootNode(); + } else + return; + + String folderName = SingleValue.ask("Folder name", "Enter folder name"); + if (folderName != null) { + try { + jcrParentNode.addNode(folderName, NodeType.NT_FOLDER); + jcrParentNode.getSession().save(); + view.nodeAdded(treeParentNode); + } catch (RepositoryException e) { + ErrorFeedback.show("Cannot create folder " + folderName + " under " + treeParentNode, e); + } + } + } else { + // ErrorFeedback.show(WorkbenchUiPlugin + // .getMessage("errorUnvalidNtFolderNodeType")); + ErrorFeedback.show("Invalid NT folder node type"); + } + } + +} diff --git a/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/jcr/handlers/AddRemoteRepository.java b/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/jcr/handlers/AddRemoteRepository.java new file mode 100644 index 0000000..dc47f6e --- /dev/null +++ b/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/jcr/handlers/AddRemoteRepository.java @@ -0,0 +1,210 @@ +package org.argeo.cms.e4.jcr.handlers; + +import java.net.URI; +import java.util.Hashtable; + +import javax.inject.Inject; +import javax.inject.Named; +import javax.jcr.Node; +import javax.jcr.Repository; +import javax.jcr.RepositoryFactory; +import javax.jcr.Session; +import javax.jcr.SimpleCredentials; + +import org.argeo.api.cms.CmsConstants; +import org.argeo.cms.ArgeoNames; +import org.argeo.cms.ArgeoTypes; +import org.argeo.cms.e4.jcr.JcrBrowserView; +import org.argeo.cms.jcr.CmsJcrUtils; +import org.argeo.cms.security.Keyring; +import org.argeo.eclipse.ui.EclipseUiException; +import org.argeo.eclipse.ui.dialogs.ErrorFeedback; +import org.argeo.jcr.JcrUtils; +import org.eclipse.e4.core.di.annotations.Execute; +import org.eclipse.e4.core.di.annotations.Optional; +import org.eclipse.e4.ui.model.application.ui.basic.MPart; +import org.eclipse.e4.ui.services.IServiceConstants; +import org.eclipse.jface.dialogs.Dialog; +import org.eclipse.jface.dialogs.IMessageProvider; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.jface.dialogs.TitleAreaDialog; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Text; + +/** + * Connect to a remote repository and, if successful publish it as an OSGi + * service. + */ +public class AddRemoteRepository { + + @Inject + private RepositoryFactory repositoryFactory; + @Inject + private Repository nodeRepository; + @Inject + @Optional + private Keyring keyring; + + @Execute + public void execute(@Named(IServiceConstants.ACTIVE_PART) MPart part) { + JcrBrowserView view = (JcrBrowserView) part.getObject(); + RemoteRepositoryLoginDialog dlg = new RemoteRepositoryLoginDialog(Display.getDefault().getActiveShell()); + if (dlg.open() == Dialog.OK) { + view.refresh(null); + } + } + + // public void setRepositoryFactory(RepositoryFactory repositoryFactory) { + // this.repositoryFactory = repositoryFactory; + // } + // + // public void setKeyring(Keyring keyring) { + // this.keyring = keyring; + // } + // + // public void setNodeRepository(Repository nodeRepository) { + // this.nodeRepository = nodeRepository; + // } + + class RemoteRepositoryLoginDialog extends TitleAreaDialog { + private static final long serialVersionUID = 2234006887750103399L; + private Text name; + private Text uri; + private Text username; + private Text password; + private Button saveInKeyring; + + public RemoteRepositoryLoginDialog(Shell parentShell) { + super(parentShell); + } + + protected Point getInitialSize() { + return new Point(600, 400); + } + + protected Control createDialogArea(Composite parent) { + Composite dialogarea = (Composite) super.createDialogArea(parent); + dialogarea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + Composite composite = new Composite(dialogarea, SWT.NONE); + composite.setLayout(new GridLayout(2, false)); + composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + setMessage("Login to remote repository", IMessageProvider.NONE); + name = createLT(composite, "Name", "remoteRepository"); + uri = createLT(composite, "URI", "http://localhost:7070/jcr/node"); + username = createLT(composite, "User", ""); + password = createLP(composite, "Password"); + + saveInKeyring = createLC(composite, "Remember password", false); + parent.pack(); + return composite; + } + + @Override + protected void createButtonsForButtonBar(Composite parent) { + super.createButtonsForButtonBar(parent); + Button test = createButton(parent, 2, "Test", false); + test.addSelectionListener(new SelectionAdapter() { + private static final long serialVersionUID = -1829962269440419560L; + + public void widgetSelected(SelectionEvent arg0) { + testConnection(); + } + }); + } + + void testConnection() { + Session session = null; + try { + URI checkedUri = new URI(uri.getText()); + String checkedUriStr = checkedUri.toString(); + + Hashtable params = new Hashtable(); + params.put(CmsConstants.LABELED_URI, checkedUriStr); + Repository repository = repositoryFactory.getRepository(params); + if (username.getText().trim().equals("")) {// anonymous + // FIXME make it more generic + session = repository.login(CmsConstants.SYS_WORKSPACE); + } else { + // FIXME use getTextChars() when upgrading to 3.7 + // see https://bugs.eclipse.org/bugs/show_bug.cgi?id=297412 + char[] pwd = password.getText().toCharArray(); + SimpleCredentials sc = new SimpleCredentials(username.getText(), pwd); + session = repository.login(sc, "main"); + MessageDialog.openInformation(getParentShell(), "Success", + "Connection to '" + uri.getText() + "' successful"); + } + } catch (Exception e) { + ErrorFeedback.show("Connection test failed for " + uri.getText(), e); + } finally { + JcrUtils.logoutQuietly(session); + } + } + + @Override + protected void okPressed() { + Session nodeSession = null; + try { + nodeSession = nodeRepository.login(); + Node home = CmsJcrUtils.getUserHome(nodeSession); + + Node remote = home.hasNode(ArgeoNames.ARGEO_REMOTE) ? home.getNode(ArgeoNames.ARGEO_REMOTE) + : home.addNode(ArgeoNames.ARGEO_REMOTE); + if (remote.hasNode(name.getText())) + throw new EclipseUiException("There is already a remote repository named " + name.getText()); + Node remoteRepository = remote.addNode(name.getText(), ArgeoTypes.ARGEO_REMOTE_REPOSITORY); + remoteRepository.setProperty(ArgeoNames.ARGEO_URI, uri.getText()); + remoteRepository.setProperty(ArgeoNames.ARGEO_USER_ID, username.getText()); + nodeSession.save(); + if (saveInKeyring.getSelection()) { + String pwdPath = remoteRepository.getPath() + '/' + ArgeoNames.ARGEO_PASSWORD; + keyring.set(pwdPath, password.getText().toCharArray()); + } + nodeSession.save(); + MessageDialog.openInformation(getParentShell(), "Repository Added", + "Remote repository '" + username.getText() + "@" + uri.getText() + "' added"); + + super.okPressed(); + } catch (Exception e) { + ErrorFeedback.show("Cannot add remote repository", e); + } finally { + JcrUtils.logoutQuietly(nodeSession); + } + } + + /** Creates label and text. */ + protected Text createLT(Composite parent, String label, String initial) { + new Label(parent, SWT.NONE).setText(label); + Text text = new Text(parent, SWT.SINGLE | SWT.LEAD | SWT.BORDER); + text.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + text.setText(initial); + return text; + } + + /** Creates label and check. */ + protected Button createLC(Composite parent, String label, Boolean initial) { + new Label(parent, SWT.NONE).setText(label); + Button check = new Button(parent, SWT.CHECK); + check.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + check.setSelection(initial); + return check; + } + + protected Text createLP(Composite parent, String label) { + new Label(parent, SWT.NONE).setText(label); + Text text = new Text(parent, SWT.SINGLE | SWT.LEAD | SWT.BORDER | SWT.PASSWORD); + text.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + return text; + } + } +} diff --git a/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/jcr/handlers/DeleteNodes.java b/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/jcr/handlers/DeleteNodes.java new file mode 100644 index 0000000..b8de06b --- /dev/null +++ b/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/jcr/handlers/DeleteNodes.java @@ -0,0 +1,95 @@ +package org.argeo.cms.e4.jcr.handlers; + +import java.util.List; + +import javax.inject.Named; +import javax.jcr.Node; +import javax.jcr.RepositoryException; + +import org.argeo.cms.e4.jcr.JcrBrowserView; +import org.argeo.cms.ui.jcr.model.SingleJcrNodeElem; +import org.argeo.cms.ui.jcr.model.WorkspaceElem; +import org.argeo.cms.ux.widgets.TreeParent; +import org.argeo.eclipse.ui.EclipseUiException; +import org.argeo.eclipse.ui.dialogs.ErrorFeedback; +import org.eclipse.e4.core.di.annotations.Execute; +import org.eclipse.e4.ui.model.application.ui.basic.MPart; +import org.eclipse.e4.ui.services.IServiceConstants; +import org.eclipse.e4.ui.workbench.modeling.ESelectionService; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.swt.widgets.Display; + +/** + * Delete the selected nodes: both in the JCR repository and in the UI view. + * Warning no check is done, except implementation dependent native checks, + * handle with care. + * + * This handler is still 'hard linked' to a GenericJcrBrowser view to enable + * correct tree refresh when a node is added. This must be corrected in future + * versions. + */ +public class DeleteNodes { + @Execute + public void execute(@Named(IServiceConstants.ACTIVE_PART) MPart part, ESelectionService selectionService) { + List selection = (List) selectionService.getSelection(); + if (selection == null) + return; + + JcrBrowserView view = (JcrBrowserView) part.getObject(); + + // confirmation + StringBuffer buf = new StringBuffer(""); + for (Object o : selection) { + SingleJcrNodeElem sjn = (SingleJcrNodeElem) o; + buf.append(sjn.getName()).append(' '); + } + Boolean doRemove = MessageDialog.openConfirm(Display.getCurrent().getActiveShell(), "Confirm deletion", + "Do you want to delete " + buf + "?"); + + // operation + if (doRemove) { + SingleJcrNodeElem ancestor = null; + WorkspaceElem rootAncestor = null; + try { + for (Object obj : selection) { + if (obj instanceof SingleJcrNodeElem) { + // Cache objects + SingleJcrNodeElem sjn = (SingleJcrNodeElem) obj; + TreeParent tp = (TreeParent) sjn.getParent(); + Node node = sjn.getNode(); + + // Jcr Remove + node.remove(); + node.getSession().save(); + // UI remove + tp.removeChild(sjn); + + // Check if the parent is the root node + if (tp instanceof WorkspaceElem) + rootAncestor = (WorkspaceElem) tp; + else + ancestor = getOlder(ancestor, (SingleJcrNodeElem) tp); + } + } + if (rootAncestor != null) + view.nodeRemoved(rootAncestor); + else if (ancestor != null) + view.nodeRemoved(ancestor); + } catch (Exception e) { + ErrorFeedback.show("Cannot delete selected node ", e); + } + } + } + + private SingleJcrNodeElem getOlder(SingleJcrNodeElem A, SingleJcrNodeElem B) { + try { + if (A == null) + return B == null ? null : B; + // Todo enhanced this method + else + return A.getNode().getDepth() <= B.getNode().getDepth() ? A : B; + } catch (RepositoryException re) { + throw new EclipseUiException("Cannot find ancestor", re); + } + } +} diff --git a/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/jcr/handlers/Refresh.java b/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/jcr/handlers/Refresh.java new file mode 100644 index 0000000..036e70a --- /dev/null +++ b/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/jcr/handlers/Refresh.java @@ -0,0 +1,43 @@ +package org.argeo.cms.e4.jcr.handlers; + +import java.util.List; + +import javax.inject.Named; + +import org.argeo.cms.e4.jcr.JcrBrowserView; +import org.argeo.cms.ui.jcr.JcrBrowserUtils; +import org.argeo.cms.ux.widgets.TreeParent; +import org.eclipse.e4.core.di.annotations.Execute; +import org.eclipse.e4.ui.model.application.ui.basic.MPart; +import org.eclipse.e4.ui.services.IServiceConstants; +import org.eclipse.e4.ui.workbench.modeling.EPartService; +import org.eclipse.e4.ui.workbench.modeling.ESelectionService; + +/** + * Force the selected objects of the active view to be refreshed doing the + * following: + *
    + *
  1. The model objects are recomputed
  2. + *
  3. the view is refreshed
  4. + *
+ */ +public class Refresh { + + @Execute + public void execute(@Named(IServiceConstants.ACTIVE_PART) MPart part, EPartService partService, + ESelectionService selectionService) { + + JcrBrowserView view = (JcrBrowserView) part.getObject(); + List selection = (List) selectionService.getSelection(); + + if (selection != null && !selection.isEmpty()) { + for (Object obj : selection) + if (obj instanceof TreeParent) { + TreeParent tp = (TreeParent) obj; + JcrBrowserUtils.forceRefreshIfNeeded(tp); + view.refresh(obj); + } + } else if (view instanceof JcrBrowserView) + view.refresh(null); // force full refresh + } +} diff --git a/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/jcr/handlers/RenameNode.java b/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/jcr/handlers/RenameNode.java new file mode 100644 index 0000000..97674ab --- /dev/null +++ b/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/jcr/handlers/RenameNode.java @@ -0,0 +1,59 @@ +package org.argeo.cms.e4.jcr.handlers; + +import java.util.List; + +import javax.inject.Named; +import javax.jcr.Node; +import javax.jcr.RepositoryException; +import javax.jcr.Session; + +import org.argeo.cms.e4.jcr.JcrBrowserView; +import org.argeo.cms.ui.jcr.model.SingleJcrNodeElem; +import org.argeo.eclipse.ui.EclipseUiException; +import org.argeo.eclipse.ui.dialogs.SingleValue; +import org.argeo.jcr.JcrUtils; +import org.eclipse.e4.core.di.annotations.Execute; +import org.eclipse.e4.ui.model.application.ui.basic.MPart; +import org.eclipse.e4.ui.services.IServiceConstants; +import org.eclipse.e4.ui.workbench.modeling.EPartService; +import org.eclipse.e4.ui.workbench.modeling.ESelectionService; + +/** + * Canonically call JCR Session#move(String, String) on the first element + * returned by HandlerUtil#getActiveWorkbenchWindow() + * (...getActivePage().getSelection()), if it is a {@link SingleJcrNodeElem}. + * The user must then fill a new name in and confirm + */ +public class RenameNode { + @Execute + public void execute(@Named(IServiceConstants.ACTIVE_PART) MPart part, EPartService partService, + ESelectionService selectionService) { + List selection = (List) selectionService.getSelection(); + if (selection == null || selection.size() != 1) + return; + JcrBrowserView view = (JcrBrowserView) part.getObject(); + + Object element = selection.get(0); + if (element instanceof SingleJcrNodeElem) { + SingleJcrNodeElem sjn = (SingleJcrNodeElem) element; + Node node = sjn.getNode(); + Session session = null; + String newName = null; + String oldPath = null; + try { + newName = SingleValue.ask("New node name", "Please provide a new name for [" + node.getName() + "]"); + // TODO sanity check and user feedback + newName = JcrUtils.replaceInvalidChars(newName); + oldPath = node.getPath(); + session = node.getSession(); + session.move(oldPath, JcrUtils.parentPath(oldPath) + "/" + newName); + session.save(); + + // Manually refresh the browser view. Must be enhanced + view.refresh(sjn); + } catch (RepositoryException e) { + throw new EclipseUiException("Unable to rename " + node + " to " + newName, e); + } + } + } +} diff --git a/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/jcr/handlers/package-info.java b/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/jcr/handlers/package-info.java new file mode 100644 index 0000000..4e075e2 --- /dev/null +++ b/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/jcr/handlers/package-info.java @@ -0,0 +1,2 @@ +/** JCR browser handlers. */ +package org.argeo.cms.e4.jcr.handlers; \ No newline at end of file diff --git a/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/jcr/package-info.java b/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/jcr/package-info.java new file mode 100644 index 0000000..3e92fb0 --- /dev/null +++ b/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/jcr/package-info.java @@ -0,0 +1,2 @@ +/** JCR browser perspective. */ +package org.argeo.cms.e4.jcr; \ No newline at end of file diff --git a/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/AbstractRoleEditor.java b/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/AbstractRoleEditor.java new file mode 100644 index 0000000..137f762 --- /dev/null +++ b/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/AbstractRoleEditor.java @@ -0,0 +1,287 @@ +package org.argeo.cms.e4.users; + +import java.util.ArrayList; +import java.util.List; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import javax.inject.Inject; + +import org.argeo.cms.auth.UserAdminUtils; +import org.argeo.cms.ui.eclipse.forms.AbstractFormPart; +import org.argeo.cms.ui.eclipse.forms.IManagedForm; +import org.argeo.cms.ui.eclipse.forms.ManagedForm; +import org.argeo.eclipse.ui.EclipseUiUtils; +import org.argeo.util.naming.LdapAttrs; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.e4.ui.di.Persist; +import org.eclipse.e4.ui.model.application.ui.basic.MPart; +import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.ScrolledComposite; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Text; +import org.osgi.service.useradmin.Authorization; +import org.osgi.service.useradmin.Role; +import org.osgi.service.useradmin.User; +import org.osgi.service.useradmin.UserAdmin; +import org.osgi.service.useradmin.UserAdminEvent; + +/** Editor for a user, might be a user or a group. */ +public abstract class AbstractRoleEditor { + + // public final static String USER_EDITOR_ID = WorkbenchUiPlugin.PLUGIN_ID + + // ".userEditor"; + // public final static String GROUP_EDITOR_ID = WorkbenchUiPlugin.PLUGIN_ID + + // ".groupEditor"; + + /* DEPENDENCY INJECTION */ + @Inject + protected UserAdminWrapper userAdminWrapper; + + @Inject + private MPart mPart; + + // @Inject + // Composite parent; + + private UserAdmin userAdmin; + + // Context + private User user; + private String username; + + private NameChangeListener listener; + + private ManagedForm managedForm; + + // public void init(IEditorSite site, IEditorInput input) throws + // PartInitException { + @PostConstruct + public void init(Composite parent) { + this.userAdmin = userAdminWrapper.getUserAdmin(); + username = mPart.getPersistedState().get(LdapAttrs.uid.name()); + user = (User) userAdmin.getRole(username); + + listener = new NameChangeListener(Display.getCurrent()); + userAdminWrapper.addListener(listener); + updateEditorTitle(null); + + managedForm = new ManagedForm(parent) { + + @Override + public void staleStateChanged() { + refresh(); + } + }; + ScrolledComposite scrolled = managedForm.getForm(); + Composite body = new Composite(scrolled, SWT.NONE); + scrolled.setContent(body); + createUi(body); + managedForm.refresh(); + } + + abstract void createUi(Composite parent); + + /** + * returns the list of all authorizations for the given user or of the current + * displayed user if parameter is null + */ + protected List getFlatGroups(User aUser) { + Authorization currAuth; + if (aUser == null) + currAuth = userAdmin.getAuthorization(this.user); + else + currAuth = userAdmin.getAuthorization(aUser); + + String[] roles = currAuth.getRoles(); + + List groups = new ArrayList(); + for (String roleStr : roles) { + User currRole = (User) userAdmin.getRole(roleStr); + if (currRole != null && !groups.contains(currRole)) + groups.add(currRole); + } + return groups; + } + + protected IManagedForm getManagedForm() { + return managedForm; + } + + /** Exposes the user (or group) that is displayed by the current editor */ + protected User getDisplayedUser() { + return user; + } + + private void setDisplayedUser(User user) { + this.user = user; + } + + void updateEditorTitle(String title) { + if (title == null) { + String commonName = UserAdminUtils.getProperty(user, LdapAttrs.cn.name()); + title = "".equals(commonName) ? user.getName() : commonName; + } + setPartName(title); + } + + protected void setPartName(String name) { + mPart.setLabel(name); + } + + // protected void addPages() { + // try { + // if (user.getType() == Role.GROUP) + // addPage(new GroupMainPage(this, userAdminWrapper, repository, nodeInstance)); + // else + // addPage(new UserMainPage(this, userAdminWrapper)); + // } catch (Exception e) { + // throw new CmsException("Cannot add pages", e); + // } + // } + + @Persist + public void doSave(IProgressMonitor monitor) { + userAdminWrapper.beginTransactionIfNeeded(); + commitPages(true); + userAdminWrapper.commitOrNotifyTransactionStateChange(); + // firePropertyChange(PROP_DIRTY); + userAdminWrapper.notifyListeners(new UserAdminEvent(null, UserAdminEvent.ROLE_REMOVED, user)); + } + + protected void commitPages(boolean b) { + managedForm.commit(b); + } + + @PreDestroy + public void dispose() { + userAdminWrapper.removeListener(listener); + managedForm.dispose(); + } + + // CONTROLERS FOR THIS EDITOR AND ITS PAGES + + class NameChangeListener extends UiUserAdminListener { + public NameChangeListener(Display display) { + super(display); + } + + @Override + public void roleChangedToUiThread(UserAdminEvent event) { + Role changedRole = event.getRole(); + if (changedRole == null || changedRole.equals(user)) { + updateEditorTitle(null); + User reloadedUser = (User) userAdminWrapper.getUserAdmin().getRole(user.getName()); + setDisplayedUser(reloadedUser); + } + } + } + + class MainInfoListener extends UiUserAdminListener { + private final AbstractFormPart part; + + public MainInfoListener(Display display, AbstractFormPart part) { + super(display); + this.part = part; + } + + @Override + public void roleChangedToUiThread(UserAdminEvent event) { + // Rollback + if (event.getRole() == null) + part.markStale(); + } + } + + class GroupChangeListener extends UiUserAdminListener { + private final AbstractFormPart part; + + public GroupChangeListener(Display display, AbstractFormPart part) { + super(display); + this.part = part; + } + + @Override + public void roleChangedToUiThread(UserAdminEvent event) { + // always mark as stale + part.markStale(); + } + } + + /** Registers a listener that will notify this part */ + class FormPartML implements ModifyListener { + private static final long serialVersionUID = 6299808129505381333L; + private AbstractFormPart formPart; + + public FormPartML(AbstractFormPart generalPart) { + this.formPart = generalPart; + } + + public void modifyText(ModifyEvent e) { + // Discard event when the control does not have the focus, typically + // to avoid all editors being marked as dirty during a Rollback + if (((Control) e.widget).isFocusControl()) + formPart.markDirty(); + } + } + + /* DEPENDENCY INJECTION */ + public void setUserAdminWrapper(UserAdminWrapper userAdminWrapper) { + this.userAdminWrapper = userAdminWrapper; + } + + /** Creates label and multiline text. */ + Text createLMT(Composite parent, String label, String value) { + Label lbl = new Label(parent, SWT.NONE); + lbl.setText(label); + lbl.setLayoutData(new GridData(SWT.LEAD, SWT.CENTER, false, false)); + Text text = new Text(parent, SWT.NONE); + text.setText(value); + text.setLayoutData(new GridData(SWT.LEAD, SWT.FILL, true, true)); + return text; + } + + /** Creates label and password. */ + Text createLP(Composite parent, String label, String value) { + Label lbl = new Label(parent, SWT.NONE); + lbl.setText(label); + lbl.setLayoutData(new GridData(SWT.LEAD, SWT.CENTER, false, false)); + Text text = new Text(parent, SWT.PASSWORD | SWT.BORDER); + text.setText(value); + text.setLayoutData(new GridData(SWT.LEAD, SWT.FILL, true, false)); + return text; + } + + /** Creates label and text. */ + Text createLT(Composite parent, String label, String value) { + Label lbl = new Label(parent, SWT.NONE); + lbl.setText(label); + lbl.setLayoutData(new GridData(SWT.LEAD, SWT.CENTER, false, false)); + lbl.setFont(EclipseUiUtils.getBoldFont(parent)); + Text text = new Text(parent, SWT.BORDER); + text.setText(value); + text.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + // CmsUiUtils.style(text, CmsWorkbenchStyles.WORKBENCH_FORM_TEXT); + return text; + } + + Text createReadOnlyLT(Composite parent, String label, String value) { + Label lbl = new Label(parent, SWT.NONE); + lbl.setText(label); + lbl.setLayoutData(new GridData(SWT.LEAD, SWT.CENTER, false, false)); + lbl.setFont(EclipseUiUtils.getBoldFont(parent)); + Text text = new Text(parent, SWT.NONE); + text.setText(value); + text.setLayoutData(new GridData(SWT.LEAD, SWT.FILL, true, false)); + text.setEditable(false); + // CmsUiUtils.style(text, CmsWorkbenchStyles.WORKBENCH_FORM_TEXT); + return text; + } + +} diff --git a/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/CmsWorkbenchStyles.java b/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/CmsWorkbenchStyles.java new file mode 100644 index 0000000..07df312 --- /dev/null +++ b/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/CmsWorkbenchStyles.java @@ -0,0 +1,8 @@ +package org.argeo.cms.e4.users; + +/** Centralize the declaration of Workbench specific CSS Styles */ +interface CmsWorkbenchStyles { + + // Specific People layouting + String WORKBENCH_FORM_TEXT = "workbench_form_text"; +} diff --git a/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/GroupEditor.java b/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/GroupEditor.java new file mode 100644 index 0000000..d54f8bc --- /dev/null +++ b/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/GroupEditor.java @@ -0,0 +1,566 @@ +package org.argeo.cms.e4.users; + +import static org.argeo.api.cms.CmsContext.WORKGROUP; +import static org.argeo.cms.auth.UserAdminUtils.setProperty; +import static org.argeo.util.naming.LdapAttrs.businessCategory; +import static org.argeo.util.naming.LdapAttrs.description; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import javax.annotation.PreDestroy; +import javax.inject.Inject; +import javax.jcr.Node; +import javax.jcr.Repository; +import javax.jcr.RepositoryException; +import javax.jcr.Session; + +import org.argeo.api.cms.CmsConstants; +import org.argeo.api.cms.CmsContext; +import org.argeo.cms.auth.UserAdminUtils; +import org.argeo.cms.e4.users.providers.CommonNameLP; +import org.argeo.cms.e4.users.providers.MailLP; +import org.argeo.cms.e4.users.providers.RoleIconLP; +import org.argeo.cms.e4.users.providers.UserFilter; +import org.argeo.cms.jcr.CmsJcrUtils; +import org.argeo.cms.swt.CmsSwtUtils; +import org.argeo.cms.ui.eclipse.forms.AbstractFormPart; +import org.argeo.cms.ui.eclipse.forms.IManagedForm; +import org.argeo.eclipse.ui.ColumnDefinition; +import org.argeo.eclipse.ui.EclipseUiUtils; +import org.argeo.eclipse.ui.parts.LdifUsersTable; +import org.argeo.jcr.JcrException; +import org.argeo.jcr.JcrUtils; +import org.argeo.util.naming.LdapAttrs; +import org.argeo.util.transaction.WorkTransaction; +import org.eclipse.e4.ui.workbench.modeling.EPartService; +import org.eclipse.jface.action.Action; +import org.eclipse.jface.action.ToolBarManager; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.viewers.TableViewer; +import org.eclipse.jface.viewers.ViewerDropAdapter; +import org.eclipse.swt.SWT; +import org.eclipse.swt.dnd.DND; +import org.eclipse.swt.dnd.DropTargetEvent; +import org.eclipse.swt.dnd.TextTransfer; +import org.eclipse.swt.dnd.Transfer; +import org.eclipse.swt.dnd.TransferData; +import org.eclipse.swt.events.ModifyListener; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Link; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Text; +import org.eclipse.swt.widgets.ToolBar; +import org.osgi.service.useradmin.Group; +import org.osgi.service.useradmin.Role; +//import org.eclipse.ui.forms.AbstractFormPart; +//import org.eclipse.ui.forms.IManagedForm; +//import org.eclipse.ui.forms.SectionPart; +//import org.eclipse.ui.forms.editor.FormEditor; +//import org.eclipse.ui.forms.editor.FormPage; +//import org.eclipse.ui.forms.widgets.FormToolkit; +//import org.eclipse.ui.forms.widgets.ScrolledForm; +//import org.eclipse.ui.forms.widgets.Section; +import org.osgi.service.useradmin.User; +import org.osgi.service.useradmin.UserAdmin; +import org.osgi.service.useradmin.UserAdminEvent; + +/** Display/edit main properties of a given group */ +public class GroupEditor extends AbstractRoleEditor { + // final static String ID = "GroupEditor.mainPage"; + + @Inject + private EPartService partService; + + // private final UserEditor editor; + @Inject + private Repository repository; + @Inject + private CmsContext nodeInstance; + // private final UserAdminWrapper userAdminWrapper; + private Session groupsSession; + + // public GroupMainPage(FormEditor editor, UserAdminWrapper userAdminWrapper, + // Repository repository, + // NodeInstance nodeInstance) { + // super(editor, ID, "Main"); + // try { + // session = repository.login(); + // } catch (RepositoryException e) { + // throw new CmsException("Cannot retrieve session of in MainGroupPage + // constructor", e); + // } + // this.editor = (UserEditor) editor; + // this.userAdminWrapper = userAdminWrapper; + // this.nodeInstance = nodeInstance; + // } + + // protected void createFormContent(final IManagedForm mf) { + // ScrolledForm form = mf.getForm(); + // Composite body = form.getBody(); + // GridLayout mainLayout = new GridLayout(); + // body.setLayout(mainLayout); + // Group group = (Group) editor.getDisplayedUser(); + // appendOverviewPart(body, group); + // appendMembersPart(body, group); + // } + + @Override + protected void createUi(Composite parent) { + try { + groupsSession = repository.login(CmsConstants.SRV_WORKSPACE); + } catch (RepositoryException e) { + throw new JcrException("Cannot retrieve session", e); + } + // ScrolledForm form = mf.getForm(); + // Composite body = form.getBody(); + // Composite body = new Composite(parent, SWT.NONE); + Composite body = parent; + GridLayout mainLayout = new GridLayout(); + body.setLayout(mainLayout); + Group group = (Group) getDisplayedUser(); + appendOverviewPart(body, group); + appendMembersPart(body, group); + } + + @PreDestroy + public void dispose() { + JcrUtils.logoutQuietly(groupsSession); + super.dispose(); + } + + /** Creates the general section */ + protected void appendOverviewPart(final Composite parent, final Group group) { + Composite body = new Composite(parent, SWT.NONE); + // GridLayout layout = new GridLayout(5, false); + GridLayout layout = new GridLayout(2, false); + body.setLayout(layout); + body.setLayoutData(CmsSwtUtils.fillWidth()); + + String cn = UserAdminUtils.getProperty(group, LdapAttrs.cn.name()); + createReadOnlyLT(body, "Name", cn); + createReadOnlyLT(body, "DN", group.getName()); + createReadOnlyLT(body, "Domain", UserAdminUtils.getDomainName(group)); + + // Description + Label descLbl = new Label(body, SWT.LEAD); + descLbl.setFont(EclipseUiUtils.getBoldFont(body)); + descLbl.setText("Description"); + descLbl.setLayoutData(new GridData(SWT.LEAD, SWT.CENTER, true, false, 2, 1)); + final Text descTxt = new Text(body, SWT.LEAD | SWT.MULTI | SWT.WRAP | SWT.BORDER); + GridData gd = EclipseUiUtils.fillWidth(); + gd.heightHint = 50; + gd.horizontalSpan = 2; + descTxt.setLayoutData(gd); + + // Mark as workgroup + Link markAsWorkgroupLk = new Link(body, SWT.NONE); + markAsWorkgroupLk.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false, 2, 1)); + + // create form part (controller) + final AbstractFormPart part = new AbstractFormPart() { + + private MainInfoListener listener; + + @Override + public void initialize(IManagedForm form) { + super.initialize(form); + listener = new MainInfoListener(parent.getDisplay(), this); + userAdminWrapper.addListener(listener); + } + + @Override + public void dispose() { + userAdminWrapper.removeListener(listener); + super.dispose(); + } + + public void commit(boolean onSave) { + // group.getProperties().put(LdapAttrs.description.name(), descTxt.getText()); + setProperty(group, description, descTxt.getText()); + super.commit(onSave); + } + + @Override + public void refresh() { + // dnTxt.setText(group.getName()); + // cnTxt.setText(UserAdminUtils.getProperty(group, LdapAttrs.cn.name())); + descTxt.setText(UserAdminUtils.getProperty(group, LdapAttrs.description.name())); + Node workgroupHome = CmsJcrUtils.getGroupHome(groupsSession, cn); + if (workgroupHome == null) + markAsWorkgroupLk.setText("Mark as workgroup"); + else + markAsWorkgroupLk.setText("Configured as workgroup"); + parent.layout(true, true); + super.refresh(); + } + }; + + markAsWorkgroupLk.addSelectionListener(new SelectionAdapter() { + private static final long serialVersionUID = -6439340898096365078L; + + @Override + public void widgetSelected(SelectionEvent e) { + + boolean confirmed = MessageDialog.openConfirm(parent.getShell(), "Mark as workgroup", + "Are you sure you want to mark " + cn + " as being a workgroup? "); + if (confirmed) { + Node workgroupHome = CmsJcrUtils.getGroupHome(groupsSession, cn); + if (workgroupHome != null) + return; // already marked as workgroup, do nothing + else { + // improve transaction management + userAdminWrapper.beginTransactionIfNeeded(); + nodeInstance.createWorkgroup(group.getName()); + setProperty(group, businessCategory, WORKGROUP); + userAdminWrapper.commitOrNotifyTransactionStateChange(); + userAdminWrapper.notifyListeners(new UserAdminEvent(null, UserAdminEvent.ROLE_CHANGED, group)); + part.refresh(); + } + } + } + }); + + ModifyListener defaultListener = new FormPartML(part); + descTxt.addModifyListener(defaultListener); + getManagedForm().addPart(part); + } + + /** Filtered table with members. Has drag and drop ability */ + protected void appendMembersPart(Composite parent, Group group) { + // Section section = tk.createSection(parent, Section.TITLE_BAR); + // section.setText("Members"); + // section.setLayoutData(EclipseUiUtils.fillAll()); + + Composite body = new Composite(parent, SWT.BORDER); + body.setLayout(new GridLayout()); + // section.setClient(body); + body.setLayoutData(EclipseUiUtils.fillAll()); + + // Define the displayed columns + List columnDefs = new ArrayList(); + columnDefs.add(new ColumnDefinition(new RoleIconLP(), "", 0, 24)); + columnDefs.add(new ColumnDefinition(new CommonNameLP(), "Name", 150)); + columnDefs.add(new ColumnDefinition(new MailLP(), "Mail", 150)); + // columnDefs.add(new ColumnDefinition(new UserNameLP(), "Distinguished Name", + // 240)); + + // Create and configure the table + LdifUsersTable userViewerCmp = new MyUserTableViewer(body, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL, + userAdminWrapper.getUserAdmin()); + + userViewerCmp.setColumnDefinitions(columnDefs); + userViewerCmp.populate(true, false); + userViewerCmp.setLayoutData(EclipseUiUtils.fillAll()); + + // Controllers + TableViewer userViewer = userViewerCmp.getTableViewer(); + userViewer.addDoubleClickListener(new UserTableDefaultDClickListener(partService)); + int operations = DND.DROP_COPY | DND.DROP_MOVE; + Transfer[] tt = new Transfer[] { TextTransfer.getInstance() }; + userViewer.addDropSupport(operations, tt, + new GroupDropListener(userAdminWrapper, userViewerCmp, (Group) getDisplayedUser())); + + AbstractFormPart part = new GroupMembersPart(userViewerCmp); + getManagedForm().addPart(part); + + // remove button + // addRemoveAbility(toolBarManager, userViewerCmp.getTableViewer(), group); + Action action = new RemoveMembershipAction(userViewer, group, "Remove selected items from this group", + SecurityAdminImages.ICON_REMOVE_DESC); + + ToolBarManager toolBarManager = new ToolBarManager(SWT.FLAT); + ToolBar toolBar = toolBarManager.createControl(body); + toolBar.setLayoutData(CmsSwtUtils.fillWidth()); + + toolBarManager.add(action); + toolBarManager.update(true); + + } + + // private LdifUsersTable createMemberPart(Composite parent, Group group) { + // + // // Define the displayed columns + // List columnDefs = new ArrayList(); + // columnDefs.add(new ColumnDefinition(new RoleIconLP(), "", 0, 24)); + // columnDefs.add(new ColumnDefinition(new CommonNameLP(), "Name", 150)); + // columnDefs.add(new ColumnDefinition(new MailLP(), "Mail", 150)); + // // columnDefs.add(new ColumnDefinition(new UserNameLP(), "Distinguished + // Name", + // // 240)); + // + // // Create and configure the table + // LdifUsersTable userViewerCmp = new MyUserTableViewer(parent, SWT.MULTI | + // SWT.H_SCROLL | SWT.V_SCROLL, + // userAdminWrapper.getUserAdmin()); + // + // userViewerCmp.setColumnDefinitions(columnDefs); + // userViewerCmp.populate(true, false); + // userViewerCmp.setLayoutData(EclipseUiUtils.fillAll()); + // + // // Controllers + // TableViewer userViewer = userViewerCmp.getTableViewer(); + // userViewer.addDoubleClickListener(new + // UserTableDefaultDClickListener(partService)); + // int operations = DND.DROP_COPY | DND.DROP_MOVE; + // Transfer[] tt = new Transfer[] { TextTransfer.getInstance() }; + // userViewer.addDropSupport(operations, tt, + // new GroupDropListener(userAdminWrapper, userViewerCmp, (Group) + // getDisplayedUser())); + // + // // userViewerCmp.refresh(); + // return userViewerCmp; + // } + + // Local viewers + private class MyUserTableViewer extends LdifUsersTable { + private static final long serialVersionUID = 8467999509931900367L; + + private final UserFilter userFilter; + + public MyUserTableViewer(Composite parent, int style, UserAdmin userAdmin) { + super(parent, style, true); + userFilter = new UserFilter(); + + } + + @Override + protected List listFilteredElements(String filter) { + // reload user and set it in the editor + Group group = (Group) getDisplayedUser(); + Role[] roles = group.getMembers(); + List users = new ArrayList(); + userFilter.setSearchText(filter); + // userFilter.setShowSystemRole(true); + for (Role role : roles) + // if (role.getType() == Role.GROUP) + if (userFilter.select(null, null, role)) + users.add((User) role); + return users; + } + } + + // private void addRemoveAbility(ToolBarManager toolBarManager, TableViewer + // userViewer, Group group) { + // // Section section = sectionPart.getSection(); + // // ToolBarManager toolBarManager = new ToolBarManager(SWT.FLAT); + // // ToolBar toolbar = toolBarManager.createControl(parent); + // // ToolBar toolbar = toolBarManager.getControl(); + // // final Cursor handCursor = new Cursor(toolbar.getDisplay(), + // SWT.CURSOR_HAND); + // // toolbar.setCursor(handCursor); + // // toolbar.addDisposeListener(new DisposeListener() { + // // private static final long serialVersionUID = 3882131405820522925L; + // // + // // public void widgetDisposed(DisposeEvent e) { + // // if ((handCursor != null) && (handCursor.isDisposed() == false)) { + // // handCursor.dispose(); + // // } + // // } + // // }); + // + // Action action = new RemoveMembershipAction(userViewer, group, "Remove + // selected items from this group", + // SecurityAdminImages.ICON_REMOVE_DESC); + // toolBarManager.add(action); + // toolBarManager.update(true); + // // section.setTextClient(toolbar); + // } + + private class RemoveMembershipAction extends Action { + private static final long serialVersionUID = -1337713097184522588L; + + private final TableViewer userViewer; + private final Group group; + + RemoveMembershipAction(TableViewer userViewer, Group group, String name, ImageDescriptor img) { + super(name, img); + this.userViewer = userViewer; + this.group = group; + } + + @Override + public void run() { + ISelection selection = userViewer.getSelection(); + if (selection.isEmpty()) + return; + + @SuppressWarnings("unchecked") + Iterator it = ((IStructuredSelection) selection).iterator(); + List users = new ArrayList(); + while (it.hasNext()) { + User currUser = it.next(); + users.add(currUser); + } + + userAdminWrapper.beginTransactionIfNeeded(); + for (User user : users) { + group.removeMember(user); + } + userAdminWrapper.commitOrNotifyTransactionStateChange(); + userAdminWrapper.notifyListeners(new UserAdminEvent(null, UserAdminEvent.ROLE_CHANGED, group)); + } + } + + // LOCAL CONTROLLERS + private class GroupMembersPart extends AbstractFormPart { + private final LdifUsersTable userViewer; + // private final Group group; + + private GroupChangeListener listener; + + public GroupMembersPart(LdifUsersTable userViewer) { + // super(section); + this.userViewer = userViewer; + // this.group = group; + } + + @Override + public void initialize(IManagedForm form) { + super.initialize(form); + listener = new GroupChangeListener(userViewer.getDisplay(), GroupMembersPart.this); + userAdminWrapper.addListener(listener); + } + + @Override + public void dispose() { + userAdminWrapper.removeListener(listener); + super.dispose(); + } + + @Override + public void refresh() { + userViewer.refresh(); + super.refresh(); + } + } + + /** + * Defines this table as being a potential target to add group membership + * (roles) to this group + */ + private class GroupDropListener extends ViewerDropAdapter { + private static final long serialVersionUID = 2893468717831451621L; + + private final UserAdminWrapper userAdminWrapper; + // private final LdifUsersTable myUserViewerCmp; + private final Group myGroup; + + public GroupDropListener(UserAdminWrapper userAdminWrapper, LdifUsersTable userTableViewerCmp, Group group) { + super(userTableViewerCmp.getTableViewer()); + this.userAdminWrapper = userAdminWrapper; + this.myGroup = group; + // this.myUserViewerCmp = userTableViewerCmp; + } + + @Override + public boolean validateDrop(Object target, int operation, TransferData transferType) { + // Target is always OK in a list only view + // TODO check if not a string + boolean validDrop = true; + return validDrop; + } + + @Override + public void drop(DropTargetEvent event) { + // TODO Is there an opportunity to perform the check before? + String newUserName = (String) event.data; + UserAdmin myUserAdmin = userAdminWrapper.getUserAdmin(); + Role role = myUserAdmin.getRole(newUserName); + if (role.getType() == Role.GROUP) { + Group newGroup = (Group) role; + Shell shell = getViewer().getControl().getShell(); + // Sanity checks + if (myGroup == newGroup) { // Equality + MessageDialog.openError(shell, "Forbidden addition ", "A group cannot be a member of itself."); + return; + } + + // Cycle + String myName = myGroup.getName(); + List myMemberships = getFlatGroups(myGroup); + if (myMemberships.contains(newGroup)) { + MessageDialog.openError(shell, "Forbidden addition: cycle", + "Cannot add " + newUserName + " to group " + myName + ". This would create a cycle"); + return; + } + + // Already member + List newGroupMemberships = getFlatGroups(newGroup); + if (newGroupMemberships.contains(myGroup)) { + MessageDialog.openError(shell, "Forbidden addition", + "Cannot add " + newUserName + " to group " + myName + ", this membership already exists"); + return; + } + userAdminWrapper.beginTransactionIfNeeded(); + myGroup.addMember(newGroup); + userAdminWrapper.commitOrNotifyTransactionStateChange(); + userAdminWrapper.notifyListeners(new UserAdminEvent(null, UserAdminEvent.ROLE_CHANGED, myGroup)); + } else if (role.getType() == Role.USER) { + // TODO check if the group is already member of this group + WorkTransaction transaction = userAdminWrapper.beginTransactionIfNeeded(); + User user = (User) role; + myGroup.addMember(user); + if (UserAdminWrapper.COMMIT_ON_SAVE) + try { + transaction.commit(); + } catch (Exception e) { + throw new IllegalStateException( + "Cannot commit transaction " + "after user group membership update", e); + } + userAdminWrapper.notifyListeners(new UserAdminEvent(null, UserAdminEvent.ROLE_CHANGED, myGroup)); + } + super.drop(event); + } + + @Override + public boolean performDrop(Object data) { + // myUserViewerCmp.refresh(); + return true; + } + } + + // LOCAL HELPERS + // private Composite addSection(FormToolkit tk, Composite parent) { + // Section section = tk.createSection(parent, SWT.NO_FOCUS); + // section.setLayoutData(EclipseUiUtils.fillWidth()); + // Composite body = tk.createComposite(section, SWT.WRAP); + // body.setLayoutData(EclipseUiUtils.fillAll()); + // section.setClient(body); + // return body; + // } + + /** Creates label and text. */ + // private Text createLT(Composite parent, String label, String value) { + // FormToolkit toolkit = getManagedForm().getToolkit(); + // Label lbl = toolkit.createLabel(parent, label); + // lbl.setLayoutData(new GridData(SWT.LEAD, SWT.CENTER, false, false)); + // lbl.setFont(EclipseUiUtils.getBoldFont(parent)); + // Text text = toolkit.createText(parent, value, SWT.BORDER); + // text.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); + // CmsUiUtils.style(text, CmsWorkbenchStyles.WORKBENCH_FORM_TEXT); + // return text; + // } + // + // Text createReadOnlyLT(Composite parent, String label, String value) { + // FormToolkit toolkit = getManagedForm().getToolkit(); + // Label lbl = toolkit.createLabel(parent, label); + // lbl.setLayoutData(new GridData(SWT.LEAD, SWT.CENTER, false, false)); + // lbl.setFont(EclipseUiUtils.getBoldFont(parent)); + // Text text = toolkit.createText(parent, value, SWT.NONE); + // text.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); + // text.setEditable(false); + // CmsUiUtils.style(text, CmsWorkbenchStyles.WORKBENCH_FORM_TEXT); + // return text; + // } + +} diff --git a/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/GroupsView.java b/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/GroupsView.java new file mode 100644 index 0000000..73e4f5d --- /dev/null +++ b/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/GroupsView.java @@ -0,0 +1,251 @@ +package org.argeo.cms.e4.users; + +import java.util.ArrayList; +import java.util.List; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import javax.inject.Inject; + +import org.argeo.api.cms.CmsConstants; +import org.argeo.api.cms.CmsLog; +import org.argeo.cms.auth.CurrentUser; +import org.argeo.cms.e4.users.providers.CommonNameLP; +import org.argeo.cms.e4.users.providers.DomainNameLP; +import org.argeo.cms.e4.users.providers.RoleIconLP; +import org.argeo.cms.e4.users.providers.UserDragListener; +import org.argeo.cms.swt.CmsException; +//import org.argeo.cms.ui.workbench.WorkbenchUiPlugin; +//import org.argeo.cms.ui.workbench.internal.useradmin.UiUserAdminListener; +//import org.argeo.cms.ui.workbench.internal.useradmin.UserAdminWrapper; +//import org.argeo.cms.ui.workbench.internal.useradmin.providers.CommonNameLP; +//import org.argeo.cms.ui.workbench.internal.useradmin.providers.DomainNameLP; +//import org.argeo.cms.ui.workbench.internal.useradmin.providers.RoleIconLP; +//import org.argeo.cms.ui.workbench.internal.useradmin.providers.UserDragListener; +//import org.argeo.cms.ui.workbench.internal.useradmin.providers.UserTableDefaultDClickListener; +import org.argeo.eclipse.ui.ColumnDefinition; +import org.argeo.eclipse.ui.EclipseUiUtils; +import org.argeo.eclipse.ui.parts.LdifUsersTable; +import org.argeo.util.naming.LdapAttrs; +import org.argeo.util.naming.LdapObjs; +import org.eclipse.e4.ui.di.Focus; +import org.eclipse.e4.ui.workbench.modeling.EPartService; +import org.eclipse.e4.ui.workbench.modeling.ESelectionService; +import org.eclipse.jface.viewers.ISelectionChangedListener; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.viewers.SelectionChangedEvent; +import org.eclipse.jface.viewers.TableViewer; +import org.eclipse.swt.SWT; +import org.eclipse.swt.dnd.DND; +import org.eclipse.swt.dnd.TextTransfer; +import org.eclipse.swt.dnd.Transfer; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Display; +//import org.eclipse.ui.part.ViewPart; +import org.osgi.framework.InvalidSyntaxException; +import org.osgi.service.useradmin.Role; +import org.osgi.service.useradmin.User; +import org.osgi.service.useradmin.UserAdminEvent; +import org.osgi.service.useradmin.UserAdminListener; + +/** List all groups with filter */ +public class GroupsView { + private final static CmsLog log = CmsLog.getLog(GroupsView.class); + // public final static String ID = WorkbenchUiPlugin.PLUGIN_ID + ".groupsView"; + + @Inject + private EPartService partService; + @Inject + private UserAdminWrapper userAdminWrapper; + + // UI Objects + private LdifUsersTable groupTableViewerCmp; + private TableViewer userViewer; + private List columnDefs = new ArrayList(); + + private UserAdminListener listener; + + @PostConstruct + public void createPartControl(Composite parent, ESelectionService selectionService) { + parent.setLayout(EclipseUiUtils.noSpaceGridLayout()); + + // boolean isAdmin = CurrentUser.isInRole(NodeConstants.ROLE_ADMIN); + + // Define the displayed columns + columnDefs.add(new ColumnDefinition(new RoleIconLP(), "", 19)); + columnDefs.add(new ColumnDefinition(new CommonNameLP(), "Name", 150)); + columnDefs.add(new ColumnDefinition(new DomainNameLP(), "Domain", 100)); + // Only show technical DN to admin + // if (isAdmin) + // columnDefs.add(new ColumnDefinition(new UserNameLP(), + // "Distinguished Name", 300)); + + // Create and configure the table + groupTableViewerCmp = new MyUserTableViewer(parent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL); + + groupTableViewerCmp.setColumnDefinitions(columnDefs); + // if (isAdmin) + // groupTableViewerCmp.populateWithStaticFilters(false, false); + // else + groupTableViewerCmp.populate(true, false); + + groupTableViewerCmp.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + + // Links + userViewer = groupTableViewerCmp.getTableViewer(); + userViewer.addDoubleClickListener(new UserTableDefaultDClickListener(partService)); + // getViewSite().setSelectionProvider(userViewer); + userViewer.addSelectionChangedListener(new ISelectionChangedListener() { + + @Override + public void selectionChanged(SelectionChangedEvent event) { + IStructuredSelection selection = (IStructuredSelection) event.getSelection(); + selectionService.setSelection(selection.toList()); + } + }); + + // Really? + groupTableViewerCmp.refresh(); + + // Drag and drop + int operations = DND.DROP_COPY | DND.DROP_MOVE; + Transfer[] tt = new Transfer[] { TextTransfer.getInstance() }; + userViewer.addDragSupport(operations, tt, new UserDragListener(userViewer)); + + // // Register a useradmin listener + // listener = new UserAdminListener() { + // @Override + // public void roleChanged(UserAdminEvent event) { + // if (userViewer != null && !userViewer.getTable().isDisposed()) + // refresh(); + // } + // }; + // userAdminWrapper.addListener(listener); + // } + + // Register a useradmin listener + listener = new MyUiUAListener(parent.getDisplay()); + userAdminWrapper.addListener(listener); + } + + private class MyUiUAListener extends UiUserAdminListener { + public MyUiUAListener(Display display) { + super(display); + } + + @Override + public void roleChangedToUiThread(UserAdminEvent event) { + if (userViewer != null && !userViewer.getTable().isDisposed()) + refresh(); + } + } + + private class MyUserTableViewer extends LdifUsersTable { + private static final long serialVersionUID = 8467999509931900367L; + + private boolean showSystemRoles = true; + + private final String[] knownProps = { LdapAttrs.uid.name(), LdapAttrs.cn.name(), LdapAttrs.DN }; + + public MyUserTableViewer(Composite parent, int style) { + super(parent, style); + showSystemRoles = CurrentUser.isInRole(CmsConstants.ROLE_ADMIN); + } + + protected void populateStaticFilters(Composite staticFilterCmp) { + staticFilterCmp.setLayout(new GridLayout()); + final Button showSystemRoleBtn = new Button(staticFilterCmp, SWT.CHECK); + showSystemRoleBtn.setText("Show system roles"); + showSystemRoles = CurrentUser.isInRole(CmsConstants.ROLE_ADMIN); + showSystemRoleBtn.setSelection(showSystemRoles); + + showSystemRoleBtn.addSelectionListener(new SelectionAdapter() { + private static final long serialVersionUID = -7033424592697691676L; + + @Override + public void widgetSelected(SelectionEvent e) { + showSystemRoles = showSystemRoleBtn.getSelection(); + refresh(); + } + + }); + } + + @Override + protected List listFilteredElements(String filter) { + Role[] roles; + try { + StringBuilder builder = new StringBuilder(); + StringBuilder tmpBuilder = new StringBuilder(); + if (EclipseUiUtils.notEmpty(filter)) + for (String prop : knownProps) { + tmpBuilder.append("("); + tmpBuilder.append(prop); + tmpBuilder.append("=*"); + tmpBuilder.append(filter); + tmpBuilder.append("*)"); + } + if (tmpBuilder.length() > 1) { + builder.append("(&(").append(LdapAttrs.objectClass.name()).append("=") + .append(LdapObjs.groupOfNames.name()).append(")"); + // hide tokens + builder.append("(!(").append(LdapAttrs.DN).append("=*").append(CmsConstants.TOKENS_BASEDN) + .append("))"); + + if (!showSystemRoles) + builder.append("(!(").append(LdapAttrs.DN).append("=*").append(CmsConstants.SYSTEM_ROLES_BASEDN) + .append("))"); + builder.append("(|"); + builder.append(tmpBuilder.toString()); + builder.append("))"); + } else { + if (!showSystemRoles) + builder.append("(&(").append(LdapAttrs.objectClass.name()).append("=") + .append(LdapObjs.groupOfNames.name()).append(")(!(").append(LdapAttrs.DN).append("=*") + .append(CmsConstants.SYSTEM_ROLES_BASEDN).append("))(!(").append(LdapAttrs.DN).append("=*") + .append(CmsConstants.TOKENS_BASEDN).append(")))"); + else + builder.append("(&(").append(LdapAttrs.objectClass.name()).append("=") + .append(LdapObjs.groupOfNames.name()).append(")(!(").append(LdapAttrs.DN).append("=*") + .append(CmsConstants.TOKENS_BASEDN).append(")))"); + + } + roles = userAdminWrapper.getUserAdmin().getRoles(builder.toString()); + } catch (InvalidSyntaxException e) { + throw new CmsException("Unable to get roles with filter: " + filter, e); + } + List users = new ArrayList(); + for (Role role : roles) + if (!users.contains(role)) + users.add((User) role); + else + log.warn("Duplicated role: " + role); + + return users; + } + } + + public void refresh() { + groupTableViewerCmp.refresh(); + } + + @PreDestroy + public void dispose() { + userAdminWrapper.removeListener(listener); + } + + @Focus + public void setFocus() { + groupTableViewerCmp.setFocus(); + } + + /* DEPENDENCY INJECTION */ + public void setUserAdminWrapper(UserAdminWrapper userAdminWrapper) { + this.userAdminWrapper = userAdminWrapper; + } +} diff --git a/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/SecurityAdminImages.java b/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/SecurityAdminImages.java new file mode 100644 index 0000000..7bbe3c7 --- /dev/null +++ b/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/SecurityAdminImages.java @@ -0,0 +1,19 @@ +package org.argeo.cms.e4.users; + +import org.argeo.cms.ui.theme.CmsImages; +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.swt.graphics.Image; + +/** Shared icons that must be declared programmatically . */ +public class SecurityAdminImages extends CmsImages { + private final static String PREFIX = "icons/"; + + public final static ImageDescriptor ICON_REMOVE_DESC = createDesc(PREFIX + "delete.png"); + public final static ImageDescriptor ICON_USER_DESC = createDesc(PREFIX + "person.png"); + + public final static Image ICON_USER = ICON_USER_DESC.createImage(); + public final static Image ICON_GROUP = createImg(PREFIX + "group.png"); + public final static Image ICON_WORKGROUP = createImg(PREFIX + "workgroup.png"); + public final static Image ICON_ROLE = createImg(PREFIX + "role.gif"); + +} diff --git a/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/UiAdminUtils.java b/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/UiAdminUtils.java new file mode 100644 index 0000000..fb48a47 --- /dev/null +++ b/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/UiAdminUtils.java @@ -0,0 +1,34 @@ +package org.argeo.cms.e4.users; + +import org.argeo.util.transaction.WorkTransaction; + +/** First effort to centralize back end methods used by the user admin UI */ +public class UiAdminUtils { + /* + * INTERNAL METHODS: Below methods are meant to stay here and are not part + * of a potential generic backend to manage the useradmin + */ + /** Easily notify the ActiveWindow that the transaction had a state change */ + public final static void notifyTransactionStateChange( + WorkTransaction userTransaction) { +// try { +// IWorkbenchWindow aww = PlatformUI.getWorkbench() +// .getActiveWorkbenchWindow(); +// ISourceProviderService sourceProviderService = (ISourceProviderService) aww +// .getService(ISourceProviderService.class); +// UserTransactionProvider esp = (UserTransactionProvider) sourceProviderService +// .getSourceProvider(UserTransactionProvider.TRANSACTION_STATE); +// esp.fireTransactionStateChange(); +// } catch (Exception e) { +// throw new CmsException("Unable to begin transaction", e); +// } + } + + /** + * Email addresses must match this regexp pattern ({@value #EMAIL_PATTERN}. + * Thanks to this tip. + */ + public final static String EMAIL_PATTERN = "^[_A-Za-z0-9-]+(\\.[_A-Za-z0-9-]+)*@[A-Za-z0-9-]+(\\.[A-Za-z0-9]+)*(\\.[A-Za-z]{2,})$"; +} diff --git a/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/UiUserAdminListener.java b/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/UiUserAdminListener.java new file mode 100644 index 0000000..eb64aba --- /dev/null +++ b/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/UiUserAdminListener.java @@ -0,0 +1,27 @@ +package org.argeo.cms.e4.users; + +import org.eclipse.swt.widgets.Display; +import org.osgi.service.useradmin.UserAdminEvent; +import org.osgi.service.useradmin.UserAdminListener; + +/** Convenience class to insure the call to refresh is done in the UI thread */ +public abstract class UiUserAdminListener implements UserAdminListener { + + private final Display display; + + public UiUserAdminListener(Display display) { + this.display = display; + } + + @Override + public void roleChanged(final UserAdminEvent event) { + display.asyncExec(new Runnable() { + @Override + public void run() { + roleChangedToUiThread(event); + } + }); + } + + public abstract void roleChangedToUiThread(UserAdminEvent event); +} diff --git a/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/UserAdminWrapper.java b/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/UserAdminWrapper.java new file mode 100644 index 0000000..dbb629c --- /dev/null +++ b/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/UserAdminWrapper.java @@ -0,0 +1,153 @@ +package org.argeo.cms.e4.users; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Hashtable; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.argeo.api.cms.CmsConstants; +import org.argeo.cms.swt.CmsException; +import org.argeo.osgi.useradmin.UserDirectory; +import org.argeo.util.directory.DirectoryConf; +import org.argeo.util.transaction.WorkTransaction; +import org.osgi.service.useradmin.UserAdmin; +import org.osgi.service.useradmin.UserAdminEvent; +import org.osgi.service.useradmin.UserAdminListener; + +/** Centralise interaction with the UserAdmin in this bundle */ +public class UserAdminWrapper { + + private UserAdmin userAdmin; + // private ServiceReference userAdminServiceReference; +// private Set uris; + private Map> userDirectories = Collections + .synchronizedMap(new LinkedHashMap<>()); + private WorkTransaction userTransaction; + + // First effort to simplify UX while managing users and groups + public final static boolean COMMIT_ON_SAVE = true; + + // Registered listeners + List listeners = new ArrayList(); + + /** + * Starts a transaction if necessary. Should always been called together with + * {@link UserAdminWrapper#commitOrNotifyTransactionStateChange()} once the + * security model changes have been performed. + */ + public WorkTransaction beginTransactionIfNeeded() { + try { + // UserTransaction userTransaction = getUserTransaction(); + if (userTransaction.isNoTransactionStatus()) { + userTransaction.begin(); + // UiAdminUtils.notifyTransactionStateChange(userTransaction); + } + return userTransaction; + } catch (Exception e) { + throw new CmsException("Unable to begin transaction", e); + } + } + + /** + * Depending on the current application configuration, it will either commit the + * current transaction or throw a notification that the transaction state has + * changed (In the later case, it must be called from the UI thread). + */ + public void commitOrNotifyTransactionStateChange() { + try { + // UserTransaction userTransaction = getUserTransaction(); + if (userTransaction.isNoTransactionStatus()) + return; + + if (UserAdminWrapper.COMMIT_ON_SAVE) + userTransaction.commit(); + else + UiAdminUtils.notifyTransactionStateChange(userTransaction); + } catch (Exception e) { + throw new CmsException("Unable to clean transaction", e); + } + } + + // TODO implement safer mechanism + public void addListener(UserAdminListener userAdminListener) { + if (!listeners.contains(userAdminListener)) + listeners.add(userAdminListener); + } + + public void removeListener(UserAdminListener userAdminListener) { + if (listeners.contains(userAdminListener)) + listeners.remove(userAdminListener); + } + + public void notifyListeners(UserAdminEvent event) { + for (UserAdminListener listener : listeners) + listener.roleChanged(event); + } + + public Map getKnownBaseDns(boolean onlyWritable) { + Map dns = new HashMap(); + for (UserDirectory userDirectory : userDirectories.keySet()) { + Boolean readOnly = userDirectory.isReadOnly(); + String baseDn = userDirectory.getBase(); + + if (onlyWritable && readOnly) + continue; + if (baseDn.equalsIgnoreCase(CmsConstants.SYSTEM_ROLES_BASEDN)) + continue; + if (baseDn.equalsIgnoreCase(CmsConstants.TOKENS_BASEDN)) + continue; + dns.put(baseDn, DirectoryConf.propertiesAsUri(userDirectories.get(userDirectory)).toString()); + + } +// for (String uri : uris) { +// if (!uri.startsWith("/")) +// continue; +// Dictionary props = UserAdminConf.uriAsProperties(uri); +// String readOnly = UserAdminConf.readOnly.getValue(props); +// String baseDn = UserAdminConf.baseDn.getValue(props); +// +// if (onlyWritable && "true".equals(readOnly)) +// continue; +// if (baseDn.equalsIgnoreCase(NodeConstants.ROLES_BASEDN)) +// continue; +// if (baseDn.equalsIgnoreCase(NodeConstants.TOKENS_BASEDN)) +// continue; +// dns.put(baseDn, uri); +// } + return dns; + } + + public UserAdmin getUserAdmin() { + return userAdmin; + } + + public WorkTransaction getUserTransaction() { + return userTransaction; + } + + /* DEPENDENCY INJECTION */ + public void setUserAdmin(UserAdmin userAdmin, Map properties) { + this.userAdmin = userAdmin; +// this.uris = Collections.unmodifiableSortedSet(new TreeSet<>(properties.keySet())); + } + + public void setUserTransaction(WorkTransaction userTransaction) { + this.userTransaction = userTransaction; + } + + public void addUserDirectory(UserDirectory userDirectory, Map properties) { + userDirectories.put(userDirectory, new Hashtable<>(properties)); + } + + public void removeUserDirectory(UserDirectory userDirectory, Map properties) { + userDirectories.remove(userDirectory); + } + + // public void setUserAdminServiceReference( + // ServiceReference userAdminServiceReference) { + // this.userAdminServiceReference = userAdminServiceReference; + // } +} diff --git a/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/UserBatchUpdateWizard.java b/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/UserBatchUpdateWizard.java new file mode 100644 index 0000000..4fc59d3 --- /dev/null +++ b/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/UserBatchUpdateWizard.java @@ -0,0 +1,606 @@ +package org.argeo.cms.e4.users; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.argeo.api.cms.CmsConstants; +import org.argeo.api.cms.CmsLog; +import org.argeo.cms.auth.CurrentUser; +import org.argeo.cms.auth.UserAdminUtils; +import org.argeo.cms.e4.users.providers.CommonNameLP; +import org.argeo.cms.e4.users.providers.DomainNameLP; +import org.argeo.cms.e4.users.providers.MailLP; +import org.argeo.cms.e4.users.providers.UserNameLP; +import org.argeo.cms.swt.CmsException; +import org.argeo.eclipse.ui.ColumnDefinition; +import org.argeo.eclipse.ui.EclipseUiUtils; +import org.argeo.eclipse.ui.parts.LdifUsersTable; +import org.argeo.util.naming.LdapAttrs; +import org.argeo.util.naming.LdapObjs; +import org.argeo.util.transaction.WorkTransaction; +import org.eclipse.jface.dialogs.IPageChangeProvider; +import org.eclipse.jface.dialogs.IPageChangedListener; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.jface.dialogs.PageChangedEvent; +import org.eclipse.jface.wizard.IWizardContainer; +import org.eclipse.jface.wizard.Wizard; +import org.eclipse.jface.wizard.WizardPage; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Combo; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Text; +import org.osgi.framework.InvalidSyntaxException; +import org.osgi.service.useradmin.Role; +import org.osgi.service.useradmin.User; +import org.osgi.service.useradmin.UserAdminEvent; + +/** Wizard to update users */ +public class UserBatchUpdateWizard extends Wizard { + + private final static CmsLog log = CmsLog.getLog(UserBatchUpdateWizard.class); + private UserAdminWrapper userAdminWrapper; + + // pages + private ChooseCommandWizardPage chooseCommandPage; + private ChooseUsersWizardPage userListPage; + private ValidateAndLaunchWizardPage validatePage; + + // Various implemented commands keys + private final static String CMD_UPDATE_PASSWORD = "resetPassword"; + private final static String CMD_UPDATE_EMAIL = "resetEmail"; + private final static String CMD_GROUP_MEMBERSHIP = "groupMembership"; + + private final Map commands = new HashMap() { + private static final long serialVersionUID = 1L; + { + put("Reset password(s)", CMD_UPDATE_PASSWORD); + put("Reset email(s)", CMD_UPDATE_EMAIL); + // TODO implement role / group management + // put("Add/Remove from group", CMD_GROUP_MEMBERSHIP); + } + }; + + public UserBatchUpdateWizard(UserAdminWrapper userAdminWrapper) { + this.userAdminWrapper = userAdminWrapper; + } + + @Override + public void addPages() { + chooseCommandPage = new ChooseCommandWizardPage(); + addPage(chooseCommandPage); + userListPage = new ChooseUsersWizardPage(); + addPage(userListPage); + validatePage = new ValidateAndLaunchWizardPage(); + addPage(validatePage); + } + + @Override + public boolean performFinish() { + if (!canFinish()) + return false; + WorkTransaction ut = userAdminWrapper.getUserTransaction(); + if (!ut.isNoTransactionStatus() && !MessageDialog.openConfirm(getShell(), "Existing Transaction", + "A user transaction is already existing, " + "are you sure you want to proceed ?")) + return false; + + // We cannot use jobs, user modifications are still meant to be done in + // the UIThread + // UpdateJob job = null; + // if (job != null) + // job.schedule(); + + if (CMD_UPDATE_PASSWORD.equals(chooseCommandPage.getCommand())) { + char[] newValue = chooseCommandPage.getPwdValue(); + if (newValue == null) + throw new CmsException("Password cannot be null or an empty string"); + ResetPassword job = new ResetPassword(userAdminWrapper, userListPage.getSelectedUsers(), newValue); + job.doUpdate(); + } else if (CMD_UPDATE_EMAIL.equals(chooseCommandPage.getCommand())) { + String newValue = chooseCommandPage.getEmailValue(); + if (newValue == null) + throw new CmsException("Password cannot be null or an empty string"); + ResetEmail job = new ResetEmail(userAdminWrapper, userListPage.getSelectedUsers(), newValue); + job.doUpdate(); + } + return true; + } + + public boolean canFinish() { + if (this.getContainer().getCurrentPage() == validatePage) + return true; + return false; + } + + private class ResetPassword { + private char[] newPwd; + private UserAdminWrapper userAdminWrapper; + private List usersToUpdate; + + public ResetPassword(UserAdminWrapper userAdminWrapper, List usersToUpdate, char[] newPwd) { + this.newPwd = newPwd; + this.usersToUpdate = usersToUpdate; + this.userAdminWrapper = userAdminWrapper; + } + + @SuppressWarnings("unchecked") + protected void doUpdate() { + userAdminWrapper.beginTransactionIfNeeded(); + try { + for (User user : usersToUpdate) { + // the char array is emptied after being used. + user.getCredentials().put(null, newPwd.clone()); + } + userAdminWrapper.commitOrNotifyTransactionStateChange(); + } catch (Exception e) { + throw new CmsException("Cannot perform batch update on users", e); + } finally { + WorkTransaction ut = userAdminWrapper.getUserTransaction(); + if (!ut.isNoTransactionStatus()) + ut.rollback(); + } + } + } + + private class ResetEmail { + private String newEmail; + private UserAdminWrapper userAdminWrapper; + private List usersToUpdate; + + public ResetEmail(UserAdminWrapper userAdminWrapper, List usersToUpdate, String newEmail) { + this.newEmail = newEmail; + this.usersToUpdate = usersToUpdate; + this.userAdminWrapper = userAdminWrapper; + } + + @SuppressWarnings("unchecked") + protected void doUpdate() { + userAdminWrapper.beginTransactionIfNeeded(); + try { + for (User user : usersToUpdate) { + // the char array is emptied after being used. + user.getProperties().put(LdapAttrs.mail.name(), newEmail); + } + + userAdminWrapper.commitOrNotifyTransactionStateChange(); + if (!usersToUpdate.isEmpty()) + userAdminWrapper.notifyListeners( + new UserAdminEvent(null, UserAdminEvent.ROLE_CHANGED, usersToUpdate.get(0))); + } catch (Exception e) { + throw new CmsException("Cannot perform batch update on users", e); + } finally { + WorkTransaction ut = userAdminWrapper.getUserTransaction(); + if (!ut.isNoTransactionStatus()) + ut.rollback(); + } + } + } + + // @SuppressWarnings("unused") + // private class AddToGroup extends UpdateJob { + // private String groupID; + // private Session session; + // + // public AddToGroup(Session session, List nodesToUpdate, + // String groupID) { + // super(session, nodesToUpdate); + // this.session = session; + // this.groupID = groupID; + // } + // + // protected void doUpdate(Node node) { + // log.info("Add/Remove to group actions are not yet implemented"); + // // TODO implement this + // // try { + // // throw new CmsException("Not yet implemented"); + // // } catch (RepositoryException re) { + // // throw new CmsException( + // // "Unable to update boolean value for node " + node, re); + // // } + // } + // } + + // /** + // * Base privileged job that will be run asynchronously to perform the + // batch + // * update + // */ + // private abstract class UpdateJob extends PrivilegedJob { + // + // private final UserAdminWrapper userAdminWrapper; + // private final List usersToUpdate; + // + // protected abstract void doUpdate(User user); + // + // public UpdateJob(UserAdminWrapper userAdminWrapper, + // List usersToUpdate) { + // super("Perform update"); + // this.usersToUpdate = usersToUpdate; + // this.userAdminWrapper = userAdminWrapper; + // } + // + // @Override + // protected IStatus doRun(IProgressMonitor progressMonitor) { + // try { + // JcrMonitor monitor = new EclipseJcrMonitor(progressMonitor); + // int total = usersToUpdate.size(); + // monitor.beginTask("Performing change", total); + // userAdminWrapper.beginTransactionIfNeeded(); + // for (User user : usersToUpdate) { + // doUpdate(user); + // monitor.worked(1); + // } + // userAdminWrapper.getUserTransaction().commit(); + // } catch (Exception e) { + // throw new CmsException( + // "Cannot perform batch update on users", e); + // } finally { + // UserTransaction ut = userAdminWrapper.getUserTransaction(); + // try { + // if (ut.getStatus() != javax.transaction.Status.STATUS_NO_TRANSACTION) + // ut.rollback(); + // } catch (IllegalStateException | SecurityException + // | SystemException e) { + // log.error("Unable to rollback session in 'finally', " + // + "the system might be in a dirty state"); + // e.printStackTrace(); + // } + // } + // return Status.OK_STATUS; + // } + // } + + // PAGES + /** + * Displays a combo box that enables user to choose which action to perform + */ + private class ChooseCommandWizardPage extends WizardPage { + private static final long serialVersionUID = -8069434295293996633L; + private Combo chooseCommandCmb; + private Button trueChk; + private Text valueTxt; + private Text pwdTxt; + private Text pwd2Txt; + + public ChooseCommandWizardPage() { + super("Choose a command to run."); + setTitle("Choose a command to run."); + } + + @Override + public void createControl(Composite parent) { + GridLayout gl = new GridLayout(); + Composite container = new Composite(parent, SWT.NO_FOCUS); + container.setLayout(gl); + + chooseCommandCmb = new Combo(container, SWT.READ_ONLY); + chooseCommandCmb.setLayoutData(EclipseUiUtils.fillWidth()); + String[] values = commands.keySet().toArray(new String[0]); + chooseCommandCmb.setItems(values); + + final Composite bottomPart = new Composite(container, SWT.NO_FOCUS); + bottomPart.setLayoutData(EclipseUiUtils.fillAll()); + bottomPart.setLayout(EclipseUiUtils.noSpaceGridLayout()); + + chooseCommandCmb.addSelectionListener(new SelectionAdapter() { + private static final long serialVersionUID = 1L; + + @Override + public void widgetSelected(SelectionEvent e) { + if (getCommand().equals(CMD_UPDATE_PASSWORD)) + populatePasswordCmp(bottomPart); + else if (getCommand().equals(CMD_UPDATE_EMAIL)) + populateEmailCmp(bottomPart); + else if (getCommand().equals(CMD_GROUP_MEMBERSHIP)) + populateGroupCmp(bottomPart); + else + populateBooleanFlagCmp(bottomPart); + checkPageComplete(); + bottomPart.layout(true, true); + } + }); + setControl(container); + } + + private void populateBooleanFlagCmp(Composite parent) { + EclipseUiUtils.clear(parent); + trueChk = new Button(parent, SWT.CHECK); + trueChk.setText("Do it. (It will to the contrary if unchecked)"); + trueChk.setSelection(true); + trueChk.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false)); + } + + private void populatePasswordCmp(Composite parent) { + EclipseUiUtils.clear(parent); + Composite body = new Composite(parent, SWT.NO_FOCUS); + + ModifyListener ml = new ModifyListener() { + private static final long serialVersionUID = -1558726363536729634L; + + @Override + public void modifyText(ModifyEvent event) { + checkPageComplete(); + } + }; + + body.setLayout(new GridLayout(2, false)); + body.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + pwdTxt = EclipseUiUtils.createGridLP(body, "New password", ml); + pwd2Txt = EclipseUiUtils.createGridLP(body, "Repeat password", ml); + } + + private void populateEmailCmp(Composite parent) { + EclipseUiUtils.clear(parent); + Composite body = new Composite(parent, SWT.NO_FOCUS); + + ModifyListener ml = new ModifyListener() { + private static final long serialVersionUID = 2147704227294268317L; + + @Override + public void modifyText(ModifyEvent event) { + checkPageComplete(); + } + }; + + body.setLayout(new GridLayout(2, false)); + body.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + valueTxt = EclipseUiUtils.createGridLT(body, "New e-mail", ml); + } + + private void checkPageComplete() { + String errorMsg = null; + if (chooseCommandCmb.getSelectionIndex() < 0) + errorMsg = "Please select an action"; + else if (CMD_UPDATE_EMAIL.equals(getCommand())) { + if (!valueTxt.getText().matches(UiAdminUtils.EMAIL_PATTERN)) + errorMsg = "Not a valid e-mail address"; + } else if (CMD_UPDATE_PASSWORD.equals(getCommand())) { + if (EclipseUiUtils.isEmpty(pwdTxt.getText()) || pwdTxt.getText().length() < 4) + errorMsg = "Please enter a password that is at least 4 character long"; + else if (!pwdTxt.getText().equals(pwd2Txt.getText())) + errorMsg = "Passwords are different"; + } + if (EclipseUiUtils.notEmpty(errorMsg)) { + setMessage(errorMsg, WizardPage.ERROR); + setPageComplete(false); + } else { + setMessage("Page complete, you can proceed to user choice", WizardPage.INFORMATION); + setPageComplete(true); + } + + getContainer().updateButtons(); + } + + private void populateGroupCmp(Composite parent) { + EclipseUiUtils.clear(parent); + trueChk = new Button(parent, SWT.CHECK); + trueChk.setText("Add to group. (It will remove user(s) from the " + "corresponding group if unchecked)"); + trueChk.setSelection(true); + trueChk.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false)); + } + + protected String getCommand() { + return commands.get(chooseCommandCmb.getItem(chooseCommandCmb.getSelectionIndex())); + } + + protected String getCommandLbl() { + return chooseCommandCmb.getItem(chooseCommandCmb.getSelectionIndex()); + } + + @SuppressWarnings("unused") + protected boolean getBoleanValue() { + // FIXME this is not consistent and will lead to errors. + if ("argeo:enabled".equals(getCommand())) + return trueChk.getSelection(); + else + return !trueChk.getSelection(); + } + + @SuppressWarnings("unused") + protected String getStringValue() { + String value = null; + if (valueTxt != null) { + value = valueTxt.getText(); + if ("".equals(value.trim())) + value = null; + } + return value; + } + + protected char[] getPwdValue() { + // We do not directly reset the password text fields: There is no + // need to over secure this process: setting a pwd to multi users + // at the same time is anyhow a bad practice and should be used only + // in test environment or for temporary access + if (pwdTxt == null || pwdTxt.isDisposed()) + return null; + else + return pwdTxt.getText().toCharArray(); + } + + protected String getEmailValue() { + // We do not directly reset the password text fields: There is no + // need to over secure this process: setting a pwd to multi users + // at the same time is anyhow a bad practice and should be used only + // in test environment or for temporary access + if (valueTxt == null || valueTxt.isDisposed()) + return null; + else + return valueTxt.getText(); + } + } + + /** + * Displays a list of users with a check box to be able to choose some of them + */ + private class ChooseUsersWizardPage extends WizardPage implements IPageChangedListener { + private static final long serialVersionUID = 7651807402211214274L; + private ChooseUserTableViewer userTableCmp; + + public ChooseUsersWizardPage() { + super("Choose Users"); + setTitle("Select users who will be impacted"); + } + + @Override + public void createControl(Composite parent) { + Composite pageCmp = new Composite(parent, SWT.NONE); + pageCmp.setLayout(EclipseUiUtils.noSpaceGridLayout()); + + // Define the displayed columns + List columnDefs = new ArrayList(); + columnDefs.add(new ColumnDefinition(new CommonNameLP(), "Common Name", 150)); + columnDefs.add(new ColumnDefinition(new MailLP(), "E-mail", 150)); + columnDefs.add(new ColumnDefinition(new DomainNameLP(), "Domain", 200)); + + // Only show technical DN to admin + if (CurrentUser.isInRole(CmsConstants.ROLE_ADMIN)) + columnDefs.add(new ColumnDefinition(new UserNameLP(), "Distinguished Name", 300)); + + userTableCmp = new ChooseUserTableViewer(pageCmp, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL); + userTableCmp.setLayoutData(EclipseUiUtils.fillAll()); + userTableCmp.setColumnDefinitions(columnDefs); + userTableCmp.populate(true, true); + userTableCmp.refresh(); + + setControl(pageCmp); + + // Add listener to update message when shown + final IWizardContainer wContainer = this.getContainer(); + if (wContainer instanceof IPageChangeProvider) { + ((IPageChangeProvider) wContainer).addPageChangedListener(this); + } + + } + + @Override + public void pageChanged(PageChangedEvent event) { + if (event.getSelectedPage() == this) { + String msg = "Chosen batch action: " + chooseCommandPage.getCommandLbl(); + ((WizardPage) event.getSelectedPage()).setMessage(msg); + } + } + + protected List getSelectedUsers() { + return userTableCmp.getSelectedUsers(); + } + + private class ChooseUserTableViewer extends LdifUsersTable { + private static final long serialVersionUID = 5080437561015853124L; + private final String[] knownProps = { LdapAttrs.uid.name(), LdapAttrs.DN, LdapAttrs.cn.name(), + LdapAttrs.givenName.name(), LdapAttrs.sn.name(), LdapAttrs.mail.name() }; + + public ChooseUserTableViewer(Composite parent, int style) { + super(parent, style); + } + + @Override + protected List listFilteredElements(String filter) { + Role[] roles; + + try { + StringBuilder builder = new StringBuilder(); + + StringBuilder tmpBuilder = new StringBuilder(); + if (EclipseUiUtils.notEmpty(filter)) + for (String prop : knownProps) { + tmpBuilder.append("("); + tmpBuilder.append(prop); + tmpBuilder.append("=*"); + tmpBuilder.append(filter); + tmpBuilder.append("*)"); + } + if (tmpBuilder.length() > 1) { + builder.append("(&(").append(LdapAttrs.objectClass.name()).append("=") + .append(LdapObjs.inetOrgPerson.name()).append(")(|"); + builder.append(tmpBuilder.toString()); + builder.append("))"); + } else + builder.append("(").append(LdapAttrs.objectClass.name()).append("=") + .append(LdapObjs.inetOrgPerson.name()).append(")"); + roles = userAdminWrapper.getUserAdmin().getRoles(builder.toString()); + } catch (InvalidSyntaxException e) { + throw new CmsException("Unable to get roles with filter: " + filter, e); + } + List users = new ArrayList(); + for (Role role : roles) + // Prevent current logged in user to perform batch on + // himself + if (!UserAdminUtils.isCurrentUser((User) role)) + users.add((User) role); + return users; + } + } + } + + /** Summary of input data before launching the process */ + private class ValidateAndLaunchWizardPage extends WizardPage implements IPageChangedListener { + private static final long serialVersionUID = 7098918351451743853L; + private ChosenUsersTableViewer userTableCmp; + + public ValidateAndLaunchWizardPage() { + super("Validate and launch"); + setTitle("Validate and launch"); + } + + @Override + public void createControl(Composite parent) { + Composite pageCmp = new Composite(parent, SWT.NO_FOCUS); + pageCmp.setLayout(EclipseUiUtils.noSpaceGridLayout()); + + List columnDefs = new ArrayList(); + columnDefs.add(new ColumnDefinition(new CommonNameLP(), "Common Name", 150)); + columnDefs.add(new ColumnDefinition(new MailLP(), "E-mail", 150)); + columnDefs.add(new ColumnDefinition(new DomainNameLP(), "Domain", 200)); + // Only show technical DN to admin + if (CurrentUser.isInRole(CmsConstants.ROLE_ADMIN)) + columnDefs.add(new ColumnDefinition(new UserNameLP(), "Distinguished Name", 300)); + userTableCmp = new ChosenUsersTableViewer(pageCmp, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL); + userTableCmp.setLayoutData(EclipseUiUtils.fillAll()); + userTableCmp.setColumnDefinitions(columnDefs); + userTableCmp.populate(false, false); + userTableCmp.refresh(); + setControl(pageCmp); + // Add listener to update message when shown + final IWizardContainer wContainer = this.getContainer(); + if (wContainer instanceof IPageChangeProvider) { + ((IPageChangeProvider) wContainer).addPageChangedListener(this); + } + } + + @Override + public void pageChanged(PageChangedEvent event) { + if (event.getSelectedPage() == this) { + @SuppressWarnings({ "unchecked", "rawtypes" }) + Object[] values = ((ArrayList) userListPage.getSelectedUsers()) + .toArray(new Object[userListPage.getSelectedUsers().size()]); + userTableCmp.getTableViewer().setInput(values); + String msg = "Following batch action: [" + chooseCommandPage.getCommandLbl() + + "] will be perfomed on the users listed below.\n"; + // + "Are you sure you want to proceed?"; + setMessage(msg); + } + } + + private class ChosenUsersTableViewer extends LdifUsersTable { + private static final long serialVersionUID = 7814764735794270541L; + + public ChosenUsersTableViewer(Composite parent, int style) { + super(parent, style); + } + + @Override + protected List listFilteredElements(String filter) { + return userListPage.getSelectedUsers(); + } + } + } +} diff --git a/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/UserEditor.java b/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/UserEditor.java new file mode 100644 index 0000000..66f4420 --- /dev/null +++ b/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/UserEditor.java @@ -0,0 +1,535 @@ +package org.argeo.cms.e4.users; + +import static org.argeo.cms.auth.UserAdminUtils.getProperty; +import static org.argeo.util.naming.LdapAttrs.cn; +import static org.argeo.util.naming.LdapAttrs.givenName; +import static org.argeo.util.naming.LdapAttrs.mail; +import static org.argeo.util.naming.LdapAttrs.sn; +import static org.argeo.util.naming.LdapAttrs.uid; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import javax.inject.Inject; + +import org.argeo.api.cms.CmsConstants; +import org.argeo.cms.auth.CurrentUser; +import org.argeo.cms.auth.UserAdminUtils; +import org.argeo.cms.e4.users.providers.CommonNameLP; +import org.argeo.cms.e4.users.providers.DomainNameLP; +import org.argeo.cms.e4.users.providers.RoleIconLP; +import org.argeo.cms.e4.users.providers.UserFilter; +import org.argeo.cms.swt.CmsSwtUtils; +import org.argeo.cms.ui.eclipse.forms.AbstractFormPart; +//import org.argeo.cms.ui.eclipse.forms.FormToolkit; +import org.argeo.cms.ui.eclipse.forms.IManagedForm; +import org.argeo.eclipse.ui.ColumnDefinition; +import org.argeo.eclipse.ui.EclipseUiUtils; +import org.argeo.eclipse.ui.parts.LdifUsersTable; +import org.argeo.util.naming.LdapAttrs; +import org.eclipse.e4.ui.workbench.modeling.EPartService; +import org.eclipse.jface.action.Action; +import org.eclipse.jface.action.ToolBarManager; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.jface.dialogs.TrayDialog; +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.viewers.TableViewer; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.jface.viewers.ViewerDropAdapter; +import org.eclipse.swt.SWT; +import org.eclipse.swt.dnd.DND; +import org.eclipse.swt.dnd.DropTargetEvent; +import org.eclipse.swt.dnd.TextTransfer; +import org.eclipse.swt.dnd.Transfer; +import org.eclipse.swt.dnd.TransferData; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Link; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Text; +import org.eclipse.swt.widgets.ToolBar; +import org.osgi.service.useradmin.Group; +import org.osgi.service.useradmin.Role; +import org.osgi.service.useradmin.User; +import org.osgi.service.useradmin.UserAdmin; +import org.osgi.service.useradmin.UserAdminEvent; + +/** Display/edit the properties of a given user */ +public class UserEditor extends AbstractRoleEditor { + // final static String ID = "UserEditor.mainPage"; + + @Inject + private EPartService partService; + + // private final UserEditor editor; + // private UserAdminWrapper userAdminWrapper; + + // Local configuration + // private final int PRE_TITLE_INDENT = 10; + + // public UserMainPage(FormEditor editor, UserAdminWrapper userAdminWrapper) { + // super(editor, ID, "Main"); + // this.editor = (UserEditor) editor; + // this.userAdminWrapper = userAdminWrapper; + // } + + // protected void createFormContent(final IManagedForm mf) { + // ScrolledForm form = mf.getForm(); + // Composite body = form.getBody(); + // GridLayout mainLayout = new GridLayout(); + // // mainLayout.marginRight = 10; + // body.setLayout(mainLayout); + // User user = editor.getDisplayedUser(); + // appendOverviewPart(body, user); + // // Remove to ability to force the password for his own user. The user + // // must then use the change pwd feature + // appendMemberOfPart(body, user); + // } + + @Override + protected void createUi(Composite body) { + // Composite body = new Composite(parent, SWT.BORDER); + GridLayout mainLayout = new GridLayout(); + // mainLayout.marginRight = 10; + body.setLayout(mainLayout); + // body.getParent().setLayout(new GridLayout()); + // body.setLayoutData(CmsUiUtils.fillAll()); + User user = getDisplayedUser(); + appendOverviewPart(body, user); + // Remove to ability to force the password for his own user. The user + // must then use the change pwd feature + appendMemberOfPart(body, user); + } + + /** Creates the general section */ + private void appendOverviewPart(final Composite parent, final User user) { + // FormToolkit tk = getManagedForm().getToolkit(); + + // Section section = tk.createSection(parent, SWT.NO_FOCUS); + // GridData gd = EclipseUiUtils.fillWidth(); + // // gd.verticalAlignment = PRE_TITLE_INDENT; + // section.setLayoutData(gd); + Composite body = new Composite(parent, SWT.NONE); + body.setLayoutData(EclipseUiUtils.fillWidth()); + // section.setClient(body); + // body.setLayout(new GridLayout(6, false)); + body.setLayout(new GridLayout(2, false)); + + Text commonName = createReadOnlyLT(body, "Name", getProperty(user, cn)); + Text distinguishedName = createReadOnlyLT(body, "Login", getProperty(user, uid)); + Text firstName = createLT(body, "First name", getProperty(user, givenName)); + Text lastName = createLT(body, "Last name", getProperty(user, sn)); + Text email = createLT(body, "Email", getProperty(user, mail)); + + Link resetPwdLk = new Link(body, SWT.NONE); + if (!UserAdminUtils.isCurrentUser(user)) { + resetPwdLk.setText("Reset password"); + } + resetPwdLk.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 2, 1)); + + // create form part (controller) + AbstractFormPart part = new AbstractFormPart() { + private MainInfoListener listener; + + @Override + public void initialize(IManagedForm form) { + super.initialize(form); + listener = new MainInfoListener(parent.getDisplay(), this); + userAdminWrapper.addListener(listener); + } + + @Override + public void dispose() { + userAdminWrapper.removeListener(listener); + super.dispose(); + } + + @SuppressWarnings("unchecked") + public void commit(boolean onSave) { + // TODO Sanity checks (mail validity...) + user.getProperties().put(LdapAttrs.givenName.name(), firstName.getText()); + user.getProperties().put(LdapAttrs.sn.name(), lastName.getText()); + user.getProperties().put(LdapAttrs.cn.name(), commonName.getText()); + user.getProperties().put(LdapAttrs.mail.name(), email.getText()); + super.commit(onSave); + } + + @Override + public void refresh() { + distinguishedName.setText(UserAdminUtils.getProperty(user, LdapAttrs.uid.name())); + commonName.setText(UserAdminUtils.getProperty(user, LdapAttrs.cn.name())); + firstName.setText(UserAdminUtils.getProperty(user, LdapAttrs.givenName.name())); + lastName.setText(UserAdminUtils.getProperty(user, LdapAttrs.sn.name())); + email.setText(UserAdminUtils.getProperty(user, LdapAttrs.mail.name())); + refreshFormTitle(user); + super.refresh(); + } + }; + + // Improve this: automatically generate CN when first or last name + // changes + ModifyListener cnML = new ModifyListener() { + private static final long serialVersionUID = 4298649222869835486L; + + @Override + public void modifyText(ModifyEvent event) { + String first = firstName.getText(); + String last = lastName.getText(); + String cn = first.trim() + " " + last.trim() + " "; + cn = cn.trim(); + commonName.setText(cn); + // getManagedForm().getForm().setText(cn); + updateEditorTitle(cn); + } + }; + firstName.addModifyListener(cnML); + lastName.addModifyListener(cnML); + + ModifyListener defaultListener = new FormPartML(part); + firstName.addModifyListener(defaultListener); + lastName.addModifyListener(defaultListener); + email.addModifyListener(defaultListener); + + if (!UserAdminUtils.isCurrentUser(user)) + resetPwdLk.addSelectionListener(new SelectionAdapter() { + private static final long serialVersionUID = 5881800534589073787L; + + @Override + public void widgetSelected(SelectionEvent e) { + new ChangePasswordDialog(user, "Reset password").open(); + } + }); + + getManagedForm().addPart(part); + } + + private class ChangePasswordDialog extends TrayDialog { + private static final long serialVersionUID = 2843538207460082349L; + + private User user; + private Text password1; + private Text password2; + private String title; + // private FormToolkit tk; + + public ChangePasswordDialog(User user, String title) { + super(Display.getDefault().getActiveShell()); + // this.tk = tk; + this.user = user; + this.title = title; + } + + protected Control createDialogArea(Composite parent) { + Composite dialogarea = (Composite) super.createDialogArea(parent); + dialogarea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + Composite body = new Composite(dialogarea, SWT.NO_FOCUS); + body.setLayoutData(EclipseUiUtils.fillAll()); + GridLayout layout = new GridLayout(2, false); + body.setLayout(layout); + + password1 = createLP(body, "New password", ""); + password2 = createLP(body, "Repeat password", ""); + parent.pack(); + return body; + } + + @SuppressWarnings("unchecked") + @Override + protected void okPressed() { + String msg = null; + + if (password1.getText().equals("")) + msg = "Password cannot be empty"; + else if (password1.getText().equals(password2.getText())) { + char[] newPassword = password1.getText().toCharArray(); + // userAdminWrapper.beginTransactionIfNeeded(); + userAdminWrapper.beginTransactionIfNeeded(); + user.getCredentials().put(null, newPassword); + userAdminWrapper.commitOrNotifyTransactionStateChange(); + super.okPressed(); + } else { + msg = "Passwords are not equals"; + } + + if (EclipseUiUtils.notEmpty(msg)) + MessageDialog.openError(getParentShell(), "Cannot reset pasword", msg); + } + + protected void configureShell(Shell shell) { + super.configureShell(shell); + shell.setText(title); + } + } + + private LdifUsersTable appendMemberOfPart(final Composite parent, User user) { + // Section section = addSection(tk, parent, "Roles"); + // Composite body = (Composite) section.getClient(); + // Composite body= parent; + Composite body = new Composite(parent, SWT.BORDER); + body.setLayout(new GridLayout()); + body.setLayoutData(CmsSwtUtils.fillAll()); + + // boolean isAdmin = CurrentUser.isInRole(NodeConstants.ROLE_ADMIN); + + // Displayed columns + List columnDefs = new ArrayList(); + columnDefs.add(new ColumnDefinition(new RoleIconLP(), "", 0, 24)); + columnDefs.add(new ColumnDefinition(new CommonNameLP(), "Name", 150)); + columnDefs.add(new ColumnDefinition(new DomainNameLP(), "Domain", 100)); + // Only show technical DN to administrators + // if (isAdmin) + // columnDefs.add(new ColumnDefinition(new UserNameLP(), "Distinguished Name", + // 300)); + + // Create and configure the table + final LdifUsersTable userViewerCmp = new MyUserTableViewer(body, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL, user); + + userViewerCmp.setColumnDefinitions(columnDefs); + // if (isAdmin) + // userViewerCmp.populateWithStaticFilters(false, false); + // else + userViewerCmp.populate(true, false); + GridData gd = EclipseUiUtils.fillAll(); + gd.heightHint = 500; + userViewerCmp.setLayoutData(gd); + + // Controllers + TableViewer userViewer = userViewerCmp.getTableViewer(); + userViewer.addDoubleClickListener(new UserTableDefaultDClickListener(partService)); + int operations = DND.DROP_COPY | DND.DROP_MOVE; + Transfer[] tt = new Transfer[] { TextTransfer.getInstance() }; + GroupDropListener dropL = new GroupDropListener(userAdminWrapper, userViewer, user); + userViewer.addDropSupport(operations, tt, dropL); + + AbstractFormPart part = new AbstractFormPart() { + + private GroupChangeListener listener; + + @Override + public void initialize(IManagedForm form) { + super.initialize(form); + listener = new GroupChangeListener(parent.getDisplay(), this); + userAdminWrapper.addListener(listener); + } + + public void commit(boolean onSave) { + super.commit(onSave); + } + + @Override + public void dispose() { + userAdminWrapper.removeListener(listener); + super.dispose(); + } + + @Override + public void refresh() { + userViewerCmp.refresh(); + super.refresh(); + } + }; + getManagedForm().addPart(part); + // addRemoveAbitily(body, userViewer, user); + // userViewerCmp.refresh(); + String tooltip = "Remove " + UserAdminUtils.getUserLocalId(user.getName()) + " from the below selected groups"; + Action action = new RemoveMembershipAction(userViewer, user, tooltip, SecurityAdminImages.ICON_REMOVE_DESC); + ToolBarManager toolBarManager = new ToolBarManager(SWT.FLAT); + ToolBar toolBar = toolBarManager.createControl(body); + toolBar.setLayoutData(CmsSwtUtils.fillWidth()); + toolBarManager.add(action); + toolBarManager.update(true); + return userViewerCmp; + } + + private class MyUserTableViewer extends LdifUsersTable { + private static final long serialVersionUID = 2653790051461237329L; + + private Button showSystemRoleBtn; + + private final User user; + private final UserFilter userFilter; + + public MyUserTableViewer(Composite parent, int style, User user) { + super(parent, style, true); + this.user = user; + userFilter = new UserFilter(); + } + + protected void populateStaticFilters(Composite staticFilterCmp) { + staticFilterCmp.setLayout(new GridLayout()); + showSystemRoleBtn = new Button(staticFilterCmp, SWT.CHECK); + showSystemRoleBtn.setText("Show system roles"); + boolean showSysRole = CurrentUser.isInRole(CmsConstants.ROLE_ADMIN); + showSystemRoleBtn.setSelection(showSysRole); + userFilter.setShowSystemRole(showSysRole); + showSystemRoleBtn.addSelectionListener(new SelectionAdapter() { + private static final long serialVersionUID = -7033424592697691676L; + + @Override + public void widgetSelected(SelectionEvent e) { + userFilter.setShowSystemRole(showSystemRoleBtn.getSelection()); + refresh(); + } + }); + } + + @Override + protected List listFilteredElements(String filter) { + List users = (List) getFlatGroups(null); + List filteredUsers = new ArrayList(); + if (users.contains(user)) + users.remove(user); + userFilter.setSearchText(filter); + for (User user : users) + if (userFilter.select(null, null, user)) + filteredUsers.add(user); + return filteredUsers; + } + } + + // private void addRemoveAbility(Composite parent, TableViewer userViewer, User + // user) { + // // Section section = sectionPart.getSection(); + // ToolBarManager toolBarManager = new ToolBarManager(SWT.FLAT); + // ToolBar toolbar = toolBarManager.createControl(parent); + // final Cursor handCursor = new Cursor(Display.getCurrent(), SWT.CURSOR_HAND); + // toolbar.setCursor(handCursor); + // toolbar.addDisposeListener(new DisposeListener() { + // private static final long serialVersionUID = 3882131405820522925L; + // + // public void widgetDisposed(DisposeEvent e) { + // if ((handCursor != null) && (handCursor.isDisposed() == false)) { + // handCursor.dispose(); + // } + // } + // }); + // + // String tooltip = "Remove " + UserAdminUtils.getUserLocalId(user.getName()) + + // " from the below selected groups"; + // Action action = new RemoveMembershipAction(userViewer, user, tooltip, + // SecurityAdminImages.ICON_REMOVE_DESC); + // toolBarManager.add(action); + // toolBarManager.update(true); + // // section.setTextClient(toolbar); + // } + + private class RemoveMembershipAction extends Action { + private static final long serialVersionUID = -1337713097184522588L; + + private final TableViewer userViewer; + private final User user; + + RemoveMembershipAction(TableViewer userViewer, User user, String name, ImageDescriptor img) { + super(name, img); + this.userViewer = userViewer; + this.user = user; + } + + @Override + public void run() { + ISelection selection = userViewer.getSelection(); + if (selection.isEmpty()) + return; + + @SuppressWarnings("unchecked") + Iterator it = ((IStructuredSelection) selection).iterator(); + List groups = new ArrayList(); + while (it.hasNext()) { + Group currGroup = it.next(); + groups.add(currGroup); + } + + userAdminWrapper.beginTransactionIfNeeded(); + for (Group group : groups) { + group.removeMember(user); + } + userAdminWrapper.commitOrNotifyTransactionStateChange(); + for (Group group : groups) { + userAdminWrapper.notifyListeners(new UserAdminEvent(null, UserAdminEvent.ROLE_CHANGED, group)); + } + } + } + + /** + * Defines the table as being a potential target to add group memberships + * (roles) to this user + */ + private class GroupDropListener extends ViewerDropAdapter { + private static final long serialVersionUID = 2893468717831451621L; + + private final UserAdminWrapper myUserAdminWrapper; + private final User myUser; + + public GroupDropListener(UserAdminWrapper userAdminWrapper, Viewer userViewer, User user) { + super(userViewer); + this.myUserAdminWrapper = userAdminWrapper; + this.myUser = user; + } + + @Override + public boolean validateDrop(Object target, int operation, TransferData transferType) { + // Target is always OK in a list only view + // TODO check if not a string + boolean validDrop = true; + return validDrop; + } + + @Override + public void drop(DropTargetEvent event) { + String name = (String) event.data; + UserAdmin myUserAdmin = myUserAdminWrapper.getUserAdmin(); + Role role = myUserAdmin.getRole(name); + // TODO this check should be done before. + if (role.getType() == Role.GROUP) { + // TODO check if the user is already member of this group + + myUserAdminWrapper.beginTransactionIfNeeded(); + Group group = (Group) role; + group.addMember(myUser); + userAdminWrapper.commitOrNotifyTransactionStateChange(); + myUserAdminWrapper.notifyListeners(new UserAdminEvent(null, UserAdminEvent.ROLE_CHANGED, group)); + } + super.drop(event); + } + + @Override + public boolean performDrop(Object data) { + // userTableViewerCmp.refresh(); + return true; + } + } + + // LOCAL HELPERS + private void refreshFormTitle(User group) { + // getManagedForm().getForm().setText(UserAdminUtils.getProperty(group, + // LdapAttrs.cn.name())); + } + + /** Appends a section with a title */ + // private Section addSection(FormToolkit tk, Composite parent, String title) { + // Section section = tk.createSection(parent, Section.TITLE_BAR); + // GridData gd = EclipseUiUtils.fillWidth(); + // gd.verticalAlignment = PRE_TITLE_INDENT; + // section.setLayoutData(gd); + // section.setText(title); + // // section.getMenu().setVisible(true); + // + // Composite body = tk.createComposite(section, SWT.WRAP); + // body.setLayoutData(EclipseUiUtils.fillAll()); + // section.setClient(body); + // + // return section; + // } + +} diff --git a/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/UserTableDefaultDClickListener.java b/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/UserTableDefaultDClickListener.java new file mode 100644 index 0000000..c6d024e --- /dev/null +++ b/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/UserTableDefaultDClickListener.java @@ -0,0 +1,39 @@ +package org.argeo.cms.e4.users; + +import org.argeo.cms.e4.CmsE4Utils; +import org.argeo.util.naming.LdapAttrs; +import org.eclipse.e4.ui.workbench.modeling.EPartService; +import org.eclipse.jface.viewers.DoubleClickEvent; +import org.eclipse.jface.viewers.IDoubleClickListener; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.osgi.service.useradmin.Group; +import org.osgi.service.useradmin.User; + +/** + * Default double click listener for the various user tables, will open the + * clicked item in the editor + */ +public class UserTableDefaultDClickListener implements IDoubleClickListener { + private final EPartService partService; + + public UserTableDefaultDClickListener(EPartService partService) { + this.partService = partService; + } + + public void doubleClick(DoubleClickEvent evt) { + if (evt.getSelection().isEmpty()) + return; + Object obj = ((IStructuredSelection) evt.getSelection()).getFirstElement(); + User user = (User) obj; + + String editorId = getEditorId(user); + CmsE4Utils.openEditor(partService, editorId, LdapAttrs.uid.name(), user.getName()); + } + + protected String getEditorId(User user) { + if (user instanceof Group) + return "org.argeo.cms.e4.partdescriptor.groupEditor"; + else + return "org.argeo.cms.e4.partdescriptor.userEditor"; + } +} diff --git a/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/UsersView.java b/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/UsersView.java new file mode 100644 index 0000000..720945c --- /dev/null +++ b/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/UsersView.java @@ -0,0 +1,182 @@ +package org.argeo.cms.e4.users; + +import java.util.ArrayList; +import java.util.List; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import javax.inject.Inject; + +import org.argeo.api.cms.CmsConstants; +import org.argeo.cms.auth.CurrentUser; +import org.argeo.cms.e4.users.providers.CommonNameLP; +import org.argeo.cms.e4.users.providers.DomainNameLP; +import org.argeo.cms.e4.users.providers.MailLP; +import org.argeo.cms.e4.users.providers.UserDragListener; +import org.argeo.cms.e4.users.providers.UserNameLP; +import org.argeo.cms.swt.CmsException; +import org.argeo.eclipse.ui.ColumnDefinition; +import org.argeo.eclipse.ui.EclipseUiUtils; +import org.argeo.eclipse.ui.parts.LdifUsersTable; +import org.argeo.util.naming.LdapAttrs; +import org.argeo.util.naming.LdapObjs; +import org.eclipse.e4.ui.di.Focus; +import org.eclipse.e4.ui.workbench.modeling.EPartService; +import org.eclipse.e4.ui.workbench.modeling.ESelectionService; +import org.eclipse.jface.viewers.ISelectionChangedListener; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.viewers.SelectionChangedEvent; +import org.eclipse.jface.viewers.TableViewer; +import org.eclipse.swt.SWT; +import org.eclipse.swt.dnd.DND; +import org.eclipse.swt.dnd.TextTransfer; +import org.eclipse.swt.dnd.Transfer; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Display; +import org.osgi.framework.InvalidSyntaxException; +import org.osgi.service.useradmin.Role; +import org.osgi.service.useradmin.User; +import org.osgi.service.useradmin.UserAdminEvent; +import org.osgi.service.useradmin.UserAdminListener; + +/** List all users with filter - based on Ldif userAdmin */ +public class UsersView { + // private final static Log log = LogFactory.getLog(UsersView.class); + + // public final static String ID = WorkbenchUiPlugin.PLUGIN_ID + ".usersView"; + + @Inject + private UserAdminWrapper userAdminWrapper; + @Inject + private EPartService partService; + + // UI Objects + private LdifUsersTable userTableViewerCmp; + private TableViewer userViewer; + private List columnDefs = new ArrayList(); + + private UserAdminListener listener; + + @PostConstruct + public void createPartControl(Composite parent, ESelectionService selectionService) { + + parent.setLayout(EclipseUiUtils.noSpaceGridLayout()); + // Define the displayed columns + columnDefs.add(new ColumnDefinition(new CommonNameLP(), "Common Name", 150)); + columnDefs.add(new ColumnDefinition(new MailLP(), "E-mail", 150)); + columnDefs.add(new ColumnDefinition(new DomainNameLP(), "Domain", 200)); + // Only show technical DN to admin + if (CurrentUser.isInRole(CmsConstants.ROLE_ADMIN)) + columnDefs.add(new ColumnDefinition(new UserNameLP(), "Distinguished Name", 300)); + + // Create and configure the table + userTableViewerCmp = new MyUserTableViewer(parent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL); + userTableViewerCmp.setLayoutData(EclipseUiUtils.fillAll()); + userTableViewerCmp.setColumnDefinitions(columnDefs); + userTableViewerCmp.populate(true, false); + + // Links + userViewer = userTableViewerCmp.getTableViewer(); + userViewer.addDoubleClickListener(new UserTableDefaultDClickListener(partService)); + userViewer.addSelectionChangedListener(new ISelectionChangedListener() { + + @Override + public void selectionChanged(SelectionChangedEvent event) { + IStructuredSelection selection = (IStructuredSelection) event.getSelection(); + selectionService.setSelection(selection.toList()); + } + }); + // getViewSite().setSelectionProvider(userViewer); + + // Really? + userTableViewerCmp.refresh(); + + // Drag and drop + int operations = DND.DROP_COPY | DND.DROP_MOVE; + Transfer[] tt = new Transfer[] { TextTransfer.getInstance() }; + userViewer.addDragSupport(operations, tt, new UserDragListener(userViewer)); + + // Register a useradmin listener + listener = new MyUiUAListener(parent.getDisplay()); + userAdminWrapper.addListener(listener); + } + + private class MyUiUAListener extends UiUserAdminListener { + public MyUiUAListener(Display display) { + super(display); + } + + @Override + public void roleChangedToUiThread(UserAdminEvent event) { + if (userViewer != null && !userViewer.getTable().isDisposed()) + refresh(); + } + } + + private class MyUserTableViewer extends LdifUsersTable { + private static final long serialVersionUID = 8467999509931900367L; + + private final String[] knownProps = { LdapAttrs.DN, LdapAttrs.uid.name(), LdapAttrs.cn.name(), + LdapAttrs.givenName.name(), LdapAttrs.sn.name(), LdapAttrs.mail.name() }; + + public MyUserTableViewer(Composite parent, int style) { + super(parent, style); + } + + @Override + protected List listFilteredElements(String filter) { + Role[] roles; + + try { + StringBuilder builder = new StringBuilder(); + + StringBuilder tmpBuilder = new StringBuilder(); + if (EclipseUiUtils.notEmpty(filter)) + for (String prop : knownProps) { + tmpBuilder.append("("); + tmpBuilder.append(prop); + tmpBuilder.append("=*"); + tmpBuilder.append(filter); + tmpBuilder.append("*)"); + } + if (tmpBuilder.length() > 1) { + builder.append("(&(").append(LdapAttrs.objectClass.name()).append("=") + .append(LdapObjs.inetOrgPerson.name()).append(")(|"); + builder.append(tmpBuilder.toString()); + builder.append("))"); + } else + builder.append("(").append(LdapAttrs.objectClass.name()).append("=") + .append(LdapObjs.inetOrgPerson.name()).append(")"); + roles = userAdminWrapper.getUserAdmin().getRoles(builder.toString()); + } catch (InvalidSyntaxException e) { + throw new CmsException("Unable to get roles with filter: " + filter, e); + } + List users = new ArrayList(); + for (Role role : roles) + // if (role.getType() == Role.USER && role.getType() != + // Role.GROUP) + users.add((User) role); + return users; + } + } + + public void refresh() { + userTableViewerCmp.refresh(); + } + + // Override generic view methods + @PreDestroy + public void dispose() { + userAdminWrapper.removeListener(listener); + } + + @Focus + public void setFocus() { + userTableViewerCmp.setFocus(); + } + + /* DEPENDENCY INJECTION */ + public void setUserAdminWrapper(UserAdminWrapper userAdminWrapper) { + this.userAdminWrapper = userAdminWrapper; + } +} diff --git a/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/handlers/DeleteGroups.java b/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/handlers/DeleteGroups.java new file mode 100644 index 0000000..742bc3f --- /dev/null +++ b/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/handlers/DeleteGroups.java @@ -0,0 +1,95 @@ +package org.argeo.cms.e4.users.handlers; + +import java.util.List; + +import javax.inject.Inject; +import javax.inject.Named; + +import org.argeo.cms.auth.UserAdminUtils; +import org.argeo.cms.e4.users.GroupsView; +import org.argeo.cms.e4.users.UserAdminWrapper; +import org.eclipse.e4.core.di.annotations.CanExecute; +import org.eclipse.e4.core.di.annotations.Execute; +import org.eclipse.e4.ui.model.application.ui.basic.MPart; +import org.eclipse.e4.ui.services.IServiceConstants; +import org.eclipse.e4.ui.workbench.modeling.ESelectionService; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.swt.widgets.Display; +import org.osgi.service.useradmin.Group; +import org.osgi.service.useradmin.UserAdmin; +import org.osgi.service.useradmin.UserAdminEvent; + +/** Delete the selected groups */ +public class DeleteGroups { + // public final static String ID = WorkbenchUiPlugin.PLUGIN_ID + + // ".deleteGroups"; + + /* DEPENDENCY INJECTION */ + @Inject + private UserAdminWrapper userAdminWrapper; + + @Inject + ESelectionService selectionService; + + @SuppressWarnings("unchecked") + @Execute + public void execute(@Named(IServiceConstants.ACTIVE_PART) MPart part, ESelectionService selectionService) { + // ISelection selection = null;// HandlerUtil.getCurrentSelection(event); + // if (selection.isEmpty()) + // return null; + // + // List groups = new ArrayList(); + // Iterator it = ((IStructuredSelection) selection).iterator(); + + List selection = (List) selectionService.getSelection(); + if (selection == null) + return; + + StringBuilder builder = new StringBuilder(); + for (Group group : selection) { + Group currGroup = group; + String groupName = UserAdminUtils.getUserLocalId(currGroup.getName()); + // TODO add checks + builder.append(groupName).append("; "); + // groups.add(currGroup); + } + + if (!MessageDialog.openQuestion(Display.getCurrent().getActiveShell(), "Delete Groups", "Are you sure that you " + + "want to delete these groups?\n" + builder.substring(0, builder.length() - 2))) + return; + + userAdminWrapper.beginTransactionIfNeeded(); + UserAdmin userAdmin = userAdminWrapper.getUserAdmin(); + // IWorkbenchPage iwp = + // HandlerUtil.getActiveWorkbenchWindow(event).getActivePage(); + for (Group group : selection) { + String groupName = group.getName(); + // TODO find a way to close the editor cleanly if opened. Cannot be + // done through the UserAdminListeners, it causes a + // java.util.ConcurrentModificationException because disposing the + // editor unregisters and disposes the listener + // IEditorPart part = iwp.findEditor(new UserEditorInput(groupName)); + // if (part != null) + // iwp.closeEditor(part, false); + userAdmin.removeRole(groupName); + } + userAdminWrapper.commitOrNotifyTransactionStateChange(); + + // Update the view + for (Group group : selection) { + userAdminWrapper.notifyListeners(new UserAdminEvent(null, UserAdminEvent.ROLE_REMOVED, group)); + } + + // return null; + } + + @CanExecute + public boolean canExecute(@Named(IServiceConstants.ACTIVE_PART) MPart part, ESelectionService selectionService) { + return part.getObject() instanceof GroupsView && selectionService.getSelection() != null; + } + + /* DEPENDENCY INJECTION */ + // public void setUserAdminWrapper(UserAdminWrapper userAdminWrapper) { + // this.userAdminWrapper = userAdminWrapper; + // } +} diff --git a/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/handlers/DeleteUsers.java b/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/handlers/DeleteUsers.java new file mode 100644 index 0000000..d1afd22 --- /dev/null +++ b/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/handlers/DeleteUsers.java @@ -0,0 +1,88 @@ +package org.argeo.cms.e4.users.handlers; + +import java.util.List; + +import javax.inject.Inject; +import javax.inject.Named; + +import org.argeo.cms.auth.UserAdminUtils; +import org.argeo.cms.e4.users.UserAdminWrapper; +import org.argeo.cms.e4.users.UsersView; +import org.eclipse.e4.core.di.annotations.CanExecute; +import org.eclipse.e4.core.di.annotations.Execute; +import org.eclipse.e4.ui.model.application.ui.basic.MPart; +import org.eclipse.e4.ui.services.IServiceConstants; +import org.eclipse.e4.ui.workbench.modeling.ESelectionService; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.swt.widgets.Display; +import org.osgi.service.useradmin.User; +import org.osgi.service.useradmin.UserAdmin; +import org.osgi.service.useradmin.UserAdminEvent; + +/** Delete the selected users */ +public class DeleteUsers { + // public final static String ID = WorkbenchUiPlugin.PLUGIN_ID + ".deleteUsers"; + + /* DEPENDENCY INJECTION */ + @Inject + private UserAdminWrapper userAdminWrapper; + + @SuppressWarnings("unchecked") + @Execute + public void execute(@Named(IServiceConstants.ACTIVE_PART) MPart part, ESelectionService selectionService) { + // ISelection selection = null;// HandlerUtil.getCurrentSelection(event); + // if (selection.isEmpty()) + // return null; + List selection = (List) selectionService.getSelection(); + if (selection == null) + return; + +// Iterator it = ((IStructuredSelection) selection).iterator(); +// List users = new ArrayList(); + StringBuilder builder = new StringBuilder(); + + for(User user:selection) { + User currUser = user; +// User currUser = it.next(); + String userName = UserAdminUtils.getUserLocalId(currUser.getName()); + if (UserAdminUtils.isCurrentUser(currUser)) { + MessageDialog.openError(Display.getCurrent().getActiveShell(), "Deletion forbidden", + "You cannot delete your own user this way."); + return; + } + builder.append(userName).append("; "); +// users.add(currUser); + } + + if (!MessageDialog.openQuestion(Display.getCurrent().getActiveShell(), "Delete Users", + "Are you sure that you want to delete these users?\n" + builder.substring(0, builder.length() - 2))) + return; + + userAdminWrapper.beginTransactionIfNeeded(); + UserAdmin userAdmin = userAdminWrapper.getUserAdmin(); + // IWorkbenchPage iwp = + // HandlerUtil.getActiveWorkbenchWindow(event).getActivePage(); + + for (User user : selection) { + String userName = user.getName(); + // TODO find a way to close the editor cleanly if opened. Cannot be + // done through the UserAdminListeners, it causes a + // java.util.ConcurrentModificationException because disposing the + // editor unregisters and disposes the listener + // IEditorPart part = iwp.findEditor(new UserEditorInput(userName)); + // if (part != null) + // iwp.closeEditor(part, false); + userAdmin.removeRole(userName); + } + userAdminWrapper.commitOrNotifyTransactionStateChange(); + + for (User user : selection) { + userAdminWrapper.notifyListeners(new UserAdminEvent(null, UserAdminEvent.ROLE_REMOVED, user)); + } + } + + @CanExecute + public boolean canExecute(@Named(IServiceConstants.ACTIVE_PART) MPart part, ESelectionService selectionService) { + return part.getObject() instanceof UsersView && selectionService.getSelection() != null; + } +} diff --git a/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/handlers/NewGroup.java b/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/handlers/NewGroup.java new file mode 100644 index 0000000..41e14e0 --- /dev/null +++ b/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/handlers/NewGroup.java @@ -0,0 +1,212 @@ +package org.argeo.cms.e4.users.handlers; + +import java.util.Dictionary; +import java.util.Map; + +import javax.inject.Inject; + +import org.argeo.cms.e4.users.UserAdminWrapper; +import org.argeo.cms.swt.CmsException; +import org.argeo.eclipse.ui.EclipseUiUtils; +import org.argeo.eclipse.ui.dialogs.ErrorFeedback; +import org.argeo.util.directory.DirectoryConf; +import org.argeo.util.naming.LdapAttrs; +import org.eclipse.e4.core.di.annotations.Execute; +import org.eclipse.jface.wizard.Wizard; +import org.eclipse.jface.wizard.WizardDialog; +import org.eclipse.jface.wizard.WizardPage; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.FocusEvent; +import org.eclipse.swt.events.FocusListener; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Combo; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Text; +import org.osgi.service.useradmin.Group; +import org.osgi.service.useradmin.Role; +import org.osgi.service.useradmin.UserAdminEvent; + +/** Create a new group */ +public class NewGroup { + // public final static String ID = WorkbenchUiPlugin.PLUGIN_ID + ".newGroup"; + + /* DEPENDENCY INJECTION */ + @Inject + private UserAdminWrapper userAdminWrapper; + + @Execute + public Object execute() { + NewGroupWizard newGroupWizard = new NewGroupWizard(); + newGroupWizard.setWindowTitle("Group creation"); + WizardDialog dialog = new WizardDialog(Display.getCurrent().getActiveShell(), newGroupWizard); + dialog.open(); + return null; + } + + private class NewGroupWizard extends Wizard { + + // Pages + private MainGroupInfoWizardPage mainGroupInfo; + + // UI fields + private Text dNameTxt, commonNameTxt, descriptionTxt; + private Combo baseDnCmb; + + public NewGroupWizard() { + } + + @Override + public void addPages() { + mainGroupInfo = new MainGroupInfoWizardPage(); + addPage(mainGroupInfo); + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Override + public boolean performFinish() { + if (!canFinish()) + return false; + String commonName = commonNameTxt.getText(); + try { + userAdminWrapper.beginTransactionIfNeeded(); + String dn = getDn(commonName); + Group group = (Group) userAdminWrapper.getUserAdmin().createRole(dn, Role.GROUP); + Dictionary props = group.getProperties(); + String descStr = descriptionTxt.getText(); + if (EclipseUiUtils.notEmpty(descStr)) + props.put(LdapAttrs.description.name(), descStr); + userAdminWrapper.commitOrNotifyTransactionStateChange(); + userAdminWrapper.notifyListeners(new UserAdminEvent(null, UserAdminEvent.ROLE_CREATED, group)); + return true; + } catch (Exception e) { + ErrorFeedback.show("Cannot create new group " + commonName, e); + return false; + } + } + + private class MainGroupInfoWizardPage extends WizardPage implements FocusListener { + private static final long serialVersionUID = -3150193365151601807L; + + public MainGroupInfoWizardPage() { + super("Main"); + setTitle("General information"); + setMessage("Please choose a domain, provide a common name " + "and a free description"); + } + + @Override + public void createControl(Composite parent) { + Composite bodyCmp = new Composite(parent, SWT.NONE); + setControl(bodyCmp); + bodyCmp.setLayout(new GridLayout(2, false)); + + dNameTxt = EclipseUiUtils.createGridLT(bodyCmp, "Distinguished name"); + dNameTxt.setEnabled(false); + + baseDnCmb = createGridLC(bodyCmp, "Base DN"); + // Initialise before adding the listener to avoid NPE + initialiseDnCmb(baseDnCmb); + baseDnCmb.addFocusListener(this); + + commonNameTxt = EclipseUiUtils.createGridLT(bodyCmp, "Common name"); + commonNameTxt.addFocusListener(this); + + Label descLbl = new Label(bodyCmp, SWT.LEAD); + descLbl.setText("Description"); + descLbl.setLayoutData(new GridData(SWT.RIGHT, SWT.TOP, false, false)); + descriptionTxt = new Text(bodyCmp, SWT.LEAD | SWT.MULTI | SWT.WRAP | SWT.BORDER); + descriptionTxt.setLayoutData(EclipseUiUtils.fillAll()); + descriptionTxt.addFocusListener(this); + + // Initialize buttons + setPageComplete(false); + getContainer().updateButtons(); + } + + @Override + public void focusLost(FocusEvent event) { + String name = commonNameTxt.getText(); + if (EclipseUiUtils.isEmpty(name)) + dNameTxt.setText(""); + else + dNameTxt.setText(getDn(name)); + + String message = checkComplete(); + if (message != null) { + setMessage(message, WizardPage.ERROR); + setPageComplete(false); + } else { + setMessage("Complete", WizardPage.INFORMATION); + setPageComplete(true); + } + getContainer().updateButtons(); + } + + @Override + public void focusGained(FocusEvent event) { + } + + /** @return the error message or null if complete */ + protected String checkComplete() { + String name = commonNameTxt.getText(); + + if (name.trim().equals("")) + return "Common name must not be empty"; + Role role = userAdminWrapper.getUserAdmin().getRole(getDn(name)); + if (role != null) + return "Group " + name + " already exists"; + return null; + } + + @Override + public void setVisible(boolean visible) { + super.setVisible(visible); + if (visible) + if (baseDnCmb.getSelectionIndex() == -1) + baseDnCmb.setFocus(); + else + commonNameTxt.setFocus(); + } + } + + private Map getDns() { + return userAdminWrapper.getKnownBaseDns(true); + } + + private String getDn(String cn) { + Map dns = getDns(); + String bdn = baseDnCmb.getText(); + if (EclipseUiUtils.notEmpty(bdn)) { + Dictionary props = DirectoryConf.uriAsProperties(dns.get(bdn)); + String dn = LdapAttrs.cn.name() + "=" + cn + "," + DirectoryConf.groupBase.getValue(props) + "," + bdn; + return dn; + } + return null; + } + + private void initialiseDnCmb(Combo combo) { + Map dns = userAdminWrapper.getKnownBaseDns(true); + if (dns.isEmpty()) + throw new CmsException("No writable base dn found. Cannot create group"); + combo.setItems(dns.keySet().toArray(new String[0])); + if (dns.size() == 1) + combo.select(0); + } + } + + private Combo createGridLC(Composite parent, String label) { + Label lbl = new Label(parent, SWT.LEAD); + lbl.setText(label); + lbl.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false)); + Combo combo = new Combo(parent, SWT.LEAD | SWT.BORDER | SWT.READ_ONLY); + combo.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); + return combo; + } + + /* DEPENDENCY INJECTION */ + public void setUserAdminWrapper(UserAdminWrapper userAdminWrapper) { + this.userAdminWrapper = userAdminWrapper; + } +} diff --git a/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/handlers/NewUser.java b/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/handlers/NewUser.java new file mode 100644 index 0000000..40a4460 --- /dev/null +++ b/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/handlers/NewUser.java @@ -0,0 +1,287 @@ +package org.argeo.cms.e4.users.handlers; + +import java.util.Dictionary; +import java.util.List; +import java.util.Map; + +import javax.inject.Inject; +import javax.naming.InvalidNameException; +import javax.naming.ldap.LdapName; +import javax.naming.ldap.Rdn; + +import org.argeo.cms.auth.UserAdminUtils; +import org.argeo.cms.e4.users.UiAdminUtils; +import org.argeo.cms.e4.users.UserAdminWrapper; +import org.argeo.cms.swt.CmsException; +import org.argeo.eclipse.ui.EclipseUiUtils; +import org.argeo.eclipse.ui.dialogs.ErrorFeedback; +import org.argeo.util.directory.DirectoryConf; +import org.argeo.util.naming.LdapAttrs; +import org.eclipse.e4.core.di.annotations.Execute; +import org.eclipse.jface.wizard.Wizard; +import org.eclipse.jface.wizard.WizardDialog; +import org.eclipse.jface.wizard.WizardPage; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Combo; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Text; +import org.osgi.service.useradmin.Role; +import org.osgi.service.useradmin.User; +import org.osgi.service.useradmin.UserAdminEvent; + +/** Open a wizard that enables creation of a new user. */ +public class NewUser { + // private final static Log log = LogFactory.getLog(NewUser.class); + // public final static String ID = WorkbenchUiPlugin.PLUGIN_ID + ".newUser"; + + /* DEPENDENCY INJECTION */ + @Inject + private UserAdminWrapper userAdminWrapper; + + @Execute + public Object execute() { + NewUserWizard newUserWizard = new NewUserWizard(); + newUserWizard.setWindowTitle("User creation"); + WizardDialog dialog = new WizardDialog(Display.getCurrent().getActiveShell(), newUserWizard); + dialog.open(); + return null; + } + + private class NewUserWizard extends Wizard { + + // pages + private MainUserInfoWizardPage mainUserInfo; + + // End user fields + private Text dNameTxt, usernameTxt, firstNameTxt, lastNameTxt, primaryMailTxt, pwd1Txt, pwd2Txt; + private Combo baseDnCmb; + + public NewUserWizard() { + + } + + @Override + public void addPages() { + mainUserInfo = new MainUserInfoWizardPage(); + addPage(mainUserInfo); + String message = "Default wizard that also eases user creation tests:\n " + + "Mail and last name are automatically " + + "generated form the uid. Password are defauted to 'demo'."; + mainUserInfo.setMessage(message, WizardPage.WARNING); + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Override + public boolean performFinish() { + if (!canFinish()) + return false; + String username = mainUserInfo.getUsername(); + userAdminWrapper.beginTransactionIfNeeded(); + try { + User user = (User) userAdminWrapper.getUserAdmin().createRole(getDn(username), Role.USER); + + Dictionary props = user.getProperties(); + + String lastNameStr = lastNameTxt.getText(); + if (EclipseUiUtils.notEmpty(lastNameStr)) + props.put(LdapAttrs.sn.name(), lastNameStr); + + String firstNameStr = firstNameTxt.getText(); + if (EclipseUiUtils.notEmpty(firstNameStr)) + props.put(LdapAttrs.givenName.name(), firstNameStr); + + String cn = UserAdminUtils.buildDefaultCn(firstNameStr, lastNameStr); + if (EclipseUiUtils.notEmpty(cn)) + props.put(LdapAttrs.cn.name(), cn); + + String mailStr = primaryMailTxt.getText(); + if (EclipseUiUtils.notEmpty(mailStr)) + props.put(LdapAttrs.mail.name(), mailStr); + + char[] password = mainUserInfo.getPassword(); + user.getCredentials().put(null, password); + userAdminWrapper.commitOrNotifyTransactionStateChange(); + userAdminWrapper.notifyListeners(new UserAdminEvent(null, UserAdminEvent.ROLE_CREATED, user)); + return true; + } catch (Exception e) { + ErrorFeedback.show("Cannot create new user " + username, e); + return false; + } + } + + private class MainUserInfoWizardPage extends WizardPage implements ModifyListener { + private static final long serialVersionUID = -3150193365151601807L; + + public MainUserInfoWizardPage() { + super("Main"); + setTitle("Required Information"); + } + + @Override + public void createControl(Composite parent) { + Composite composite = new Composite(parent, SWT.NONE); + composite.setLayout(new GridLayout(2, false)); + dNameTxt = EclipseUiUtils.createGridLT(composite, "Distinguished name", this); + dNameTxt.setEnabled(false); + + baseDnCmb = createGridLC(composite, "Base DN"); + initialiseDnCmb(baseDnCmb); + baseDnCmb.addModifyListener(this); + baseDnCmb.addModifyListener(new ModifyListener() { + private static final long serialVersionUID = -1435351236582736843L; + + @Override + public void modifyText(ModifyEvent event) { + String name = usernameTxt.getText(); + dNameTxt.setText(getDn(name)); + } + }); + + usernameTxt = EclipseUiUtils.createGridLT(composite, "Local ID", this); + usernameTxt.addModifyListener(new ModifyListener() { + private static final long serialVersionUID = -1435351236582736843L; + + @Override + public void modifyText(ModifyEvent event) { + String name = usernameTxt.getText(); + if (name.trim().equals("")) { + dNameTxt.setText(""); + lastNameTxt.setText(""); + primaryMailTxt.setText(""); + pwd1Txt.setText(""); + pwd2Txt.setText(""); + } else { + dNameTxt.setText(getDn(name)); + lastNameTxt.setText(name.toUpperCase()); + primaryMailTxt.setText(getMail(name)); + pwd1Txt.setText("demo"); + pwd2Txt.setText("demo"); + } + } + }); + + primaryMailTxt = EclipseUiUtils.createGridLT(composite, "Email", this); + firstNameTxt = EclipseUiUtils.createGridLT(composite, "First name", this); + lastNameTxt = EclipseUiUtils.createGridLT(composite, "Last name", this); + pwd1Txt = EclipseUiUtils.createGridLP(composite, "Password", this); + pwd2Txt = EclipseUiUtils.createGridLP(composite, "Repeat password", this); + setControl(composite); + + // Initialize buttons + setPageComplete(false); + getContainer().updateButtons(); + } + + @Override + public void modifyText(ModifyEvent event) { + String message = checkComplete(); + if (message != null) { + setMessage(message, WizardPage.ERROR); + setPageComplete(false); + } else { + setMessage("Complete", WizardPage.INFORMATION); + setPageComplete(true); + } + getContainer().updateButtons(); + } + + /** @return error message or null if complete */ + protected String checkComplete() { + String name = usernameTxt.getText(); + + if (name.trim().equals("")) + return "User name must not be empty"; + Role role = userAdminWrapper.getUserAdmin().getRole(getDn(name)); + if (role != null) + return "User " + name + " already exists"; + if (!primaryMailTxt.getText().matches(UiAdminUtils.EMAIL_PATTERN)) + return "Not a valid email address"; + if (lastNameTxt.getText().trim().equals("")) + return "Specify a last name"; + if (pwd1Txt.getText().trim().equals("")) + return "Specify a password"; + if (pwd2Txt.getText().trim().equals("")) + return "Repeat the password"; + if (!pwd2Txt.getText().equals(pwd1Txt.getText())) + return "Passwords are different"; + return null; + } + + @Override + public void setVisible(boolean visible) { + super.setVisible(visible); + if (visible) + if (baseDnCmb.getSelectionIndex() == -1) + baseDnCmb.setFocus(); + else + usernameTxt.setFocus(); + } + + public String getUsername() { + return usernameTxt.getText(); + } + + public char[] getPassword() { + return pwd1Txt.getTextChars(); + } + + } + + private Map getDns() { + return userAdminWrapper.getKnownBaseDns(true); + } + + private String getDn(String uid) { + Map dns = getDns(); + String bdn = baseDnCmb.getText(); + if (EclipseUiUtils.notEmpty(bdn)) { + Dictionary props = DirectoryConf.uriAsProperties(dns.get(bdn)); + String dn = LdapAttrs.uid.name() + "=" + uid + "," + DirectoryConf.userBase.getValue(props) + "," + bdn; + return dn; + } + return null; + } + + private void initialiseDnCmb(Combo combo) { + Map dns = userAdminWrapper.getKnownBaseDns(true); + if (dns.isEmpty()) + throw new CmsException("No writable base dn found. Cannot create user"); + combo.setItems(dns.keySet().toArray(new String[0])); + if (dns.size() == 1) + combo.select(0); + } + + private String getMail(String username) { + if (baseDnCmb.getSelectionIndex() == -1) + return null; + String baseDn = baseDnCmb.getText(); + try { + LdapName name = new LdapName(baseDn); + List rdns = name.getRdns(); + return username + "@" + (String) rdns.get(1).getValue() + '.' + (String) rdns.get(0).getValue(); + } catch (InvalidNameException e) { + throw new CmsException("Unable to generate mail for " + username + " with base dn " + baseDn, e); + } + } + } + + private Combo createGridLC(Composite parent, String label) { + Label lbl = new Label(parent, SWT.LEAD); + lbl.setText(label); + lbl.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false)); + Combo combo = new Combo(parent, SWT.LEAD | SWT.BORDER | SWT.READ_ONLY); + combo.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); + return combo; + } + + /* DEPENDENCY INJECTION */ + public void setUserAdminWrapper(UserAdminWrapper userAdminWrapper) { + this.userAdminWrapper = userAdminWrapper; + } +} diff --git a/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/handlers/package-info.java b/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/handlers/package-info.java new file mode 100644 index 0000000..cf3db1d --- /dev/null +++ b/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/handlers/package-info.java @@ -0,0 +1,2 @@ +/** Users management handlers. */ +package org.argeo.cms.e4.users.handlers; \ No newline at end of file diff --git a/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/package-info.java b/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/package-info.java new file mode 100644 index 0000000..c6f14b0 --- /dev/null +++ b/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/package-info.java @@ -0,0 +1,2 @@ +/** Users management perspective. */ +package org.argeo.cms.e4.users; \ No newline at end of file diff --git a/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/providers/CommonNameLP.java b/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/providers/CommonNameLP.java new file mode 100644 index 0000000..2d8db67 --- /dev/null +++ b/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/providers/CommonNameLP.java @@ -0,0 +1,21 @@ +package org.argeo.cms.e4.users.providers; + +import org.argeo.cms.auth.UserAdminUtils; +import org.argeo.util.naming.LdapAttrs; +import org.osgi.service.useradmin.User; + +/** Simply declare a label provider that returns the common name of a user */ +public class CommonNameLP extends UserAdminAbstractLP { + private static final long serialVersionUID = 5256703081044911941L; + + @Override + public String getText(User user) { + return UserAdminUtils.getProperty(user, LdapAttrs.cn.name()); + } + + @Override + public String getToolTipText(Object element) { + return UserAdminUtils.getProperty((User) element, LdapAttrs.DN); + } + +} diff --git a/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/providers/DomainNameLP.java b/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/providers/DomainNameLP.java new file mode 100644 index 0000000..e23729d --- /dev/null +++ b/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/providers/DomainNameLP.java @@ -0,0 +1,14 @@ +package org.argeo.cms.e4.users.providers; + +import org.argeo.cms.auth.UserAdminUtils; +import org.osgi.service.useradmin.User; + +/** The human friendly domain name for the corresponding user. */ +public class DomainNameLP extends UserAdminAbstractLP { + private static final long serialVersionUID = 5256703081044911941L; + + @Override + public String getText(User user) { + return UserAdminUtils.getDomainName(user); + } +} diff --git a/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/providers/MailLP.java b/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/providers/MailLP.java new file mode 100644 index 0000000..52d3b85 --- /dev/null +++ b/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/providers/MailLP.java @@ -0,0 +1,15 @@ +package org.argeo.cms.e4.users.providers; + +import org.argeo.cms.auth.UserAdminUtils; +import org.argeo.util.naming.LdapAttrs; +import org.osgi.service.useradmin.User; + +/** Simply declare a label provider that returns the Primary Mail of a user */ +public class MailLP extends UserAdminAbstractLP { + private static final long serialVersionUID = 8329764452141982707L; + + @Override + public String getText(User user) { + return UserAdminUtils.getProperty(user, LdapAttrs.mail.name()); + } +} diff --git a/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/providers/RoleIconLP.java b/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/providers/RoleIconLP.java new file mode 100644 index 0000000..8e12eed --- /dev/null +++ b/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/providers/RoleIconLP.java @@ -0,0 +1,35 @@ +package org.argeo.cms.e4.users.providers; + +import org.argeo.api.cms.CmsContext; +import org.argeo.api.cms.CmsConstants; +import org.argeo.cms.auth.UserAdminUtils; +import org.argeo.cms.e4.users.SecurityAdminImages; +import org.argeo.util.naming.LdapAttrs; +import org.eclipse.swt.graphics.Image; +import org.osgi.service.useradmin.Role; +import org.osgi.service.useradmin.User; + +/** Provide a bundle specific image depending on the current user type */ +public class RoleIconLP extends UserAdminAbstractLP { + private static final long serialVersionUID = 6550449442061090388L; + + @Override + public String getText(User user) { + return ""; + } + + @Override + public Image getImage(Object element) { + User user = (User) element; + String dn = user.getName(); + if (dn.endsWith(CmsConstants.SYSTEM_ROLES_BASEDN)) + return SecurityAdminImages.ICON_ROLE; + else if (user.getType() == Role.GROUP) { + String businessCategory = UserAdminUtils.getProperty(user, LdapAttrs.businessCategory); + if (businessCategory != null && businessCategory.equals(CmsContext.WORKGROUP)) + return SecurityAdminImages.ICON_WORKGROUP; + return SecurityAdminImages.ICON_GROUP; + } else + return SecurityAdminImages.ICON_USER; + } +} diff --git a/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/providers/UserAdminAbstractLP.java b/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/providers/UserAdminAbstractLP.java new file mode 100644 index 0000000..29873db --- /dev/null +++ b/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/providers/UserAdminAbstractLP.java @@ -0,0 +1,66 @@ +package org.argeo.cms.e4.users.providers; + +import javax.naming.InvalidNameException; +import javax.naming.ldap.LdapName; + +import org.argeo.cms.auth.UserAdminUtils; +import org.argeo.cms.swt.CmsException; +import org.eclipse.jface.resource.JFaceResources; +import org.eclipse.jface.viewers.ColumnLabelProvider; +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Font; +import org.eclipse.swt.widgets.Display; +import org.osgi.service.useradmin.User; + +/** + * Utility class that add font modifications to a column label provider + * depending on the given user properties + */ +public abstract class UserAdminAbstractLP extends ColumnLabelProvider { + private static final long serialVersionUID = 137336765024922368L; + + // private Font italic; + private Font bold; + + @Override + public Font getFont(Object element) { + // Self as bold + try { + LdapName selfUserName = UserAdminUtils.getCurrentUserLdapName(); + String userName = ((User) element).getName(); + LdapName userLdapName = new LdapName(userName); + if (userLdapName.equals(selfUserName)) { + if (bold == null) + bold = JFaceResources.getFontRegistry() + .defaultFontDescriptor().setStyle(SWT.BOLD) + .createFont(Display.getCurrent()); + return bold; + } + } catch (InvalidNameException e) { + throw new CmsException("cannot parse dn for " + element, e); + } + + // Disabled as Italic + // Node userProfile = (Node) elem; + // if (!userProfile.getProperty(ARGEO_ENABLED).getBoolean()) + // return italic; + + return null; + // return super.getFont(element); + } + + @Override + public String getText(Object element) { + User user = (User) element; + return getText(user); + } + + public void setDisplay(Display display) { + // italic = JFaceResources.getFontRegistry().defaultFontDescriptor() + // .setStyle(SWT.ITALIC).createFont(display); + bold = JFaceResources.getFontRegistry().defaultFontDescriptor() + .setStyle(SWT.BOLD).createFont(Display.getCurrent()); + } + + public abstract String getText(User user); +} diff --git a/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/providers/UserDragListener.java b/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/providers/UserDragListener.java new file mode 100644 index 0000000..56a2624 --- /dev/null +++ b/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/providers/UserDragListener.java @@ -0,0 +1,40 @@ +package org.argeo.cms.e4.users.providers; + +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.swt.dnd.DragSourceEvent; +import org.eclipse.swt.dnd.DragSourceListener; +import org.osgi.service.useradmin.User; + +/** Default drag listener to modify group and users via the UI */ +public class UserDragListener implements DragSourceListener { + private static final long serialVersionUID = -2074337775033781454L; + private final Viewer viewer; + + public UserDragListener(Viewer viewer) { + this.viewer = viewer; + } + + public void dragStart(DragSourceEvent event) { + // TODO implement finer checks + IStructuredSelection selection = (IStructuredSelection) viewer + .getSelection(); + if (selection.isEmpty() || selection.size() > 1) + event.doit = false; + else + event.doit = true; + } + + public void dragSetData(DragSourceEvent event) { + // TODO Support multiple selection + Object obj = ((IStructuredSelection) viewer.getSelection()) + .getFirstElement(); + if (obj != null) { + User user = (User) obj; + event.data = user.getName(); + } + } + + public void dragFinished(DragSourceEvent event) { + } +} diff --git a/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/providers/UserFilter.java b/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/providers/UserFilter.java new file mode 100644 index 0000000..7a7bfbf --- /dev/null +++ b/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/providers/UserFilter.java @@ -0,0 +1,58 @@ +package org.argeo.cms.e4.users.providers; + +import static org.argeo.eclipse.ui.EclipseUiUtils.notEmpty; + +import org.argeo.api.cms.CmsConstants; +import org.argeo.cms.auth.UserAdminUtils; +import org.argeo.util.naming.LdapAttrs; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.jface.viewers.ViewerFilter; +import org.osgi.service.useradmin.User; + +/** + * Filter user list using JFace mechanism on the client (yet on the server) side + * rather than having the UserAdmin to process the search + */ +public class UserFilter extends ViewerFilter { + private static final long serialVersionUID = 5082509381672880568L; + + private String searchString; + private boolean showSystemRole = true; + + private final String[] knownProps = { LdapAttrs.DN, LdapAttrs.cn.name(), LdapAttrs.givenName.name(), + LdapAttrs.sn.name(), LdapAttrs.uid.name(), LdapAttrs.description.name(), LdapAttrs.mail.name() }; + + public void setSearchText(String s) { + // ensure that the value can be used for matching + if (notEmpty(s)) + searchString = ".*" + s.toLowerCase() + ".*"; + else + searchString = ".*"; + } + + public void setShowSystemRole(boolean showSystemRole) { + this.showSystemRole = showSystemRole; + } + + @Override + public boolean select(Viewer viewer, Object parentElement, Object element) { + User user = (User) element; + if (!showSystemRole && user.getName().matches(".*(" + CmsConstants.SYSTEM_ROLES_BASEDN + ")")) + // UserAdminUtils.getProperty(user, LdifName.dn.name()) + // .toLowerCase().endsWith(AuthConstants.ROLES_BASEDN)) + return false; + + if (searchString == null || searchString.length() == 0) + return true; + + if (user.getName().matches(searchString)) + return true; + + for (String key : knownProps) { + String currVal = UserAdminUtils.getProperty(user, key); + if (notEmpty(currVal) && currVal.toLowerCase().matches(searchString)) + return true; + } + return false; + } +} diff --git a/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/providers/UserNameLP.java b/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/providers/UserNameLP.java new file mode 100644 index 0000000..3cd00eb --- /dev/null +++ b/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/providers/UserNameLP.java @@ -0,0 +1,13 @@ +package org.argeo.cms.e4.users.providers; + +import org.osgi.service.useradmin.User; + +/** Simply declare a label provider that returns the username of a user */ +public class UserNameLP extends UserAdminAbstractLP { + private static final long serialVersionUID = 6550449442061090388L; + + @Override + public String getText(User user) { + return user.getName(); + } +} diff --git a/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/providers/package-info.java b/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/providers/package-info.java new file mode 100644 index 0000000..33bef8d --- /dev/null +++ b/org.argeo.cms.jcr.e4/src/org/argeo/cms/e4/users/providers/package-info.java @@ -0,0 +1,2 @@ +/** Users management content providers. */ +package org.argeo.cms.e4.users.providers; \ No newline at end of file diff --git a/org.argeo.cms.jcr.ui/src/org/argeo/cms/e4/maintenance/AbstractOsgiComposite.java b/org.argeo.cms.jcr.ui/src/org/argeo/cms/e4/maintenance/AbstractOsgiComposite.java new file mode 100644 index 0000000..4fd1d68 --- /dev/null +++ b/org.argeo.cms.jcr.ui/src/org/argeo/cms/e4/maintenance/AbstractOsgiComposite.java @@ -0,0 +1,41 @@ +package org.argeo.cms.e4.maintenance; + +import java.util.Collection; + +import org.argeo.api.cms.CmsLog; +import org.argeo.cms.swt.CmsSwtUtils; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.widgets.Composite; +import org.osgi.framework.BundleContext; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.InvalidSyntaxException; +import org.osgi.framework.ServiceReference; + +abstract class AbstractOsgiComposite extends Composite { + private static final long serialVersionUID = -4097415973477517137L; + protected final BundleContext bc = FrameworkUtil.getBundle(getClass()).getBundleContext(); + protected final CmsLog log = CmsLog.getLog(getClass()); + + public AbstractOsgiComposite(Composite parent, int style) { + super(parent, style); + parent.setLayout(CmsSwtUtils.noSpaceGridLayout()); + setLayout(CmsSwtUtils.noSpaceGridLayout()); + setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false)); + initUi(style); + } + + protected abstract void initUi(int style); + + protected T getService(Class clazz) { + return bc.getService(bc.getServiceReference(clazz)); + } + + protected Collection> getServiceReferences(Class clazz, String filter) { + try { + return bc.getServiceReferences(clazz, filter); + } catch (InvalidSyntaxException e) { + throw new IllegalArgumentException("Filter " + filter + " is invalid", e); + } + } +} diff --git a/org.argeo.cms.jcr.ui/src/org/argeo/cms/e4/maintenance/Browse.java b/org.argeo.cms.jcr.ui/src/org/argeo/cms/e4/maintenance/Browse.java new file mode 100644 index 0000000..a536da0 --- /dev/null +++ b/org.argeo.cms.jcr.ui/src/org/argeo/cms/e4/maintenance/Browse.java @@ -0,0 +1,576 @@ +package org.argeo.cms.e4.maintenance; + +import static org.eclipse.swt.SWT.RIGHT; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.LinkedHashMap; + +import javax.jcr.ItemNotFoundException; +import javax.jcr.Node; +import javax.jcr.NodeIterator; +import javax.jcr.Property; +import javax.jcr.PropertyIterator; +import javax.jcr.PropertyType; +import javax.jcr.RepositoryException; +import javax.jcr.Value; + +import org.argeo.api.cms.ux.Cms2DSize; +import org.argeo.cms.swt.CmsException; +import org.argeo.cms.swt.CmsSwtUtils; +import org.argeo.cms.ui.CmsUiProvider; +import org.argeo.cms.ui.util.CmsLink; +import org.argeo.cms.ui.widgets.EditableImage; +import org.argeo.cms.ui.widgets.Img; +import org.argeo.jcr.JcrUtils; +import org.eclipse.jface.viewers.ColumnLabelProvider; +import org.eclipse.jface.viewers.ILazyContentProvider; +import org.eclipse.jface.viewers.ISelectionChangedListener; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.viewers.SelectionChangedEvent; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.jface.viewers.TableViewer; +import org.eclipse.jface.viewers.TableViewerColumn; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.ScrolledComposite; +import org.eclipse.swt.events.ControlAdapter; +import org.eclipse.swt.events.ControlEvent; +import org.eclipse.swt.events.KeyEvent; +import org.eclipse.swt.events.KeyListener; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Table; +import org.eclipse.swt.widgets.TableColumn; +import org.eclipse.swt.widgets.Text; + +public class Browse implements CmsUiProvider { + + // Some local constants to experiment. should be cleaned + private final static String BROWSE_PREFIX = "browse#"; + private final static int THUMBNAIL_WIDTH = 400; + private final static int COLUMN_WIDTH = 160; + private DateFormat timeFormatter = new SimpleDateFormat("dd-MM-yyyy', 'HH:mm"); + + // keep a cache of the opened nodes + // Key is the path + private LinkedHashMap browserCols = new LinkedHashMap(); + private Composite nodeDisplayParent; + private Composite colViewer; + private ScrolledComposite scrolledCmp; + private Text parentPathTxt; + private Text filterTxt; + private Node currEdited; + + private String initialPath; + + @Override + public Control createUi(Composite parent, Node context) throws RepositoryException { + if (context == null) + // return null; + throw new CmsException("Context cannot be null"); + GridLayout layout = CmsSwtUtils.noSpaceGridLayout(); + layout.numColumns = 2; + parent.setLayout(layout); + + // Left + Composite leftCmp = new Composite(parent, SWT.NO_FOCUS); + leftCmp.setLayoutData(CmsSwtUtils.fillAll()); + createBrowserPart(leftCmp, context); + + // Right + nodeDisplayParent = new Composite(parent, SWT.NO_FOCUS | SWT.BORDER); + GridData gd = new GridData(SWT.RIGHT, SWT.FILL, false, true); + gd.widthHint = THUMBNAIL_WIDTH; + nodeDisplayParent.setLayoutData(gd); + createNodeView(nodeDisplayParent, context); + + // INIT + setEdited(context); + initialPath = context.getPath(); + + // Workaround we don't yet manage the delete to display parent of the + // initial context node + + return null; + } + + private void createBrowserPart(Composite parent, Node context) throws RepositoryException { + GridLayout layout = CmsSwtUtils.noSpaceGridLayout(); + parent.setLayout(layout); + Composite filterCmp = new Composite(parent, SWT.NO_FOCUS); + filterCmp.setLayoutData(CmsSwtUtils.fillWidth()); + + // top filter + addFilterPanel(filterCmp); + + // scrolled composite + scrolledCmp = new ScrolledComposite(parent, SWT.H_SCROLL | SWT.BORDER | SWT.NO_FOCUS); + scrolledCmp.setLayoutData(CmsSwtUtils.fillAll()); + scrolledCmp.setExpandVertical(true); + scrolledCmp.setExpandHorizontal(true); + scrolledCmp.setShowFocusedControl(true); + + colViewer = new Composite(scrolledCmp, SWT.NO_FOCUS); + scrolledCmp.setContent(colViewer); + scrolledCmp.addControlListener(new ControlAdapter() { + private static final long serialVersionUID = 6589392045145698201L; + + @Override + public void controlResized(ControlEvent e) { + Rectangle r = scrolledCmp.getClientArea(); + scrolledCmp.setMinSize(colViewer.computeSize(SWT.DEFAULT, r.height)); + } + }); + initExplorer(colViewer, context); + } + + private Control initExplorer(Composite parent, Node context) throws RepositoryException { + parent.setLayout(CmsSwtUtils.noSpaceGridLayout()); + createBrowserColumn(parent, context); + return null; + } + + private Control createBrowserColumn(Composite parent, Node context) throws RepositoryException { + // TODO style is not correctly managed. + FilterEntitiesVirtualTable table = new FilterEntitiesVirtualTable(parent, SWT.BORDER | SWT.NO_FOCUS, context); + // CmsUiUtils.style(table, ArgeoOrgStyle.browserColumn.style()); + table.filterList("*"); + table.setLayoutData(new GridData(SWT.LEFT, SWT.FILL, false, true)); + browserCols.put(context.getPath(), table); + return null; + } + + public void addFilterPanel(Composite parent) { + + parent.setLayout(CmsSwtUtils.noSpaceGridLayout(new GridLayout(2, false))); + + // Text Area for the filter + parentPathTxt = new Text(parent, SWT.NO_FOCUS); + parentPathTxt.setEditable(false); + filterTxt = new Text(parent, SWT.SEARCH | SWT.ICON_CANCEL); + filterTxt.setMessage("Filter current list"); + filterTxt.setLayoutData(CmsSwtUtils.fillWidth()); + filterTxt.addModifyListener(new ModifyListener() { + private static final long serialVersionUID = 7709303319740056286L; + + public void modifyText(ModifyEvent event) { + modifyFilter(false); + } + }); + + filterTxt.addKeyListener(new KeyListener() { + private static final long serialVersionUID = -4523394262771183968L; + + @Override + public void keyReleased(KeyEvent e) { + } + + @Override + public void keyPressed(KeyEvent e) { + boolean shiftPressed = (e.stateMask & SWT.SHIFT) != 0; + // boolean altPressed = (e.stateMask & SWT.ALT) != 0; + FilterEntitiesVirtualTable currTable = null; + if (currEdited != null) { + FilterEntitiesVirtualTable table = browserCols.get(getPath(currEdited)); + if (table != null && !table.isDisposed()) + currTable = table; + } + + try { + if (e.keyCode == SWT.ARROW_DOWN) + currTable.setFocus(); + else if (e.keyCode == SWT.BS) { + if (filterTxt.getText().equals("") + && !(getPath(currEdited).equals("/") || getPath(currEdited).equals(initialPath))) { + setEdited(currEdited.getParent()); + e.doit = false; + filterTxt.setFocus(); + } + } else if (e.keyCode == SWT.TAB && !shiftPressed) { + if (currEdited.getNodes(filterTxt.getText() + "*").getSize() == 1) { + setEdited(currEdited.getNodes(filterTxt.getText() + "*").nextNode()); + } + filterTxt.setFocus(); + e.doit = false; + } + } catch (RepositoryException e1) { + throw new CmsException("Unexpected error in key management for " + currEdited + "with filter " + + filterTxt.getText(), e1); + } + + } + }); + } + + private void setEdited(Node node) { + try { + currEdited = node; + CmsSwtUtils.clear(nodeDisplayParent); + createNodeView(nodeDisplayParent, currEdited); + nodeDisplayParent.layout(); + refreshFilters(node); + refreshBrowser(node); + } catch (RepositoryException re) { + throw new CmsException("Unable to update browser for " + node, re); + } + } + + private void refreshFilters(Node node) throws RepositoryException { + String currNodePath = node.getPath(); + parentPathTxt.setText(currNodePath); + filterTxt.setText(""); + filterTxt.getParent().layout(); + } + + private void refreshBrowser(Node node) throws RepositoryException { + + // Retrieve + String currNodePath = node.getPath(); + String currParPath = ""; + if (!"/".equals(currNodePath)) + currParPath = JcrUtils.parentPath(currNodePath); + if ("".equals(currParPath)) + currParPath = "/"; + + Object[][] colMatrix = new Object[browserCols.size()][2]; + + int i = 0, j = -1, k = -1; + for (String path : browserCols.keySet()) { + colMatrix[i][0] = path; + colMatrix[i][1] = browserCols.get(path); + if (j >= 0 && k < 0 && !currNodePath.equals("/")) { + boolean leaveOpened = path.startsWith(currNodePath); + + // workaround for same name siblings + // fix me weird side effect when we go left or click on anb + // already selected, unfocused node + if (leaveOpened && (path.lastIndexOf("/") == 0 && currNodePath.lastIndexOf("/") == 0 + || JcrUtils.parentPath(path).equals(JcrUtils.parentPath(currNodePath)))) + leaveOpened = JcrUtils.lastPathElement(path).equals(JcrUtils.lastPathElement(currNodePath)); + + if (!leaveOpened) + k = i; + } + if (currParPath.equals(path)) + j = i; + i++; + } + + if (j >= 0 && k >= 0) + // remove useless cols + for (int l = i - 1; l >= k; l--) { + browserCols.remove(colMatrix[l][0]); + ((FilterEntitiesVirtualTable) colMatrix[l][1]).dispose(); + } + + // Remove disposed columns + // TODO investigate and fix the mechanism that leave them there after + // disposal + if (browserCols.containsKey(currNodePath)) { + FilterEntitiesVirtualTable currCol = browserCols.get(currNodePath); + if (currCol.isDisposed()) + browserCols.remove(currNodePath); + } + + if (!browserCols.containsKey(currNodePath)) + createBrowserColumn(colViewer, node); + + colViewer.setLayout(CmsSwtUtils.noSpaceGridLayout(new GridLayout(browserCols.size(), false))); + // colViewer.pack(); + colViewer.layout(); + // also resize the scrolled composite + scrolledCmp.layout(); + scrolledCmp.getShowFocusedControl(); + // colViewer.getParent().layout(); + // if (JcrUtils.parentPath(currNodePath).equals(currBrowserKey)) { + // } else { + // } + } + + private void modifyFilter(boolean fromOutside) { + if (!fromOutside) + if (currEdited != null) { + String filter = filterTxt.getText() + "*"; + FilterEntitiesVirtualTable table = browserCols.get(getPath(currEdited)); + if (table != null && !table.isDisposed()) + table.filterList(filter); + } + + } + + private String getPath(Node node) { + try { + return node.getPath(); + } catch (RepositoryException e) { + throw new CmsException("Unable to get path for node " + node, e); + } + } + + private Cms2DSize imageWidth = new Cms2DSize(250, 0); + + /** + * Recreates the content of the box that displays information about the current + * selected node. + */ + private Control createNodeView(Composite parent, Node context) throws RepositoryException { + + parent.setLayout(new GridLayout(2, false)); + + if (isImg(context)) { + EditableImage image = new Img(parent, RIGHT, context, imageWidth); + image.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, true, false, 2, 1)); + } + + // Name and primary type + Label contextL = new Label(parent, SWT.NONE); + CmsSwtUtils.markup(contextL); + contextL.setText("" + context.getName() + ""); + new Label(parent, SWT.NONE).setText(context.getPrimaryNodeType().getName()); + + // Children + for (NodeIterator nIt = context.getNodes(); nIt.hasNext();) { + Node child = nIt.nextNode(); + new CmsLink(child.getName(), BROWSE_PREFIX + child.getPath()).createUi(parent, context); + new Label(parent, SWT.NONE).setText(child.getPrimaryNodeType().getName()); + } + + // Properties + for (PropertyIterator pIt = context.getProperties(); pIt.hasNext();) { + Property property = pIt.nextProperty(); + Label label = new Label(parent, SWT.NONE); + label.setText(property.getName()); + label.setToolTipText(JcrUtils.getPropertyDefinitionAsString(property)); + new Label(parent, SWT.NONE).setText(getPropAsString(property)); + } + + return null; + } + + private boolean isImg(Node node) throws RepositoryException { + // TODO support images + return false; +// return node.hasNode(JCR_CONTENT) && node.isNodeType(CmsTypes.CMS_IMAGE); + } + + private String getPropAsString(Property property) throws RepositoryException { + String result = ""; + if (property.isMultiple()) { + result = getMultiAsString(property, ", "); + } else { + Value value = property.getValue(); + if (value.getType() == PropertyType.BINARY) + result = ""; + else if (value.getType() == PropertyType.DATE) + result = timeFormatter.format(value.getDate().getTime()); + else + result = value.getString(); + } + return result; + } + + private String getMultiAsString(Property property, String separator) throws RepositoryException { + if (separator == null) + separator = "; "; + Value[] values = property.getValues(); + StringBuilder builder = new StringBuilder(); + for (Value val : values) { + String currStr = val.getString(); + if (!"".equals(currStr.trim())) + builder.append(currStr).append(separator); + } + if (builder.lastIndexOf(separator) >= 0) + return builder.substring(0, builder.length() - separator.length()); + else + return builder.toString(); + } + + /** Almost canonical implementation of a table that display entities */ + private class FilterEntitiesVirtualTable extends Composite { + private static final long serialVersionUID = 8798147431706283824L; + + // Context + private Node context; + + // UI Objects + private TableViewer entityViewer; + + // enable management of multiple columns + Node getNode() { + return context; + } + + @Override + public boolean setFocus() { + if (entityViewer.getTable().isDisposed()) + return false; + if (entityViewer.getSelection().isEmpty()) { + Object first = entityViewer.getElementAt(0); + if (first != null) { + entityViewer.setSelection(new StructuredSelection(first), true); + } + } + return entityViewer.getTable().setFocus(); + } + + void filterList(String filter) { + try { + NodeIterator nit = context.getNodes(filter); + refreshFilteredList(nit); + } catch (RepositoryException e) { + throw new CmsException("Unable to filter " + getNode() + " children with filter " + filter, e); + } + + } + + public FilterEntitiesVirtualTable(Composite parent, int style, Node context) { + super(parent, SWT.NO_FOCUS); + this.context = context; + populate(); + } + + protected void populate() { + Composite parent = this; + GridLayout layout = CmsSwtUtils.noSpaceGridLayout(); + + this.setLayout(layout); + createTableViewer(parent); + } + + private void createTableViewer(final Composite parent) { + // the list + // We must limit the size of the table otherwise the full list is + // loaded + // before the layout happens + Composite listCmp = new Composite(parent, SWT.NO_FOCUS); + GridData gd = new GridData(SWT.LEFT, SWT.FILL, false, true); + gd.widthHint = COLUMN_WIDTH; + listCmp.setLayoutData(gd); + listCmp.setLayout(CmsSwtUtils.noSpaceGridLayout()); + + entityViewer = new TableViewer(listCmp, SWT.VIRTUAL | SWT.SINGLE); + Table table = entityViewer.getTable(); + + table.setLayoutData(CmsSwtUtils.fillAll()); + table.setLinesVisible(true); + table.setHeaderVisible(false); + CmsSwtUtils.markup(table); + + CmsSwtUtils.style(table, MaintenanceStyles.BROWSER_COLUMN); + + // first column + TableViewerColumn column = new TableViewerColumn(entityViewer, SWT.NONE); + TableColumn tcol = column.getColumn(); + tcol.setWidth(COLUMN_WIDTH); + tcol.setResizable(true); + column.setLabelProvider(new SimpleNameLP()); + + entityViewer.setContentProvider(new MyLazyCP(entityViewer)); + entityViewer.addSelectionChangedListener(new ISelectionChangedListener() { + + @Override + public void selectionChanged(SelectionChangedEvent event) { + IStructuredSelection selection = (IStructuredSelection) entityViewer.getSelection(); + if (selection.isEmpty()) + return; + else + setEdited((Node) selection.getFirstElement()); + + } + }); + + table.addKeyListener(new KeyListener() { + private static final long serialVersionUID = -330694313896036230L; + + @Override + public void keyReleased(KeyEvent e) { + } + + @Override + public void keyPressed(KeyEvent e) { + + IStructuredSelection selection = (IStructuredSelection) entityViewer.getSelection(); + Node selected = null; + if (!selection.isEmpty()) + selected = ((Node) selection.getFirstElement()); + try { + if (e.keyCode == SWT.ARROW_RIGHT) { + if (selected != null) { + setEdited(selected); + browserCols.get(selected.getPath()).setFocus(); + } + } else if (e.keyCode == SWT.ARROW_LEFT) { + try { + selected = getNode().getParent(); + String newPath = selected.getPath(); // getNode().getParent() + setEdited(selected); + if (browserCols.containsKey(newPath)) + browserCols.get(newPath).setFocus(); + } catch (ItemNotFoundException ie) { + // root silent + } + } + } catch (RepositoryException ie) { + throw new CmsException("Error while managing arrow " + "events in the browser for " + selected, + ie); + } + } + }); + } + + private class MyLazyCP implements ILazyContentProvider { + private static final long serialVersionUID = 1L; + private TableViewer viewer; + private Object[] elements; + + public MyLazyCP(TableViewer viewer) { + this.viewer = viewer; + } + + public void dispose() { + } + + public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { + // IMPORTANT: don't forget this: an exception will be thrown if + // a selected object is not part of the results anymore. + viewer.setSelection(null); + this.elements = (Object[]) newInput; + } + + public void updateElement(int index) { + viewer.replace(elements[index], index); + } + } + + protected void refreshFilteredList(NodeIterator children) { + Object[] rows = JcrUtils.nodeIteratorToList(children).toArray(); + entityViewer.setInput(rows); + entityViewer.setItemCount(rows.length); + entityViewer.refresh(); + } + + public class SimpleNameLP extends ColumnLabelProvider { + private static final long serialVersionUID = 2465059387875338553L; + + @Override + public String getText(Object element) { + if (element instanceof Node) { + Node curr = ((Node) element); + try { + return curr.getName(); + } catch (RepositoryException e) { + throw new CmsException("Unable to get name for" + curr); + } + } + return super.getText(element); + } + } + } +} \ No newline at end of file diff --git a/org.argeo.cms.jcr.ui/src/org/argeo/cms/e4/maintenance/ConnectivityDeploymentUi.java b/org.argeo.cms.jcr.ui/src/org/argeo/cms/e4/maintenance/ConnectivityDeploymentUi.java new file mode 100644 index 0000000..97f3e67 --- /dev/null +++ b/org.argeo.cms.jcr.ui/src/org/argeo/cms/e4/maintenance/ConnectivityDeploymentUi.java @@ -0,0 +1,48 @@ +package org.argeo.cms.e4.maintenance; + +import org.argeo.cms.swt.CmsSwtUtils; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Label; +import org.osgi.framework.ServiceReference; +import org.osgi.service.http.HttpService; +import org.osgi.service.useradmin.UserAdmin; + +class ConnectivityDeploymentUi extends AbstractOsgiComposite { + private static final long serialVersionUID = 590221539553514693L; + + public ConnectivityDeploymentUi(Composite parent, int style) { + super(parent, style); + } + + @Override + protected void initUi(int style) { + StringBuffer text = new StringBuffer(); + text.append("Provided Servers
"); + + ServiceReference userAdminRef = bc.getServiceReference(HttpService.class); + if (userAdminRef != null) { + // FIXME use constants + Object httpPort = userAdminRef.getProperty("http.port"); + Object httpsPort = userAdminRef.getProperty("https.port"); + if (httpPort != null) + text.append("http ").append(httpPort).append("
"); + if (httpsPort != null) + text.append("https ").append(httpsPort).append("
"); + + } + + text.append("
"); + text.append("Referenced Servers
"); + + Label label = new Label(this, SWT.NONE); + label.setData(new GridData(SWT.FILL, SWT.FILL, false, false)); + CmsSwtUtils.markup(label); + label.setText(text.toString()); + } + + protected boolean isDeployed() { + return bc.getServiceReference(UserAdmin.class) != null; + } +} diff --git a/org.argeo.cms.jcr.ui/src/org/argeo/cms/e4/maintenance/DataDeploymentUi.java b/org.argeo.cms.jcr.ui/src/org/argeo/cms/e4/maintenance/DataDeploymentUi.java new file mode 100644 index 0000000..ef95bde --- /dev/null +++ b/org.argeo.cms.jcr.ui/src/org/argeo/cms/e4/maintenance/DataDeploymentUi.java @@ -0,0 +1,139 @@ +package org.argeo.cms.e4.maintenance; + +import java.io.File; +import java.io.IOException; +import java.nio.file.FileStore; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collection; + +import org.apache.jackrabbit.core.RepositoryContext; +import org.apache.jackrabbit.core.config.RepositoryConfig; +import org.argeo.api.cms.CmsConstants; +import org.argeo.cms.swt.CmsSwtUtils; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Label; +import org.osgi.framework.ServiceReference; + +class DataDeploymentUi extends AbstractOsgiComposite { + private static final long serialVersionUID = 590221539553514693L; + + public DataDeploymentUi(Composite parent, int style) { + super(parent, style); + } + + @Override + protected void initUi(int style) { + if (isDeployed()) { + initCurrentUi(this); + } else { + initNewUi(this); + } + } + + private void initNewUi(Composite parent) { +// try { +// ConfigurationAdmin confAdmin = bc.getService(bc.getServiceReference(ConfigurationAdmin.class)); +// Configuration[] confs = confAdmin.listConfigurations( +// "(" + ConfigurationAdmin.SERVICE_FACTORYPID + "=" + NodeConstants.NODE_REPOS_FACTORY_PID + ")"); +// if (confs == null || confs.length == 0) { +// Group buttonGroup = new Group(parent, SWT.NONE); +// buttonGroup.setText("Repository Type"); +// buttonGroup.setLayout(new GridLayout(2, true)); +// buttonGroup.setLayoutData(new GridData(GridData.FILL_VERTICAL)); +// +// SelectionListener selectionListener = new SelectionAdapter() { +// private static final long serialVersionUID = 6247064348421088092L; +// +// public void widgetSelected(SelectionEvent event) { +// Button radio = (Button) event.widget; +// if (!radio.getSelection()) +// return; +// log.debug(event); +// JackrabbitType nodeType = (JackrabbitType) radio.getData(); +// if (log.isDebugEnabled()) +// log.debug(" selected = " + nodeType.name()); +// }; +// }; +// +// for (JackrabbitType nodeType : JackrabbitType.values()) { +// Button radio = new Button(buttonGroup, SWT.RADIO); +// radio.setText(nodeType.name()); +// radio.setData(nodeType); +// if (nodeType.equals(JackrabbitType.localfs)) +// radio.setSelection(true); +// radio.addSelectionListener(selectionListener); +// } +// +// } else if (confs.length == 1) { +// +// } else { +// throw new CmsException("Multiple repos not yet supported"); +// } +// } catch (Exception e) { +// throw new CmsException("Cannot initialize UI", e); +// } + + } + + private void initCurrentUi(Composite parent) { + parent.setLayout(new GridLayout()); + Collection> contexts = getServiceReferences(RepositoryContext.class, + "(" + CmsConstants.CN + "=*)"); + StringBuffer text = new StringBuffer(); + text.append("Jackrabbit Repositories
"); + for (ServiceReference sr : contexts) { + RepositoryContext repositoryContext = bc.getService(sr); + String alias = sr.getProperty(CmsConstants.CN).toString(); + String rootNodeId = repositoryContext.getRootNodeId().toString(); + RepositoryConfig repositoryConfig = repositoryContext.getRepositoryConfig(); + Path repoHomePath = new File(repositoryConfig.getHomeDir()).toPath().toAbsolutePath(); + // TODO check data store + + text.append("" + alias + "
"); + text.append("rootNodeId: " + rootNodeId + "
"); + try { + FileStore fileStore = Files.getFileStore(repoHomePath); + text.append("partition: " + fileStore.toString() + "
"); + text.append( + percentUsed(fileStore) + " used (" + humanReadable(fileStore.getUsableSpace()) + " free)
"); + } catch (IOException e) { + log.error("Cannot check fileStore for " + repoHomePath, e); + } + } + Label label = new Label(parent, SWT.NONE); + label.setData(new GridData(SWT.FILL, SWT.FILL, false, false)); + CmsSwtUtils.markup(label); + label.setText("" + text.toString() + ""); + } + + private String humanReadable(long bytes) { + long mb = bytes / (1024 * 1024); + return mb >= 2048 ? Long.toString(mb / 1024) + " GB" : Long.toString(mb) + " MB"; + } + + private String percentUsed(FileStore fs) throws IOException { + long used = fs.getTotalSpace() - fs.getUnallocatedSpace(); + long percent = used * 100 / fs.getTotalSpace(); + if (log.isTraceEnabled()) { + // output identical to `df -B 1`) + log.trace(fs.getTotalSpace() + "," + used + "," + fs.getUsableSpace()); + } + String span; + if (percent < 80) + span = ""; + else if (percent < 95) + span = ""; + else + span = ""; + return span + percent + "%"; + } + + protected boolean isDeployed() { + return bc.getServiceReference(RepositoryContext.class) != null; + } + +} diff --git a/org.argeo.cms.jcr.ui/src/org/argeo/cms/e4/maintenance/DeploymentEntryPoint.java b/org.argeo.cms.jcr.ui/src/org/argeo/cms/e4/maintenance/DeploymentEntryPoint.java new file mode 100644 index 0000000..0a28dc5 --- /dev/null +++ b/org.argeo.cms.jcr.ui/src/org/argeo/cms/e4/maintenance/DeploymentEntryPoint.java @@ -0,0 +1,95 @@ +package org.argeo.cms.e4.maintenance; + +import java.util.GregorianCalendar; +import java.util.TimeZone; + +import org.argeo.api.cms.CmsConstants; +import org.argeo.api.cms.CmsContext; +import org.argeo.api.cms.CmsDeployment; +import org.argeo.api.cms.CmsState; +import org.argeo.cms.swt.CmsSwtUtils; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.FillLayout; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Group; +import org.eclipse.swt.widgets.Label; +import org.osgi.framework.BundleContext; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.ServiceReference; + +class DeploymentEntryPoint { + private final BundleContext bc = FrameworkUtil.getBundle(getClass()).getBundleContext(); + + protected void createContents(Composite parent) { + // FIXME manage authentication if needed + // if (!CurrentUser.roles().contains(AuthConstants.ROLE_ADMIN)) + // return; + + // parent.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + if (isDesktop()) { + parent.setLayout(new GridLayout(2, true)); + } else { + // TODO add scrolling + parent.setLayout(new GridLayout(1, true)); + } + + initHighLevelSummary(parent); + + Group securityGroup = createHighLevelGroup(parent, "Security"); + securityGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false)); + new SecurityDeploymentUi(securityGroup, SWT.NONE); + + Group dataGroup = createHighLevelGroup(parent, "Data"); + dataGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false)); + new DataDeploymentUi(dataGroup, SWT.NONE); + + Group logGroup = createHighLevelGroup(parent, "Notifications"); + logGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, true)); + new LogDeploymentUi(logGroup, SWT.NONE); + + Group connectivityGroup = createHighLevelGroup(parent, "Connectivity"); + new ConnectivityDeploymentUi(connectivityGroup, SWT.NONE); + connectivityGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, true)); + + } + + private void initHighLevelSummary(Composite parent) { + Composite composite = new Composite(parent, SWT.NONE); + GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, false); + if (isDesktop()) + gridData.horizontalSpan = 3; + composite.setLayoutData(gridData); + composite.setLayout(new FillLayout()); + + ServiceReference nodeStateRef = bc.getServiceReference(CmsState.class); + if (nodeStateRef == null) + throw new IllegalStateException("No CMS state available"); + CmsState nodeState = bc.getService(nodeStateRef); + ServiceReference nodeDeploymentRef = bc.getServiceReference(CmsContext.class); + Label label = new Label(composite, SWT.WRAP); + CmsSwtUtils.markup(label); + if (nodeDeploymentRef == null) { + label.setText("Not yet deployed on, please configure below."); + } else { + Object stateUuid = nodeStateRef.getProperty(CmsConstants.CN); + CmsContext nodeDeployment = bc.getService(nodeDeploymentRef); + GregorianCalendar calendar = new GregorianCalendar(); + calendar.setTimeInMillis(nodeDeployment.getAvailableSince()); + calendar.setTimeZone(TimeZone.getDefault()); + label.setText("Deployment state " + stateUuid + ", available since " + calendar.getTime() + ""); + } + } + + private static Group createHighLevelGroup(Composite parent, String text) { + Group group = new Group(parent, SWT.NONE); + group.setText(text); + CmsSwtUtils.markup(group); + return group; + } + + private boolean isDesktop() { + return true; + } +} diff --git a/org.argeo.cms.jcr.ui/src/org/argeo/cms/e4/maintenance/LogDeploymentUi.java b/org.argeo.cms.jcr.ui/src/org/argeo/cms/e4/maintenance/LogDeploymentUi.java new file mode 100644 index 0000000..fa5d3da --- /dev/null +++ b/org.argeo.cms.jcr.ui/src/org/argeo/cms/e4/maintenance/LogDeploymentUi.java @@ -0,0 +1,73 @@ +package org.argeo.cms.e4.maintenance; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Enumeration; +import java.util.GregorianCalendar; +import java.util.TimeZone; + +import org.argeo.cms.swt.CmsSwtUtils; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Text; +import org.osgi.service.log.LogEntry; +import org.osgi.service.log.LogListener; +import org.osgi.service.log.LogReaderService; + +class LogDeploymentUi extends AbstractOsgiComposite implements LogListener { + private static final long serialVersionUID = 590221539553514693L; + + private DateFormat dateFormat = new SimpleDateFormat("MMdd HH:mm"); + + private Display display; + private Text logDisplay; + + public LogDeploymentUi(Composite parent, int style) { + super(parent, style); + } + + @Override + protected void initUi(int style) { + LogReaderService logReader = getService(LogReaderService.class); + // FIXME use server push + // logReader.addLogListener(this); + this.display = getDisplay(); + this.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + logDisplay = new Text(this, SWT.WRAP | SWT.MULTI | SWT.READ_ONLY); + logDisplay.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + CmsSwtUtils.markup(logDisplay); + Enumeration logEntries = (Enumeration) logReader.getLog(); + while (logEntries.hasMoreElements()) + logDisplay.append(printEntry(logEntries.nextElement())); + } + + private String printEntry(LogEntry entry) { + StringBuilder sb = new StringBuilder(); + GregorianCalendar calendar = new GregorianCalendar(TimeZone.getDefault()); + calendar.setTimeInMillis(entry.getTime()); + sb.append(dateFormat.format(calendar.getTime())).append(' '); + sb.append(entry.getMessage()); + sb.append('\n'); + return sb.toString(); + } + + @Override + public void logged(LogEntry entry) { + if (display.isDisposed()) + return; + display.asyncExec(() -> { + if (logDisplay.isDisposed()) + return; + logDisplay.append(printEntry(entry)); + }); + display.wake(); + } + + // @Override + // public void dispose() { + // super.dispose(); + // getService(LogReaderService.class).removeLogListener(this); + // } +} diff --git a/org.argeo.cms.jcr.ui/src/org/argeo/cms/e4/maintenance/MaintenanceStyles.java b/org.argeo.cms.jcr.ui/src/org/argeo/cms/e4/maintenance/MaintenanceStyles.java new file mode 100644 index 0000000..df1be51 --- /dev/null +++ b/org.argeo.cms.jcr.ui/src/org/argeo/cms/e4/maintenance/MaintenanceStyles.java @@ -0,0 +1,10 @@ +package org.argeo.cms.e4.maintenance; + +/** Specific styles used by the various maintenance pages . */ +public interface MaintenanceStyles { + // General + public final static String PREFIX = "maintenance_"; + + // Browser + public final static String BROWSER_COLUMN = "browser_column"; + } diff --git a/org.argeo.cms.jcr.ui/src/org/argeo/cms/e4/maintenance/NonAdminPage.java b/org.argeo.cms.jcr.ui/src/org/argeo/cms/e4/maintenance/NonAdminPage.java new file mode 100644 index 0000000..cb38ce8 --- /dev/null +++ b/org.argeo.cms.jcr.ui/src/org/argeo/cms/e4/maintenance/NonAdminPage.java @@ -0,0 +1,30 @@ +package org.argeo.cms.e4.maintenance; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; + +import org.argeo.cms.swt.CmsSwtUtils; +import org.argeo.cms.ui.CmsUiProvider; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Label; + +public class NonAdminPage implements CmsUiProvider{ + + @Override + public Control createUi(Composite parent, Node context) + throws RepositoryException { + Composite body = new Composite(parent, SWT.NO_FOCUS); + body.setLayoutData(CmsSwtUtils.fillAll()); + body.setLayout(new GridLayout()); + Label label = new Label(body, SWT.NONE); + label.setText("You should be an admin to perform maintenance operations. " + + "Are you sure you are logged in?"); + label.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, true, true)); + return null; + } + +} diff --git a/org.argeo.cms.jcr.ui/src/org/argeo/cms/e4/maintenance/SecurityDeploymentUi.java b/org.argeo.cms.jcr.ui/src/org/argeo/cms/e4/maintenance/SecurityDeploymentUi.java new file mode 100644 index 0000000..3492c54 --- /dev/null +++ b/org.argeo.cms.jcr.ui/src/org/argeo/cms/e4/maintenance/SecurityDeploymentUi.java @@ -0,0 +1,85 @@ +package org.argeo.cms.e4.maintenance; + +import java.net.URI; + +import org.argeo.cms.swt.CmsSwtUtils; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Label; +import org.osgi.framework.InvalidSyntaxException; +import org.osgi.framework.ServiceReference; +import org.osgi.service.useradmin.Role; +import org.osgi.service.useradmin.UserAdmin; + +class SecurityDeploymentUi extends AbstractOsgiComposite { + private static final long serialVersionUID = 590221539553514693L; + + public SecurityDeploymentUi(Composite parent, int style) { + super(parent, style); + } + + @Override + protected void initUi(int style) { + if (isDeployed()) { + initCurrentUi(this); + } else { + initNewUi(this); + } + } + + private void initNewUi(Composite parent) { + new Label(parent, SWT.NONE).setText("Security is not configured"); + } + + private void initCurrentUi(Composite parent) { + ServiceReference userAdminRef = bc.getServiceReference(UserAdmin.class); + UserAdmin userAdmin = bc.getService(userAdminRef); + StringBuffer text = new StringBuffer(); + text.append("Domains
"); + domains: for (String key : userAdminRef.getPropertyKeys()) { + if (!key.startsWith("/")) + continue domains; + URI uri; + try { + uri = new URI(key); + } catch (Exception e) { + // ignore non URI keys + continue domains; + } + + String rootDn = uri.getPath().substring(1, uri.getPath().length()); + // FIXME make reading query options more robust, using utils + boolean readOnly = uri.getQuery().equals("readOnly=true"); + if (readOnly) + text.append(""); + else + text.append(""); + + text.append(rootDn); + text.append("
"); + try { + Role[] roles = userAdmin.getRoles("(dn=*," + rootDn + ")"); + long userCount = 0; + long groupCount = 0; + for (Role role : roles) { + if (role.getType() == Role.USER) + userCount++; + else + groupCount++; + } + text.append(" " + userCount + " users, " + groupCount +" groups.
"); + } catch (InvalidSyntaxException e) { + log.error("Invalid syntax", e); + } + } + Label label = new Label(parent, SWT.NONE); + label.setData(new GridData(SWT.FILL, SWT.FILL, false, false)); + CmsSwtUtils.markup(label); + label.setText(text.toString()); + } + + protected boolean isDeployed() { + return bc.getServiceReference(UserAdmin.class) != null; + } +} diff --git a/org.argeo.cms.jcr.ui/src/org/argeo/cms/e4/maintenance/package-info.java b/org.argeo.cms.jcr.ui/src/org/argeo/cms/e4/maintenance/package-info.java new file mode 100644 index 0000000..e4d2ad4 --- /dev/null +++ b/org.argeo.cms.jcr.ui/src/org/argeo/cms/e4/maintenance/package-info.java @@ -0,0 +1,2 @@ +/** Maintenance perspective. */ +package org.argeo.cms.e4.maintenance; \ No newline at end of file -- 2.30.2