org.argeo.slc.repo \
org.argeo.slc.rpmfactory \
org.argeo.slc.jcr \
+swt/org.argeo.tool.swt \
+swt/org.argeo.tool.devops.e4 \
swt/rap/org.argeo.tool.rap.cli \
swt/rap/org.argeo.tool.server \
org.argeo.cms \
org.argeo.cms.jcr \
swt/org.argeo.cms \
+swt/org.argeo.cms.jcr \
swt/rap/org.argeo.cms \
clean:
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-17"/>
+ <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+ <classpathentry kind="src" path="src"/>
+ <classpathentry kind="output" path="bin"/>
+</classpath>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>org.argeo.tool.devops.e4</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.eclipse.jdt.core.javabuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.pde.ManifestBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.pde.SchemaBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.pde.ds.core.builder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.eclipse.pde.PluginNature</nature>
+ <nature>org.eclipse.jdt.core.javanature</nature>
+ </natures>
+</projectDescription>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" configuration-policy="optional" name="CMS Admin RAP">
+ <implementation class="org.argeo.cms.jcr.e4.rap.CmsE4AdminApp"/>
+ <service>
+ <provide interface="org.eclipse.rap.rwt.application.ApplicationConfiguration"/>
+ <property name="contextName" type="String" value="slc/tool"/>
+ </service>
+</scr:component>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" immediate="true" name="Home Repository">
+ <implementation class="org.argeo.cms.e4.OsgiFilterContextFunction"/>
+ <property name="service.context.key" type="String" value="(cn=ego)"/>
+ <service>
+ <provide interface="org.eclipse.e4.core.contexts.IContextFunction"/>
+ </service>
+</scr:component>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" immediate="true" name="User Admin Wrapper">
+ <implementation class="org.argeo.cms.e4.users.UserAdminWrapper"/>
+ <reference bind="setUserTransaction" cardinality="1..1" interface="org.argeo.util.transaction.WorkTransaction" name="UserTransaction" policy="static"/>
+ <reference bind="setUserAdmin" cardinality="1..1" interface="org.osgi.service.useradmin.UserAdmin" name="UserAdmin" policy="static"/>
+ <service>
+ <provide interface="org.argeo.cms.e4.users.UserAdminWrapper"/>
+ </service>
+ <reference bind="addUserDirectory" cardinality="0..n" interface="org.argeo.osgi.useradmin.UserDirectory" name="UserDirectory" policy="static" unbind="removeUserDirectory"/>
+</scr:component>
--- /dev/null
+Bundle-ActivationPolicy: lazy
+
+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,\
+org.argeo.api.acr,\
+*
+
+Service-Component: OSGI-INF/homeRepository.xml,\
+OSGI-INF/userAdminWrapper.xml,\
+OSGI-INF/cmsAdminRap.xml,\
--- /dev/null
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+ .
--- /dev/null
+<?xml version="1.0" encoding="ASCII"?>
+<application:Application xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:advanced="http://www.eclipse.org/ui/2010/UIModel/application/ui/advanced" xmlns:application="http://www.eclipse.org/ui/2010/UIModel/application" xmlns:basic="http://www.eclipse.org/ui/2010/UIModel/application/ui/basic" xmlns:menu="http://www.eclipse.org/ui/2010/UIModel/application/ui/menu" xmi:id="_XqkCQKknEeObFrG_clJBYA" elementId="">
+ <children xsi:type="basic:TrimmedWindow" xmi:id="_Zdy6cKknEeObFrG_clJBYA" elementId="org.argeo.cms.e4.apps.admin.trimmedwindow.0" label="" x="10" y="10" width="500" height="500">
+ <persistedState key="styleOverride" value="8"/>
+ <tags>shellMaximized</tags>
+ <tags>auth.cn=admin,ou=roles,ou=node</tags>
+ <children xsi:type="advanced:PerspectiveStack" xmi:id="_jXVqsCk4Eein5vuhpK-Dew" elementId="org.argeo.cms.e4.perspectivestack.0" selectedElement="_xOVlsDvOEeiF1foPJZSZkw">
+ <children xsi:type="advanced:Perspective" xmi:id="_xOVlsDvOEeiF1foPJZSZkw" elementId="org.argeo.cms.e4.perspective.users" label="Users" iconURI="platform:/plugin/org.argeo.tool.swt/icons/group.png">
+ <tags>auth.cn=admin,ou=roles,ou=node</tags>
+ <children xsi:type="basic:PartSashContainer" xmi:id="_1tQoEDvOEeiF1foPJZSZkw" elementId="org.argeo.cms.e4.partsashcontainer.2" horizontal="true">
+ <children xsi:type="basic:PartStack" xmi:id="_vtbKkDvkEeiF1foPJZSZkw" elementId="org.argeo.cms.e4.partstack.4" containerData="4000" selectedElement="_9gukYDvOEeiF1foPJZSZkw">
+ <children xsi:type="basic:Part" xmi:id="_9gukYDvOEeiF1foPJZSZkw" elementId="org.argeo.cms.e4.part.users" containerData="" contributionURI="bundleclass://org.argeo.tool.devops.e4/org.argeo.cms.e4.users.UsersView" label="Users" iconURI="platform:/plugin/org.argeo.tool.swt/icons/person.png">
+ <handlers xmi:id="_0mN68DvjEeiF1foPJZSZkw" elementId="org.argeo.cms.e4.handler.4" contributionURI="bundleclass://org.argeo.tool.devops.e4/org.argeo.cms.e4.users.handlers.NewUser" command="_uL5i4DvjEeiF1foPJZSZkw"/>
+ <handlers xmi:id="_ODLdgDvkEeiF1foPJZSZkw" elementId="org.argeo.cms.e4.handler.5" contributionURI="bundleclass://org.argeo.tool.devops.e4/org.argeo.cms.e4.users.handlers.DeleteUsers" command="_xkcMADvjEeiF1foPJZSZkw"/>
+ <toolbar xmi:id="_jLWmkDvjEeiF1foPJZSZkw" elementId="org.argeo.cms.e4.toolbar.1">
+ <children xsi:type="menu:HandledToolItem" xmi:id="_jy_OUDvjEeiF1foPJZSZkw" elementId="org.argeo.cms.e4.handledtoolitem.new" label="New" iconURI="platform:/plugin/org.argeo.tool.swt/icons/add.png" command="_uL5i4DvjEeiF1foPJZSZkw"/>
+ <children xsi:type="menu:HandledToolItem" xmi:id="_9qszMDvjEeiF1foPJZSZkw" elementId="org.argeo.cms.e4.handledtoolitem.delete" label="Delete" iconURI="platform:/plugin/org.argeo.tool.swt/icons/delete.png" command="_xkcMADvjEeiF1foPJZSZkw"/>
+ </toolbar>
+ </children>
+ </children>
+ <children xsi:type="basic:PartStack" xmi:id="__g1a8DvOEeiF1foPJZSZkw" elementId="org.argeo.cms.e4.partstack.3" containerData="4000">
+ <tags>usersEditorArea</tags>
+ </children>
+ <children xsi:type="basic:PartStack" xmi:id="_-mFn8DvkEeiF1foPJZSZkw" elementId="org.argeo.cms.e4.partstack.5" containerData="2000">
+ <children xsi:type="basic:Part" xmi:id="_6etk4DvOEeiF1foPJZSZkw" elementId="org.argeo.cms.e4.part.groups" containerData="" contributionURI="bundleclass://org.argeo.tool.devops.e4/org.argeo.cms.e4.users.GroupsView" label="Groups" iconURI="platform:/plugin/org.argeo.tool.swt/icons/group.png">
+ <handlers xmi:id="_cmShoDvkEeiF1foPJZSZkw" elementId="org.argeo.cms.e4.handler.6" contributionURI="bundleclass://org.argeo.tool.devops.e4/org.argeo.cms.e4.users.handlers.NewGroup" command="_uL5i4DvjEeiF1foPJZSZkw"/>
+ <handlers xmi:id="_fbYfcDvkEeiF1foPJZSZkw" elementId="org.argeo.cms.e4.handler.7" contributionURI="bundleclass://org.argeo.tool.devops.e4/org.argeo.cms.e4.users.handlers.DeleteGroups" command="_xkcMADvjEeiF1foPJZSZkw"/>
+ <toolbar xmi:id="_Us0rADvkEeiF1foPJZSZkw" elementId="org.argeo.cms.e4.toolbar.2">
+ <children xsi:type="menu:HandledToolItem" xmi:id="_VQTLgDvkEeiF1foPJZSZkw" elementId="org.argeo.cms.e4.handledtoolitem.new" label="New" iconURI="platform:/plugin/org.argeo.tool.swt/icons/add.png" command="_uL5i4DvjEeiF1foPJZSZkw"/>
+ <children xsi:type="menu:HandledToolItem" xmi:id="_XfME8DvkEeiF1foPJZSZkw" elementId="org.argeo.cms.e4.handledtoolitem.delete" label="Delete" iconURI="platform:/plugin/org.argeo.tool.swt/icons/delete.png" command="_xkcMADvjEeiF1foPJZSZkw"/>
+ </toolbar>
+ </children>
+ </children>
+ </children>
+ </children>
+ <children xsi:type="advanced:Perspective" xmi:id="_jvjWYCk4Eein5vuhpK-Dew" elementId="org.argeo.cms.e4.perspective.data" label="Data" iconURI="platform:/plugin/org.argeo.tool.swt/icons/nodes.gif">
+ <children xsi:type="basic:PartSashContainer" xmi:id="_h3tvMCkxEein5vuhpK-Dew" elementId="org.argeo.cms.e4.partsashcontainer.0" selectedElement="_0B9SECkxEein5vuhpK-Dew" horizontal="true">
+ <children xsi:type="basic:PartStack" xmi:id="_0B9SECkxEein5vuhpK-Dew" elementId="org.argeo.cms.e4.partstack.0" containerData="4000" selectedElement="_WAjPkCkTEein5vuhpK-Dew">
+ <children xsi:type="basic:Part" xmi:id="_WAjPkCkTEein5vuhpK-Dew" elementId="org.argeo.cms.e4.jcrbrowser" containerData="" contributionURI="bundleclass://org.argeo.tool.devops.e4/org.argeo.cms.e4.jcr.JcrBrowserView" label="JCR" iconURI="platform:/plugin/org.argeo.tool.swt/icons/browser.gif">
+ <menus xsi:type="menu:PopupMenu" xmi:id="_eXiUECqREeidr6NYQH6GbQ" elementId="org.argeo.cms.e4.popupmenu.nodeViewer">
+ <children xsi:type="menu:HandledMenuItem" xmi:id="_GVeO8CqhEeidr6NYQH6GbQ" elementId="org.argeo.cms.e4.handledmenuitem.refresh" label="Refresh" iconURI="platform:/plugin/org.argeo.tool.swt/icons/refresh.png" command="_TOKHsCqYEeidr6NYQH6GbQ"/>
+ <children xsi:type="menu:HandledMenuItem" xmi:id="_fU238CqREeidr6NYQH6GbQ" elementId="org.argeo.cms.e4.handledmenuitem.addfoldernode" label="Add folder" iconURI="platform:/plugin/org.argeo.tool.swt/icons/addFolder.gif" command="_RgE5cCqREeidr6NYQH6GbQ"/>
+ <children xsi:type="menu:HandledMenuItem" xmi:id="_U4o9cCqhEeidr6NYQH6GbQ" elementId="org.argeo.cms.e4.handledmenuitem.rename" label="Rename" iconURI="platform:/plugin/org.argeo.tool.swt/icons/rename.gif" command="_ZrcUMCqYEeidr6NYQH6GbQ"/>
+ <children xsi:type="menu:HandledMenuItem" xmi:id="_Ncxo0CqhEeidr6NYQH6GbQ" elementId="org.argeo.cms.e4.handledmenuitem.remove" label="Remove" iconURI="platform:/plugin/org.argeo.tool.swt/icons/remove.gif" command="_ChJ-4CqYEeidr6NYQH6GbQ"/>
+ </menus>
+ <menus xmi:id="_oRg_ACqTEeidr6NYQH6GbQ" elementId="org.argeo.cms.e4.menu.0">
+ <tags>ViewMenu</tags>
+ <children xsi:type="menu:HandledMenuItem" xmi:id="_yJR8ECqYEeidr6NYQH6GbQ" elementId="org.argeo.cms.e4.handledmenuitem.refresh" label="Refresh" iconURI="platform:/plugin/org.argeo.tool.swt/icons/refresh.png" command="_TOKHsCqYEeidr6NYQH6GbQ"/>
+ <children xsi:type="menu:HandledMenuItem" xmi:id="_o6HQECqTEeidr6NYQH6GbQ" elementId="org.argeo.cms.e4.handledmenuitem.addfoldernode" label="Add folder" iconURI="platform:/plugin/org.argeo.tool.swt/icons/addFolder.gif" command="_RgE5cCqREeidr6NYQH6GbQ"/>
+ <children xsi:type="menu:HandledMenuItem" xmi:id="_5D7aACqYEeidr6NYQH6GbQ" elementId="org.argeo.cms.e4.handledmenuitem.rename" label="Rename" iconURI="platform:/plugin/org.argeo.tool.swt/icons/rename.gif" command="_ZrcUMCqYEeidr6NYQH6GbQ"/>
+ <children xsi:type="menu:HandledMenuItem" xmi:id="_7rR2wCqYEeidr6NYQH6GbQ" elementId="org.argeo.cms.e4.handledmenuitem.delete" label="Delete" iconURI="platform:/plugin/org.argeo.tool.swt/icons/remove.gif" command="_ChJ-4CqYEeidr6NYQH6GbQ"/>
+ <children xsi:type="menu:HandledMenuItem" xmi:id="_XsHLgFgQEeiknZQLx-vtnA" elementId="org.argeo.cms.e4.handledmenuitem.0" iconURI="platform:/plugin/org.argeo.tool.swt/icons/addRepo.gif" command="_ZWpasFgQEeiknZQLx-vtnA"/>
+ </menus>
+ </children>
+ </children>
+ <children xsi:type="basic:PartStack" xmi:id="_mHrEUCk4Eein5vuhpK-Dew" elementId="org.argeo.cms.e4.partstack.1" containerData="6000">
+ <tags>dataExplorer</tags>
+ </children>
+ </children>
+ </children>
+ <children xsi:type="advanced:Perspective" xmi:id="_u5ZakFhJEeiknZQLx-vtnA" elementId="org.argeo.cms.e4.perspective.monitoring" label="Monitoring" iconURI="platform:/plugin/org.argeo.tool.swt/icons/bundles.gif">
+ <children xsi:type="basic:PartStack" xmi:id="_7i7t8FhJEeiknZQLx-vtnA" elementId="org.argeo.cms.e4.partstack.6">
+ <children xsi:type="basic:Part" xmi:id="_Z-3cMFhbEeiknZQLx-vtnA" elementId="org.argeo.cms.e4.part.osgiConfigurations" contributionURI="bundleclass://org.argeo.tool.devops.e4/org.argeo.cms.e4.monitoring.OsgiConfigurationsView" label="OSGi Configurations" iconURI="platform:/plugin/org.argeo.tool.swt/icons/node.gif"/>
+ <children xsi:type="basic:Part" xmi:id="_8dM90FhJEeiknZQLx-vtnA" elementId="org.argeo.cms.e4.part.cmsSessions" contributionURI="bundleclass://org.argeo.tool.devops.e4/org.argeo.cms.e4.monitoring.CmsSessionsView" label="CMS Sessions" iconURI="platform:/plugin/org.argeo.tool.swt/icons/person-logged-in.png"/>
+ <children xsi:type="basic:Part" xmi:id="_KqRZIFhNEeiknZQLx-vtnA" elementId="org.argeo.cms.e4.part.modules" contributionURI="bundleclass://org.argeo.tool.devops.e4/org.argeo.cms.e4.monitoring.ModulesView" label="Modules" iconURI="platform:/plugin/org.argeo.tool.swt/icons/bundles.gif"/>
+ <children xsi:type="basic:Part" xmi:id="_dXtIoFhNEeiknZQLx-vtnA" elementId="org.argeo.cms.e4.part.bundles" contributionURI="bundleclass://org.argeo.tool.devops.e4/org.argeo.cms.e4.monitoring.BundlesView" label="Bundles" iconURI="platform:/plugin/org.argeo.tool.swt/icons/bundles.gif"/>
+ </children>
+ </children>
+ <children xsi:type="advanced:Perspective" xmi:id="_ABK2ADsNEeiUntFYWh-hFg" elementId="org.argeo.cms.e4.perspective.files" label="Files" iconURI="platform:/plugin/org.argeo.tool.swt/icons/file.gif">
+ <children xsi:type="basic:PartSashContainer" xmi:id="_FPimEDsSEeiUntFYWh-hFg" elementId="org.argeo.cms.e4.partsashcontainer.1" horizontal="true">
+ <children xsi:type="basic:PartStack" xmi:id="_H93NgDsSEeiUntFYWh-hFg" elementId="org.argeo.cms.e4.partstack.2" containerData="4000">
+ <children xsi:type="basic:Part" xmi:id="_Izxh0DsSEeiUntFYWh-hFg" elementId="org.argeo.cms.e4.part.files" contributionURI="bundleclass://org.argeo.tool.devops.e4/org.argeo.cms.e4.files.NodeFsBrowserView" label="Files" iconURI="platform:/plugin/org.argeo.tool.swt/icons/file.gif"/>
+ </children>
+ <children xsi:type="basic:Part" xmi:id="_TMqBMDsSEeiUntFYWh-hFg" elementId="org.argeo.cms.e4.part.0" containerData="6000"/>
+ </children>
+ </children>
+ </children>
+ <handlers xmi:id="_Vwax0DvrEeiF1foPJZSZkw" elementId="org.argeo.cms.e4.handler.8" contributionURI="bundleclass://org.argeo.cms.e4/org.argeo.cms.e4.handlers.OpenPerspective" command="_AF1UsDvrEeiF1foPJZSZkw"/>
+ <trimBars xmi:id="_euVxMCk2Eein5vuhpK-Dew" elementId="org.argeo.cms.e4.trimbar.0" side="Left">
+ <children xsi:type="menu:ToolBar" xmi:id="_fotHsCk2Eein5vuhpK-Dew" elementId="org.argeo.cms.e4.toolbar.0">
+ <children xsi:type="menu:HandledToolItem" xmi:id="_jCSQgDvrEeiF1foPJZSZkw" elementId="org.argeo.cms.e4.handledtoolitem.users" label="Users" iconURI="platform:/plugin/org.argeo.tool.swt/icons/group.png" command="_AF1UsDvrEeiF1foPJZSZkw">
+ <tags>auth.cn=admin,ou=roles,ou=node</tags>
+ <parameters xmi:id="_lu_uYDvrEeiF1foPJZSZkw" elementId="org.argeo.cms.e4.parameter.2" name="perspectiveId" value="org.argeo.cms.e4.perspective.users"/>
+ </children>
+ <children xsi:type="menu:HandledToolItem" xmi:id="_jfUM4Ck2Eein5vuhpK-Dew" elementId="org.argeo.cms.e4.handledtoolitem.test" label="Data" iconURI="platform:/plugin/org.argeo.tool.swt/icons/nodes.gif" command="_AF1UsDvrEeiF1foPJZSZkw">
+ <parameters xmi:id="_KDlXQDvrEeiF1foPJZSZkw" elementId="org.argeo.cms.e4.parameter.0" name="perspectiveId" value="org.argeo.cms.e4.perspective.data"/>
+ </children>
+ <children xsi:type="menu:HandledToolItem" xmi:id="_dhv80FhKEeiknZQLx-vtnA" elementId="org.argeo.cms.e4.handledtoolitem.monitoring" label="Monitoring" iconURI="platform:/plugin/org.argeo.tool.swt/icons/bundles.gif" command="_AF1UsDvrEeiF1foPJZSZkw">
+ <parameters xmi:id="_kjN0cFhKEeiknZQLx-vtnA" elementId="org.argeo.cms.e4.parameter.3" name="perspectiveId" value="org.argeo.cms.e4.perspective.monitoring"/>
+ </children>
+ <children xsi:type="menu:HandledToolItem" xmi:id="_b0OHUDvrEeiF1foPJZSZkw" elementId="org.argeo.cms.e4.handledtoolitem.files" label="Files" iconURI="platform:/plugin/org.argeo.tool.swt/icons/file.gif" command="_AF1UsDvrEeiF1foPJZSZkw">
+ <parameters xmi:id="_fXvRYDvrEeiF1foPJZSZkw" elementId="org.argeo.cms.e4.parameter.1" name="perspectiveId" value="org.argeo.cms.e4.perspective.files"/>
+ </children>
+ <children xsi:type="menu:ToolBarSeparator" xmi:id="_wuoL8FhLEeiknZQLx-vtnA" elementId="org.argeo.cms.e4.toolbarseparator.0"/>
+ <children xsi:type="menu:HandledToolItem" xmi:id="_2v8DkFhKEeiknZQLx-vtnA" elementId="org.argeo.cms.e4.handledtoolitem.logout" label="Log out" iconURI="platform:/plugin/org.argeo.tool.swt/icons/logout.png" command="_PsWd0FhLEeiknZQLx-vtnA"/>
+ </children>
+ </trimBars>
+ </children>
+ <handlers xmi:id="_Xp-P4CqREeidr6NYQH6GbQ" elementId="org.argeo.cms.e4.handler.0" contributionURI="bundleclass://org.argeo.tool.devops.e4/org.argeo.cms.e4.jcr.handlers.AddFolderNode" command="_RgE5cCqREeidr6NYQH6GbQ"/>
+ <handlers xmi:id="_jbnNwCqYEeidr6NYQH6GbQ" elementId="org.argeo.cms.e4.handler.1" contributionURI="bundleclass://org.argeo.tool.devops.e4/org.argeo.cms.e4.jcr.handlers.DeleteNodes" command="_ChJ-4CqYEeidr6NYQH6GbQ"/>
+ <handlers xmi:id="_loxB0CqYEeidr6NYQH6GbQ" elementId="org.argeo.cms.e4.handler.2" contributionURI="bundleclass://org.argeo.tool.devops.e4/org.argeo.cms.e4.jcr.handlers.Refresh" command="_TOKHsCqYEeidr6NYQH6GbQ"/>
+ <handlers xmi:id="_omPfkCqYEeidr6NYQH6GbQ" elementId="org.argeo.cms.e4.handler.3" contributionURI="bundleclass://org.argeo.tool.devops.e4/org.argeo.cms.e4.jcr.handlers.RenameNode" command="_ZrcUMCqYEeidr6NYQH6GbQ"/>
+ <handlers xmi:id="_dUg-cFgQEeiknZQLx-vtnA" elementId="org.argeo.cms.e4.handler.9" contributionURI="bundleclass://org.argeo.tool.devops.e4/org.argeo.cms.e4.jcr.handlers.AddRemoteRepository" command="_ZWpasFgQEeiknZQLx-vtnA"/>
+ <handlers xmi:id="_RQyFAFhLEeiknZQLx-vtnA" elementId="org.argeo.cms.e4.handler.10" contributionURI="bundleclass://org.argeo.cms.e4/org.argeo.cms.e4.handlers.CloseWorkbench" command="_PsWd0FhLEeiknZQLx-vtnA"/>
+ <descriptors xmi:id="_XzfoMCqlEeidr6NYQH6GbQ" elementId="org.argeo.cms.e4.partdescriptor.nodeEditor" label="Node Editor" iconURI="platform:/plugin/org.argeo.tool.swt/icons/node.gif" allowMultiple="true" category="dataExplorer" closeable="true" contributionURI="bundleclass://org.argeo.tool.devops.e4/org.argeo.cms.e4.jcr.JcrNodeEditor"/>
+ <descriptors xmi:id="_sAdNwDvdEeiF1foPJZSZkw" elementId="org.argeo.cms.e4.partdescriptor.userEditor" label="User Editor" iconURI="platform:/plugin/org.argeo.tool.swt/icons/person.png" allowMultiple="true" category="usersEditorArea" closeable="true" dirtyable="true" contributionURI="bundleclass://org.argeo.tool.devops.e4/org.argeo.cms.e4.users.UserEditor"/>
+ <descriptors xmi:id="_5nK7EDvdEeiF1foPJZSZkw" elementId="org.argeo.cms.e4.partdescriptor.groupEditor" label="Group Editor" iconURI="platform:/plugin/org.argeo.tool.swt/icons/group.png" allowMultiple="true" category="usersEditorArea" closeable="true" dirtyable="true" contributionURI="bundleclass://org.argeo.tool.devops.e4/org.argeo.cms.e4.users.GroupEditor"/>
+ <commands xmi:id="_RgE5cCqREeidr6NYQH6GbQ" elementId="org.argeo.cms.e4.command.addFolderNode" commandName="Add folder node" category="_MDkwUCqYEeidr6NYQH6GbQ"/>
+ <commands xmi:id="_ChJ-4CqYEeidr6NYQH6GbQ" elementId="org.argeo.cms.e4.command.deleteNodes" commandName="Delete nodes" category="_MDkwUCqYEeidr6NYQH6GbQ"/>
+ <commands xmi:id="_TOKHsCqYEeidr6NYQH6GbQ" elementId="org.argeo.cms.e4.command.refreshNodes" commandName="Refresh nodes" category="_MDkwUCqYEeidr6NYQH6GbQ"/>
+ <commands xmi:id="_ZrcUMCqYEeidr6NYQH6GbQ" elementId="org.argeo.cms.e4.command.renameNode" commandName="Rename node" category="_MDkwUCqYEeidr6NYQH6GbQ"/>
+ <commands xmi:id="_uL5i4DvjEeiF1foPJZSZkw" elementId="org.argeo.cms.e4.command.add" commandName="Add"/>
+ <commands xmi:id="_xkcMADvjEeiF1foPJZSZkw" elementId="org.argeo.cms.e4.command.delete" commandName="Delete"/>
+ <commands xmi:id="_AF1UsDvrEeiF1foPJZSZkw" elementId="org.argeo.cms.e4.command.openPerspective" commandName="Open Perspective">
+ <parameters xmi:id="_F3WAUDvrEeiF1foPJZSZkw" elementId="perspectiveId" name="Perspective Id" optional="false"/>
+ </commands>
+ <commands xmi:id="_ZWpasFgQEeiknZQLx-vtnA" elementId="org.argeo.cms.e4.command.addRemoteRepository" commandName="Add Remote Repository"/>
+ <commands xmi:id="_PsWd0FhLEeiknZQLx-vtnA" elementId="org.argeo.cms.e4.command.logout" commandName="Log out"/>
+ <addons xmi:id="_XqkCQaknEeObFrG_clJBYA" elementId="org.eclipse.e4.core.commands.service" contributionURI="bundleclass://org.eclipse.e4.core.commands/org.eclipse.e4.core.commands.CommandServiceAddon"/>
+ <addons xmi:id="_XqkCQqknEeObFrG_clJBYA" elementId="org.eclipse.e4.ui.contexts.service" contributionURI="bundleclass://org.eclipse.e4.ui.services/org.eclipse.e4.ui.services.ContextServiceAddon"/>
+ <addons xmi:id="_XqkCQ6knEeObFrG_clJBYA" elementId="org.eclipse.e4.ui.bindings.service" contributionURI="bundleclass://org.eclipse.e4.ui.bindings/org.eclipse.e4.ui.bindings.BindingServiceAddon"/>
+ <addons xmi:id="_XqkCRKknEeObFrG_clJBYA" elementId="org.eclipse.e4.ui.workbench.commands.model" contributionURI="bundleclass://org.eclipse.e4.ui.workbench/org.eclipse.e4.ui.internal.workbench.addons.CommandProcessingAddon"/>
+ <addons xmi:id="_XqkCRaknEeObFrG_clJBYA" elementId="org.eclipse.e4.ui.workbench.contexts.model" contributionURI="bundleclass://org.eclipse.e4.ui.workbench/org.eclipse.e4.ui.internal.workbench.addons.ContextProcessingAddon"/>
+ <addons xmi:id="_XqkCRqknEeObFrG_clJBYA" elementId="org.eclipse.e4.ui.workbench.bindings.model" contributionURI="bundleclass://org.eclipse.e4.ui.workbench.swt/org.eclipse.e4.ui.workbench.swt.util.BindingProcessingAddon"/>
+ <addons xmi:id="_XqkCR6knEeObFrG_clJBYA" elementId="org.eclipse.e4.ui.workbench.handler.model" contributionURI="bundleclass://org.eclipse.e4.ui.workbench/org.eclipse.e4.ui.internal.workbench.addons.HandlerProcessingAddon"/>
+ <addons xmi:id="_8VnK8OdKEeijEOqYKRSeoQ" elementId="org.argeo.cms.e4.addon.locale" contributionURI="bundleclass://org.argeo.cms.e4/org.argeo.cms.e4.addons.LocaleAddon"/>
+ <addons xmi:id="_-xeJYOdKEeijEOqYKRSeoQ" elementId="org.argeo.cms.e4.addon.auth" contributionURI="bundleclass://org.argeo.cms.e4/org.argeo.cms.e4.addons.AuthAddon"/>
+ <categories xmi:id="_MDkwUCqYEeidr6NYQH6GbQ" elementId="org.argeo.cms.e4.category.jcrBrowser" name="JCR Browser"/>
+</application:Application>
\ No newline at end of file
--- /dev/null
+package org.argeo.cms.e4.files;
+
+import java.net.URI;
+import java.nio.file.FileSystem;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.spi.FileSystemProvider;
+
+import javax.annotation.PostConstruct;
+import javax.inject.Inject;
+
+import org.argeo.eclipse.ui.fs.SimpleFsBrowser;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.Composite;
+
+/** Browse the node file system. */
+public class NodeFsBrowserView {
+ // public final static String ID = WorkbenchUiPlugin.PLUGIN_ID +
+ // ".nodeFsBrowserView";
+
+ @Inject
+ FileSystemProvider nodeFileSystemProvider;
+
+ @PostConstruct
+ public void createPartControl(Composite parent) {
+ try {
+ // URI uri = new URI("node://root:demo@localhost:7070/");
+ URI uri = new URI("node:///");
+ FileSystem fileSystem = nodeFileSystemProvider.getFileSystem(uri);
+ if (fileSystem == null)
+ fileSystem = nodeFileSystemProvider.newFileSystem(uri, null);
+ Path nodePath = fileSystem.getPath("/");
+
+ Path localPath = Paths.get(System.getProperty("user.home"));
+
+ SimpleFsBrowser browser = new SimpleFsBrowser(parent, SWT.NO_FOCUS);
+ browser.setInput(nodePath, localPath);
+// AdvancedFsBrowser browser = new AdvancedFsBrowser();
+// browser.createUi(parent, localPath);
+ } catch (Exception e) {
+ throw new RuntimeException("Cannot open file system browser", e);
+ }
+ }
+
+ public void setFocus() {
+ }
+}
--- /dev/null
+/** Files browser perspective. */
+package org.argeo.cms.e4.files;
\ No newline at end of file
--- /dev/null
+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);
+ }
+}
--- /dev/null
+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<Property> propList = new ArrayList<Property>();
+ 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() {
+ }
+ }
+}
--- /dev/null
+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 "<binary>";
+ 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<Event> 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<Event> 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;
+ }
+}
--- /dev/null
+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);
+ }
+
+}
--- /dev/null
+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);
+ }
+
+}
--- /dev/null
+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");
+ }
+
+}
--- /dev/null
+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");
+ }
+ }
+
+}
--- /dev/null
+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<String, String> params = new Hashtable<String, String>();
+ 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;
+ }
+ }
+}
--- /dev/null
+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);
+ }
+ }
+}
--- /dev/null
+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:
+ * <ol>
+ * <li>The model objects are recomputed</li>
+ * <li>the view is refreshed</li>
+ * </ol>
+ */
+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
+ }
+}
--- /dev/null
+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);
+ }
+ }
+ }
+}
--- /dev/null
+/** JCR browser handlers. */
+package org.argeo.cms.e4.jcr.handlers;
\ No newline at end of file
--- /dev/null
+/** JCR browser perspective. */
+package org.argeo.cms.e4.jcr;
\ No newline at end of file
--- /dev/null
+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> T getService(Class<? extends T> clazz) {
+ return bc.getService(bc.getServiceReference(clazz));
+ }
+
+ protected <T> Collection<ServiceReference<T>> getServiceReferences(Class<T> clazz, String filter) {
+ try {
+ return bc.getServiceReferences(clazz, filter);
+ } catch (InvalidSyntaxException e) {
+ throw new IllegalArgumentException("Filter " + filter + " is invalid", e);
+ }
+ }
+}
--- /dev/null
+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<String, FilterEntitiesVirtualTable> browserCols = new LinkedHashMap<String, Browse.FilterEntitiesVirtualTable>();
+ 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("<b>" + context.getName() + "</b>");
+ 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 = "<binary>";
+ 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
--- /dev/null
+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("<span style='font-variant: small-caps;'>Provided Servers</span><br/>");
+
+ ServiceReference<HttpService> 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("<b>http</b> ").append(httpPort).append("<br/>");
+ if (httpsPort != null)
+ text.append("<b>https</b> ").append(httpsPort).append("<br/>");
+
+ }
+
+ text.append("<br/>");
+ text.append("<span style='font-variant: small-caps;'>Referenced Servers</span><br/>");
+
+ 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;
+ }
+}
--- /dev/null
+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<ServiceReference<RepositoryContext>> contexts = getServiceReferences(RepositoryContext.class,
+ "(" + CmsConstants.CN + "=*)");
+ StringBuffer text = new StringBuffer();
+ text.append("<span style='font-variant: small-caps;'>Jackrabbit Repositories</span><br/>");
+ for (ServiceReference<RepositoryContext> 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("<b>" + alias + "</b><br/>");
+ text.append("rootNodeId: " + rootNodeId + "<br/>");
+ try {
+ FileStore fileStore = Files.getFileStore(repoHomePath);
+ text.append("partition: " + fileStore.toString() + "<br/>");
+ text.append(
+ percentUsed(fileStore) + " used (" + humanReadable(fileStore.getUsableSpace()) + " free)<br/>");
+ } 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("<span style=''>" + text.toString() + "</span>");
+ }
+
+ 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 = "<span style='color:green;font-weight:bold'>";
+ else if (percent < 95)
+ span = "<span style='color:orange;font-weight:bold'>";
+ else
+ span = "<span style='color:red;font-weight:bold'>";
+ return span + percent + "%</span>";
+ }
+
+ protected boolean isDeployed() {
+ return bc.getServiceReference(RepositoryContext.class) != null;
+ }
+
+}
--- /dev/null
+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<CmsState> nodeStateRef = bc.getServiceReference(CmsState.class);
+ if (nodeStateRef == null)
+ throw new IllegalStateException("No CMS state available");
+ CmsState nodeState = bc.getService(nodeStateRef);
+ ServiceReference<CmsContext> 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 <b>" + calendar.getTime() + "</b>");
+ }
+ }
+
+ 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;
+ }
+}
--- /dev/null
+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<LogEntry> logEntries = (Enumeration<LogEntry>) 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);
+ // }
+}
--- /dev/null
+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";
+ }
--- /dev/null
+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;
+ }
+
+}
--- /dev/null
+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<UserAdmin> userAdminRef = bc.getServiceReference(UserAdmin.class);
+ UserAdmin userAdmin = bc.getService(userAdminRef);
+ StringBuffer text = new StringBuffer();
+ text.append("<span style='font-variant: small-caps;'>Domains</span><br/>");
+ 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("<span style='font-weight:bold;font-style: italic'>");
+ else
+ text.append("<span style='font-weight:bold'>");
+
+ text.append(rootDn);
+ text.append("</span><br/>");
+ 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.<br/>");
+ } 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;
+ }
+}
--- /dev/null
+/** Maintenance perspective. */
+package org.argeo.cms.e4.maintenance;
\ No newline at end of file
--- /dev/null
+package org.argeo.cms.e4.monitoring;
+
+import org.argeo.cms.ux.widgets.TreeParent;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.ServiceReference;
+
+/** A tree element representing a {@link Bundle} */
+class BundleNode extends TreeParent {
+ private final Bundle bundle;
+
+ public BundleNode(Bundle bundle) {
+ this(bundle, false);
+ }
+
+ @SuppressWarnings("rawtypes")
+ public BundleNode(Bundle bundle, boolean hasChildren) {
+ super(bundle.getSymbolicName());
+ this.bundle = bundle;
+
+ if (hasChildren) {
+ // REFERENCES
+ ServiceReference[] usedServices = bundle.getServicesInUse();
+ if (usedServices != null) {
+ for (ServiceReference sr : usedServices) {
+ if (sr != null)
+ addChild(new ServiceReferenceNode(sr, false));
+ }
+ }
+
+ // SERVICES
+ ServiceReference[] registeredServices = bundle
+ .getRegisteredServices();
+ if (registeredServices != null) {
+ for (ServiceReference sr : registeredServices) {
+ if (sr != null)
+ addChild(new ServiceReferenceNode(sr, true));
+ }
+ }
+ }
+
+ }
+
+ Bundle getBundle() {
+ return bundle;
+ }
+}
--- /dev/null
+//package org.argeo.eclipse.ui.workbench.osgi;
+//public class BundlesView {}
+
+package org.argeo.cms.e4.monitoring;
+
+import javax.annotation.PostConstruct;
+
+import org.argeo.eclipse.ui.ColumnViewerComparator;
+import org.argeo.eclipse.ui.specific.EclipseUiSpecificUtils;
+import org.eclipse.e4.ui.di.Focus;
+import org.eclipse.jface.viewers.ColumnLabelProvider;
+import org.eclipse.jface.viewers.IStructuredContentProvider;
+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.widgets.Composite;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
+
+/**
+ * Overview of the bundles as a table. Equivalent to Equinox 'ss' console
+ * command.
+ */
+public class BundlesView {
+ private final static BundleContext bc = FrameworkUtil.getBundle(BundlesView.class).getBundleContext();
+ private TableViewer viewer;
+
+ @PostConstruct
+ public void createPartControl(Composite parent) {
+ viewer = new TableViewer(parent);
+ viewer.setContentProvider(new BundleContentProvider());
+ viewer.getTable().setHeaderVisible(true);
+
+ EclipseUiSpecificUtils.enableToolTipSupport(viewer);
+
+ // ID
+ TableViewerColumn column = new TableViewerColumn(viewer, SWT.NONE);
+ column.getColumn().setWidth(30);
+ column.getColumn().setText("ID");
+ column.getColumn().setAlignment(SWT.RIGHT);
+ column.setLabelProvider(new ColumnLabelProvider() {
+ private static final long serialVersionUID = -3122136344359358605L;
+
+ public String getText(Object element) {
+ return Long.toString(((Bundle) element).getBundleId());
+ }
+ });
+ new ColumnViewerComparator(column);
+
+ // State
+ column = new TableViewerColumn(viewer, SWT.NONE);
+ column.getColumn().setWidth(18);
+ column.getColumn().setText("State");
+ column.setLabelProvider(new StateLabelProvider());
+ new ColumnViewerComparator(column);
+
+ // Symbolic name
+ column = new TableViewerColumn(viewer, SWT.NONE);
+ column.getColumn().setWidth(250);
+ column.getColumn().setText("Symbolic Name");
+ column.setLabelProvider(new ColumnLabelProvider() {
+ private static final long serialVersionUID = -4280840684440451080L;
+
+ public String getText(Object element) {
+ return ((Bundle) element).getSymbolicName();
+ }
+ });
+ new ColumnViewerComparator(column);
+
+ // Version
+ column = new TableViewerColumn(viewer, SWT.NONE);
+ column.getColumn().setWidth(250);
+ column.getColumn().setText("Version");
+ column.setLabelProvider(new ColumnLabelProvider() {
+ private static final long serialVersionUID = 6871926308708629989L;
+
+ public String getText(Object element) {
+ Bundle bundle = (org.osgi.framework.Bundle) element;
+ return bundle.getVersion().toString();
+ }
+ });
+ new ColumnViewerComparator(column);
+
+ viewer.setInput(bc);
+
+ }
+
+ @Focus
+ public void setFocus() {
+ if (viewer != null)
+ viewer.getControl().setFocus();
+ }
+
+ /** Content provider managing the array of bundles */
+ private static class BundleContentProvider implements IStructuredContentProvider {
+ private static final long serialVersionUID = -8533792785725875977L;
+
+ public Object[] getElements(Object inputElement) {
+ if (inputElement instanceof BundleContext) {
+ BundleContext bc = (BundleContext) inputElement;
+ return bc.getBundles();
+ }
+ return null;
+ }
+
+ public void dispose() {
+ }
+
+ public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
+ }
+ }
+}
--- /dev/null
+//package org.argeo.eclipse.ui.workbench.osgi;
+//public class BundlesView {}
+
+package org.argeo.cms.e4.monitoring;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import javax.annotation.PostConstruct;
+
+import org.argeo.api.cms.CmsSession;
+import org.argeo.cms.auth.RoleNameUtils;
+import org.argeo.eclipse.ui.ColumnViewerComparator;
+import org.argeo.eclipse.ui.specific.EclipseUiSpecificUtils;
+import org.argeo.util.LangUtils;
+import org.eclipse.e4.ui.di.Focus;
+import org.eclipse.jface.viewers.ColumnLabelProvider;
+import org.eclipse.jface.viewers.IStructuredContentProvider;
+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.widgets.Composite;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceReference;
+
+/**
+ * Overview of the active CMS sessions.
+ */
+public class CmsSessionsView {
+ private final static BundleContext bc = FrameworkUtil.getBundle(CmsSessionsView.class).getBundleContext();
+
+ private TableViewer viewer;
+
+ @PostConstruct
+ public void createPartControl(Composite parent) {
+ viewer = new TableViewer(parent);
+ viewer.setContentProvider(new CmsSessionContentProvider());
+ viewer.getTable().setHeaderVisible(true);
+
+ EclipseUiSpecificUtils.enableToolTipSupport(viewer);
+
+ int longColWidth = 150;
+ int smallColWidth = 100;
+
+ // Display name
+ TableViewerColumn column = new TableViewerColumn(viewer, SWT.NONE);
+ column.getColumn().setWidth(longColWidth);
+ column.getColumn().setText("User");
+ column.setLabelProvider(new ColumnLabelProvider() {
+ private static final long serialVersionUID = -5234573509093747505L;
+
+ public String getText(Object element) {
+ return ((CmsSession) element).getDisplayName();
+ }
+
+ public String getToolTipText(Object element) {
+ return ((CmsSession) element).getUserDn().toString();
+ }
+ });
+ new ColumnViewerComparator(column);
+
+ // Creation time
+ column = new TableViewerColumn(viewer, SWT.NONE);
+ column.getColumn().setWidth(smallColWidth);
+ column.getColumn().setText("Since");
+ column.setLabelProvider(new ColumnLabelProvider() {
+ private static final long serialVersionUID = -5234573509093747505L;
+
+ public String getText(Object element) {
+ return LangUtils.since(((CmsSession) element).getCreationTime());
+ }
+
+ public String getToolTipText(Object element) {
+ return ((CmsSession) element).getCreationTime().toString();
+ }
+ });
+ new ColumnViewerComparator(column);
+
+ // Username
+ column = new TableViewerColumn(viewer, SWT.NONE);
+ column.getColumn().setWidth(smallColWidth);
+ column.getColumn().setText("Username");
+ column.setLabelProvider(new ColumnLabelProvider() {
+ private static final long serialVersionUID = -5234573509093747505L;
+
+ public String getText(Object element) {
+ String userDn = ((CmsSession) element).getUserDn();
+ return RoleNameUtils.getLastRdnValue(userDn);
+ }
+
+ public String getToolTipText(Object element) {
+ return ((CmsSession) element).getUserDn().toString();
+ }
+ });
+ new ColumnViewerComparator(column);
+
+ // UUID
+ column = new TableViewerColumn(viewer, SWT.NONE);
+ column.getColumn().setWidth(smallColWidth);
+ column.getColumn().setText("UUID");
+ column.setLabelProvider(new ColumnLabelProvider() {
+ private static final long serialVersionUID = -5234573509093747505L;
+
+ public String getText(Object element) {
+ return ((CmsSession) element).getUuid().toString();
+ }
+
+ public String getToolTipText(Object element) {
+ return getText(element);
+ }
+ });
+ new ColumnViewerComparator(column);
+
+ // Local ID
+ column = new TableViewerColumn(viewer, SWT.NONE);
+ column.getColumn().setWidth(smallColWidth);
+ column.getColumn().setText("Local ID");
+ column.setLabelProvider(new ColumnLabelProvider() {
+ private static final long serialVersionUID = -5234573509093747505L;
+
+ public String getText(Object element) {
+ return ((CmsSession) element).getLocalId();
+ }
+
+ public String getToolTipText(Object element) {
+ return getText(element);
+ }
+ });
+ new ColumnViewerComparator(column);
+
+ viewer.setInput(bc);
+
+ }
+
+ @Focus
+ public void setFocus() {
+ if (viewer != null)
+ viewer.getControl().setFocus();
+ }
+
+ /** Content provider managing the array of bundles */
+ private static class CmsSessionContentProvider implements IStructuredContentProvider {
+ private static final long serialVersionUID = -8533792785725875977L;
+
+ public Object[] getElements(Object inputElement) {
+ if (inputElement instanceof BundleContext) {
+ BundleContext bc = (BundleContext) inputElement;
+ Collection<ServiceReference<CmsSession>> srs;
+ try {
+ srs = bc.getServiceReferences(CmsSession.class, null);
+ } catch (InvalidSyntaxException e) {
+ throw new IllegalArgumentException("Cannot retrieve CMS sessions", e);
+ }
+ List<CmsSession> res = new ArrayList<>();
+ for (ServiceReference<CmsSession> sr : srs) {
+ res.add(bc.getService(sr));
+ }
+ return res.toArray();
+ }
+ return null;
+ }
+
+ public void dispose() {
+ }
+
+ public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
+ }
+ }
+}
--- /dev/null
+package org.argeo.cms.e4.monitoring;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.annotation.PostConstruct;
+
+import org.argeo.cms.ux.widgets.TreeParent;
+import org.eclipse.e4.ui.di.Focus;
+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.widgets.Composite;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
+
+/** The OSGi runtime from a module perspective. */
+public class ModulesView {
+ private final static BundleContext bc = FrameworkUtil.getBundle(ModulesView.class).getBundleContext();
+ private TreeViewer viewer;
+
+ @PostConstruct
+ public void createPartControl(Composite parent) {
+ viewer = new TreeViewer(parent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL);
+ viewer.setContentProvider(new ModulesContentProvider());
+ viewer.setLabelProvider(new ModulesLabelProvider());
+ viewer.setInput(bc);
+ }
+
+ @Focus
+ public void setFocus() {
+ viewer.getTree().setFocus();
+ }
+
+ private class ModulesContentProvider implements ITreeContentProvider {
+ private static final long serialVersionUID = 3819934804640641721L;
+
+ public Object[] getElements(Object inputElement) {
+ return getChildren(inputElement);
+ }
+
+ public Object[] getChildren(Object parentElement) {
+ if (parentElement instanceof BundleContext) {
+ BundleContext bundleContext = (BundleContext) parentElement;
+ Bundle[] bundles = bundleContext.getBundles();
+
+ List<BundleNode> modules = new ArrayList<BundleNode>();
+ for (Bundle bundle : bundles) {
+ if (bundle.getState() == Bundle.ACTIVE)
+ modules.add(new BundleNode(bundle, true));
+ }
+ return modules.toArray();
+ } else if (parentElement instanceof TreeParent) {
+ return ((TreeParent) parentElement).getChildren();
+ } else {
+ return null;
+ }
+ }
+
+ public Object getParent(Object element) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ public boolean hasChildren(Object element) {
+ if (element instanceof TreeParent) {
+ return ((TreeParent) element).hasChildren();
+ }
+ return false;
+ }
+
+ public void dispose() {
+ }
+
+ public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
+ }
+ }
+
+ private class ModulesLabelProvider extends StateLabelProvider {
+ private static final long serialVersionUID = 5290046145534824722L;
+
+ @Override
+ public String getText(Object element) {
+ if (element instanceof BundleNode)
+ return element.toString() + " [" + ((BundleNode) element).getBundle().getBundleId() + "]";
+ return element.toString();
+ }
+ }
+}
--- /dev/null
+package org.argeo.cms.e4.monitoring;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.Dictionary;
+import java.util.List;
+
+import javax.annotation.PostConstruct;
+
+import org.argeo.cms.swt.CmsException;
+import org.argeo.util.LangUtils;
+import org.eclipse.jface.viewers.ColumnLabelProvider;
+import org.eclipse.jface.viewers.ITreeContentProvider;
+import org.eclipse.jface.viewers.TreeViewer;
+import org.eclipse.jface.viewers.TreeViewerColumn;
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.widgets.Composite;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.service.cm.Configuration;
+import org.osgi.service.cm.ConfigurationAdmin;
+
+public class OsgiConfigurationsView {
+ private final static BundleContext bc = FrameworkUtil.getBundle(OsgiConfigurationsView.class).getBundleContext();
+
+ @PostConstruct
+ public void createPartControl(Composite parent) {
+ ConfigurationAdmin configurationAdmin = bc.getService(bc.getServiceReference(ConfigurationAdmin.class));
+
+ TreeViewer viewer = new TreeViewer(parent);
+ // viewer.getTree().setHeaderVisible(true);
+
+ TreeViewerColumn tvc = new TreeViewerColumn(viewer, SWT.NONE);
+ tvc.getColumn().setWidth(400);
+ tvc.setLabelProvider(new ColumnLabelProvider() {
+ private static final long serialVersionUID = 835407996597566763L;
+
+ @Override
+ public String getText(Object element) {
+ if (element instanceof Configuration) {
+ return ((Configuration) element).getPid();
+ } else if (element instanceof Prop) {
+ return ((Prop) element).key;
+ }
+ return super.getText(element);
+ }
+
+ @Override
+ public Image getImage(Object element) {
+ if (element instanceof Configuration)
+ return OsgiExplorerImages.CONFIGURATION;
+ return null;
+ }
+
+ });
+
+ tvc = new TreeViewerColumn(viewer, SWT.NONE);
+ tvc.getColumn().setWidth(400);
+ tvc.setLabelProvider(new ColumnLabelProvider() {
+ private static final long serialVersionUID = 6999659261190014687L;
+
+ @Override
+ public String getText(Object element) {
+ if (element instanceof Configuration) {
+ // return ((Configuration) element).getFactoryPid();
+ return null;
+ } else if (element instanceof Prop) {
+ return ((Prop) element).value.toString();
+ }
+ return super.getText(element);
+ }
+ });
+
+ viewer.setContentProvider(new ConfigurationsContentProvider());
+ viewer.setInput(configurationAdmin);
+ }
+
+ static class ConfigurationsContentProvider implements ITreeContentProvider {
+ private static final long serialVersionUID = -4892768279440981042L;
+ private ConfigurationComparator configurationComparator = new ConfigurationComparator();
+
+ @Override
+ public void dispose() {
+ }
+
+ @Override
+ public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
+ }
+
+ @Override
+ public Object[] getElements(Object inputElement) {
+ ConfigurationAdmin configurationAdmin = (ConfigurationAdmin) inputElement;
+ try {
+ Configuration[] configurations = configurationAdmin.listConfigurations(null);
+ Arrays.sort(configurations, configurationComparator);
+ return configurations;
+ } catch (IOException | InvalidSyntaxException e) {
+ throw new CmsException("Cannot list configurations", e);
+ }
+ }
+
+ @Override
+ public Object[] getChildren(Object parentElement) {
+ if (parentElement instanceof Configuration) {
+ List<Prop> res = new ArrayList<>();
+ Configuration configuration = (Configuration) parentElement;
+ Dictionary<String, Object> props = configuration.getProperties();
+ keys: for (String key : LangUtils.keys(props)) {
+ if (Constants.SERVICE_PID.equals(key))
+ continue keys;
+ if (ConfigurationAdmin.SERVICE_FACTORYPID.equals(key))
+ continue keys;
+ res.add(new Prop(configuration, key, props.get(key)));
+ }
+ return res.toArray(new Prop[res.size()]);
+ }
+ return null;
+ }
+
+ @Override
+ public Object getParent(Object element) {
+ if (element instanceof Prop)
+ return ((Prop) element).configuration;
+ return null;
+ }
+
+ @Override
+ public boolean hasChildren(Object element) {
+ if (element instanceof Configuration)
+ return true;
+ return false;
+ }
+
+ }
+
+ static class Prop {
+ final Configuration configuration;
+ final String key;
+ final Object value;
+
+ public Prop(Configuration configuration, String key, Object value) {
+ this.configuration = configuration;
+ this.key = key;
+ this.value = value;
+ }
+
+ }
+
+ static class ConfigurationComparator implements Comparator<Configuration> {
+
+ @Override
+ public int compare(Configuration o1, Configuration o2) {
+ return o1.getPid().compareTo(o2.getPid());
+ }
+
+ }
+}
--- /dev/null
+package org.argeo.cms.e4.monitoring;
+
+import org.argeo.cms.ui.theme.CmsImages;
+import org.eclipse.swt.graphics.Image;
+
+/** Shared icons. */
+public class OsgiExplorerImages extends CmsImages {
+ public final static Image INSTALLED = createIcon("installed.gif");
+ public final static Image RESOLVED = createIcon("resolved.gif");
+ public final static Image STARTING = createIcon("starting.gif");
+ public final static Image ACTIVE = createIcon("active.gif");
+ public final static Image SERVICE_PUBLISHED = createIcon("service_published.gif");
+ public final static Image SERVICE_REFERENCED = createIcon("service_referenced.gif");
+ public final static Image CONFIGURATION = createIcon("node.gif");
+}
--- /dev/null
+package org.argeo.cms.e4.monitoring;
+
+import org.argeo.cms.ux.widgets.TreeParent;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.ServiceReference;
+
+/** A tree element representing a {@link ServiceReference} */
+@SuppressWarnings({ "rawtypes" })
+class ServiceReferenceNode extends TreeParent {
+ private final ServiceReference serviceReference;
+ private final boolean published;
+
+ public ServiceReferenceNode(ServiceReference serviceReference,
+ boolean published) {
+ super(serviceReference.toString());
+ this.serviceReference = serviceReference;
+ this.published = published;
+
+ if (isPublished()) {
+ Bundle[] usedBundles = serviceReference.getUsingBundles();
+ if (usedBundles != null) {
+ for (Bundle b : usedBundles) {
+ if (b != null)
+ addChild(new BundleNode(b));
+ }
+ }
+ } else {
+ Bundle provider = serviceReference.getBundle();
+ addChild(new BundleNode(provider));
+ }
+
+ for (String key : serviceReference.getPropertyKeys()) {
+ addChild(new TreeParent(key + "="
+ + serviceReference.getProperty(key)));
+ }
+
+ }
+
+ public ServiceReference getServiceReference() {
+ return serviceReference;
+ }
+
+ public boolean isPublished() {
+ return published;
+ }
+}
--- /dev/null
+package org.argeo.cms.e4.monitoring;
+
+import org.eclipse.jface.viewers.ColumnLabelProvider;
+import org.eclipse.swt.graphics.Image;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.Constants;
+
+/** Label provider showing the sate of bundles */
+class StateLabelProvider extends ColumnLabelProvider {
+ private static final long serialVersionUID = -7885583135316000733L;
+
+ @Override
+ public Image getImage(Object element) {
+ int state;
+ if (element instanceof Bundle)
+ state = ((Bundle) element).getState();
+ else if (element instanceof BundleNode)
+ state = ((BundleNode) element).getBundle().getState();
+ else if (element instanceof ServiceReferenceNode)
+ if (((ServiceReferenceNode) element).isPublished())
+ return OsgiExplorerImages.SERVICE_PUBLISHED;
+ else
+ return OsgiExplorerImages.SERVICE_REFERENCED;
+ else
+ return null;
+
+ switch (state) {
+ case Bundle.UNINSTALLED:
+ return OsgiExplorerImages.INSTALLED;
+ case Bundle.INSTALLED:
+ return OsgiExplorerImages.INSTALLED;
+ case Bundle.RESOLVED:
+ return OsgiExplorerImages.RESOLVED;
+ case Bundle.STARTING:
+ return OsgiExplorerImages.STARTING;
+ case Bundle.STOPPING:
+ return OsgiExplorerImages.STARTING;
+ case Bundle.ACTIVE:
+ return OsgiExplorerImages.ACTIVE;
+ default:
+ return null;
+ }
+ }
+
+ @Override
+ public String getText(Object element) {
+ return null;
+ }
+
+ @Override
+ public String getToolTipText(Object element) {
+ Bundle bundle = (Bundle) element;
+ Integer state = bundle.getState();
+ switch (state) {
+ case Bundle.UNINSTALLED:
+ return "UNINSTALLED";
+ case Bundle.INSTALLED:
+ return "INSTALLED";
+ case Bundle.RESOLVED:
+ return "RESOLVED";
+ case Bundle.STARTING:
+ String activationPolicy = bundle.getHeaders()
+ .get(Constants.BUNDLE_ACTIVATIONPOLICY).toString();
+
+ // .get("Bundle-ActivationPolicy").toString();
+ // FIXME constant triggers the compilation failure
+ if (activationPolicy != null
+ && activationPolicy.equals(Constants.ACTIVATION_LAZY))
+ // && activationPolicy.equals("lazy"))
+ // FIXME constant triggers the compilation failure
+ // && activationPolicy.equals(Constants.ACTIVATION_LAZY))
+ return "<<LAZY>>";
+ return "STARTING";
+ case Bundle.STOPPING:
+ return "STOPPING";
+ case Bundle.ACTIVE:
+ return "ACTIVE";
+ default:
+ return null;
+ }
+ }
+}
--- /dev/null
+/** Monitoring perspective. */
+package org.argeo.cms.e4.monitoring;
\ No newline at end of file
--- /dev/null
+package org.argeo.cms.e4.parts;
+
+import java.time.ZonedDateTime;
+
+import javax.annotation.PostConstruct;
+
+import org.argeo.api.cms.CmsSession;
+import org.argeo.cms.auth.CurrentUser;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+
+/** A canonical view of the logged in user. */
+public class EgoDashboard {
+// private BundleContext bc = FrameworkUtil.getBundle(EgoDashboard.class).getBundleContext();
+
+ @PostConstruct
+ public void createPartControl(Composite p) {
+ p.setLayout(new GridLayout());
+ String username = CurrentUser.getUsername();
+
+ CmsSwtUtils.lbl(p, "<strong>" + CurrentUser.getDisplayName() + "</strong>");
+ CmsSwtUtils.txt(p, username);
+ CmsSwtUtils.lbl(p, "Roles:");
+ roles: for (String role : CurrentUser.roles()) {
+ if (username.equals(role))
+ continue roles;
+ CmsSwtUtils.txt(p, role);
+ }
+
+// Subject subject = Subject.getSubject(AccessController.getContext());
+// if (subject != null) {
+ CmsSession cmsSession = CurrentUser.getCmsSession();
+ ZonedDateTime loggedIndSince = cmsSession.getCreationTime();
+ CmsSwtUtils.lbl(p, "Session:");
+ CmsSwtUtils.txt(p, cmsSession.getUuid().toString());
+ CmsSwtUtils.lbl(p, "Logged in since:");
+ CmsSwtUtils.txt(p, loggedIndSince.toString());
+// }
+ }
+}
--- /dev/null
+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<User> getFlatGroups(User aUser) {
+ Authorization currAuth;
+ if (aUser == null)
+ currAuth = userAdmin.getAuthorization(this.user);
+ else
+ currAuth = userAdmin.getAuthorization(aUser);
+
+ String[] roles = currAuth.getRoles();
+
+ List<User> groups = new ArrayList<User>();
+ 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;
+ }
+
+}
--- /dev/null
+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";
+}
--- /dev/null
+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.swt.useradmin.LdifUsersTable;
+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.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("<a>Mark as workgroup</a>");
+ 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<ColumnDefinition> columnDefs = new ArrayList<ColumnDefinition>();
+ 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<ColumnDefinition> columnDefs = new ArrayList<ColumnDefinition>();
+ // 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<User> listFilteredElements(String filter) {
+ // reload user and set it in the editor
+ Group group = (Group) getDisplayedUser();
+ Role[] roles = group.getMembers();
+ List<User> users = new ArrayList<User>();
+ 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<User> it = ((IStructuredSelection) selection).iterator();
+ List<User> users = new ArrayList<User>();
+ 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<User> 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<User> 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;
+ // }
+
+}
--- /dev/null
+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.swt.useradmin.LdifUsersTable;
+//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.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<ColumnDefinition> columnDefs = new ArrayList<ColumnDefinition>();
+
+ 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<User> 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<User> users = new ArrayList<User>();
+ 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;
+ }
+}
--- /dev/null
+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");
+
+}
--- /dev/null
+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 <a href=
+ * "http://www.mkyong.com/regular-expressions/how-to-validate-email-address-with-regular-expression/"
+ * >this tip</a>.
+ */
+ 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,})$";
+}
--- /dev/null
+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);
+}
--- /dev/null
+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<UserAdmin> userAdminServiceReference;
+// private Set<String> uris;
+ private Map<UserDirectory, Hashtable<String, String>> 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<UserAdminListener> listeners = new ArrayList<UserAdminListener>();
+
+ /**
+ * 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<String, String> getKnownBaseDns(boolean onlyWritable) {
+ Map<String, String> dns = new HashMap<String, String>();
+ 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<String, ?> 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<String, String> 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<String, String> properties) {
+ userDirectories.put(userDirectory, new Hashtable<>(properties));
+ }
+
+ public void removeUserDirectory(UserDirectory userDirectory, Map<String, String> properties) {
+ userDirectories.remove(userDirectory);
+ }
+
+ // public void setUserAdminServiceReference(
+ // ServiceReference<UserAdmin> userAdminServiceReference) {
+ // this.userAdminServiceReference = userAdminServiceReference;
+ // }
+}
--- /dev/null
+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.cms.swt.useradmin.LdifUsersTable;
+import org.argeo.eclipse.ui.ColumnDefinition;
+import org.argeo.eclipse.ui.EclipseUiUtils;
+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<String, String> commands = new HashMap<String, String>() {
+ 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<User> usersToUpdate;
+
+ public ResetPassword(UserAdminWrapper userAdminWrapper, List<User> 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<User> usersToUpdate;
+
+ public ResetEmail(UserAdminWrapper userAdminWrapper, List<User> 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<Node> 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<User> usersToUpdate;
+ //
+ // protected abstract void doUpdate(User user);
+ //
+ // public UpdateJob(UserAdminWrapper userAdminWrapper,
+ // List<User> 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<ColumnDefinition> columnDefs = new ArrayList<ColumnDefinition>();
+ 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<User> 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<User> 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<User> users = new ArrayList<User>();
+ 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<ColumnDefinition> columnDefs = new ArrayList<ColumnDefinition>();
+ 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<User> listFilteredElements(String filter) {
+ return userListPage.getSelectedUsers();
+ }
+ }
+ }
+}
--- /dev/null
+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.swt.useradmin.LdifUsersTable;
+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.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("<a>Reset password</a>");
+ }
+ 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<ColumnDefinition> columnDefs = new ArrayList<ColumnDefinition>();
+ 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<User> listFilteredElements(String filter) {
+ List<User> users = (List<User>) getFlatGroups(null);
+ List<User> filteredUsers = new ArrayList<User>();
+ 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<Group> it = ((IStructuredSelection) selection).iterator();
+ List<Group> groups = new ArrayList<Group>();
+ 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;
+ // }
+
+}
--- /dev/null
+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";
+ }
+}
--- /dev/null
+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.cms.swt.useradmin.LdifUsersTable;
+import org.argeo.eclipse.ui.ColumnDefinition;
+import org.argeo.eclipse.ui.EclipseUiUtils;
+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<ColumnDefinition> columnDefs = new ArrayList<ColumnDefinition>();
+
+ 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<User> 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<User> users = new ArrayList<User>();
+ 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;
+ }
+}
--- /dev/null
+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<Group> groups = new ArrayList<Group>();
+ // Iterator<Group> it = ((IStructuredSelection) selection).iterator();
+
+ List<Group> selection = (List<Group>) 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;
+ // }
+}
--- /dev/null
+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<User> selection = (List<User>) selectionService.getSelection();
+ if (selection == null)
+ return;
+
+// Iterator<User> it = ((IStructuredSelection) selection).iterator();
+// List<User> users = new ArrayList<User>();
+ 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;
+ }
+}
--- /dev/null
+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<String, String> getDns() {
+ return userAdminWrapper.getKnownBaseDns(true);
+ }
+
+ private String getDn(String cn) {
+ Map<String, String> dns = getDns();
+ String bdn = baseDnCmb.getText();
+ if (EclipseUiUtils.notEmpty(bdn)) {
+ Dictionary<String, ?> 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<String, String> 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;
+ }
+}
--- /dev/null
+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<String, String> getDns() {
+ return userAdminWrapper.getKnownBaseDns(true);
+ }
+
+ private String getDn(String uid) {
+ Map<String, String> dns = getDns();
+ String bdn = baseDnCmb.getText();
+ if (EclipseUiUtils.notEmpty(bdn)) {
+ Dictionary<String, ?> 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<String, String> 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<Rdn> 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;
+ }
+}
--- /dev/null
+/** Users management handlers. */
+package org.argeo.cms.e4.users.handlers;
\ No newline at end of file
--- /dev/null
+/** Users management perspective. */
+package org.argeo.cms.e4.users;
\ No newline at end of file
--- /dev/null
+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);
+ }
+
+}
--- /dev/null
+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);
+ }
+}
--- /dev/null
+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());
+ }
+}
--- /dev/null
+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;
+ }
+}
--- /dev/null
+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);
+}
--- /dev/null
+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) {
+ }
+}
--- /dev/null
+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;
+ }
+}
--- /dev/null
+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();
+ }
+}
--- /dev/null
+/** Users management content providers. */
+package org.argeo.cms.e4.users.providers;
\ No newline at end of file
--- /dev/null
+package org.argeo.cms.jcr.e4.rap;
+
+import org.argeo.cms.e4.rap.AbstractRapE4App;
+import org.eclipse.rap.rwt.application.Application;
+
+/**
+ * Access to canonical views of the core CMS concepts, useful for devleopers and
+ * operators.
+ */
+public class CmsE4AdminApp extends AbstractRapE4App {
+ @Override
+ protected void addEntryPoints(Application application) {
+ addE4EntryPoint(application, "/devops", "org.argeo.tool.devops.e4/e4xmi/devops.e4xmi",
+ customise("Argeo Tool DevOps"));
+ }
+
+}
--- /dev/null
+package org.argeo.cms.swt.useradmin;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.argeo.eclipse.ui.ColumnDefinition;
+import org.argeo.eclipse.ui.EclipseUiException;
+import org.argeo.eclipse.ui.EclipseUiUtils;
+import org.eclipse.jface.layout.TableColumnLayout;
+import org.eclipse.jface.viewers.CheckboxTableViewer;
+import org.eclipse.jface.viewers.ColumnLabelProvider;
+import org.eclipse.jface.viewers.ColumnWeightData;
+import org.eclipse.jface.viewers.IStructuredContentProvider;
+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.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.Composite;
+import org.eclipse.swt.widgets.Link;
+import org.eclipse.swt.widgets.Table;
+import org.eclipse.swt.widgets.TableColumn;
+import org.eclipse.swt.widgets.Text;
+import org.osgi.service.useradmin.User;
+
+/**
+ * Generic composite that display a filter and a table viewer to display users
+ * (can also be groups)
+ *
+ * Warning: this class does not extends <code>TableViewer</code>. Use the
+ * getTableViewer method to access it.
+ *
+ */
+public abstract class LdifUsersTable extends Composite {
+ private static final long serialVersionUID = -7385959046279360420L;
+
+ // Context
+ // private UserAdmin userAdmin;
+
+ // Configuration
+ private List<ColumnDefinition> columnDefs = new ArrayList<ColumnDefinition>();
+ private boolean hasFilter;
+ private boolean preventTableLayout = false;
+ private boolean hasSelectionColumn;
+ private int tableStyle;
+
+ // Local UI Objects
+ private TableViewer usersViewer;
+ private Text filterTxt;
+
+ /* EXPOSED METHODS */
+
+ /**
+ * @param parent
+ * @param style
+ */
+ public LdifUsersTable(Composite parent, int style) {
+ super(parent, SWT.NO_FOCUS);
+ this.tableStyle = style;
+ }
+
+ // TODO workaround the bug of the table layout in the Form
+ public LdifUsersTable(Composite parent, int style, boolean preventTableLayout) {
+ super(parent, SWT.NO_FOCUS);
+ this.tableStyle = style;
+ this.preventTableLayout = preventTableLayout;
+ }
+
+ /** This must be called before the call to populate method */
+ public void setColumnDefinitions(List<ColumnDefinition> columnDefinitions) {
+ this.columnDefs = columnDefinitions;
+ }
+
+ /**
+ *
+ * @param addFilter
+ * choose to add a field to filter results or not
+ * @param addSelection
+ * choose to add a column to select some of the displayed results or
+ * not
+ */
+ public void populate(boolean addFilter, boolean addSelection) {
+ // initialization
+ Composite parent = this;
+ hasFilter = addFilter;
+ hasSelectionColumn = addSelection;
+
+ // Main Layout
+ GridLayout layout = EclipseUiUtils.noSpaceGridLayout();
+ layout.verticalSpacing = 5;
+ this.setLayout(layout);
+ if (hasFilter)
+ createFilterPart(parent);
+
+ Composite tableComp = new Composite(parent, SWT.NO_FOCUS);
+ tableComp.setLayoutData(EclipseUiUtils.fillAll());
+ usersViewer = createTableViewer(tableComp);
+ usersViewer.setContentProvider(new UsersContentProvider());
+ }
+
+ /**
+ *
+ * @param showMore
+ * display static filters on creation
+ * @param addSelection
+ * choose to add a column to select some of the displayed results or
+ * not
+ */
+ public void populateWithStaticFilters(boolean showMore, boolean addSelection) {
+ // initialization
+ Composite parent = this;
+ hasFilter = true;
+ hasSelectionColumn = addSelection;
+
+ // Main Layout
+ GridLayout layout = EclipseUiUtils.noSpaceGridLayout();
+ layout.verticalSpacing = 5;
+ this.setLayout(layout);
+ createStaticFilterPart(parent, showMore);
+
+ Composite tableComp = new Composite(parent, SWT.NO_FOCUS);
+ tableComp.setLayoutData(EclipseUiUtils.fillAll());
+ usersViewer = createTableViewer(tableComp);
+ usersViewer.setContentProvider(new UsersContentProvider());
+ }
+
+ /** Enable access to the selected users or groups */
+ public List<User> getSelectedUsers() {
+ if (hasSelectionColumn) {
+ Object[] elements = ((CheckboxTableViewer) usersViewer).getCheckedElements();
+
+ List<User> result = new ArrayList<User>();
+ for (Object obj : elements) {
+ result.add((User) obj);
+ }
+ return result;
+ } else
+ throw new EclipseUiException(
+ "Unvalid request: no selection column " + "has been created for the current table");
+ }
+
+ /** Returns the User table viewer, typically to add doubleclick listener */
+ public TableViewer getTableViewer() {
+ return usersViewer;
+ }
+
+ /**
+ * Force the refresh of the underlying table using the current filter string if
+ * relevant
+ */
+ public void refresh() {
+ String filter = hasFilter ? filterTxt.getText().trim() : null;
+ if ("".equals(filter))
+ filter = null;
+ refreshFilteredList(filter);
+ }
+
+ /** Effective repository request: caller must implement this method */
+ abstract protected List<User> listFilteredElements(String filter);
+
+ // protected List<User> listFilteredElements(String filter) {
+ // List<User> users = new ArrayList<User>();
+ // try {
+ // Role[] roles = userAdmin.getRoles(filter);
+ // // Display all users and groups
+ // for (Role role : roles)
+ // users.add((User) role);
+ // } catch (InvalidSyntaxException e) {
+ // throw new EclipseUiException("Unable to get roles with filter: "
+ // + filter, e);
+ // }
+ // return users;
+ // }
+
+ /* GENERIC COMPOSITE METHODS */
+ @Override
+ public boolean setFocus() {
+ if (hasFilter)
+ return filterTxt.setFocus();
+ else
+ return usersViewer.getTable().setFocus();
+ }
+
+ @Override
+ public void dispose() {
+ super.dispose();
+ }
+
+ /* LOCAL CLASSES AND METHODS */
+ // Will be usefull to rather use a virtual table viewer
+ private void refreshFilteredList(String filter) {
+ List<User> users = listFilteredElements(filter);
+ usersViewer.setInput(users.toArray());
+ }
+
+ private class UsersContentProvider implements IStructuredContentProvider {
+ private static final long serialVersionUID = 1L;
+
+ public Object[] getElements(Object inputElement) {
+ return (Object[]) inputElement;
+ }
+
+ public void dispose() {
+ }
+
+ public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
+ }
+ }
+
+ /* MANAGE FILTER */
+ private void createFilterPart(Composite parent) {
+ // Text Area for the filter
+ filterTxt = new Text(parent, SWT.BORDER | SWT.SEARCH | SWT.ICON_SEARCH | SWT.ICON_CANCEL);
+ filterTxt.setLayoutData(new GridData(GridData.FILL, GridData.FILL, true, false));
+ filterTxt.addModifyListener(new ModifyListener() {
+ private static final long serialVersionUID = 1L;
+
+ public void modifyText(ModifyEvent event) {
+ refreshFilteredList(filterTxt.getText());
+ }
+ });
+ }
+
+ private void createStaticFilterPart(Composite parent, boolean showMore) {
+ Composite filterComp = new Composite(parent, SWT.NO_FOCUS);
+ filterComp.setLayout(new GridLayout(2, false));
+ filterComp.setLayoutData(EclipseUiUtils.fillWidth());
+ // generic search
+ filterTxt = new Text(filterComp, SWT.BORDER | SWT.SEARCH | SWT.ICON_SEARCH | SWT.ICON_CANCEL);
+ filterTxt.setLayoutData(new GridData(GridData.FILL, GridData.FILL, true, false));
+ // filterTxt.setLayoutData(new GridData(GridData.GRAB_HORIZONTAL |
+ // GridData.HORIZONTAL_ALIGN_FILL));
+ filterTxt.addModifyListener(new ModifyListener() {
+ private static final long serialVersionUID = 1L;
+
+ public void modifyText(ModifyEvent event) {
+ refreshFilteredList(filterTxt.getText());
+ }
+ });
+
+ // add static filter abilities
+ Link moreLk = new Link(filterComp, SWT.NONE);
+ Composite staticFilterCmp = new Composite(filterComp, SWT.NO_FOCUS);
+ staticFilterCmp.setLayoutData(EclipseUiUtils.fillWidth(2));
+ populateStaticFilters(staticFilterCmp);
+
+ MoreLinkListener listener = new MoreLinkListener(moreLk, staticFilterCmp, showMore);
+ // initialise the layout
+ listener.refresh();
+ moreLk.addSelectionListener(listener);
+ }
+
+ /** Overwrite to add static filters */
+ protected void populateStaticFilters(Composite staticFilterCmp) {
+ }
+
+ // private void addMoreSL(final Link more) {
+ // more.addSelectionListener( }
+
+ private class MoreLinkListener extends SelectionAdapter {
+ private static final long serialVersionUID = -524987616510893463L;
+ private boolean isShown;
+ private final Composite staticFilterCmp;
+ private final Link moreLk;
+
+ public MoreLinkListener(Link moreLk, Composite staticFilterCmp, boolean isShown) {
+ this.moreLk = moreLk;
+ this.staticFilterCmp = staticFilterCmp;
+ this.isShown = isShown;
+ }
+
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ isShown = !isShown;
+ refresh();
+ }
+
+ public void refresh() {
+ GridData gd = (GridData) staticFilterCmp.getLayoutData();
+ if (isShown) {
+ moreLk.setText("<a> Less... </a>");
+ gd.heightHint = SWT.DEFAULT;
+ } else {
+ moreLk.setText("<a> More... </a>");
+ gd.heightHint = 0;
+ }
+ forceLayout();
+ }
+ }
+
+ private void forceLayout() {
+ LdifUsersTable.this.getParent().layout(true, true);
+ }
+
+ private TableViewer createTableViewer(final Composite parent) {
+
+ int style = tableStyle | SWT.H_SCROLL | SWT.V_SCROLL;
+ if (hasSelectionColumn)
+ style = style | SWT.CHECK;
+ Table table = new Table(parent, style);
+ TableColumnLayout layout = new TableColumnLayout();
+
+ // TODO the table layout does not works with the scrolled form
+
+ if (preventTableLayout) {
+ parent.setLayout(EclipseUiUtils.noSpaceGridLayout());
+ table.setLayoutData(EclipseUiUtils.fillAll());
+ } else
+ parent.setLayout(layout);
+
+ TableViewer viewer;
+ if (hasSelectionColumn)
+ viewer = new CheckboxTableViewer(table);
+ else
+ viewer = new TableViewer(table);
+ table.setLinesVisible(true);
+ table.setHeaderVisible(true);
+
+ TableViewerColumn column;
+ // int offset = 0;
+ if (hasSelectionColumn) {
+ // offset = 1;
+ column = ViewerUtils.createTableViewerColumn(viewer, "", SWT.NONE, 25);
+ column.setLabelProvider(new ColumnLabelProvider() {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public String getText(Object element) {
+ return null;
+ }
+ });
+ layout.setColumnData(column.getColumn(), new ColumnWeightData(25, 25, false));
+
+ SelectionAdapter selectionAdapter = new SelectionAdapter() {
+ private static final long serialVersionUID = 1L;
+
+ boolean allSelected = false;
+
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ allSelected = !allSelected;
+ ((CheckboxTableViewer) usersViewer).setAllChecked(allSelected);
+ }
+ };
+ column.getColumn().addSelectionListener(selectionAdapter);
+ }
+
+ // NodeViewerComparator comparator = new NodeViewerComparator();
+ // TODO enable the sort by click on the header
+ // int i = offset;
+ for (ColumnDefinition colDef : columnDefs)
+ createTableColumn(viewer, layout, colDef);
+
+ // column = ViewerUtils.createTableViewerColumn(viewer,
+ // colDef.getHeaderLabel(), SWT.NONE, colDef.getColumnSize());
+ // column.setLabelProvider(new CLProvider(colDef.getPropertyName()));
+ // column.getColumn().addSelectionListener(
+ // JcrUiUtils.getNodeSelectionAdapter(i,
+ // colDef.getPropertyType(), colDef.getPropertyName(),
+ // comparator, viewer));
+ // i++;
+ // }
+
+ // IMPORTANT: initialize comparator before setting it
+ // JcrColumnDefinition firstCol = colDefs.get(0);
+ // comparator.setColumn(firstCol.getPropertyType(),
+ // firstCol.getPropertyName());
+ // viewer.setComparator(comparator);
+
+ return viewer;
+ }
+
+ /** Default creation of a column for a user table */
+ private TableViewerColumn createTableColumn(TableViewer tableViewer, TableColumnLayout layout,
+ ColumnDefinition columnDef) {
+
+ boolean resizable = true;
+ TableViewerColumn tvc = new TableViewerColumn(tableViewer, SWT.NONE);
+ TableColumn column = tvc.getColumn();
+
+ column.setText(columnDef.getLabel());
+ column.setWidth(columnDef.getMinWidth());
+ column.setResizable(resizable);
+
+ ColumnLabelProvider lp = columnDef.getLabelProvider();
+ // add a reference to the display to enable font management
+ // if (lp instanceof UserAdminAbstractLP)
+ // ((UserAdminAbstractLP) lp).setDisplay(tableViewer.getTable()
+ // .getDisplay());
+ tvc.setLabelProvider(lp);
+
+ layout.setColumnData(column, new ColumnWeightData(columnDef.getWeight(), columnDef.getMinWidth(), resizable));
+
+ return tvc;
+ }
+}
--- /dev/null
+package org.argeo.cms.swt.useradmin;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.eclipse.ui.ColumnDefinition;
+import org.argeo.eclipse.ui.EclipseUiException;
+import org.argeo.eclipse.ui.EclipseUiUtils;
+import org.argeo.util.naming.LdapAttrs;
+import org.argeo.util.naming.LdapObjs;
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.jface.dialogs.TrayDialog;
+import org.eclipse.jface.viewers.DoubleClickEvent;
+import org.eclipse.jface.viewers.IDoubleClickListener;
+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.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.layout.FillLayout;
+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.Shell;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.service.useradmin.Group;
+import org.osgi.service.useradmin.Role;
+import org.osgi.service.useradmin.User;
+import org.osgi.service.useradmin.UserAdmin;
+
+/** Dialog with a user (or group) list to pick up one */
+public class PickUpUserDialog extends TrayDialog {
+ private static final long serialVersionUID = -1420106871173920369L;
+
+ // Business objects
+ private final UserAdmin userAdmin;
+ private User selectedUser;
+
+ // this page widgets and UI objects
+ private String title;
+ private LdifUsersTable userTableViewerCmp;
+ private TableViewer userViewer;
+ private List<ColumnDefinition> columnDefs = new ArrayList<ColumnDefinition>();
+
+ /**
+ * A dialog to pick up a group or a user, showing a table with default
+ * columns
+ */
+ public PickUpUserDialog(Shell parentShell, String title, UserAdmin userAdmin) {
+ super(parentShell);
+ this.title = title;
+ this.userAdmin = userAdmin;
+
+ columnDefs.add(new ColumnDefinition(new UserLP(UserLP.COL_ICON), "",
+ 24, 24));
+ columnDefs.add(new ColumnDefinition(
+ new UserLP(UserLP.COL_DISPLAY_NAME), "Common Name", 150, 100));
+ columnDefs.add(new ColumnDefinition(new UserLP(UserLP.COL_DOMAIN),
+ "Domain", 100, 120));
+ columnDefs.add(new ColumnDefinition(new UserLP(UserLP.COL_DN),
+ "Distinguished Name", 300, 100));
+ }
+
+ /** A dialog to pick up a group or a user */
+ public PickUpUserDialog(Shell parentShell, String title,
+ UserAdmin userAdmin, List<ColumnDefinition> columnDefs) {
+ super(parentShell);
+ this.title = title;
+ this.userAdmin = userAdmin;
+ this.columnDefs = columnDefs;
+ }
+
+ @Override
+ protected void okPressed() {
+ if (getSelected() == null)
+ MessageDialog.openError(getShell(), "No user chosen",
+ "Please, choose a user or press Cancel.");
+ else
+ super.okPressed();
+ }
+
+ protected Control createDialogArea(Composite parent) {
+ Composite dialogArea = (Composite) super.createDialogArea(parent);
+ dialogArea.setLayout(new FillLayout());
+
+ Composite bodyCmp = new Composite(dialogArea, SWT.NO_FOCUS);
+ bodyCmp.setLayout(new GridLayout());
+
+ // Create and configure the table
+ userTableViewerCmp = new MyUserTableViewer(bodyCmp, SWT.MULTI
+ | SWT.H_SCROLL | SWT.V_SCROLL);
+
+ userTableViewerCmp.setColumnDefinitions(columnDefs);
+ userTableViewerCmp.populateWithStaticFilters(false, false);
+ GridData gd = EclipseUiUtils.fillAll();
+ gd.minimumHeight = 300;
+ userTableViewerCmp.setLayoutData(gd);
+ userTableViewerCmp.refresh();
+
+ // Controllers
+ userViewer = userTableViewerCmp.getTableViewer();
+ userViewer.addDoubleClickListener(new MyDoubleClickListener());
+ userViewer
+ .addSelectionChangedListener(new MySelectionChangedListener());
+
+ parent.pack();
+ return dialogArea;
+ }
+
+ public User getSelected() {
+ if (selectedUser == null)
+ return null;
+ else
+ return selectedUser;
+ }
+
+ protected void configureShell(Shell shell) {
+ super.configureShell(shell);
+ shell.setText(title);
+ }
+
+ class MyDoubleClickListener implements IDoubleClickListener {
+ public void doubleClick(DoubleClickEvent evt) {
+ if (evt.getSelection().isEmpty())
+ return;
+
+ Object obj = ((IStructuredSelection) evt.getSelection())
+ .getFirstElement();
+ if (obj instanceof User) {
+ selectedUser = (User) obj;
+ okPressed();
+ }
+ }
+ }
+
+ class MySelectionChangedListener implements ISelectionChangedListener {
+ @Override
+ public void selectionChanged(SelectionChangedEvent event) {
+ if (event.getSelection().isEmpty()) {
+ selectedUser = null;
+ return;
+ }
+ Object obj = ((IStructuredSelection) event.getSelection())
+ .getFirstElement();
+ if (obj instanceof Group) {
+ selectedUser = (Group) obj;
+ }
+ }
+ }
+
+ private class MyUserTableViewer extends LdifUsersTable {
+ private static final long serialVersionUID = 8467999509931900367L;
+
+ private final String[] knownProps = { LdapAttrs.uid.name(),
+ LdapAttrs.cn.name(), LdapAttrs.DN };
+
+ private Button showSystemRoleBtn;
+ private Button showUserBtn;
+
+ public MyUserTableViewer(Composite parent, int style) {
+ super(parent, style);
+ }
+
+ protected void populateStaticFilters(Composite staticFilterCmp) {
+ staticFilterCmp.setLayout(new GridLayout());
+ showSystemRoleBtn = new Button(staticFilterCmp, SWT.CHECK);
+ showSystemRoleBtn.setText("Show system roles ");
+
+ showUserBtn = new Button(staticFilterCmp, SWT.CHECK);
+ showUserBtn.setText("Show users ");
+
+ SelectionListener sl = new SelectionAdapter() {
+ private static final long serialVersionUID = -7033424592697691676L;
+
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ refresh();
+ }
+ };
+
+ showSystemRoleBtn.addSelectionListener(sl);
+ showUserBtn.addSelectionListener(sl);
+ }
+
+ @Override
+ protected List<User> listFilteredElements(String filter) {
+ Role[] roles;
+ try {
+ StringBuilder builder = new StringBuilder();
+
+ StringBuilder filterBuilder = new StringBuilder();
+ if (notNull(filter))
+ for (String prop : knownProps) {
+ filterBuilder.append("(");
+ filterBuilder.append(prop);
+ filterBuilder.append("=*");
+ filterBuilder.append(filter);
+ filterBuilder.append("*)");
+ }
+
+ String typeStr = "(" + LdapAttrs.objectClass.name() + "="
+ + LdapObjs.groupOfNames.name() + ")";
+ if ((showUserBtn.getSelection()))
+ typeStr = "(|(" + LdapAttrs.objectClass.name() + "="
+ + LdapObjs.inetOrgPerson.name() + ")" + typeStr
+ + ")";
+
+ if (!showSystemRoleBtn.getSelection())
+ typeStr = "(& " + typeStr + "(!(" + LdapAttrs.DN + "=*"
+ + CmsConstants.SYSTEM_ROLES_BASEDN + ")))";
+
+ if (filterBuilder.length() > 1) {
+ builder.append("(&" + typeStr);
+ builder.append("(|");
+ builder.append(filterBuilder.toString());
+ builder.append("))");
+ } else {
+ builder.append(typeStr);
+ }
+ roles = userAdmin.getRoles(builder.toString());
+ } catch (InvalidSyntaxException e) {
+ throw new EclipseUiException(
+ "Unable to get roles with filter: " + filter, e);
+ }
+ List<User> users = new ArrayList<User>();
+ for (Role role : roles)
+ if (!users.contains(role))
+ users.add((User) role);
+ return users;
+ }
+ }
+
+ private boolean notNull(String string) {
+ if (string == null)
+ return false;
+ else
+ return !"".equals(string.trim());
+ }
+}
--- /dev/null
+package org.argeo.cms.swt.useradmin;
+
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.cms.auth.UserAdminUtils;
+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.graphics.Image;
+import org.eclipse.swt.widgets.Display;
+import org.osgi.service.useradmin.Role;
+import org.osgi.service.useradmin.User;
+
+/** Centralize label providers for the group table */
+class UserLP extends ColumnLabelProvider {
+ private static final long serialVersionUID = -4645930210988368571L;
+
+ final static String COL_ICON = "colID.icon";
+ final static String COL_DN = "colID.dn";
+ final static String COL_DISPLAY_NAME = "colID.displayName";
+ final static String COL_DOMAIN = "colID.domain";
+
+ final String currType;
+
+ // private Font italic;
+ private Font bold;
+
+ UserLP(String colId) {
+ this.currType = colId;
+ }
+
+ @Override
+ public Font getFont(Object element) {
+ // Current user as bold
+ if (UserAdminUtils.isCurrentUser(((User) element))) {
+ if (bold == null)
+ bold = JFaceResources.getFontRegistry().defaultFontDescriptor().setStyle(SWT.BOLD)
+ .createFont(Display.getCurrent());
+ return bold;
+ }
+ return null;
+ }
+
+ @Override
+ public Image getImage(Object element) {
+ if (COL_ICON.equals(currType)) {
+ User user = (User) element;
+ String dn = user.getName();
+ if (dn.endsWith(CmsConstants.SYSTEM_ROLES_BASEDN))
+ return UsersImages.ICON_ROLE;
+ else if (user.getType() == Role.GROUP)
+ return UsersImages.ICON_GROUP;
+ else
+ return UsersImages.ICON_USER;
+ } else
+ return null;
+ }
+
+ @Override
+ public String getText(Object element) {
+ User user = (User) element;
+ return getText(user);
+
+ }
+
+ public String getText(User user) {
+ if (COL_DN.equals(currType))
+ return user.getName();
+ else if (COL_DISPLAY_NAME.equals(currType))
+ return UserAdminUtils.getCommonName(user);
+ else if (COL_DOMAIN.equals(currType))
+ return UserAdminUtils.getDomainName(user);
+ else
+ return "";
+ }
+}
--- /dev/null
+package org.argeo.cms.swt.useradmin;
+
+import org.argeo.cms.ui.theme.CmsImages;
+import org.eclipse.swt.graphics.Image;
+
+/** Specific users icons. */
+public class UsersImages {
+ private final static String PREFIX = "icons/";
+
+ public final static Image ICON_USER = CmsImages.createImg(PREFIX + "person.png");
+ public final static Image ICON_GROUP = CmsImages.createImg(PREFIX + "group.png");
+ public final static Image ICON_ROLE = CmsImages.createImg(PREFIX + "role.gif");
+ public final static Image ICON_CHANGE_PASSWORD = CmsImages.createImg(PREFIX + "security.gif");
+}
--- /dev/null
+package org.argeo.cms.swt.useradmin;
+
+import org.eclipse.jface.viewers.TableViewer;
+import org.eclipse.jface.viewers.TableViewerColumn;
+import org.eclipse.jface.viewers.TreeViewer;
+import org.eclipse.jface.viewers.TreeViewerColumn;
+import org.eclipse.swt.widgets.Table;
+import org.eclipse.swt.widgets.TableColumn;
+import org.eclipse.swt.widgets.TreeColumn;
+
+/**
+ * Centralise useful methods to manage JFace Table, Tree and TreeColumn viewers.
+ */
+public class ViewerUtils {
+
+ /**
+ * Creates a basic column for the given table. For the time being, we do not
+ * support movable columns.
+ */
+ public static TableColumn createColumn(Table parent, String name, int style, int width) {
+ TableColumn result = new TableColumn(parent, style);
+ result.setText(name);
+ result.setWidth(width);
+ result.setResizable(true);
+ return result;
+ }
+
+ /**
+ * Creates a TableViewerColumn for the given viewer. For the time being, we do
+ * not support movable columns.
+ */
+ public static TableViewerColumn createTableViewerColumn(TableViewer parent, String name, int style, int width) {
+ TableViewerColumn tvc = new TableViewerColumn(parent, style);
+ TableColumn column = tvc.getColumn();
+ column.setText(name);
+ column.setWidth(width);
+ column.setResizable(true);
+ return tvc;
+ }
+
+ // public static TableViewerColumn createTableViewerColumn(TableViewer parent,
+ // Localized name, int style, int width) {
+ // return createTableViewerColumn(parent, name.lead(), style, width);
+ // }
+
+ /**
+ * Creates a TreeViewerColumn for the given viewer. For the time being, we do
+ * not support movable columns.
+ */
+ public static TreeViewerColumn createTreeViewerColumn(TreeViewer parent, String name, int style, int width) {
+ TreeViewerColumn tvc = new TreeViewerColumn(parent, style);
+ TreeColumn column = tvc.getColumn();
+ column.setText(name);
+ column.setWidth(width);
+ column.setResizable(true);
+ return tvc;
+ }
+}
--- /dev/null
+/** SWT/JFace users management components. */
+package org.argeo.cms.swt.useradmin;
\ No newline at end of file
--- /dev/null
+package org.argeo.cms.ui.eclipse.forms;
+/**
+ * AbstractFormPart implements IFormPart interface and can be used as a
+ * convenient base class for concrete form parts. If a method contains
+ * code that must be called, look for instructions to call 'super'
+ * when overriding.
+ *
+ * @see org.eclipse.ui.forms.widgets.Section
+ * @since 1.0
+ */
+public abstract class AbstractFormPart implements IFormPart {
+ private IManagedForm managedForm;
+ private boolean dirty = false;
+ private boolean stale = true;
+ /**
+ * @see org.eclipse.ui.forms.IFormPart#initialize(org.eclipse.ui.forms.IManagedForm)
+ */
+ public void initialize(IManagedForm form) {
+ this.managedForm = form;
+ }
+ /**
+ * Returns the form that manages this part.
+ *
+ * @return the managed form
+ */
+ public IManagedForm getManagedForm() {
+ return managedForm;
+ }
+ /**
+ * Disposes the part. Subclasses should override to release any system
+ * resources.
+ */
+ public void dispose() {
+ }
+ /**
+ * Commits the part. Subclasses should call 'super' when overriding.
+ *
+ * @param onSave
+ * <code>true</code> if the request to commit has arrived as a
+ * result of the 'save' action.
+ */
+ public void commit(boolean onSave) {
+ dirty = false;
+ }
+ /**
+ * Sets the overall form input. Subclases may elect to override the method
+ * and adjust according to the form input.
+ *
+ * @param input
+ * the form input object
+ * @return <code>false</code>
+ */
+ public boolean setFormInput(Object input) {
+ return false;
+ }
+ /**
+ * Instructs the part to grab keyboard focus.
+ */
+ public void setFocus() {
+ }
+ /**
+ * Refreshes the section after becoming stale (falling behind data in the
+ * model). Subclasses must call 'super' when overriding this method.
+ */
+ public void refresh() {
+ stale = false;
+ // since we have refreshed, any changes we had in the
+ // part are gone and we are not dirty
+ dirty = false;
+ }
+ /**
+ * Marks the part dirty. Subclasses should call this method as a result of
+ * user interaction with the widgets in the section.
+ */
+ public void markDirty() {
+ dirty = true;
+ managedForm.dirtyStateChanged();
+ }
+ /**
+ * Tests whether the part is dirty i.e. its widgets have state that is
+ * newer than the data in the model.
+ *
+ * @return <code>true</code> if the part is dirty, <code>false</code>
+ * otherwise.
+ */
+ public boolean isDirty() {
+ return dirty;
+ }
+ /**
+ * Tests whether the part is stale i.e. its widgets have state that is
+ * older than the data in the model.
+ *
+ * @return <code>true</code> if the part is stale, <code>false</code>
+ * otherwise.
+ */
+ public boolean isStale() {
+ return stale;
+ }
+ /**
+ * Marks the part stale. Subclasses should call this method as a result of
+ * model notification that indicates that the content of the section is no
+ * longer in sync with the model.
+ */
+ public void markStale() {
+ stale = true;
+ managedForm.staleStateChanged();
+ }
+}
--- /dev/null
+package org.argeo.cms.ui.eclipse.forms;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.eclipse.jface.resource.JFaceResources;
+import org.eclipse.jface.resource.LocalResourceManager;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.RGB;
+//import org.eclipse.swt.internal.graphics.Graphics;
+import org.eclipse.swt.widgets.Display;
+
+/**
+ * Manages colors that will be applied to forms and form widgets. The colors are
+ * chosen to make the widgets look correct in the editor area. If a different
+ * set of colors is needed, subclass this class and override 'initialize' and/or
+ * 'initializeColors'.
+ *
+ * @since 1.0
+ */
+public class FormColors {
+ /**
+ * Key for the form title foreground color.
+ *
+ * @deprecated use <code>IFormColors.TITLE</code>.
+ */
+ public static final String TITLE = IFormColors.TITLE;
+
+ /**
+ * Key for the tree/table border color.
+ *
+ * @deprecated use <code>IFormColors.BORDER</code>
+ */
+ public static final String BORDER = IFormColors.BORDER;
+
+ /**
+ * Key for the section separator color.
+ *
+ * @deprecated use <code>IFormColors.SEPARATOR</code>.
+ */
+ public static final String SEPARATOR = IFormColors.SEPARATOR;
+
+ /**
+ * Key for the section title bar background.
+ *
+ * @deprecated use <code>IFormColors.TB_BG
+ */
+ public static final String TB_BG = IFormColors.TB_BG;
+
+ /**
+ * Key for the section title bar foreground.
+ *
+ * @deprecated use <code>IFormColors.TB_FG</code>
+ */
+ public static final String TB_FG = IFormColors.TB_FG;
+
+ /**
+ * Key for the section title bar gradient.
+ *
+ * @deprecated use <code>IFormColors.TB_GBG</code>
+ */
+ public static final String TB_GBG = IFormColors.TB_GBG;
+
+ /**
+ * Key for the section title bar border.
+ *
+ * @deprecated use <code>IFormColors.TB_BORDER</code>.
+ */
+ public static final String TB_BORDER = IFormColors.TB_BORDER;
+
+ /**
+ * Key for the section toggle color. Since 3.1, this color is used for all
+ * section styles.
+ *
+ * @deprecated use <code>IFormColors.TB_TOGGLE</code>.
+ */
+ public static final String TB_TOGGLE = IFormColors.TB_TOGGLE;
+
+ /**
+ * Key for the section toggle hover color.
+ *
+ * @deprecated use <code>IFormColors.TB_TOGGLE_HOVER</code>.
+ */
+ public static final String TB_TOGGLE_HOVER = IFormColors.TB_TOGGLE_HOVER;
+
+ protected Map colorRegistry = new HashMap(10);
+
+ private LocalResourceManager resources;
+
+ protected Color background;
+
+ protected Color foreground;
+
+ private boolean shared;
+
+ protected Display display;
+
+ protected Color border;
+
+ /**
+ * Creates form colors using the provided display.
+ *
+ * @param display
+ * the display to use
+ */
+ public FormColors(Display display) {
+ this.display = display;
+ initialize();
+ }
+
+ /**
+ * Returns the display used to create colors.
+ *
+ * @return the display
+ */
+ public Display getDisplay() {
+ return display;
+ }
+
+ /**
+ * Initializes the colors. Subclasses can override this method to change the
+ * way colors are created. Alternatively, only the color table can be
+ * modified by overriding <code>initializeColorTable()</code>.
+ *
+ * @see #initializeColorTable
+ */
+ protected void initialize() {
+ background = display.getSystemColor(SWT.COLOR_LIST_BACKGROUND);
+ foreground = display.getSystemColor(SWT.COLOR_LIST_FOREGROUND);
+ initializeColorTable();
+ updateBorderColor();
+ }
+
+ /**
+ * Allocates colors for the following keys: BORDER, SEPARATOR and
+ * TITLE. Subclasses can override to allocate these colors differently.
+ */
+ protected void initializeColorTable() {
+ createTitleColor();
+ createColor(IFormColors.SEPARATOR, getColor(IFormColors.TITLE).getRGB());
+ RGB black = getSystemColor(SWT.COLOR_BLACK);
+ RGB borderRGB = getSystemColor(SWT.COLOR_TITLE_INACTIVE_BACKGROUND_GRADIENT);
+ createColor(IFormColors.BORDER, blend(borderRGB, black, 80));
+ }
+
+ /**
+ * Allocates colors for the section tool bar (all the keys that start with
+ * TB). Since these colors are only needed when TITLE_BAR style is used with
+ * the Section widget, they are not needed all the time and are allocated on
+ * demand. Consequently, this method will do nothing if the colors have been
+ * already initialized. Call this method prior to using colors with the TB
+ * keys to ensure they are available.
+ */
+ public void initializeSectionToolBarColors() {
+ if (colorRegistry.containsKey(IFormColors.TB_BG))
+ return;
+ createTitleBarGradientColors();
+ createTitleBarOutlineColors();
+ createTwistieColors();
+ }
+
+ /**
+ * Allocates additional colors for the form header, namely background
+ * gradients, bottom separator keylines and DND highlights. Since these
+ * colors are only needed for clients that want to use these particular
+ * style of header rendering, they are not needed all the time and are
+ * allocated on demand. Consequently, this method will do nothing if the
+ * colors have been already initialized. Call this method prior to using
+ * color keys with the H_ prefix to ensure they are available.
+ */
+ protected void initializeFormHeaderColors() {
+ if (colorRegistry.containsKey(IFormColors.H_BOTTOM_KEYLINE2))
+ return;
+ createFormHeaderColors();
+ }
+
+ /**
+ * Returns the RGB value of the system color represented by the code
+ * argument, as defined in <code>SWT</code> class.
+ *
+ * @param code
+ * the system color constant as defined in <code>SWT</code>
+ * class.
+ * @return the RGB value of the system color
+ */
+ public RGB getSystemColor(int code) {
+ return getDisplay().getSystemColor(code).getRGB();
+ }
+
+ /**
+ * Creates the color for the specified key using the provided RGB object.
+ * The color object will be returned and also put into the registry. When
+ * the class is disposed, the color will be disposed with it.
+ *
+ * @param key
+ * the unique color key
+ * @param rgb
+ * the RGB object
+ * @return the allocated color object
+ */
+ public Color createColor(String key, RGB rgb) {
+ // RAP [rh] changes due to missing Color constructor
+// Color c = getResourceManager().createColor(rgb);
+// Color prevC = (Color) colorRegistry.get(key);
+// if (prevC != null && !prevC.isDisposed())
+// getResourceManager().destroyColor(prevC.getRGB());
+// Color c = Graphics.getColor(rgb);
+ Color c = new Color(display, rgb);
+ colorRegistry.put(key, c);
+ return c;
+ }
+
+ /**
+ * Creates a color that can be used for areas of the form that is inactive.
+ * These areas can contain images, links, controls and other content but are
+ * considered auxilliary to the main content area.
+ *
+ * <p>
+ * The color should not be disposed because it is managed by this class.
+ *
+ * @return the inactive form color
+ */
+ public Color getInactiveBackground() {
+ String key = "__ncbg__"; //$NON-NLS-1$
+ Color color = getColor(key);
+ if (color == null) {
+ RGB sel = getSystemColor(SWT.COLOR_LIST_SELECTION);
+ // a blend of 95% white and 5% list selection system color
+ RGB ncbg = blend(sel, getSystemColor(SWT.COLOR_WHITE), 5);
+ color = createColor(key, ncbg);
+ }
+ return color;
+ }
+
+ /**
+ * Creates the color for the specified key using the provided RGB values.
+ * The color object will be returned and also put into the registry. If
+ * there is already another color object under the same key in the registry,
+ * the existing object will be disposed. When the class is disposed, the
+ * color will be disposed with it.
+ *
+ * @param key
+ * the unique color key
+ * @param r
+ * red value
+ * @param g
+ * green value
+ * @param b
+ * blue value
+ * @return the allocated color object
+ */
+ public Color createColor(String key, int r, int g, int b) {
+ return createColor(key, new RGB(r,g,b));
+ }
+
+ /**
+ * Computes the border color relative to the background. Allocated border
+ * color is designed to work well with white. Otherwise, stanard widget
+ * background color will be used.
+ */
+ protected void updateBorderColor() {
+ if (isWhiteBackground())
+ border = getColor(IFormColors.BORDER);
+ else {
+ border = display.getSystemColor(SWT.COLOR_WIDGET_BACKGROUND);
+ Color bg = getImpliedBackground();
+ if (border.getRed() == bg.getRed()
+ && border.getGreen() == bg.getGreen()
+ && border.getBlue() == bg.getBlue())
+ border = display.getSystemColor(SWT.COLOR_WIDGET_DARK_SHADOW);
+ }
+ }
+
+ /**
+ * Sets the background color. All the toolkits that use this class will
+ * share the same background.
+ *
+ * @param bg
+ * background color
+ */
+ public void setBackground(Color bg) {
+ this.background = bg;
+ updateBorderColor();
+ updateFormHeaderColors();
+ }
+
+ /**
+ * Sets the foreground color. All the toolkits that use this class will
+ * share the same foreground.
+ *
+ * @param fg
+ * foreground color
+ */
+ public void setForeground(Color fg) {
+ this.foreground = fg;
+ }
+
+ /**
+ * Returns the current background color.
+ *
+ * @return the background color
+ */
+ public Color getBackground() {
+ return background;
+ }
+
+ /**
+ * Returns the current foreground color.
+ *
+ * @return the foreground color
+ */
+ public Color getForeground() {
+ return foreground;
+ }
+
+ /**
+ * Returns the computed border color. Border color depends on the background
+ * and is recomputed whenever the background changes.
+ *
+ * @return the current border color
+ */
+ public Color getBorderColor() {
+ return border;
+ }
+
+ /**
+ * Tests if the background is white. White background has RGB value
+ * 255,255,255.
+ *
+ * @return <samp>true</samp> if background is white, <samp>false</samp>
+ * otherwise.
+ */
+ public boolean isWhiteBackground() {
+ Color bg = getImpliedBackground();
+ return bg.getRed() == 255 && bg.getGreen() == 255
+ && bg.getBlue() == 255;
+ }
+
+ /**
+ * Returns the color object for the provided key or <samp>null </samp> if
+ * not in the registry.
+ *
+ * @param key
+ * the color key
+ * @return color object if found, or <samp>null </samp> if not.
+ */
+ public Color getColor(String key) {
+ if (key.startsWith(IFormColors.TB_PREFIX))
+ initializeSectionToolBarColors();
+ else if (key.startsWith(IFormColors.H_PREFIX))
+ initializeFormHeaderColors();
+ return (Color) colorRegistry.get(key);
+ }
+
+ /**
+ * Disposes all the colors in the registry.
+ */
+ public void dispose() {
+ if (resources != null)
+ resources.dispose();
+ resources = null;
+ colorRegistry = null;
+ }
+
+ /**
+ * Marks the colors shared. This prevents toolkits that share this object
+ * from disposing it.
+ */
+ public void markShared() {
+ this.shared = true;
+ }
+
+ /**
+ * Tests if the colors are shared.
+ *
+ * @return <code>true</code> if shared, <code>false</code> otherwise.
+ */
+ public boolean isShared() {
+ return shared;
+ }
+
+ /**
+ * Blends c1 and c2 based in the provided ratio.
+ *
+ * @param c1
+ * first color
+ * @param c2
+ * second color
+ * @param ratio
+ * percentage of the first color in the blend (0-100)
+ * @return the RGB value of the blended color
+ */
+ public static RGB blend(RGB c1, RGB c2, int ratio) {
+ int r = blend(c1.red, c2.red, ratio);
+ int g = blend(c1.green, c2.green, ratio);
+ int b = blend(c1.blue, c2.blue, ratio);
+ return new RGB(r, g, b);
+ }
+
+ /**
+ * Tests the source RGB for range.
+ *
+ * @param rgb
+ * the tested RGB
+ * @param from
+ * range start (excluding the value itself)
+ * @param to
+ * range end (excluding the value itself)
+ * @return <code>true</code> if at least one of the primary colors in the
+ * source RGB are within the provided range, <code>false</code>
+ * otherwise.
+ */
+ public static boolean testAnyPrimaryColor(RGB rgb, int from, int to) {
+ if (testPrimaryColor(rgb.red, from, to))
+ return true;
+ if (testPrimaryColor(rgb.green, from, to))
+ return true;
+ if (testPrimaryColor(rgb.blue, from, to))
+ return true;
+ return false;
+ }
+
+ /**
+ * Tests the source RGB for range.
+ *
+ * @param rgb
+ * the tested RGB
+ * @param from
+ * range start (excluding the value itself)
+ * @param to
+ * tange end (excluding the value itself)
+ * @return <code>true</code> if at least two of the primary colors in the
+ * source RGB are within the provided range, <code>false</code>
+ * otherwise.
+ */
+ public static boolean testTwoPrimaryColors(RGB rgb, int from, int to) {
+ int total = 0;
+ if (testPrimaryColor(rgb.red, from, to))
+ total++;
+ if (testPrimaryColor(rgb.green, from, to))
+ total++;
+ if (testPrimaryColor(rgb.blue, from, to))
+ total++;
+ return total >= 2;
+ }
+
+ /**
+ * Blends two primary color components based on the provided ratio.
+ *
+ * @param v1
+ * first component
+ * @param v2
+ * second component
+ * @param ratio
+ * percentage of the first component in the blend
+ * @return
+ */
+ private static int blend(int v1, int v2, int ratio) {
+ int b = (ratio * v1 + (100 - ratio) * v2) / 100;
+ return Math.min(255, b);
+ }
+
+ private Color getImpliedBackground() {
+ if (getBackground() != null)
+ return getBackground();
+ return getDisplay().getSystemColor(SWT.COLOR_WIDGET_BACKGROUND);
+ }
+
+ private static boolean testPrimaryColor(int value, int from, int to) {
+ return value > from && value < to;
+ }
+
+ private void createTitleColor() {
+ /*
+ * RGB rgb = getSystemColor(SWT.COLOR_LIST_SELECTION); // test too light
+ * if (testTwoPrimaryColors(rgb, 120, 151)) rgb = blend(rgb, BLACK, 80);
+ * else if (testTwoPrimaryColors(rgb, 150, 256)) rgb = blend(rgb, BLACK,
+ * 50); createColor(TITLE, rgb);
+ */
+ RGB bg = getImpliedBackground().getRGB();
+ RGB listSelection = getSystemColor(SWT.COLOR_LIST_SELECTION);
+ RGB listForeground = getSystemColor(SWT.COLOR_LIST_FOREGROUND);
+ RGB rgb = listSelection;
+
+ // Group 1
+ // Rule: If at least 2 of the LIST_SELECTION RGB values are equal to or
+ // between 0 and 120, then use 100% LIST_SELECTION as it is (no
+ // additions)
+ // Examples: XP Default, Win Classic Standard, Win High Con White, Win
+ // Classic Marine
+ if (testTwoPrimaryColors(listSelection, -1, 121))
+ rgb = listSelection;
+ // Group 2
+ // When LIST_BACKGROUND = white (255, 255, 255) or not black, text
+ // colour = LIST_SELECTION @ 100% Opacity + 50% LIST_FOREGROUND over
+ // LIST_BACKGROUND
+ // Rule: If at least 2 of the LIST_SELECTION RGB values are equal to or
+ // between 121 and 255, then add 50% LIST_FOREGROUND to LIST_SELECTION
+ // foreground colour
+ // Examples: Win Vista, XP Silver, XP Olive , Win Classic Plum, OSX
+ // Aqua, OSX Graphite, Linux GTK
+ else if (testTwoPrimaryColors(listSelection, 120, 256)
+ || (bg.red == 0 && bg.green == 0 && bg.blue == 0))
+ rgb = blend(listSelection, listForeground, 50);
+ // Group 3
+ // When LIST_BACKGROUND = black (0, 0, 0), text colour = LIST_SELECTION
+ // @ 100% Opacity + 50% LIST_FOREGROUND over LIST_BACKGROUND
+ // Rule: If LIST_BACKGROUND = 0, 0, 0, then add 50% LIST_FOREGROUND to
+ // LIST_SELECTION foreground colour
+ // Examples: Win High Con Black, Win High Con #1, Win High Con #2
+ // (covered in the second part of the OR clause above)
+ createColor(IFormColors.TITLE, rgb);
+ }
+
+ private void createTwistieColors() {
+ RGB rgb = getColor(IFormColors.TITLE).getRGB();
+ RGB white = getSystemColor(SWT.COLOR_WHITE);
+ createColor(TB_TOGGLE, rgb);
+ rgb = blend(rgb, white, 60);
+ createColor(TB_TOGGLE_HOVER, rgb);
+ }
+
+ private void createTitleBarGradientColors() {
+ RGB tbBg = getSystemColor(SWT.COLOR_TITLE_BACKGROUND);
+ RGB bg = getImpliedBackground().getRGB();
+
+ // Group 1
+ // Rule: If at least 2 of the RGB values are equal to or between 180 and
+ // 255, then apply specified opacity for Group 1
+ // Examples: Vista, XP Silver, Wn High Con #2
+ // Gradient Bottom = TITLE_BACKGROUND @ 30% Opacity over LIST_BACKGROUND
+ // Gradient Top = TITLE BACKGROUND @ 0% Opacity over LIST_BACKGROUND
+ if (testTwoPrimaryColors(tbBg, 179, 256))
+ tbBg = blend(tbBg, bg, 30);
+
+ // Group 2
+ // Rule: If at least 2 of the RGB values are equal to or between 121 and
+ // 179, then apply specified opacity for Group 2
+ // Examples: XP Olive, OSX Graphite, Linux GTK, Wn High Con Black
+ // Gradient Bottom = TITLE_BACKGROUND @ 20% Opacity over LIST_BACKGROUND
+ // Gradient Top = TITLE BACKGROUND @ 0% Opacity over LIST_BACKGROUND
+ else if (testTwoPrimaryColors(tbBg, 120, 180))
+ tbBg = blend(tbBg, bg, 20);
+
+ // Group 3
+ // Rule: Everything else
+ // Examples: XP Default, Wn Classic Standard, Wn Marine, Wn Plum, OSX
+ // Aqua, Wn High Con White, Wn High Con #1
+ // Gradient Bottom = TITLE_BACKGROUND @ 10% Opacity over LIST_BACKGROUND
+ // Gradient Top = TITLE BACKGROUND @ 0% Opacity over LIST_BACKGROUND
+ else {
+ tbBg = blend(tbBg, bg, 10);
+ }
+
+ createColor(IFormColors.TB_BG, tbBg);
+
+ // for backward compatibility
+ createColor(TB_GBG, tbBg);
+ }
+
+ private void createTitleBarOutlineColors() {
+ // title bar outline - border color
+ RGB tbBorder = getSystemColor(SWT.COLOR_TITLE_BACKGROUND);
+ RGB bg = getImpliedBackground().getRGB();
+ // Group 1
+ // Rule: If at least 2 of the RGB values are equal to or between 180 and
+ // 255, then apply specified opacity for Group 1
+ // Examples: Vista, XP Silver, Wn High Con #2
+ // Keyline = TITLE_BACKGROUND @ 70% Opacity over LIST_BACKGROUND
+ if (testTwoPrimaryColors(tbBorder, 179, 256))
+ tbBorder = blend(tbBorder, bg, 70);
+
+ // Group 2
+ // Rule: If at least 2 of the RGB values are equal to or between 121 and
+ // 179, then apply specified opacity for Group 2
+ // Examples: XP Olive, OSX Graphite, Linux GTK, Wn High Con Black
+
+ // Keyline = TITLE_BACKGROUND @ 50% Opacity over LIST_BACKGROUND
+ else if (testTwoPrimaryColors(tbBorder, 120, 180))
+ tbBorder = blend(tbBorder, bg, 50);
+
+ // Group 3
+ // Rule: Everything else
+ // Examples: XP Default, Wn Classic Standard, Wn Marine, Wn Plum, OSX
+ // Aqua, Wn High Con White, Wn High Con #1
+
+ // Keyline = TITLE_BACKGROUND @ 30% Opacity over LIST_BACKGROUND
+ else {
+ tbBorder = blend(tbBorder, bg, 30);
+ }
+ createColor(FormColors.TB_BORDER, tbBorder);
+ }
+
+ private void updateFormHeaderColors() {
+ if (colorRegistry.containsKey(IFormColors.H_GRADIENT_END)) {
+ disposeIfFound(IFormColors.H_GRADIENT_END);
+ disposeIfFound(IFormColors.H_GRADIENT_START);
+ disposeIfFound(IFormColors.H_BOTTOM_KEYLINE1);
+ disposeIfFound(IFormColors.H_BOTTOM_KEYLINE2);
+ disposeIfFound(IFormColors.H_HOVER_LIGHT);
+ disposeIfFound(IFormColors.H_HOVER_FULL);
+ initializeFormHeaderColors();
+ }
+ }
+
+ private void disposeIfFound(String key) {
+ Color color = getColor(key);
+ if (color != null) {
+ colorRegistry.remove(key);
+ // RAP [rh] changes due to missing Color#dispose()
+// color.dispose();
+ }
+ }
+
+ private void createFormHeaderColors() {
+ createFormHeaderGradientColors();
+ createFormHeaderKeylineColors();
+ createFormHeaderDNDColors();
+ }
+
+ private void createFormHeaderGradientColors() {
+ RGB titleBg = getSystemColor(SWT.COLOR_TITLE_BACKGROUND);
+ Color bgColor = getImpliedBackground();
+ RGB bg = bgColor.getRGB();
+ RGB bottom, top;
+ // Group 1
+ // Rule: If at least 2 of the RGB values are equal to or between 180 and
+ // 255, then apply specified opacity for Group 1
+ // Examples: Vista, XP Silver, Wn High Con #2
+ // Gradient Bottom = TITLE_BACKGROUND @ 30% Opacity over LIST_BACKGROUND
+ // Gradient Top = TITLE BACKGROUND @ 0% Opacity over LIST_BACKGROUND
+ if (testTwoPrimaryColors(titleBg, 179, 256)) {
+ bottom = blend(titleBg, bg, 30);
+ top = bg;
+ }
+
+ // Group 2
+ // Rule: If at least 2 of the RGB values are equal to or between 121 and
+ // 179, then apply specified opacity for Group 2
+ // Examples: XP Olive, OSX Graphite, Linux GTK, Wn High Con Black
+ // Gradient Bottom = TITLE_BACKGROUND @ 20% Opacity over LIST_BACKGROUND
+ // Gradient Top = TITLE BACKGROUND @ 0% Opacity over LIST_BACKGROUND
+ else if (testTwoPrimaryColors(titleBg, 120, 180)) {
+ bottom = blend(titleBg, bg, 20);
+ top = bg;
+ }
+
+ // Group 3
+ // Rule: If at least 2 of the RGB values are equal to or between 0 and
+ // 120, then apply specified opacity for Group 3
+ // Examples: XP Default, Wn Classic Standard, Wn Marine, Wn Plum, OSX
+ // Aqua, Wn High Con White, Wn High Con #1
+ // Gradient Bottom = TITLE_BACKGROUND @ 10% Opacity over LIST_BACKGROUND
+ // Gradient Top = TITLE BACKGROUND @ 0% Opacity over LIST_BACKGROUND
+ else {
+ bottom = blend(titleBg, bg, 10);
+ top = bg;
+ }
+ createColor(IFormColors.H_GRADIENT_END, top);
+ createColor(IFormColors.H_GRADIENT_START, bottom);
+ }
+
+ private void createFormHeaderKeylineColors() {
+ RGB titleBg = getSystemColor(SWT.COLOR_TITLE_BACKGROUND);
+ Color bgColor = getImpliedBackground();
+ RGB bg = bgColor.getRGB();
+ RGB keyline2;
+ // H_BOTTOM_KEYLINE1
+ createColor(IFormColors.H_BOTTOM_KEYLINE1, new RGB(255, 255, 255));
+
+ // H_BOTTOM_KEYLINE2
+ // Group 1
+ // Rule: If at least 2 of the RGB values are equal to or between 180 and
+ // 255, then apply specified opacity for Group 1
+ // Examples: Vista, XP Silver, Wn High Con #2
+ // Keyline = TITLE_BACKGROUND @ 70% Opacity over LIST_BACKGROUND
+ if (testTwoPrimaryColors(titleBg, 179, 256))
+ keyline2 = blend(titleBg, bg, 70);
+
+ // Group 2
+ // Rule: If at least 2 of the RGB values are equal to or between 121 and
+ // 179, then apply specified opacity for Group 2
+ // Examples: XP Olive, OSX Graphite, Linux GTK, Wn High Con Black
+ // Keyline = TITLE_BACKGROUND @ 50% Opacity over LIST_BACKGROUND
+ else if (testTwoPrimaryColors(titleBg, 120, 180))
+ keyline2 = blend(titleBg, bg, 50);
+
+ // Group 3
+ // Rule: If at least 2 of the RGB values are equal to or between 0 and
+ // 120, then apply specified opacity for Group 3
+ // Examples: XP Default, Wn Classic Standard, Wn Marine, Wn Plum, OSX
+ // Aqua, Wn High Con White, Wn High Con #1
+
+ // Keyline = TITLE_BACKGROUND @ 30% Opacity over LIST_BACKGROUND
+ else
+ keyline2 = blend(titleBg, bg, 30);
+ // H_BOTTOM_KEYLINE2
+ createColor(IFormColors.H_BOTTOM_KEYLINE2, keyline2);
+ }
+
+ private void createFormHeaderDNDColors() {
+ RGB titleBg = getSystemColor(SWT.COLOR_TITLE_BACKGROUND_GRADIENT);
+ Color bgColor = getImpliedBackground();
+ RGB bg = bgColor.getRGB();
+ RGB light, full;
+ // ALL Themes
+ //
+ // Light Highlight
+ // When *near* the 'hot' area
+ // Rule: If near the title in the 'hot' area, show background highlight
+ // TITLE_BACKGROUND_GRADIENT @ 40%
+ light = blend(titleBg, bg, 40);
+ // Full Highlight
+ // When *on* the title area (regions 1 and 2)
+ // Rule: If near the title in the 'hot' area, show background highlight
+ // TITLE_BACKGROUND_GRADIENT @ 60%
+ full = blend(titleBg, bg, 60);
+ // H_DND_LIGHT
+ // H_DND_FULL
+ createColor(IFormColors.H_HOVER_LIGHT, light);
+ createColor(IFormColors.H_HOVER_FULL, full);
+ }
+
+ private LocalResourceManager getResourceManager() {
+ if (resources == null)
+ resources = new LocalResourceManager(JFaceResources.getResources());
+ return resources;
+ }
+}
--- /dev/null
+package org.argeo.cms.ui.eclipse.forms;
+
+import java.util.HashMap;
+
+import org.eclipse.jface.resource.DeviceResourceException;
+import org.eclipse.jface.resource.FontDescriptor;
+import org.eclipse.jface.resource.JFaceResources;
+import org.eclipse.jface.resource.LocalResourceManager;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Device;
+import org.eclipse.swt.graphics.Font;
+import org.eclipse.swt.graphics.FontData;
+//import org.eclipse.swt.internal.graphics.Graphics;
+import org.eclipse.swt.widgets.Display;
+
+public class FormFonts {
+ private static FormFonts instance;
+
+ public static FormFonts getInstance() {
+ if (instance == null)
+ instance = new FormFonts();
+ return instance;
+ }
+
+ private LocalResourceManager resources;
+ private HashMap descriptors;
+
+ private FormFonts() {
+ }
+
+ private class BoldFontDescriptor extends FontDescriptor {
+ private FontData[] fFontData;
+
+ BoldFontDescriptor(Font font) {
+ // RAP [if] Changes due to different way of creating fonts
+ // fFontData = font.getFontData();
+ // for (int i = 0; i < fFontData.length; i++) {
+ // fFontData[i].setStyle(fFontData[i].getStyle() | SWT.BOLD);
+ // }
+ FontData fontData = font.getFontData()[0];
+ // Font boldFont = Graphics.getFont( fontData.getName(),
+ // fontData.getHeight(),
+ // fontData.getStyle() | SWT.BOLD );
+ Font boldFont = new Font(Display.getCurrent(), fontData.getName(), fontData.getHeight(),
+ fontData.getStyle() | SWT.BOLD);
+ fFontData = boldFont.getFontData();
+ }
+
+ public boolean equals(Object obj) {
+ if (obj instanceof BoldFontDescriptor) {
+ BoldFontDescriptor desc = (BoldFontDescriptor) obj;
+ if (desc.fFontData.length != fFontData.length)
+ return false;
+ for (int i = 0; i < fFontData.length; i++)
+ if (!fFontData[i].equals(desc.fFontData[i]))
+ return false;
+ return true;
+ }
+ return false;
+ }
+
+ public int hashCode() {
+ int hash = 0;
+ for (int i = 0; i < fFontData.length; i++)
+ hash = hash * 7 + fFontData[i].hashCode();
+ return hash;
+ }
+
+ public Font createFont(Device device) throws DeviceResourceException {
+ // RAP [if] Changes due to different way of creating fonts
+ return new Font(device, fFontData[0]);
+ // return Graphics.getFont( fFontData[ 0 ] );
+ }
+
+ public void destroyFont(Font previouslyCreatedFont) {
+ // RAP [if] unnecessary
+ // previouslyCreatedFont.dispose();
+ }
+ }
+
+ public Font getBoldFont(Display display, Font font) {
+ checkHashMaps();
+ BoldFontDescriptor desc = new BoldFontDescriptor(font);
+ Font result = getResourceManager().createFont(desc);
+ descriptors.put(result, desc);
+ return result;
+ }
+
+ public boolean markFinished(Font boldFont) {
+ checkHashMaps();
+ BoldFontDescriptor desc = (BoldFontDescriptor) descriptors.get(boldFont);
+ if (desc != null) {
+ getResourceManager().destroyFont(desc);
+ if (getResourceManager().find(desc) == null) {
+ descriptors.remove(boldFont);
+ validateHashMaps();
+ }
+ return true;
+
+ }
+ // if the image was not found, dispose of it for the caller
+ // RAP [if] unnecessary
+ // boldFont.dispose();
+ return false;
+ }
+
+ private LocalResourceManager getResourceManager() {
+ if (resources == null)
+ resources = new LocalResourceManager(JFaceResources.getResources());
+ return resources;
+ }
+
+ private void checkHashMaps() {
+ if (descriptors == null)
+ descriptors = new HashMap();
+ }
+
+ private void validateHashMaps() {
+ if (descriptors.size() == 0)
+ descriptors = null;
+ }
+}
--- /dev/null
+package org.argeo.cms.ui.eclipse.forms;
+
+import org.eclipse.jface.resource.JFaceResources;
+import org.eclipse.jface.window.Window;
+import org.eclipse.swt.SWT;
+//import org.eclipse.swt.custom.CCombo;
+import org.eclipse.swt.custom.ScrolledComposite;
+import org.eclipse.swt.events.FocusAdapter;
+import org.eclipse.swt.events.FocusEvent;
+import org.eclipse.swt.events.KeyAdapter;
+import org.eclipse.swt.events.KeyEvent;
+import org.eclipse.swt.events.MouseAdapter;
+import org.eclipse.swt.events.MouseEvent;
+// RAP [rh] Paint events missing
+//import org.eclipse.swt.events.PaintEvent;
+//import org.eclipse.swt.events.PaintListener;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.Font;
+//RAP [rh] GC missing
+//import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Point;
+//import org.eclipse.swt.graphics.RGB;
+//import org.eclipse.swt.graphics.Rectangle;
+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.Event;
+import org.eclipse.swt.widgets.Label;
+//import org.eclipse.swt.widgets.Listener;
+import org.eclipse.swt.widgets.Table;
+import org.eclipse.swt.widgets.Text;
+import org.eclipse.swt.widgets.Tree;
+import org.eclipse.swt.widgets.Widget;
+//import org.eclipse.ui.forms.FormColors;
+//import org.eclipse.ui.forms.HyperlinkGroup;
+//import org.eclipse.ui.forms.IFormColors;
+//import org.eclipse.ui.internal.forms.widgets.FormFonts;
+//import org.eclipse.ui.internal.forms.widgets.FormUtil;
+
+/**
+ * The toolkit is responsible for creating SWT controls adapted to work in
+ * Eclipse forms. In addition to changing their presentation properties (fonts,
+ * colors etc.), various listeners are attached to make them behave correctly in
+ * the form context.
+ * <p>
+ * In addition to being the control factory, the toolkit is also responsible for
+ * painting flat borders for select controls, managing hyperlink groups and
+ * control colors.
+ * <p>
+ * The toolkit creates some of the most common controls used to populate Eclipse
+ * forms. Controls that must be created using their constructors,
+ * <code>adapt()</code> method is available to change its properties in the
+ * same way as with the supported toolkit controls.
+ * <p>
+ * Typically, one toolkit object is created per workbench part (for example, an
+ * editor or a form wizard). The toolkit is disposed when the part is disposed.
+ * To conserve resources, it is possible to create one color object for the
+ * entire plug-in and share it between several toolkits. The plug-in is
+ * responsible for disposing the colors (disposing the toolkit that uses shared
+ * color object will not dispose the colors).
+ * <p>
+ * FormToolkit is normally instantiated, but can also be subclassed if some of
+ * the methods needs to be modified. In those cases, <code>super</code> must
+ * be called to preserve normal behaviour.
+ *
+ * @since 1.0
+ */
+public class FormToolkit {
+ public static final String KEY_DRAW_BORDER = "FormWidgetFactory.drawBorder"; //$NON-NLS-1$
+
+ public static final String TREE_BORDER = "treeBorder"; //$NON-NLS-1$
+
+ public static final String TEXT_BORDER = "textBorder"; //$NON-NLS-1$
+
+ private int borderStyle = SWT.NULL;
+
+ private FormColors colors;
+
+ private int orientation = Window.getDefaultOrientation();
+
+ // private KeyListener deleteListener;
+ // RAP [rh] Paint events missing
+// private BorderPainter borderPainter;
+
+ private BoldFontHolder boldFontHolder;
+
+// private HyperlinkGroup hyperlinkGroup;
+
+ private boolean isDisposed = false;
+
+ /* default */
+ VisibilityHandler visibilityHandler;
+
+ /* default */
+ KeyboardHandler keyboardHandler;
+
+ // RAP [rh] Paint events missing
+// private class BorderPainter implements PaintListener {
+// public void paintControl(PaintEvent event) {
+// Composite composite = (Composite) event.widget;
+// Control[] children = composite.getChildren();
+// for (int i = 0; i < children.length; i++) {
+// Control c = children[i];
+// boolean inactiveBorder = false;
+// boolean textBorder = false;
+// if (!c.isVisible())
+// continue;
+// /*
+// * if (c.getEnabled() == false && !(c instanceof CCombo))
+// * continue;
+// */
+// if (c instanceof Hyperlink)
+// continue;
+// Object flag = c.getData(KEY_DRAW_BORDER);
+// if (flag != null) {
+// if (flag.equals(Boolean.FALSE))
+// continue;
+// if (flag.equals(TREE_BORDER))
+// inactiveBorder = true;
+// else if (flag.equals(TEXT_BORDER))
+// textBorder = true;
+// }
+// if (getBorderStyle() == SWT.BORDER) {
+// if (!inactiveBorder && !textBorder) {
+// continue;
+// }
+// if (c instanceof Text || c instanceof Table
+// || c instanceof Tree)
+// continue;
+// }
+// if (!inactiveBorder
+// && (c instanceof Text || c instanceof CCombo || textBorder)) {
+// Rectangle b = c.getBounds();
+// GC gc = event.gc;
+// gc.setForeground(c.getBackground());
+// gc.drawRectangle(b.x - 1, b.y - 1, b.width + 1,
+// b.height + 1);
+// // gc.setForeground(getBorderStyle() == SWT.BORDER ? colors
+// // .getBorderColor() : colors.getForeground());
+// gc.setForeground(colors.getBorderColor());
+// if (c instanceof CCombo)
+// gc.drawRectangle(b.x - 1, b.y - 1, b.width + 1,
+// b.height + 1);
+// else
+// gc.drawRectangle(b.x - 1, b.y - 2, b.width + 1,
+// b.height + 3);
+// } else if (inactiveBorder || c instanceof Table
+// || c instanceof Tree) {
+// Rectangle b = c.getBounds();
+// GC gc = event.gc;
+// gc.setForeground(colors.getBorderColor());
+// gc.drawRectangle(b.x - 1, b.y - 1, b.width + 1,
+// b.height + 1);
+// }
+// }
+// }
+// }
+
+ private static class VisibilityHandler extends FocusAdapter {
+ public void focusGained(FocusEvent e) {
+ Widget w = e.widget;
+ if (w instanceof Control) {
+ FormUtil.ensureVisible((Control) w);
+ }
+ }
+ }
+
+ private static class KeyboardHandler extends KeyAdapter {
+ public void keyPressed(KeyEvent e) {
+ Widget w = e.widget;
+ if (w instanceof Control) {
+ if (e.doit)
+ FormUtil.processKey(e.keyCode, (Control) w);
+ }
+ }
+ }
+
+ private class BoldFontHolder {
+ private Font normalFont;
+
+ private Font boldFont;
+
+ public BoldFontHolder() {
+ }
+
+ public Font getBoldFont(Font font) {
+ createBoldFont(font);
+ return boldFont;
+ }
+
+ private void createBoldFont(Font font) {
+ if (normalFont == null || !normalFont.equals(font)) {
+ normalFont = font;
+ dispose();
+ }
+ if (boldFont == null) {
+ boldFont = FormFonts.getInstance().getBoldFont(colors.getDisplay(),
+ normalFont);
+ }
+ }
+
+ public void dispose() {
+ if (boldFont != null) {
+ FormFonts.getInstance().markFinished(boldFont);
+ boldFont = null;
+ }
+ }
+ }
+
+ /**
+ * Creates a toolkit that is self-sufficient (will manage its own colors).
+ * <p>
+ * Clients that call this method must call {@link #dispose()} when they
+ * are finished using the toolkit.
+ *
+ */
+ public FormToolkit(Display display) {
+ this(new FormColors(display));
+ }
+
+ /**
+ * Creates a toolkit that will use the provided (shared) colors. The toolkit
+ * will dispose the colors if and only if they are <b>not</b> marked as
+ * shared via the <code>markShared()</code> method.
+ * <p>
+ * Clients that call this method must call {@link #dispose()} when they
+ * are finished using the toolkit.
+ *
+ * @param colors
+ * the shared colors
+ */
+ public FormToolkit(FormColors colors) {
+ this.colors = colors;
+ initialize();
+ }
+
+ /**
+ * Creates a button as a part of the form.
+ *
+ * @param parent
+ * the button parent
+ * @param text
+ * an optional text for the button (can be <code>null</code>)
+ * @param style
+ * the button style (for example, <code>SWT.PUSH</code>)
+ * @return the button widget
+ */
+ public Button createButton(Composite parent, String text, int style) {
+ Button button = new Button(parent, style | SWT.FLAT | orientation);
+ if (text != null)
+ button.setText(text);
+ adapt(button, true, true);
+ return button;
+ }
+
+ /**
+ * Creates the composite as a part of the form.
+ *
+ * @param parent
+ * the composite parent
+ * @return the composite widget
+ */
+ public Composite createComposite(Composite parent) {
+ return createComposite(parent, SWT.NULL);
+ }
+
+ /**
+ * Creates the composite as part of the form using the provided style.
+ *
+ * @param parent
+ * the composite parent
+ * @param style
+ * the composite style
+ * @return the composite widget
+ */
+ public Composite createComposite(Composite parent, int style) {
+// Composite composite = new LayoutComposite(parent, style | orientation);
+ Composite composite = new Composite(parent, style | orientation);
+ adapt(composite);
+ return composite;
+ }
+
+ /**
+ * Creats the composite that can server as a separator between various parts
+ * of a form. Separator height should be controlled by setting the height
+ * hint on the layout data for the composite.
+ *
+ * @param parent
+ * the separator parent
+ * @return the separator widget
+ */
+// RAP [rh] createCompositeSeparator: currently no useful implementation possible, delete?
+ public Composite createCompositeSeparator(Composite parent) {
+ final Composite composite = new Composite(parent, orientation);
+// RAP [rh] GC and paint events missing
+// composite.addListener(SWT.Paint, new Listener() {
+// public void handleEvent(Event e) {
+// if (composite.isDisposed())
+// return;
+// Rectangle bounds = composite.getBounds();
+// GC gc = e.gc;
+// gc.setForeground(colors.getColor(IFormColors.SEPARATOR));
+// if (colors.getBackground() != null)
+// gc.setBackground(colors.getBackground());
+// gc.fillGradientRectangle(0, 0, bounds.width, bounds.height,
+// false);
+// }
+// });
+// if (parent instanceof Section)
+// ((Section) parent).setSeparatorControl(composite);
+ return composite;
+ }
+
+ /**
+ * Creates a label as a part of the form.
+ *
+ * @param parent
+ * the label parent
+ * @param text
+ * the label text
+ * @return the label widget
+ */
+ public Label createLabel(Composite parent, String text) {
+ return createLabel(parent, text, SWT.NONE);
+ }
+
+ /**
+ * Creates a label as a part of the form.
+ *
+ * @param parent
+ * the label parent
+ * @param text
+ * the label text
+ * @param style
+ * the label style
+ * @return the label widget
+ */
+ public Label createLabel(Composite parent, String text, int style) {
+ Label label = new Label(parent, style | orientation);
+ if (text != null)
+ label.setText(text);
+ adapt(label, false, false);
+ return label;
+ }
+
+ /**
+ * Creates a hyperlink as a part of the form. The hyperlink will be added to
+ * the hyperlink group that belongs to this toolkit.
+ *
+ * @param parent
+ * the hyperlink parent
+ * @param text
+ * the text of the hyperlink
+ * @param style
+ * the hyperlink style
+ * @return the hyperlink widget
+ */
+// public Hyperlink createHyperlink(Composite parent, String text, int style) {
+// Hyperlink hyperlink = new Hyperlink(parent, style | orientation);
+// if (text != null)
+// hyperlink.setText(text);
+// hyperlink.addFocusListener(visibilityHandler);
+// hyperlink.addKeyListener(keyboardHandler);
+// hyperlinkGroup.add(hyperlink);
+// return hyperlink;
+// }
+
+ /**
+ * Creates an image hyperlink as a part of the form. The hyperlink will be
+ * added to the hyperlink group that belongs to this toolkit.
+ *
+ * @param parent
+ * the hyperlink parent
+ * @param style
+ * the hyperlink style
+ * @return the image hyperlink widget
+ */
+// public ImageHyperlink createImageHyperlink(Composite parent, int style) {
+// ImageHyperlink hyperlink = new ImageHyperlink(parent, style
+// | orientation);
+// hyperlink.addFocusListener(visibilityHandler);
+// hyperlink.addKeyListener(keyboardHandler);
+// hyperlinkGroup.add(hyperlink);
+// return hyperlink;
+// }
+
+ /**
+ * Creates a rich text as a part of the form.
+ *
+ * @param parent
+ * the rich text parent
+ * @param trackFocus
+ * if <code>true</code>, the toolkit will monitor focus
+ * transfers to ensure that the hyperlink in focus is visible in
+ * the form.
+ * @return the rich text widget
+ * @since 1.2
+ */
+// public FormText createFormText(Composite parent, boolean trackFocus) {
+// FormText engine = new FormText(parent, SWT.WRAP | orientation);
+// engine.marginWidth = 1;
+// engine.marginHeight = 0;
+// engine.setHyperlinkSettings(getHyperlinkGroup());
+// adapt(engine, trackFocus, true);
+// engine.setMenu(parent.getMenu());
+// return engine;
+// }
+
+ /**
+ * Adapts a control to be used in a form that is associated with this
+ * toolkit. This involves adjusting colors and optionally adding handlers to
+ * ensure focus tracking and keyboard management.
+ *
+ * @param control
+ * a control to adapt
+ * @param trackFocus
+ * if <code>true</code>, form will be scrolled horizontally
+ * and/or vertically if needed to ensure that the control is
+ * visible when it gains focus. Set it to <code>false</code> if
+ * the control is not capable of gaining focus.
+ * @param trackKeyboard
+ * if <code>true</code>, the control that is capable of
+ * gaining focus will be tracked for certain keys that are
+ * important to the underlying form (for example, PageUp,
+ * PageDown, ScrollUp, ScrollDown etc.). Set it to
+ * <code>false</code> if the control is not capable of gaining
+ * focus or these particular key event are already used by the
+ * control.
+ */
+ public void adapt(Control control, boolean trackFocus, boolean trackKeyboard) {
+ control.setBackground(colors.getBackground());
+ control.setForeground(colors.getForeground());
+// if (control instanceof ExpandableComposite) {
+// ExpandableComposite ec = (ExpandableComposite) control;
+// if (ec.toggle != null) {
+// if (trackFocus)
+// ec.toggle.addFocusListener(visibilityHandler);
+// if (trackKeyboard)
+// ec.toggle.addKeyListener(keyboardHandler);
+// }
+// if (ec.textLabel != null) {
+// if (trackFocus)
+// ec.textLabel.addFocusListener(visibilityHandler);
+// if (trackKeyboard)
+// ec.textLabel.addKeyListener(keyboardHandler);
+// }
+// return;
+// }
+ if (trackFocus)
+ control.addFocusListener(visibilityHandler);
+ if (trackKeyboard)
+ control.addKeyListener(keyboardHandler);
+ }
+
+ /**
+ * Adapts a composite to be used in a form associated with this toolkit.
+ *
+ * @param composite
+ * the composite to adapt
+ */
+ public void adapt(Composite composite) {
+ composite.setBackground(colors.getBackground());
+ composite.addMouseListener(new MouseAdapter() {
+ public void mouseDown(MouseEvent e) {
+ ((Control) e.widget).setFocus();
+ }
+ });
+ if (composite.getParent() != null)
+ composite.setMenu(composite.getParent().getMenu());
+ }
+
+ /**
+ * A helper method that ensures the provided control is visible when
+ * ScrolledComposite is somewhere in the parent chain. If scroll bars are
+ * visible and the control is clipped, the client of the scrolled composite
+ * will be scrolled to reveal the control.
+ *
+ * @param c
+ * the control to reveal
+ */
+ public static void ensureVisible(Control c) {
+ FormUtil.ensureVisible(c);
+ }
+
+ /**
+ * Creates a section as a part of the form.
+ *
+ * @param parent
+ * the section parent
+ * @param sectionStyle
+ * the section style
+ * @return the section widget
+ */
+// public Section createSection(Composite parent, int sectionStyle) {
+// Section section = new Section(parent, orientation, sectionStyle);
+// section.setMenu(parent.getMenu());
+// adapt(section, true, true);
+// if (section.toggle != null) {
+// section.toggle.setHoverDecorationColor(colors
+// .getColor(IFormColors.TB_TOGGLE_HOVER));
+// section.toggle.setDecorationColor(colors
+// .getColor(IFormColors.TB_TOGGLE));
+// }
+// section.setFont(boldFontHolder.getBoldFont(parent.getFont()));
+// if ((sectionStyle & Section.TITLE_BAR) != 0
+// || (sectionStyle & Section.SHORT_TITLE_BAR) != 0) {
+// colors.initializeSectionToolBarColors();
+// section.setTitleBarBackground(colors.getColor(IFormColors.TB_BG));
+// section.setTitleBarBorderColor(colors
+// .getColor(IFormColors.TB_BORDER));
+// }
+// // call setTitleBarForeground regardless as it also sets the label color
+// section.setTitleBarForeground(colors
+// .getColor(IFormColors.TB_TOGGLE));
+// return section;
+// }
+
+ /**
+ * Creates an expandable composite as a part of the form.
+ *
+ * @param parent
+ * the expandable composite parent
+ * @param expansionStyle
+ * the expandable composite style
+ * @return the expandable composite widget
+ */
+// public ExpandableComposite createExpandableComposite(Composite parent,
+// int expansionStyle) {
+// ExpandableComposite ec = new ExpandableComposite(parent, orientation,
+// expansionStyle);
+// ec.setMenu(parent.getMenu());
+// adapt(ec, true, true);
+// ec.setFont(boldFontHolder.getBoldFont(ec.getFont()));
+// return ec;
+// }
+
+ /**
+ * Creates a separator label as a part of the form.
+ *
+ * @param parent
+ * the separator parent
+ * @param style
+ * the separator style
+ * @return the separator label
+ */
+ public Label createSeparator(Composite parent, int style) {
+ Label label = new Label(parent, SWT.SEPARATOR | style | orientation);
+ label.setBackground(colors.getBackground());
+ label.setForeground(colors.getBorderColor());
+ return label;
+ }
+
+ /**
+ * Creates a table as a part of the form.
+ *
+ * @param parent
+ * the table parent
+ * @param style
+ * the table style
+ * @return the table widget
+ */
+ public Table createTable(Composite parent, int style) {
+ Table table = new Table(parent, style | borderStyle | orientation);
+ adapt(table, false, false);
+ // hookDeleteListener(table);
+ return table;
+ }
+
+ /**
+ * Creates a text as a part of the form.
+ *
+ * @param parent
+ * the text parent
+ * @param value
+ * the text initial value
+ * @return the text widget
+ */
+ public Text createText(Composite parent, String value) {
+ return createText(parent, value, SWT.SINGLE);
+ }
+
+ /**
+ * Creates a text as a part of the form.
+ *
+ * @param parent
+ * the text parent
+ * @param value
+ * the text initial value
+ * @param style
+ * the text style
+ * @return the text widget
+ */
+ public Text createText(Composite parent, String value, int style) {
+ Text text = new Text(parent, borderStyle | style | orientation);
+ if (value != null)
+ text.setText(value);
+ text.setForeground(colors.getForeground());
+ text.setBackground(colors.getBackground());
+ text.addFocusListener(visibilityHandler);
+ return text;
+ }
+
+ /**
+ * Creates a tree widget as a part of the form.
+ *
+ * @param parent
+ * the tree parent
+ * @param style
+ * the tree style
+ * @return the tree widget
+ */
+ public Tree createTree(Composite parent, int style) {
+ Tree tree = new Tree(parent, borderStyle | style | orientation);
+ adapt(tree, false, false);
+ // hookDeleteListener(tree);
+ return tree;
+ }
+
+ /**
+ * Creates a scrolled form widget in the provided parent. If you do not
+ * require scrolling because there is already a scrolled composite up the
+ * parent chain, use 'createForm' instead.
+ *
+ * @param parent
+ * the scrolled form parent
+ * @return the form that can scroll itself
+ * @see #createForm
+ */
+ public ScrolledComposite createScrolledForm(Composite parent) {
+ ScrolledComposite form = new ScrolledComposite(parent, SWT.V_SCROLL
+ | SWT.H_SCROLL | orientation);
+ form.setExpandHorizontal(true);
+ form.setExpandVertical(true);
+ form.setBackground(colors.getBackground());
+ form.setForeground(colors.getColor(IFormColors.TITLE));
+ form.setFont(JFaceResources.getHeaderFont());
+ return form;
+ }
+
+ /**
+ * Creates a form widget in the provided parent. Note that this widget does
+ * not scroll its content, so make sure there is a scrolled composite up the
+ * parent chain. If you require scrolling, use 'createScrolledForm' instead.
+ *
+ * @param parent
+ * the form parent
+ * @return the form that does not scroll
+ * @see #createScrolledForm
+ */
+// public Form createForm(Composite parent) {
+// Form formContent = new Form(parent, orientation);
+// formContent.setBackground(colors.getBackground());
+// formContent.setForeground(colors.getColor(IFormColors.TITLE));
+// formContent.setFont(JFaceResources.getHeaderFont());
+// return formContent;
+// }
+
+ /**
+ * Takes advantage of the gradients and other capabilities to decorate the
+ * form heading using colors computed based on the current skin and
+ * operating system.
+ *
+ * @param form
+ * the form to decorate
+ */
+
+// public void decorateFormHeading(Form form) {
+// Color top = colors.getColor(IFormColors.H_GRADIENT_END);
+// Color bot = colors.getColor(IFormColors.H_GRADIENT_START);
+// form.setTextBackground(new Color[] { top, bot }, new int[] { 100 },
+// true);
+// form.setHeadColor(IFormColors.H_BOTTOM_KEYLINE1, colors
+// .getColor(IFormColors.H_BOTTOM_KEYLINE1));
+// form.setHeadColor(IFormColors.H_BOTTOM_KEYLINE2, colors
+// .getColor(IFormColors.H_BOTTOM_KEYLINE2));
+// form.setHeadColor(IFormColors.H_HOVER_LIGHT, colors
+// .getColor(IFormColors.H_HOVER_LIGHT));
+// form.setHeadColor(IFormColors.H_HOVER_FULL, colors
+// .getColor(IFormColors.H_HOVER_FULL));
+// form.setHeadColor(IFormColors.TB_TOGGLE, colors
+// .getColor(IFormColors.TB_TOGGLE));
+// form.setHeadColor(IFormColors.TB_TOGGLE_HOVER, colors
+// .getColor(IFormColors.TB_TOGGLE_HOVER));
+// form.setSeparatorVisible(true);
+// }
+
+ /**
+ * Creates a scrolled page book widget as a part of the form.
+ *
+ * @param parent
+ * the page book parent
+ * @param style
+ * the text style
+ * @return the scrolled page book widget
+ */
+// public ScrolledPageBook createPageBook(Composite parent, int style) {
+// ScrolledPageBook book = new ScrolledPageBook(parent, style
+// | orientation);
+// adapt(book, true, true);
+// book.setMenu(parent.getMenu());
+// return book;
+// }
+
+ /**
+ * Disposes the toolkit.
+ */
+ public void dispose() {
+ if (isDisposed) {
+ return;
+ }
+ isDisposed = true;
+ if (colors.isShared() == false) {
+ colors.dispose();
+ colors = null;
+ }
+ boldFontHolder.dispose();
+ }
+
+ /**
+ * Returns the hyperlink group that manages hyperlinks for this toolkit.
+ *
+ * @return the hyperlink group
+ */
+// public HyperlinkGroup getHyperlinkGroup() {
+// return hyperlinkGroup;
+// }
+
+ /**
+ * Sets the background color for the entire toolkit. The method delegates
+ * the call to the FormColors object and also updates the hyperlink group so
+ * that hyperlinks and other objects are in sync.
+ *
+ * @param bg
+ * the new background color
+ */
+ public void setBackground(Color bg) {
+// hyperlinkGroup.setBackground(bg);
+ colors.setBackground(bg);
+ }
+
+ /**
+ * Refreshes the hyperlink colors by loading from JFace settings.
+ */
+// public void refreshHyperlinkColors() {
+// hyperlinkGroup.initializeDefaultForegrounds(colors.getDisplay());
+// }
+
+// RAP [rh] paintBordersFor not useful as no GC to actually paint borders
+// /**
+// * Paints flat borders for widgets created by this toolkit within the
+// * provided parent. Borders will not be painted if the global border style
+// * is SWT.BORDER (i.e. if native borders are used). Call this method during
+// * creation of a form composite to get the borders of its children painted.
+// * Care should be taken when selection layout margins. At least one pixel
+// * pargin width and height must be chosen to allow the toolkit to paint the
+// * border on the parent around the widgets.
+// * <p>
+// * Borders are painted for some controls that are selected by the toolkit by
+// * default. If a control needs a border but is not on its list, it is
+// * possible to force border in the following way:
+// *
+// * <pre>
+// *
+// *
+// *
+// * widget.setData(FormToolkit.KEY_DRAW_BORDER, FormToolkit.TREE_BORDER);
+// *
+// * or
+// *
+// * widget.setData(FormToolkit.KEY_DRAW_BORDER, FormToolkit.TEXT_BORDER);
+// *
+// *
+// *
+// * </pre>
+// *
+// * @param parent
+// * the parent that owns the children for which the border needs
+// * to be painted.
+// */
+// public void paintBordersFor(Composite parent) {
+// // if (borderStyle == SWT.BORDER)
+// // return;
+// if (borderPainter == null)
+// borderPainter = new BorderPainter();
+// parent.addPaintListener(borderPainter);
+// }
+
+ /**
+ * Returns the colors used by this toolkit.
+ *
+ * @return the color object
+ */
+ public FormColors getColors() {
+ return colors;
+ }
+
+ /**
+ * Returns the border style used for various widgets created by this
+ * toolkit. The intent of the toolkit is to create controls with styles that
+ * yield a 'flat' appearance. On systems where the native borders are
+ * already flat, we set the style to SWT.BORDER and don't paint the borders
+ * ourselves. Otherwise, the style is set to SWT.NULL, and borders are
+ * painted by the toolkit.
+ *
+ * @return the global border style
+ */
+ public int getBorderStyle() {
+ return borderStyle;
+ }
+
+ /**
+ * Returns the margin required around the children whose border is being
+ * painted by the toolkit using {@link #paintBordersFor(Composite)}. Since
+ * the border is painted around the controls on the parent, a number of
+ * pixels needs to be reserved for this border. For windowing systems where
+ * the native border is used, this margin is 0.
+ *
+ * @return the margin in the parent when children have their border painted
+ */
+ public int getBorderMargin() {
+ return getBorderStyle() == SWT.BORDER ? 0 : 2;
+ }
+
+ /**
+ * Sets the border style to be used when creating widgets. The toolkit
+ * chooses the correct style based on the platform but this value can be
+ * changed using this method.
+ *
+ * @param style
+ * <code>SWT.BORDER</code> or <code>SWT.NULL</code>
+ * @see #getBorderStyle
+ */
+ public void setBorderStyle(int style) {
+ this.borderStyle = style;
+ }
+
+ /**
+ * A utility method that ensures that the control is visible in the scrolled
+ * composite. The prerequisite for this method is that the control has a
+ * class that extends ScrolledComposite somewhere in the parent chain. If
+ * the control is partially or fully clipped, the composite is scrolled to
+ * set by setting the origin to the control origin.
+ *
+ * @param c
+ * the control to make visible
+ * @param verticalOnly
+ * if <code>true</code>, the scrolled composite will be
+ * scrolled only vertically if needed. Otherwise, the scrolled
+ * composite origin will be set to the control origin.
+ */
+ public static void setControlVisible(Control c, boolean verticalOnly) {
+ ScrolledComposite scomp = FormUtil.getScrolledComposite(c);
+ if (scomp == null)
+ return;
+ Point location = FormUtil.getControlLocation(scomp, c);
+ scomp.setOrigin(location);
+ }
+
+ private void initialize() {
+ initializeBorderStyle();
+// hyperlinkGroup = new HyperlinkGroup(colors.getDisplay());
+// hyperlinkGroup.setBackground(colors.getBackground());
+ visibilityHandler = new VisibilityHandler();
+ keyboardHandler = new KeyboardHandler();
+ boldFontHolder = new BoldFontHolder();
+ }
+
+// RAP [rh] revise detection of border style: can't ask OS here
+ private void initializeBorderStyle() {
+// String osname = System.getProperty("os.name"); //$NON-NLS-1$
+// String osversion = System.getProperty("os.version"); //$NON-NLS-1$
+// if (osname.startsWith("Windows") && "5.1".compareTo(osversion) <= 0) { //$NON-NLS-1$ //$NON-NLS-2$
+// // Skinned widgets used on newer Windows (e.g. XP (5.1), Vista
+// // (6.0))
+// // Check for Windows Classic. If not used, set the style to BORDER
+// RGB rgb = colors.getSystemColor(SWT.COLOR_WIDGET_BACKGROUND);
+// if (rgb.red != 212 || rgb.green != 208 || rgb.blue != 200)
+// borderStyle = SWT.BORDER;
+// } else if (osname.startsWith("Mac")) //$NON-NLS-1$
+// borderStyle = SWT.BORDER;
+
+ borderStyle = SWT.BORDER;
+ }
+
+ /**
+ * Returns the orientation that all the widgets created by this toolkit will
+ * inherit, if set. Can be <code>SWT.NULL</code>,
+ * <code>SWT.LEFT_TO_RIGHT</code> and <code>SWT.RIGHT_TO_LEFT</code>.
+ *
+ * @return orientation style for this toolkit, or <code>SWT.NULL</code> if
+ * not set. The default orientation is inherited from the Window
+ * default orientation.
+ * @see org.eclipse.jface.window.Window#getDefaultOrientation()
+ */
+
+ public int getOrientation() {
+ return orientation;
+ }
+
+ /**
+ * Sets the orientation that all the widgets created by this toolkit will
+ * inherit. Can be <code>SWT.NULL</code>, <code>SWT.LEFT_TO_RIGHT</code>
+ * and <code>SWT.RIGHT_TO_LEFT</code>.
+ *
+ * @param orientation
+ * style for this toolkit.
+ */
+
+ public void setOrientation(int orientation) {
+ this.orientation = orientation;
+ }
+}
--- /dev/null
+package org.argeo.cms.ui.eclipse.forms;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.ScrolledComposite;
+import org.eclipse.swt.events.MouseEvent;
+//import org.eclipse.swt.graphics.Device;
+import org.eclipse.swt.graphics.FontMetrics;
+import org.eclipse.swt.graphics.GC;
+//import org.eclipse.swt.graphics.Image;
+//import org.eclipse.swt.graphics.ImageData;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.widgets.Combo;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Layout;
+//import org.eclipse.swt.widgets.ScrollBar;
+import org.eclipse.swt.widgets.Text;
+//import org.eclipse.ui.forms.widgets.ColumnLayout;
+//import org.eclipse.ui.forms.widgets.Form;
+//import org.eclipse.ui.forms.widgets.FormText;
+//import org.eclipse.ui.forms.widgets.FormToolkit;
+//import org.eclipse.ui.forms.widgets.ILayoutExtension;
+//
+//import com.ibm.icu.text.BreakIterator;
+
+public class FormUtil {
+ public static final String PLUGIN_ID = "org.eclipse.ui.forms"; //$NON-NLS-1$
+
+ static final int H_SCROLL_INCREMENT = 5;
+
+ static final int V_SCROLL_INCREMENT = 64;
+
+ public static final String DEBUG = PLUGIN_ID + "/debug"; //$NON-NLS-1$
+
+ public static final String DEBUG_TEXT = DEBUG + "/text"; //$NON-NLS-1$
+ public static final String DEBUG_TEXTSIZE = DEBUG + "/textsize"; //$NON-NLS-1$
+
+ public static final String DEBUG_FOCUS = DEBUG + "/focus"; //$NON-NLS-1$
+
+ public static final String FOCUS_SCROLLING = "focusScrolling"; //$NON-NLS-1$
+
+ public static final String IGNORE_BODY = "__ignore_body__"; //$NON-NLS-1$
+
+ public static Text createText(Composite parent, String label,
+ FormToolkit factory) {
+ return createText(parent, label, factory, 1);
+ }
+
+ public static Text createText(Composite parent, String label,
+ FormToolkit factory, int span) {
+ factory.createLabel(parent, label);
+ Text text = factory.createText(parent, ""); //$NON-NLS-1$
+ int hfill = span == 1 ? GridData.FILL_HORIZONTAL
+ : GridData.HORIZONTAL_ALIGN_FILL;
+ GridData gd = new GridData(hfill | GridData.VERTICAL_ALIGN_CENTER);
+ gd.horizontalSpan = span;
+ text.setLayoutData(gd);
+ return text;
+ }
+
+ public static Text createText(Composite parent, String label,
+ FormToolkit factory, int span, int style) {
+ Label l = factory.createLabel(parent, label);
+ if ((style & SWT.MULTI) != 0) {
+ GridData gd = new GridData(GridData.VERTICAL_ALIGN_BEGINNING);
+ l.setLayoutData(gd);
+ }
+ Text text = factory.createText(parent, "", style); //$NON-NLS-1$
+ int hfill = span == 1 ? GridData.FILL_HORIZONTAL
+ : GridData.HORIZONTAL_ALIGN_FILL;
+ GridData gd = new GridData(hfill | GridData.VERTICAL_ALIGN_CENTER);
+ gd.horizontalSpan = span;
+ text.setLayoutData(gd);
+ return text;
+ }
+
+ public static Text createText(Composite parent, FormToolkit factory,
+ int span) {
+ Text text = factory.createText(parent, ""); //$NON-NLS-1$
+ int hfill = span == 1 ? GridData.FILL_HORIZONTAL
+ : GridData.HORIZONTAL_ALIGN_FILL;
+ GridData gd = new GridData(hfill | GridData.VERTICAL_ALIGN_CENTER);
+ gd.horizontalSpan = span;
+ text.setLayoutData(gd);
+ return text;
+ }
+
+ public static int computeMinimumWidth(GC gc, String text) {
+// BreakIterator wb = BreakIterator.getWordInstance();
+// wb.setText(text);
+// int last = 0;
+//
+// int width = 0;
+//
+// for (int loc = wb.first(); loc != BreakIterator.DONE; loc = wb.next()) {
+// String word = text.substring(last, loc);
+// Point extent = gc.textExtent(word);
+// width = Math.max(width, extent.x);
+// last = loc;
+// }
+// String lastWord = text.substring(last);
+// Point extent = gc.textExtent(lastWord);
+// width = Math.max(width, extent.x);
+// return width;
+ return 0;
+ }
+
+ public static Point computeWrapSize(GC gc, String text, int wHint) {
+// BreakIterator wb = BreakIterator.getWordInstance();
+// wb.setText(text);
+ FontMetrics fm = gc.getFontMetrics();
+ int lineHeight = fm.getHeight();
+
+ int saved = 0;
+ int last = 0;
+ int height = lineHeight;
+ int maxWidth = 0;
+// for (int loc = wb.first(); loc != BreakIterator.DONE; loc = wb.next()) {
+// String word = text.substring(saved, loc);
+// Point extent = gc.textExtent(word);
+// if (extent.x > wHint) {
+// // overflow
+// saved = last;
+// height += extent.y;
+// // switch to current word so maxWidth will accommodate very long single words
+// word = text.substring(last, loc);
+// extent = gc.textExtent(word);
+// }
+// maxWidth = Math.max(maxWidth, extent.x);
+// last = loc;
+// }
+ /*
+ * Correct the height attribute in case it was calculated wrong due to wHint being less than maxWidth.
+ * The recursive call proved to be the only thing that worked in all cases. Some attempts can be made
+ * to estimate the height, but the algorithm needs to be run again to be sure.
+ */
+ if (maxWidth > wHint)
+ return computeWrapSize(gc, text, maxWidth);
+ return new Point(maxWidth, height);
+ }
+
+// RAP [rh] paintWrapText unnecessary
+// public static void paintWrapText(GC gc, String text, Rectangle bounds) {
+// paintWrapText(gc, text, bounds, false);
+// }
+
+// RAP [rh] paintWrapText unnecessary
+// public static void paintWrapText(GC gc, String text, Rectangle bounds,
+// boolean underline) {
+// BreakIterator wb = BreakIterator.getWordInstance();
+// wb.setText(text);
+// FontMetrics fm = gc.getFontMetrics();
+// int lineHeight = fm.getHeight();
+// int descent = fm.getDescent();
+//
+// int saved = 0;
+// int last = 0;
+// int y = bounds.y;
+// int width = bounds.width;
+//
+// for (int loc = wb.first(); loc != BreakIterator.DONE; loc = wb.next()) {
+// String line = text.substring(saved, loc);
+// Point extent = gc.textExtent(line);
+//
+// if (extent.x > width) {
+// // overflow
+// String prevLine = text.substring(saved, last);
+// gc.drawText(prevLine, bounds.x, y, true);
+// if (underline) {
+// Point prevExtent = gc.textExtent(prevLine);
+// int lineY = y + lineHeight - descent + 1;
+// gc
+// .drawLine(bounds.x, lineY, bounds.x + prevExtent.x,
+// lineY);
+// }
+//
+// saved = last;
+// y += lineHeight;
+// }
+// last = loc;
+// }
+// // paint the last line
+// String lastLine = text.substring(saved, last);
+// gc.drawText(lastLine, bounds.x, y, true);
+// if (underline) {
+// int lineY = y + lineHeight - descent + 1;
+// Point lastExtent = gc.textExtent(lastLine);
+// gc.drawLine(bounds.x, lineY, bounds.x + lastExtent.x, lineY);
+// }
+// }
+
+ public static ScrolledComposite getScrolledComposite(Control c) {
+ Composite parent = c.getParent();
+
+ while (parent != null) {
+ if (parent instanceof ScrolledComposite) {
+ return (ScrolledComposite) parent;
+ }
+ parent = parent.getParent();
+ }
+ return null;
+ }
+
+ public static void ensureVisible(Control c) {
+ ScrolledComposite scomp = getScrolledComposite(c);
+ if (scomp != null) {
+ Object data = scomp.getData(FOCUS_SCROLLING);
+ if (data == null || !data.equals(Boolean.FALSE))
+ FormUtil.ensureVisible(scomp, c);
+ }
+ }
+
+ public static void ensureVisible(ScrolledComposite scomp, Control control) {
+ // if the control is a FormText we do not need to scroll since it will
+ // ensure visibility of its segments as necessary
+// if (control instanceof FormText)
+// return;
+ Point controlSize = control.getSize();
+ Point controlOrigin = getControlLocation(scomp, control);
+ ensureVisible(scomp, controlOrigin, controlSize);
+ }
+
+ public static void ensureVisible(ScrolledComposite scomp,
+ Point controlOrigin, Point controlSize) {
+ Rectangle area = scomp.getClientArea();
+ Point scompOrigin = scomp.getOrigin();
+
+ int x = scompOrigin.x;
+ int y = scompOrigin.y;
+
+ // horizontal right, but only if the control is smaller
+ // than the client area
+ if (controlSize.x < area.width
+ && (controlOrigin.x + controlSize.x > scompOrigin.x
+ + area.width)) {
+ x = controlOrigin.x + controlSize.x - area.width;
+ }
+ // horizontal left - make sure the left edge of
+ // the control is showing
+ if (controlOrigin.x < x) {
+ if (controlSize.x < area.width)
+ x = controlOrigin.x + controlSize.x - area.width;
+ else
+ x = controlOrigin.x;
+ }
+ // vertical bottom
+ if (controlSize.y < area.height
+ && (controlOrigin.y + controlSize.y > scompOrigin.y
+ + area.height)) {
+ y = controlOrigin.y + controlSize.y - area.height;
+ }
+ // vertical top - make sure the top of
+ // the control is showing
+ if (controlOrigin.y < y) {
+ if (controlSize.y < area.height)
+ y = controlOrigin.y + controlSize.y - area.height;
+ else
+ y = controlOrigin.y;
+ }
+
+ if (scompOrigin.x != x || scompOrigin.y != y) {
+ // scroll to reveal
+ scomp.setOrigin(x, y);
+ }
+ }
+
+ public static void ensureVisible(ScrolledComposite scomp, Control control,
+ MouseEvent e) {
+ Point controlOrigin = getControlLocation(scomp, control);
+ int rX = controlOrigin.x + e.x;
+ int rY = controlOrigin.y + e.y;
+ Rectangle area = scomp.getClientArea();
+ Point scompOrigin = scomp.getOrigin();
+
+ int x = scompOrigin.x;
+ int y = scompOrigin.y;
+ // System.out.println("Ensure: area="+area+", origin="+scompOrigin+",
+ // cloc="+controlOrigin+", csize="+controlSize+", x="+x+", y="+y);
+
+ // horizontal right
+ if (rX > scompOrigin.x + area.width) {
+ x = rX - area.width;
+ }
+ // horizontal left
+ else if (rX < x) {
+ x = rX;
+ }
+ // vertical bottom
+ if (rY > scompOrigin.y + area.height) {
+ y = rY - area.height;
+ }
+ // vertical top
+ else if (rY < y) {
+ y = rY;
+ }
+
+ if (scompOrigin.x != x || scompOrigin.y != y) {
+ // scroll to reveal
+ scomp.setOrigin(x, y);
+ }
+ }
+
+ public static Point getControlLocation(ScrolledComposite scomp,
+ Control control) {
+ int x = 0;
+ int y = 0;
+ Control content = scomp.getContent();
+ Control currentControl = control;
+ for (;;) {
+ if (currentControl == content)
+ break;
+ Point location = currentControl.getLocation();
+ // if (location.x > 0)
+ // x += location.x;
+ // if (location.y > 0)
+ // y += location.y;
+ x += location.x;
+ y += location.y;
+ currentControl = currentControl.getParent();
+ }
+ return new Point(x, y);
+ }
+
+ static void scrollVertical(ScrolledComposite scomp, boolean up) {
+ scroll(scomp, 0, up ? -V_SCROLL_INCREMENT : V_SCROLL_INCREMENT);
+ }
+
+ static void scrollHorizontal(ScrolledComposite scomp, boolean left) {
+ scroll(scomp, left ? -H_SCROLL_INCREMENT : H_SCROLL_INCREMENT, 0);
+ }
+
+ static void scrollPage(ScrolledComposite scomp, boolean up) {
+ Rectangle clientArea = scomp.getClientArea();
+ int increment = up ? -clientArea.height : clientArea.height;
+ scroll(scomp, 0, increment);
+ }
+
+ static void scroll(ScrolledComposite scomp, int xoffset, int yoffset) {
+ Point origin = scomp.getOrigin();
+ Point contentSize = scomp.getContent().getSize();
+ int xorigin = origin.x + xoffset;
+ int yorigin = origin.y + yoffset;
+ xorigin = Math.max(xorigin, 0);
+ xorigin = Math.min(xorigin, contentSize.x - 1);
+ yorigin = Math.max(yorigin, 0);
+ yorigin = Math.min(yorigin, contentSize.y - 1);
+ scomp.setOrigin(xorigin, yorigin);
+ }
+
+// RAP [rh] FormUtil#updatePageIncrement: empty implementation
+ public static void updatePageIncrement(ScrolledComposite scomp) {
+// ScrollBar vbar = scomp.getVerticalBar();
+// if (vbar != null) {
+// Rectangle clientArea = scomp.getClientArea();
+// int increment = clientArea.height - 5;
+// vbar.setPageIncrement(increment);
+// }
+// ScrollBar hbar = scomp.getHorizontalBar();
+// if (hbar != null) {
+// Rectangle clientArea = scomp.getClientArea();
+// int increment = clientArea.width - 5;
+// hbar.setPageIncrement(increment);
+// }
+ }
+
+ public static void processKey(int keyCode, Control c) {
+ if (c.isDisposed()) {
+ return;
+ }
+ ScrolledComposite scomp = FormUtil.getScrolledComposite(c);
+ if (scomp != null) {
+ if (c instanceof Combo)
+ return;
+ switch (keyCode) {
+ case SWT.ARROW_DOWN:
+ if (scomp.getData("novarrows") == null) //$NON-NLS-1$
+ FormUtil.scrollVertical(scomp, false);
+ break;
+ case SWT.ARROW_UP:
+ if (scomp.getData("novarrows") == null) //$NON-NLS-1$
+ FormUtil.scrollVertical(scomp, true);
+ break;
+ case SWT.ARROW_LEFT:
+ FormUtil.scrollHorizontal(scomp, true);
+ break;
+ case SWT.ARROW_RIGHT:
+ FormUtil.scrollHorizontal(scomp, false);
+ break;
+ case SWT.PAGE_UP:
+ FormUtil.scrollPage(scomp, true);
+ break;
+ case SWT.PAGE_DOWN:
+ FormUtil.scrollPage(scomp, false);
+ break;
+ }
+ }
+ }
+
+ public static boolean isWrapControl(Control c) {
+ if ((c.getStyle() & SWT.WRAP) != 0)
+ return true;
+ if (c instanceof Composite) {
+ return false;
+// return ((Composite) c).getLayout() instanceof ILayoutExtension;
+ }
+ return false;
+ }
+
+ public static int getWidthHint(int wHint, Control c) {
+ boolean wrap = isWrapControl(c);
+ return wrap ? wHint : SWT.DEFAULT;
+ }
+
+ public static int getHeightHint(int hHint, Control c) {
+ if (c instanceof Composite) {
+ Layout layout = ((Composite) c).getLayout();
+// if (layout instanceof ColumnLayout)
+// return hHint;
+ }
+ return SWT.DEFAULT;
+ }
+
+ public static int computeMinimumWidth(Control c, boolean changed) {
+ if (c instanceof Composite) {
+ Layout layout = ((Composite) c).getLayout();
+// if (layout instanceof ILayoutExtension)
+// return ((ILayoutExtension) layout).computeMinimumWidth(
+// (Composite) c, changed);
+ }
+ return c.computeSize(FormUtil.getWidthHint(5, c), SWT.DEFAULT, changed).x;
+ }
+
+ public static int computeMaximumWidth(Control c, boolean changed) {
+ if (c instanceof Composite) {
+ Layout layout = ((Composite) c).getLayout();
+// if (layout instanceof ILayoutExtension)
+// return ((ILayoutExtension) layout).computeMaximumWidth(
+// (Composite) c, changed);
+ }
+ return c.computeSize(SWT.DEFAULT, SWT.DEFAULT, changed).x;
+ }
+
+// public static Form getForm(Control c) {
+// Composite parent = c.getParent();
+// while (parent != null) {
+// if (parent instanceof Form) {
+// return (Form) parent;
+// }
+// parent = parent.getParent();
+// }
+// return null;
+// }
+
+// RAP [rh] FormUtil#createAlphaMashImage unnecessary
+// public static Image createAlphaMashImage(Device device, Image srcImage) {
+// Rectangle bounds = srcImage.getBounds();
+// int alpha = 0;
+// int calpha = 0;
+// ImageData data = srcImage.getImageData();
+// // Create a new image with alpha values alternating
+// // between fully transparent (0) and fully opaque (255).
+// // This image will show the background through the
+// // transparent pixels.
+// for (int i = 0; i < bounds.height; i++) {
+// // scan line
+// alpha = calpha;
+// for (int j = 0; j < bounds.width; j++) {
+// // column
+// data.setAlpha(j, i, alpha);
+// alpha = alpha == 255 ? 0 : 255;
+// }
+// calpha = calpha == 255 ? 0 : 255;
+// }
+// return new Image(device, data);
+// }
+
+ public static boolean mnemonicMatch(String text, char key) {
+ char mnemonic = findMnemonic(text);
+ if (mnemonic == '\0')
+ return false;
+ return Character.toUpperCase(key) == Character.toUpperCase(mnemonic);
+ }
+
+ private static char findMnemonic(String string) {
+ int index = 0;
+ int length = string.length();
+ do {
+ while (index < length && string.charAt(index) != '&')
+ index++;
+ if (++index >= length)
+ return '\0';
+ if (string.charAt(index) != '&')
+ return string.charAt(index);
+ index++;
+ } while (index < length);
+ return '\0';
+ }
+
+ public static void setFocusScrollingEnabled(Control c, boolean enabled) {
+ ScrolledComposite scomp = null;
+
+ if (c instanceof ScrolledComposite)
+ scomp = (ScrolledComposite)c;
+ else
+ scomp = getScrolledComposite(c);
+ if (scomp!=null)
+ scomp.setData(FormUtil.FOCUS_SCROLLING, enabled?null:Boolean.FALSE);
+ }
+
+ // RAP [rh] FormUtil#setAntialias unnecessary
+// public static void setAntialias(GC gc, int style) {
+// if (!gc.getAdvanced()) {
+// gc.setAdvanced(true);
+// if (!gc.getAdvanced())
+// return;
+// }
+// gc.setAntialias(style);
+// }
+}
--- /dev/null
+package org.argeo.cms.ui.eclipse.forms;
+
+/**
+ * A place to hold all the color constants used in the forms package.
+ *
+ * @since 1.0
+ */
+
+public interface IFormColors {
+ /**
+ * A prefix for all the keys.
+ */
+ String PREFIX = "org.eclipse.ui.forms."; //$NON-NLS-1$
+ /**
+ * Key for the form title foreground color.
+ */
+ String TITLE = PREFIX + "TITLE"; //$NON-NLS-1$
+
+ /**
+ * A prefix for the header color constants.
+ */
+ String H_PREFIX = PREFIX + "H_"; //$NON-NLS-1$
+ /*
+ * A prefix for the section title bar color constants.
+ */
+ String TB_PREFIX = PREFIX + "TB_"; //$NON-NLS-1$
+ /**
+ * Key for the form header background gradient ending color.
+ */
+ String H_GRADIENT_END = H_PREFIX + "GRADIENT_END"; //$NON-NLS-1$
+
+ /**
+ * Key for the form header background gradient starting color.
+ *
+ */
+ String H_GRADIENT_START = H_PREFIX + "GRADIENT_START"; //$NON-NLS-1$
+ /**
+ * Key for the form header bottom keyline 1 color.
+ *
+ */
+ String H_BOTTOM_KEYLINE1 = H_PREFIX + "BOTTOM_KEYLINE1"; //$NON-NLS-1$
+ /**
+ * Key for the form header bottom keyline 2 color.
+ *
+ */
+ String H_BOTTOM_KEYLINE2 = H_PREFIX + "BOTTOM_KEYLINE2"; //$NON-NLS-1$
+ /**
+ * Key for the form header light hover color.
+ *
+ */
+ String H_HOVER_LIGHT = H_PREFIX + "H_HOVER_LIGHT"; //$NON-NLS-1$
+ /**
+ * Key for the form header full hover color.
+ *
+ */
+ String H_HOVER_FULL = H_PREFIX + "H_HOVER_FULL"; //$NON-NLS-1$
+
+ /**
+ * Key for the tree/table border color.
+ */
+ String BORDER = PREFIX + "BORDER"; //$NON-NLS-1$
+
+ /**
+ * Key for the section separator color.
+ */
+ String SEPARATOR = PREFIX + "SEPARATOR"; //$NON-NLS-1$
+
+ /**
+ * Key for the section title bar background.
+ */
+ String TB_BG = TB_PREFIX + "BG"; //$NON-NLS-1$
+
+ /**
+ * Key for the section title bar foreground.
+ */
+ String TB_FG = TB_PREFIX + "FG"; //$NON-NLS-1$
+
+ /**
+ * Key for the section title bar gradient.
+ * @deprecated Since 3.3, this color is not used any more. The
+ * tool bar gradient is created starting from {@link #TB_BG} to
+ * the section background color.
+ */
+ String TB_GBG = TB_BG;
+
+ /**
+ * Key for the section title bar border.
+ */
+ String TB_BORDER = TB_PREFIX + "BORDER"; //$NON-NLS-1$
+
+ /**
+ * Key for the section toggle color. Since 3.1, this color is used for all
+ * section styles.
+ */
+ String TB_TOGGLE = TB_PREFIX + "TOGGLE"; //$NON-NLS-1$
+
+ /**
+ * Key for the section toggle hover color.
+ *
+ */
+ String TB_TOGGLE_HOVER = TB_PREFIX + "TOGGLE_HOVER"; //$NON-NLS-1$
+}
\ No newline at end of file
--- /dev/null
+package org.argeo.cms.ui.eclipse.forms;
+
+/**
+ * Classes that implement this interface can be added to the managed form and
+ * take part in the form life cycle. The part is initialized with the form and
+ * will be asked to accept focus. The part can receive form input and can elect
+ * to do something according to it (for example, select an object that matches
+ * the input).
+ * <p>
+ * The form part has two 'out of sync' states in respect to the model(s) that
+ * feed the form: <b>dirty</b> and <b>stale</b>. When a part is dirty, it
+ * means that the user interacted with it and now its widgets contain state that
+ * is newer than the model. In order to sync up with the model, 'commit' needs
+ * to be called. In contrast, the model can change 'under' the form (as a result
+ * of some actions outside the form), resulting in data in the model being
+ * 'newer' than the content presented in the form. A 'stale' form part is
+ * brought in sync with the model by calling 'refresh'. The part is responsible
+ * for notifying the form when one of these states change in the part. The form
+ * reserves the right to handle this notification in the most appropriate way
+ * for the situation (for example, if the form is in a page of the multi-page
+ * editor, it may do nothing for stale parts if the page is currently not
+ * showing).
+ * <p>
+ * When the form is disposed, each registered part is disposed as well. Parts
+ * are responsible for releasing any system resources they created and for
+ * removing themselves as listeners from all event providers.
+ *
+ * @see IManagedForm
+ * @since 1.0
+ *
+ */
+public interface IFormPart {
+ /**
+ * Initializes the part.
+ *
+ * @param form
+ * the managed form that manages the part
+ */
+ void initialize(IManagedForm form);
+
+ /**
+ * Disposes the part allowing it to release allocated resources.
+ */
+ void dispose();
+
+ /**
+ * Returns true if the part has been modified with respect to the data
+ * loaded from the model.
+ *
+ * @return true if the part has been modified with respect to the data
+ * loaded from the model
+ */
+ boolean isDirty();
+
+ /**
+ * If part is displaying information loaded from a model, this method
+ * instructs it to commit the new (modified) data back into the model.
+ *
+ * @param onSave
+ * indicates if commit is called during 'save' operation or for
+ * some other reason (for example, if form is contained in a
+ * wizard or a multi-page editor and the user is about to leave
+ * the page).
+ */
+ void commit(boolean onSave);
+
+ /**
+ * Notifies the part that an object has been set as overall form's input.
+ * The part can elect to react by revealing or selecting the object, or do
+ * nothing if not applicable.
+ *
+ * @return <code>true</code> if the part has selected and revealed the
+ * input object, <code>false</code> otherwise.
+ */
+ boolean setFormInput(Object input);
+
+ /**
+ * Instructs form part to transfer focus to the widget that should has focus
+ * in that part. The method can do nothing (if it has no widgets capable of
+ * accepting focus).
+ */
+ void setFocus();
+
+ /**
+ * Tests whether the form part is stale and needs refreshing. Parts can
+ * receive notification from models that will make their content stale, but
+ * may need to delay refreshing to improve performance (for example, there
+ * is no need to immediately refresh a part on a form that is current on a
+ * hidden page).
+ * <p>
+ * It is important to differentiate 'stale' and 'dirty' states. Part is
+ * 'dirty' if user interacted with its editable widgets and changed the
+ * values. In contrast, part is 'stale' when the data it presents in the
+ * widgets has been changed in the model without direct user interaction.
+ *
+ * @return <code>true</code> if the part needs refreshing,
+ * <code>false</code> otherwise.
+ */
+ boolean isStale();
+
+ /**
+ * Refreshes the part completely from the information freshly obtained from
+ * the model. The method will not be called if the part is not stale.
+ * Otherwise, the part is responsible for clearing the 'stale' flag after
+ * refreshing itself.
+ */
+ void refresh();
+}
--- /dev/null
+package org.argeo.cms.ui.eclipse.forms;
+
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.swt.custom.ScrolledComposite;
+//import org.eclipse.ui.forms.widgets.FormToolkit;
+//import org.eclipse.ui.forms.widgets.ScrolledForm;
+
+/**
+ * Managed form wraps a form widget and adds life cycle methods for form parts.
+ * A form part is a portion of the form that participates in form life cycle
+ * events.
+ * <p>
+ * There is no 1/1 mapping between widgets and form parts. A widget like Section
+ * can be a part by itself, but a number of widgets can gather around one form
+ * part.
+ * <p>
+ * This interface should not be extended or implemented. New form instances
+ * should be created using ManagedForm.
+ *
+ * @see ManagedForm
+ * @since 1.0
+ * @noimplement This interface is not intended to be implemented by clients.
+ * @noextend This interface is not intended to be extended by clients.
+ */
+public interface IManagedForm {
+ /**
+ * Initializes the form by looping through the managed parts and
+ * initializing them. Has no effect if already called once.
+ */
+ public void initialize();
+
+ /**
+ * Returns the toolkit used by this form.
+ *
+ * @return the toolkit
+ */
+ public FormToolkit getToolkit();
+
+ /**
+ * Returns the form widget managed by this form.
+ *
+ * @return the form widget
+ */
+ public ScrolledComposite getForm();
+
+ /**
+ * Reflows the form as a result of the layout change.
+ *
+ * @param changed
+ * if <code>true</code>, discard cached layout information
+ */
+ public void reflow(boolean changed);
+
+ /**
+ * A part can use this method to notify other parts that implement
+ * IPartSelectionListener about selection changes.
+ *
+ * @param part
+ * the part that broadcasts the selection
+ * @param selection
+ * the selection in the part
+ */
+ public void fireSelectionChanged(IFormPart part, ISelection selection);
+
+ /**
+ * Returns all the parts currently managed by this form.
+ *
+ * @return the managed parts
+ */
+ IFormPart[] getParts();
+
+ /**
+ * Adds the new part to the form.
+ *
+ * @param part
+ * the part to add
+ */
+ void addPart(IFormPart part);
+
+ /**
+ * Removes the part from the form.
+ *
+ * @param part
+ * the part to remove
+ */
+ void removePart(IFormPart part);
+
+ /**
+ * Sets the input of this page to the provided object.
+ *
+ * @param input
+ * the new page input
+ * @return <code>true</code> if the form contains this object,
+ * <code>false</code> otherwise.
+ */
+ boolean setInput(Object input);
+
+ /**
+ * Returns the current page input.
+ *
+ * @return page input object or <code>null</code> if not applicable.
+ */
+ Object getInput();
+
+ /**
+ * Tests if form is dirty. A managed form is dirty if at least one managed
+ * part is dirty.
+ *
+ * @return <code>true</code> if at least one managed part is dirty,
+ * <code>false</code> otherwise.
+ */
+ boolean isDirty();
+
+ /**
+ * Notifies the form that the dirty state of one of its parts has changed.
+ * The global dirty state of the form can be obtained by calling 'isDirty'.
+ *
+ * @see #isDirty
+ */
+ void dirtyStateChanged();
+
+ /**
+ * Commits the dirty form. All pending changes in the widgets are flushed
+ * into the model.
+ *
+ * @param onSave
+ */
+ void commit(boolean onSave);
+
+ /**
+ * Tests if form is stale. A managed form is stale if at least one managed
+ * part is stale. This can happen when the underlying model changes,
+ * resulting in the presentation of the part being out of sync with the
+ * model and needing refreshing.
+ *
+ * @return <code>true</code> if the form is stale, <code>false</code>
+ * otherwise.
+ */
+ boolean isStale();
+
+ /**
+ * Notifies the form that the stale state of one of its parts has changed.
+ * The global stale state of the form can be obtained by calling 'isStale'.
+ */
+ void staleStateChanged();
+
+ /**
+ * Refreshes the form by refreshing every part that is stale.
+ */
+ void refresh();
+
+ /**
+ * Sets the container that owns this form. Depending on the context, the
+ * container may be wizard, editor page, editor etc.
+ *
+ * @param container
+ * the container of this form
+ */
+ void setContainer(Object container);
+
+ /**
+ * Returns the container of this form.
+ *
+ * @return the form container
+ */
+ Object getContainer();
+
+ /**
+ * Returns the message manager that will keep track of messages in this
+ * form.
+ *
+ * @return the message manager instance
+ */
+// IMessageManager getMessageManager();
+}
--- /dev/null
+package org.argeo.cms.ui.eclipse.forms;
+
+import org.eclipse.jface.viewers.ISelection;
+
+/**
+ * Form parts can implement this interface if they want to be
+ * notified when another part on the same form changes selection
+ * state.
+ *
+ * @see IFormPart
+ * @since 1.0
+ */
+public interface IPartSelectionListener {
+ /**
+ * Called when the provided part has changed selection state.
+ *
+ * @param part
+ * the selection source
+ * @param selection
+ * the new selection
+ */
+ public void selectionChanged(IFormPart part, ISelection selection);
+}
--- /dev/null
+package org.argeo.cms.ui.eclipse.forms;
+
+import java.util.Vector;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.swt.custom.ScrolledComposite;
+import org.eclipse.swt.widgets.Composite;
+//import org.eclipse.ui.forms.widgets.FormToolkit;
+//import org.eclipse.ui.forms.widgets.ScrolledForm;
+
+/**
+ * Managed form wraps a form widget and adds life cycle methods for form parts.
+ * A form part is a portion of the form that participates in form life cycle
+ * events.
+ * <p>
+ * There is requirement for 1/1 mapping between widgets and form parts. A widget
+ * like Section can be a part by itself, but a number of widgets can join around
+ * one form part.
+ * <p>
+ * Note to developers: this class is left public to allow its use beyond the
+ * original intention (inside a multi-page editor's page). You should limit the
+ * use of this class to make new instances inside a form container (wizard page,
+ * dialog etc.). Clients that need access to the class should not do it
+ * directly. Instead, they should do it through IManagedForm interface as much
+ * as possible.
+ *
+ * @since 1.0
+ */
+public class ManagedForm implements IManagedForm {
+ private Object input;
+
+ private ScrolledComposite form;
+
+ private FormToolkit toolkit;
+
+ private Object container;
+
+ private boolean ownsToolkit;
+
+ private boolean initialized;
+
+ private Vector parts = new Vector();
+
+ /**
+ * Creates a managed form in the provided parent. Form toolkit and widget
+ * will be created and owned by this object.
+ *
+ * @param parent
+ * the parent widget
+ */
+ public ManagedForm(Composite parent) {
+ toolkit = new FormToolkit(parent.getDisplay());
+ ownsToolkit = true;
+ form = toolkit.createScrolledForm(parent);
+
+ }
+
+ /**
+ * Creates a managed form that will use the provided toolkit and
+ *
+ * @param toolkit
+ * @param form
+ */
+ public ManagedForm(FormToolkit toolkit, ScrolledComposite form) {
+ this.form = form;
+ this.toolkit = toolkit;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.ui.forms.IManagedForm#addPart(org.eclipse.ui.forms.IFormPart)
+ */
+ public void addPart(IFormPart part) {
+ parts.add(part);
+ part.initialize(this);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.ui.forms.IManagedForm#removePart(org.eclipse.ui.forms.IFormPart)
+ */
+ public void removePart(IFormPart part) {
+ parts.remove(part);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.ui.forms.IManagedForm#getParts()
+ */
+ public IFormPart[] getParts() {
+ return (IFormPart[]) parts.toArray(new IFormPart[parts.size()]);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.ui.forms.IManagedForm#getToolkit()
+ */
+ public FormToolkit getToolkit() {
+ return toolkit;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.ui.forms.IManagedForm#getForm()
+ */
+ public ScrolledComposite getForm() {
+ return form;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.ui.forms.IManagedForm#reflow(boolean)
+ */
+ public void reflow(boolean changed) {
+// form.reflow(changed);
+ }
+
+ /**
+ * A part can use this method to notify other parts that implement
+ * IPartSelectionListener about selection changes.
+ *
+ * @param part
+ * the part that broadcasts the selection
+ * @param selection
+ * the selection in the part
+ * @see IPartSelectionListener
+ */
+ public void fireSelectionChanged(IFormPart part, ISelection selection) {
+ for (int i = 0; i < parts.size(); i++) {
+ IFormPart cpart = (IFormPart) parts.get(i);
+ if (part.equals(cpart))
+ continue;
+// if (cpart instanceof IPartSelectionListener) {
+// ((IPartSelectionListener) cpart).selectionChanged(part,
+// selection);
+// }
+ }
+ }
+
+ /**
+ * Initializes the form by looping through the managed parts and
+ * initializing them. Has no effect if already called once.
+ */
+ public void initialize() {
+ if (initialized)
+ return;
+ for (int i = 0; i < parts.size(); i++) {
+ IFormPart part = (IFormPart) parts.get(i);
+ part.initialize(this);
+ }
+ initialized = true;
+ }
+
+ /**
+ * Disposes all the parts in this form.
+ */
+ public void dispose() {
+ for (int i = 0; i < parts.size(); i++) {
+ IFormPart part = (IFormPart) parts.get(i);
+ part.dispose();
+ }
+ if (ownsToolkit) {
+ toolkit.dispose();
+ }
+ }
+
+ /**
+ * Refreshes the form by refreshes all the stale parts. Since 3.1, this
+ * method is performed on a UI thread when called from another thread so it
+ * is not needed to wrap the call in <code>Display.syncExec</code> or
+ * <code>asyncExec</code>.
+ */
+ public void refresh() {
+ Thread t = Thread.currentThread();
+ Thread dt = toolkit.getColors().getDisplay().getThread();
+ if (t.equals(dt))
+ doRefresh();
+ else {
+ toolkit.getColors().getDisplay().asyncExec(new Runnable() {
+ public void run() {
+ doRefresh();
+ }
+ });
+ }
+ }
+
+ private void doRefresh() {
+ int nrefreshed = 0;
+ for (int i = 0; i < parts.size(); i++) {
+ IFormPart part = (IFormPart) parts.get(i);
+ if (part.isStale()) {
+ part.refresh();
+ nrefreshed++;
+ }
+ }
+// if (nrefreshed > 0)
+// form.reflow(true);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.ui.forms.IManagedForm#commit(boolean)
+ */
+ public void commit(boolean onSave) {
+ for (int i = 0; i < parts.size(); i++) {
+ IFormPart part = (IFormPart) parts.get(i);
+ if (part.isDirty())
+ part.commit(onSave);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.ui.forms.IManagedForm#setInput(java.lang.Object)
+ */
+ public boolean setInput(Object input) {
+ boolean pageResult = false;
+
+ this.input = input;
+ for (int i = 0; i < parts.size(); i++) {
+ IFormPart part = (IFormPart) parts.get(i);
+ boolean result = part.setFormInput(input);
+ if (result)
+ pageResult = true;
+ }
+ return pageResult;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.ui.forms.IManagedForm#getInput()
+ */
+ public Object getInput() {
+ return input;
+ }
+
+ /**
+ * Transfers the focus to the first form part.
+ */
+ public void setFocus() {
+ if (parts.size() > 0) {
+ IFormPart part = (IFormPart) parts.get(0);
+ part.setFocus();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.ui.forms.IManagedForm#isDirty()
+ */
+ public boolean isDirty() {
+ for (int i = 0; i < parts.size(); i++) {
+ IFormPart part = (IFormPart) parts.get(i);
+ if (part.isDirty())
+ return true;
+ }
+ return false;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.ui.forms.IManagedForm#isStale()
+ */
+ public boolean isStale() {
+ for (int i = 0; i < parts.size(); i++) {
+ IFormPart part = (IFormPart) parts.get(i);
+ if (part.isStale())
+ return true;
+ }
+ return false;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.ui.forms.IManagedForm#dirtyStateChanged()
+ */
+ public void dirtyStateChanged() {
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.ui.forms.IManagedForm#staleStateChanged()
+ */
+ public void staleStateChanged() {
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.ui.forms.IManagedForm#getContainer()
+ */
+ public Object getContainer() {
+ return container;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.ui.forms.IManagedForm#setContainer(java.lang.Object)
+ */
+ public void setContainer(Object container) {
+ this.container = container;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.ui.forms.IManagedForm#getMessageManager()
+ */
+// public IMessageManager getMessageManager() {
+// return form.getMessageManager();
+// }
+}
--- /dev/null
+package org.argeo.cms.ui.eclipse.forms.editor;
+
+import org.argeo.cms.ui.eclipse.forms.FormToolkit;
+import org.eclipse.jface.dialogs.IPageChangeProvider;
+
+/**
+ * This class forms a base of multi-page form editors that typically use one or
+ * more pages with forms and one page for raw source of the editor input.
+ * <p>
+ * Pages are added 'lazily' i.e. adding a page reserves a tab for it but does
+ * not cause the page control to be created. Page control is created when an
+ * attempt is made to select the page in question. This allows editors with
+ * several tabs and complex pages to open quickly.
+ * <p>
+ * Subclasses should extend this class and implement <code>addPages</code>
+ * method. One of the two <code>addPage</code> methods should be called to
+ * contribute pages to the editor. One adds complete (standalone) editors as
+ * nested tabs. These editors will be created right away and will be hooked so
+ * that key bindings, selection service etc. is compatible with the one for the
+ * standalone case. The other method adds classes that implement
+ * <code>IFormPage</code> interface. These pages will be created lazily and
+ * they will share the common key binding and selection service. Since 3.1,
+ * FormEditor is a page change provider. It allows listeners to attach to it and
+ * get notified when pages are changed. This new API in JFace allows dynamic
+ * help to update on page changes.
+ *
+ * @since 1.0
+ */
+// RAP [if] As RAP is still using workbench 3.4, the implementation of
+// IPageChangeProvider is missing from MultiPageEditorPart. Remove this code
+// with the adoption of workbench > 3.5
+//public abstract class FormEditor extends MultiPageEditorPart {
+public abstract class FormEditor implements
+ IPageChangeProvider {
+ private FormToolkit formToolkit;
+
+
+public FormToolkit getToolkit() {
+ return formToolkit;
+ }
+
+public void editorDirtyStateChanged() {
+
+}
+
+public FormPage getActivePageInstance() {
+ return null;
+}
+
+ // RAP [if] As RAP is still using workbench 3.4, the implementation of
+// IPageChangeProvider is missing from MultiPageEditorPart. Remove this code
+// with the adoption of workbench > 3.5
+// private ListenerList pageListeners = new ListenerList();
+//
+// /*
+// * (non-Javadoc)
+// *
+// * @see org.eclipse.jface.dialogs.IPageChangeProvider#addPageChangedListener(org.eclipse.jface.dialogs.IPageChangedListener)
+// */
+// public void addPageChangedListener(IPageChangedListener listener) {
+// pageListeners.add(listener);
+// }
+//
+// /*
+// * (non-Javadoc)
+// *
+// * @see org.eclipse.jface.dialogs.IPageChangeProvider#removePageChangedListener(org.eclipse.jface.dialogs.IPageChangedListener)
+// */
+// public void removePageChangedListener(IPageChangedListener listener) {
+// pageListeners.remove(listener);
+// }
+//
+// private void firePageChanged(final PageChangedEvent event) {
+// Object[] listeners = pageListeners.getListeners();
+// for (int i = 0; i < listeners.length; ++i) {
+// final IPageChangedListener l = (IPageChangedListener) listeners[i];
+// SafeRunnable.run(new SafeRunnable() {
+// public void run() {
+// l.pageChanged(event);
+// }
+// });
+// }
+// }
+// RAPEND [if]
+}
--- /dev/null
+package org.argeo.cms.ui.eclipse.forms.editor;
+import org.argeo.cms.ui.eclipse.forms.IManagedForm;
+import org.argeo.cms.ui.eclipse.forms.ManagedForm;
+import org.eclipse.swt.custom.BusyIndicator;
+import org.eclipse.swt.custom.ScrolledComposite;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+/**
+ * A base class that all pages that should be added to FormEditor must subclass.
+ * Form page has an instance of PageForm that extends managed form. Subclasses
+ * should override method 'createFormContent(ManagedForm)' to fill the form with
+ * content. Note that page itself can be loaded lazily (on first open).
+ * Consequently, the call to create the form content can come after the editor
+ * has been opened for a while (in fact, it is possible to open and close the
+ * editor and never create the form because no attempt has been made to show the
+ * page).
+ *
+ * @since 1.0
+ */
+public class FormPage implements IFormPage {
+ private FormEditor editor;
+ private PageForm mform;
+ private int index;
+ private String id;
+
+ private String partName;
+
+
+
+ public void setPartName(String partName) {
+ this.partName = partName;
+ }
+ private static class PageForm extends ManagedForm {
+ public PageForm(FormPage page, ScrolledComposite form) {
+ super(page.getEditor().getToolkit(), form);
+ setContainer(page);
+ }
+
+ public FormPage getPage() {
+ return (FormPage)getContainer();
+ }
+ public void dirtyStateChanged() {
+ getPage().getEditor().editorDirtyStateChanged();
+ }
+ public void staleStateChanged() {
+ if (getPage().isActive())
+ refresh();
+ }
+ }
+ /**
+ * A constructor that creates the page and initializes it with the editor.
+ *
+ * @param editor
+ * the parent editor
+ * @param id
+ * the unique identifier
+ * @param title
+ * the page title
+ */
+ public FormPage(FormEditor editor, String id, String title) {
+ this(id, title);
+ initialize(editor);
+ }
+ /**
+ * The constructor. The parent editor need to be passed in the
+ * <code>initialize</code> method if this constructor is used.
+ *
+ * @param id
+ * a unique page identifier
+ * @param title
+ * a user-friendly page title
+ */
+ public FormPage(String id, String title) {
+ this.id = id;
+ setPartName(title);
+ }
+ /**
+ * Initializes the form page.
+ *
+ * @see IEditorPart#init
+ */
+// public void init(IEditorSite site, IEditorInput input) {
+// setSite(site);
+// setInput(input);
+// }
+ /**
+ * Primes the form page with the parent editor instance.
+ *
+ * @param editor
+ * the parent editor
+ */
+ public void initialize(FormEditor editor) {
+ this.editor = editor;
+ }
+ /**
+ * Returns the parent editor.
+ *
+ * @return parent editor instance
+ */
+ public FormEditor getEditor() {
+ return editor;
+ }
+ /**
+ * Returns the managed form owned by this page.
+ *
+ * @return the managed form
+ */
+ public IManagedForm getManagedForm() {
+ return mform;
+ }
+ /**
+ * Implements the required method by refreshing the form when set active.
+ * Subclasses must call super when overriding this method.
+ */
+ public void setActive(boolean active) {
+ if (active) {
+ // We are switching to this page - refresh it
+ // if needed.
+ if (mform != null)
+ mform.refresh();
+ }
+ }
+ /**
+ * Tests if the page is active by asking the parent editor if this page is
+ * the currently active page.
+ *
+ * @return <code>true</code> if the page is currently active,
+ * <code>false</code> otherwise.
+ */
+ public boolean isActive() {
+ return this.equals(editor.getActivePageInstance());
+ }
+ /**
+ * Creates the part control by creating the managed form using the parent
+ * editor's toolkit. Subclasses should override
+ * <code>createFormContent(IManagedForm)</code> to populate the form with
+ * content.
+ *
+ * @param parent
+ * the page parent composite
+ */
+ public void createPartControl(Composite parent) {
+ ScrolledComposite form = editor.getToolkit().createScrolledForm(parent);
+ mform = new PageForm(this, form);
+ BusyIndicator.showWhile(parent.getDisplay(), new Runnable() {
+ public void run() {
+ createFormContent(mform);
+ }
+ });
+ }
+ /**
+ * Subclasses should override this method to create content in the form
+ * hosted in this page.
+ *
+ * @param managedForm
+ * the form hosted in this page.
+ */
+ protected void createFormContent(IManagedForm managedForm) {
+ }
+ /**
+ * Returns the form page control.
+ *
+ * @return managed form's control
+ */
+ public Control getPartControl() {
+ return mform != null ? mform.getForm() : null;
+ }
+ /**
+ * Disposes the managed form.
+ */
+ public void dispose() {
+ if (mform != null)
+ mform.dispose();
+ }
+ /**
+ * Returns the unique identifier that can be used to reference this page.
+ *
+ * @return the unique page identifier
+ */
+ public String getId() {
+ return id;
+ }
+ /**
+ * Returns <code>null</code>- form page has no title image. Subclasses
+ * may override.
+ *
+ * @return <code>null</code>
+ */
+ public Image getTitleImage() {
+ return null;
+ }
+ /**
+ * Sets the focus by delegating to the managed form.
+ */
+ public void setFocus() {
+ if (mform != null)
+ mform.setFocus();
+ }
+ /**
+ * @see org.eclipse.ui.ISaveablePart#doSave(org.eclipse.core.runtime.IProgressMonitor)
+ */
+// public void doSave(IProgressMonitor monitor) {
+// if (mform != null)
+// mform.commit(true);
+// }
+ /**
+ * @see org.eclipse.ui.ISaveablePart#doSaveAs()
+ */
+ public void doSaveAs() {
+ }
+ /**
+ * @see org.eclipse.ui.ISaveablePart#isSaveAsAllowed()
+ */
+ public boolean isSaveAsAllowed() {
+ return false;
+ }
+ /**
+ * Implemented by testing if the managed form is dirty.
+ *
+ * @return <code>true</code> if the managed form is dirty,
+ * <code>false</code> otherwise.
+ *
+ * @see org.eclipse.ui.ISaveablePart#isDirty()
+ */
+ public boolean isDirty() {
+ return mform != null ? mform.isDirty() : false;
+ }
+ /**
+ * Preserves the page index.
+ *
+ * @param index
+ * the assigned page index
+ */
+ public void setIndex(int index) {
+ this.index = index;
+ }
+ /**
+ * Returns the saved page index.
+ *
+ * @return the page index
+ */
+ public int getIndex() {
+ return index;
+ }
+ /**
+ * Form pages are not editors.
+ *
+ * @return <code>false</code>
+ */
+ public boolean isEditor() {
+ return false;
+ }
+ /**
+ * Attempts to select and reveal the given object by passing the request to
+ * the managed form.
+ *
+ * @param object
+ * the object to select and reveal in the page if possible.
+ * @return <code>true</code> if the page has been successfully selected
+ * and revealed by one of the managed form parts, <code>false</code>
+ * otherwise.
+ */
+ public boolean selectReveal(Object object) {
+ if (mform != null)
+ return mform.setInput(object);
+ return false;
+ }
+ /**
+ * By default, editor will be allowed to flip the page.
+ * @return <code>true</code>
+ */
+ public boolean canLeaveThePage() {
+ return true;
+ }
+}
--- /dev/null
+package org.argeo.cms.ui.eclipse.forms.editor;
+import org.argeo.cms.ui.eclipse.forms.IManagedForm;
+import org.eclipse.swt.widgets.Control;
+/**
+ * Interface that all GUI pages need to implement in order
+ * to be added to FormEditor part. The interface makes
+ * several assumptions:
+ * <ul>
+ * <li>The form page has a managed form</li>
+ * <li>The form page has a unique id</li>
+ * <li>The form page can be GUI but can also wrap a complete
+ * editor class (in that case, it should return <code>true</code>
+ * from <code>isEditor()</code> method).</li>
+ * <li>The form page is lazy i.e. understands that
+ * its part control will be created at the last possible
+ * moment.</li>.
+ * </ul>
+ * <p>Existing editors can be wrapped by implementing
+ * this interface. In this case, 'isEditor' should return <code>true</code>.
+ * A common editor to wrap in <code>TextEditor</code> that is
+ * often added to show the raw source code of the file open into
+ * the multi-page editor.
+ *
+ * @since 1.0
+ */
+public interface IFormPage {
+ /**
+ * @param editor
+ * the form editor that this page belongs to
+ */
+ void initialize(FormEditor editor);
+ /**
+ * Returns the editor this page belongs to.
+ *
+ * @return the form editor
+ */
+ FormEditor getEditor();
+ /**
+ * Returns the managed form of this page, unless this is a source page.
+ *
+ * @return the managed form or <samp>null </samp> if this is a source page.
+ */
+ IManagedForm getManagedForm();
+ /**
+ * Indicates whether the page has become the active in the editor. Classes
+ * that implement this interface may use this method to commit the page (on
+ * <code>false</code>) or lazily create and/or populate the content on
+ * <code>true</code>.
+ *
+ * @param active
+ * <code>true</code> if page should be visible, <code>false</code>
+ * otherwise.
+ */
+ void setActive(boolean active);
+ /**
+ * Returns <samp>true </samp> if page is currently active, false if not.
+ *
+ * @return <samp>true </samp> for active page.
+ */
+ boolean isActive();
+ /**
+ * Tests if the content of the page is in a state that allows the
+ * editor to flip to another page. Typically, pages that contain
+ * raw source with syntax errors should not allow editors to
+ * leave them until errors are corrected.
+ * @return <code>true</code> if the editor can flip to another page,
+ * <code>false</code> otherwise.
+ */
+ boolean canLeaveThePage();
+ /**
+ * Returns the control associated with this page.
+ *
+ * @return the control of this page if created or <samp>null </samp> if the
+ * page has not been shown yet.
+ */
+ Control getPartControl();
+ /**
+ * Page must have a unique id that can be used to show it without knowing
+ * its relative position in the editor.
+ *
+ * @return the unique page identifier
+ */
+ String getId();
+ /**
+ * Returns the position of the page in the editor.
+ *
+ * @return the zero-based index of the page in the editor.
+ */
+ int getIndex();
+ /**
+ * Sets the position of the page in the editor.
+ *
+ * @param index
+ * the zero-based index of the page in the editor.
+ */
+ void setIndex(int index);
+ /**
+ * Tests whether this page wraps a complete editor that
+ * can be registered on its own, or represents a page
+ * that cannot exist outside the multi-page editor context.
+ *
+ * @return <samp>true </samp> if the page wraps an editor,
+ * <samp>false </samp> if this is a form page.
+ */
+ boolean isEditor();
+ /**
+ * A hint to bring the provided object into focus. If the object is in a
+ * tree or table control, select it. If it is shown on a scrollable page,
+ * ensure that it is visible. If the object is not presented in
+ * the page, <code>false</code> should be returned to allow another
+ * page to try.
+ *
+ * @param object
+ * object to select and reveal
+ * @return <code>true</code> if the request was successful, <code>false</code>
+ * otherwise.
+ */
+ boolean selectReveal(Object object);
+}
--- /dev/null
+package org.argeo.cms.ui.jcr;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.Observable;
+import java.util.TreeMap;
+
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.api.cms.CmsLog;
+
+public class DefaultRepositoryRegister extends Observable implements RepositoryRegister {
+ /** Key for a JCR repository alias */
+ private final static String CN = CmsConstants.CN;
+ /** Key for a JCR repository URI */
+ // public final static String JCR_REPOSITORY_URI = "argeo.jcr.repository.uri";
+ private final static CmsLog log = CmsLog.getLog(DefaultRepositoryRegister.class);
+
+ /** Read only map which will be directly exposed. */
+ private Map<String, Repository> repositories = Collections.unmodifiableMap(new TreeMap<String, Repository>());
+
+ @SuppressWarnings("rawtypes")
+ public synchronized Repository getRepository(Map parameters) throws RepositoryException {
+ if (!parameters.containsKey(CN))
+ throw new RepositoryException("Parameter " + CN + " has to be defined.");
+ String alias = parameters.get(CN).toString();
+ if (!repositories.containsKey(alias))
+ throw new RepositoryException("No repository registered with alias " + alias);
+
+ return repositories.get(alias);
+ }
+
+ /** Access to the read-only map */
+ public synchronized Map<String, Repository> getRepositories() {
+ return repositories;
+ }
+
+ /** Registers a service, typically called when OSGi services are bound. */
+ @SuppressWarnings("rawtypes")
+ public synchronized void register(Repository repository, Map properties) {
+ String alias;
+ if (properties == null || !properties.containsKey(CN)) {
+ log.warn("Cannot register a repository if no " + CN + " property is specified.");
+ return;
+ }
+ alias = properties.get(CN).toString();
+ Map<String, Repository> map = new TreeMap<String, Repository>(repositories);
+ map.put(alias, repository);
+ repositories = Collections.unmodifiableMap(map);
+ setChanged();
+ notifyObservers(alias);
+ }
+
+ /** Unregisters a service, typically called when OSGi services are unbound. */
+ @SuppressWarnings("rawtypes")
+ public synchronized void unregister(Repository repository, Map properties) {
+ // TODO: also check bean name?
+ if (properties == null || !properties.containsKey(CN)) {
+ log.warn("Cannot unregister a repository without property " + CN);
+ return;
+ }
+
+ String alias = properties.get(CN).toString();
+ Map<String, Repository> map = new TreeMap<String, Repository>(repositories);
+ if (map.remove(alias) == null) {
+ log.warn("No repository was registered with alias " + alias);
+ return;
+ }
+ repositories = Collections.unmodifiableMap(map);
+ setChanged();
+ notifyObservers(alias);
+ }
+}
--- /dev/null
+package org.argeo.cms.ui.jcr;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.nodetype.NodeType;
+import javax.jcr.version.Version;
+import javax.jcr.version.VersionHistory;
+import javax.jcr.version.VersionIterator;
+import javax.jcr.version.VersionManager;
+
+import org.argeo.eclipse.ui.EclipseUiException;
+import org.eclipse.jface.viewers.ITreeContentProvider;
+import org.eclipse.jface.viewers.Viewer;
+
+/**
+ * Display some version information of a JCR full versionable node in a tree
+ * like structure
+ */
+public class FullVersioningTreeContentProvider implements ITreeContentProvider {
+ private static final long serialVersionUID = 8691772509491211112L;
+
+ /**
+ * Sends back the first level of the Tree. input element must be a single
+ * node object
+ */
+ public Object[] getElements(Object inputElement) {
+ try {
+ Node rootNode = (Node) inputElement;
+ String curPath = rootNode.getPath();
+ VersionManager vm = rootNode.getSession().getWorkspace()
+ .getVersionManager();
+
+ VersionHistory vh = vm.getVersionHistory(curPath);
+ List<Version> result = new ArrayList<Version>();
+ VersionIterator vi = vh.getAllLinearVersions();
+
+ while (vi.hasNext()) {
+ result.add(vi.nextVersion());
+ }
+ return result.toArray();
+ } catch (RepositoryException re) {
+ throw new EclipseUiException(
+ "Unexpected error while getting version elements", re);
+ }
+ }
+
+ public Object[] getChildren(Object parentElement) {
+ try {
+ if (parentElement instanceof Version) {
+ List<Node> tmp = new ArrayList<Node>();
+ tmp.add(((Version) parentElement).getFrozenNode());
+ return tmp.toArray();
+ }
+ } catch (RepositoryException re) {
+ throw new EclipseUiException("Unexpected error while getting child "
+ + "node for version element", re);
+ }
+ return null;
+ }
+
+ public Object getParent(Object element) {
+ try {
+ // this will not work in a simpleVersionning environment, parent is
+ // not a node.
+ if (element instanceof Node
+ && ((Node) element).isNodeType(NodeType.NT_FROZEN_NODE)) {
+ Node node = (Node) element;
+ return node.getParent();
+ } else
+ return null;
+ } catch (RepositoryException e) {
+ return null;
+ }
+ }
+
+ public boolean hasChildren(Object element) {
+ try {
+ if (element instanceof Version)
+ return true;
+ else if (element instanceof Node)
+ return ((Node) element).hasNodes();
+ else
+ return false;
+ } catch (RepositoryException e) {
+ throw new EclipseUiException("Cannot check children of " + element, e);
+ }
+ }
+
+ public void dispose() {
+ }
+
+ public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
+ }
+
+}
--- /dev/null
+package org.argeo.cms.ui.jcr;
+
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+
+import org.argeo.cms.ui.jcr.model.RepositoriesElem;
+import org.argeo.cms.ui.jcr.model.RepositoryElem;
+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;
+
+/** Useful methods to manage the JCR Browser */
+public class JcrBrowserUtils {
+
+ public static String getPropertyTypeAsString(Property prop) {
+ try {
+ return PropertyType.nameFromValue(prop.getType());
+ } catch (RepositoryException e) {
+ throw new EclipseUiException("Cannot check type for " + prop, e);
+ }
+ }
+
+ /** Insure that the UI component is not stale, refresh if needed */
+ public static void forceRefreshIfNeeded(TreeParent element) {
+ Node curNode = null;
+
+ boolean doRefresh = false;
+
+ try {
+ if (element instanceof SingleJcrNodeElem) {
+ curNode = ((SingleJcrNodeElem) element).getNode();
+ } else if (element instanceof WorkspaceElem) {
+ curNode = ((WorkspaceElem) element).getRootNode();
+ }
+
+ if (curNode != null && element.getChildren().length != curNode.getNodes().getSize())
+ doRefresh = true;
+ else if (element instanceof RepositoryElem) {
+ RepositoryElem rn = (RepositoryElem) element;
+ if (rn.isConnected()) {
+ String[] wkpNames = rn.getAccessibleWorkspaceNames();
+ if (element.getChildren().length != wkpNames.length)
+ doRefresh = true;
+ }
+ } else if (element instanceof RepositoriesElem) {
+ doRefresh = true;
+ // Always force refresh for RepositoriesElem : the condition
+ // below does not take remote repository into account and it is
+ // not trivial to do so.
+
+ // RepositoriesElem rn = (RepositoriesElem) element;
+ // if (element.getChildren().length !=
+ // rn.getRepositoryRegister()
+ // .getRepositories().size())
+ // doRefresh = true;
+ }
+ if (doRefresh) {
+ element.clearChildren();
+ element.getChildren();
+ }
+ } catch (RepositoryException re) {
+ throw new EclipseUiException("Unexpected error while synchronising the UI with the JCR repository", re);
+ }
+ }
+}
--- /dev/null
+package org.argeo.cms.ui.jcr;
+
+import javax.jcr.Node;
+
+import org.argeo.cms.ui.jcr.model.RepositoryElem;
+import org.argeo.cms.ui.jcr.model.SingleJcrNodeElem;
+import org.argeo.cms.ui.jcr.model.WorkspaceElem;
+import org.eclipse.jface.viewers.DoubleClickEvent;
+import org.eclipse.jface.viewers.IDoubleClickListener;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.viewers.TreeViewer;
+
+/** Centralizes the management of double click on a NodeTreeViewer */
+public class JcrDClickListener implements IDoubleClickListener {
+ // private final static Log log = LogFactory
+ // .getLog(GenericNodeDoubleClickListener.class);
+
+ private TreeViewer nodeViewer;
+
+ // private JcrFileProvider jfp;
+ // private FileHandler fileHandler;
+
+ public JcrDClickListener(TreeViewer nodeViewer) {
+ this.nodeViewer = nodeViewer;
+ // jfp = new JcrFileProvider();
+ // Commented out. see https://www.argeo.org/bugzilla/show_bug.cgi?id=188
+ // fileHandler = null;
+ // fileHandler = new FileHandler(jfp);
+ }
+
+ public void doubleClick(DoubleClickEvent event) {
+ if (event.getSelection() == null || event.getSelection().isEmpty())
+ return;
+ Object obj = ((IStructuredSelection) event.getSelection()).getFirstElement();
+ if (obj instanceof RepositoryElem) {
+ RepositoryElem rpNode = (RepositoryElem) obj;
+ if (rpNode.isConnected()) {
+ rpNode.logout();
+ } else {
+ rpNode.login();
+ }
+ nodeViewer.refresh(obj);
+ } else if (obj instanceof WorkspaceElem) {
+ WorkspaceElem wn = (WorkspaceElem) obj;
+ if (wn.isConnected())
+ wn.logout();
+ else
+ wn.login();
+ nodeViewer.refresh(obj);
+ } else if (obj instanceof SingleJcrNodeElem) {
+ SingleJcrNodeElem sjn = (SingleJcrNodeElem) obj;
+ Node node = sjn.getNode();
+ openNode(node);
+ }
+ }
+
+ protected void openNode(Node node) {
+ // TODO implement generic behaviour
+ }
+}
--- /dev/null
+package org.argeo.cms.ui.jcr;
+
+import org.argeo.cms.ui.theme.CmsImages;
+import org.eclipse.swt.graphics.Image;
+
+/** Shared icons. */
+public class JcrImages {
+ public final static Image NODE = CmsImages.createIcon("node.gif");
+ public final static Image FOLDER = CmsImages.createIcon("folder.gif");
+ public final static Image FILE = CmsImages.createIcon("file.gif");
+ public final static Image BINARY = CmsImages.createIcon("binary.png");
+ public final static Image HOME = CmsImages.createIcon("person-logged-in.png");
+ public final static Image SORT = CmsImages.createIcon("sort.gif");
+ public final static Image REMOVE = CmsImages.createIcon("remove.gif");
+
+ public final static Image REPOSITORIES = CmsImages.createIcon("repositories.gif");
+ public final static Image REPOSITORY_DISCONNECTED = CmsImages.createIcon("repository_disconnected.gif");
+ public final static Image REPOSITORY_CONNECTED = CmsImages.createIcon("repository_connected.gif");
+ public final static Image REMOTE_DISCONNECTED = CmsImages.createIcon("remote_disconnected.gif");
+ public final static Image REMOTE_CONNECTED = CmsImages.createIcon("remote_connected.gif");
+ public final static Image WORKSPACE_DISCONNECTED = CmsImages.createIcon("workspace_disconnected.png");
+ public final static Image WORKSPACE_CONNECTED = CmsImages.createIcon("workspace_connected.png");
+
+}
--- /dev/null
+package org.argeo.cms.ui.jcr;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.RepositoryException;
+
+import org.argeo.eclipse.ui.EclipseUiException;
+import org.argeo.eclipse.ui.jcr.util.JcrItemsComparator;
+import org.eclipse.jface.viewers.ITreeContentProvider;
+import org.eclipse.jface.viewers.Viewer;
+
+/**
+ * Implementation of the {@code ITreeContentProvider} in order to display a
+ * single JCR node and its children in a tree like structure
+ */
+public class JcrTreeContentProvider implements ITreeContentProvider {
+ private static final long serialVersionUID = -2128326504754297297L;
+ // private Node rootNode;
+ private JcrItemsComparator itemComparator = new JcrItemsComparator();
+
+ /**
+ * Sends back the first level of the Tree. input element must be a single node
+ * object
+ */
+ public Object[] getElements(Object inputElement) {
+ Node rootNode = (Node) inputElement;
+ return childrenNodes(rootNode);
+ }
+
+ public Object[] getChildren(Object parentElement) {
+ return childrenNodes((Node) parentElement);
+ }
+
+ public Object getParent(Object element) {
+ try {
+ Node node = (Node) element;
+ if (!node.getPath().equals("/"))
+ return node.getParent();
+ else
+ return null;
+ } catch (RepositoryException e) {
+ return null;
+ }
+ }
+
+ public boolean hasChildren(Object element) {
+ try {
+ return ((Node) element).hasNodes();
+ } catch (RepositoryException e) {
+ throw new EclipseUiException("Cannot check children existence on " + element, e);
+ }
+ }
+
+ protected Object[] childrenNodes(Node parentNode) {
+ try {
+ List<Node> children = new ArrayList<Node>();
+ NodeIterator nit = parentNode.getNodes();
+ while (nit.hasNext()) {
+ Node node = nit.nextNode();
+// if (node.getName().startsWith("rep:") || node.getName().startsWith("jcr:")
+// || node.getName().startsWith("nt:"))
+// continue nodes;
+ children.add(node);
+ }
+ Node[] arr = children.toArray(new Node[0]);
+ Arrays.sort(arr, itemComparator);
+ return arr;
+ } catch (RepositoryException e) {
+ throw new EclipseUiException("Cannot list children of " + parentNode, e);
+ }
+ }
+
+ public void dispose() {
+ }
+
+ public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
+ }
+}
--- /dev/null
+package org.argeo.cms.ui.jcr;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.RepositoryFactory;
+import javax.jcr.Session;
+import javax.jcr.nodetype.NodeType;
+
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.cms.jcr.CmsJcrUtils;
+import org.argeo.cms.security.Keyring;
+import org.argeo.cms.ui.jcr.model.RepositoriesElem;
+import org.argeo.cms.ui.jcr.model.SingleJcrNodeElem;
+import org.argeo.cms.ux.widgets.TreeParent;
+import org.eclipse.jface.viewers.ITreeContentProvider;
+import org.eclipse.jface.viewers.Viewer;
+
+/**
+ * Implementation of the {@code ITreeContentProvider} to display multiple
+ * repository environment in a tree like structure
+ */
+public class NodeContentProvider implements ITreeContentProvider {
+ private static final long serialVersionUID = -4083809398848374403L;
+ final private RepositoryRegister repositoryRegister;
+ final private RepositoryFactory repositoryFactory;
+
+ // Current user session on the default workspace of the argeo Node
+ final private Session userSession;
+ final private Keyring keyring;
+ private boolean sortChildren;
+
+ // Reference for cleaning
+ private SingleJcrNodeElem homeNode = null;
+ private RepositoriesElem repositoriesNode = null;
+
+ // Utils
+ private TreeBrowserComparator itemComparator = new TreeBrowserComparator();
+
+ public NodeContentProvider(Session userSession, Keyring keyring,
+ RepositoryRegister repositoryRegister,
+ RepositoryFactory repositoryFactory, Boolean sortChildren) {
+ this.userSession = userSession;
+ this.keyring = keyring;
+ this.repositoryRegister = repositoryRegister;
+ this.repositoryFactory = repositoryFactory;
+ this.sortChildren = sortChildren;
+ }
+
+ public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
+ if (newInput == null)// dispose
+ return;
+
+ if (userSession != null) {
+ Node userHome = CmsJcrUtils.getUserHome(userSession);
+ if (userHome != null) {
+ // TODO : find a way to dynamically get alias for the node
+ if (homeNode != null)
+ homeNode.dispose();
+ homeNode = new SingleJcrNodeElem(null, userHome,
+ userSession.getUserID(), CmsConstants.EGO_REPOSITORY);
+ }
+ }
+ if (repositoryRegister != null) {
+ if (repositoriesNode != null)
+ repositoriesNode.dispose();
+ repositoriesNode = new RepositoriesElem("Repositories",
+ repositoryRegister, repositoryFactory, null, userSession,
+ keyring);
+ }
+ }
+
+ /**
+ * Sends back the first level of the Tree. Independent from inputElement
+ * that can be null
+ */
+ public Object[] getElements(Object inputElement) {
+ List<Object> objs = new ArrayList<Object>();
+ if (homeNode != null)
+ objs.add(homeNode);
+ if (repositoriesNode != null)
+ objs.add(repositoriesNode);
+ return objs.toArray();
+ }
+
+ public Object[] getChildren(Object parentElement) {
+ if (parentElement instanceof TreeParent) {
+ if (sortChildren) {
+ Object[] tmpArr = ((TreeParent) parentElement).getChildren();
+ if (tmpArr == null)
+ return new Object[0];
+ TreeParent[] arr = new TreeParent[tmpArr.length];
+ for (int i = 0; i < tmpArr.length; i++)
+ arr[i] = (TreeParent) tmpArr[i];
+ Arrays.sort(arr, itemComparator);
+ return arr;
+ } else
+ return ((TreeParent) parentElement).getChildren();
+ } else
+ return new Object[0];
+ }
+
+ /**
+ * Sets whether the content provider should order the children nodes or not.
+ * It is user duty to call a full refresh of the tree after changing this
+ * parameter.
+ */
+ public void setSortChildren(boolean sortChildren) {
+ this.sortChildren = sortChildren;
+ }
+
+ public Object getParent(Object element) {
+ if (element instanceof TreeParent) {
+ return ((TreeParent) element).getParent();
+ } else
+ return null;
+ }
+
+ public boolean hasChildren(Object element) {
+ if (element instanceof RepositoriesElem) {
+ RepositoryRegister rr = ((RepositoriesElem) element)
+ .getRepositoryRegister();
+ return rr.getRepositories().size() > 0;
+ } else if (element instanceof TreeParent) {
+ TreeParent tp = (TreeParent) element;
+ return tp.hasChildren();
+ }
+ return false;
+ }
+
+ public void dispose() {
+ if (homeNode != null)
+ homeNode.dispose();
+ if (repositoriesNode != null) {
+ // logs out open sessions
+ // see https://bugzilla.argeo.org/show_bug.cgi?id=23
+ repositoriesNode.dispose();
+ }
+ }
+
+ /**
+ * Specific comparator for this view. See specification here:
+ * https://www.argeo.org/bugzilla/show_bug.cgi?id=139
+ */
+ private class TreeBrowserComparator implements Comparator<TreeParent> {
+
+ public int category(TreeParent element) {
+ if (element instanceof SingleJcrNodeElem) {
+ Node node = ((SingleJcrNodeElem) element).getNode();
+ try {
+ if (node.isNodeType(NodeType.NT_FOLDER))
+ return 5;
+ } catch (RepositoryException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+ return 10;
+ }
+
+ public int compare(TreeParent o1, TreeParent o2) {
+ int cat1 = category(o1);
+ int cat2 = category(o2);
+
+ if (cat1 != cat2) {
+ return cat1 - cat2;
+ }
+ return o1.getName().compareTo(o2.getName());
+ }
+ }
+}
--- /dev/null
+package org.argeo.cms.ui.jcr;
+
+import javax.jcr.NamespaceException;
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.RepositoryException;
+import javax.jcr.nodetype.NodeType;
+
+import org.argeo.api.cms.CmsLog;
+import org.argeo.cms.ui.jcr.model.RemoteRepositoryElem;
+import org.argeo.cms.ui.jcr.model.RepositoriesElem;
+import org.argeo.cms.ui.jcr.model.RepositoryElem;
+import org.argeo.cms.ui.jcr.model.SingleJcrNodeElem;
+import org.argeo.cms.ui.jcr.model.WorkspaceElem;
+import org.argeo.eclipse.ui.EclipseUiException;
+import org.eclipse.jface.viewers.ColumnLabelProvider;
+import org.eclipse.swt.graphics.Image;
+
+/** Provides reasonable defaults for know JCR types. */
+public class NodeLabelProvider extends ColumnLabelProvider {
+ private static final long serialVersionUID = -3662051696443321843L;
+
+ private final static CmsLog log = CmsLog.getLog(NodeLabelProvider.class);
+
+ public String getText(Object element) {
+ try {
+ if (element instanceof SingleJcrNodeElem) {
+ SingleJcrNodeElem sjn = (SingleJcrNodeElem) element;
+ return getText(sjn.getNode());
+ } else if (element instanceof Node) {
+ return getText((Node) element);
+ } else
+ return super.getText(element);
+ } catch (RepositoryException e) {
+ throw new EclipseUiException("Unexpected JCR error while getting node name.");
+ }
+ }
+
+ protected String getText(Node node) throws RepositoryException {
+ String label = node.getName();
+ StringBuffer mixins = new StringBuffer("");
+ for (NodeType type : node.getMixinNodeTypes())
+ mixins.append(' ').append(type.getName());
+
+ return label + " [" + node.getPrimaryNodeType().getName() + mixins + "]";
+ }
+
+ @Override
+ public Image getImage(Object element) {
+ if (element instanceof RemoteRepositoryElem) {
+ if (((RemoteRepositoryElem) element).isConnected())
+ return JcrImages.REMOTE_CONNECTED;
+ else
+ return JcrImages.REMOTE_DISCONNECTED;
+ } else if (element instanceof RepositoryElem) {
+ if (((RepositoryElem) element).isConnected())
+ return JcrImages.REPOSITORY_CONNECTED;
+ else
+ return JcrImages.REPOSITORY_DISCONNECTED;
+ } else if (element instanceof WorkspaceElem) {
+ if (((WorkspaceElem) element).isConnected())
+ return JcrImages.WORKSPACE_CONNECTED;
+ else
+ return JcrImages.WORKSPACE_DISCONNECTED;
+ } else if (element instanceof RepositoriesElem) {
+ return JcrImages.REPOSITORIES;
+ } else if (element instanceof SingleJcrNodeElem) {
+ Node nodeElem = ((SingleJcrNodeElem) element).getNode();
+ return getImage(nodeElem);
+
+ // if (element instanceof Node) {
+ // return getImage((Node) element);
+ // } else if (element instanceof WrappedNode) {
+ // return getImage(((WrappedNode) element).getNode());
+ // } else if (element instanceof NodesWrapper) {
+ // return getImage(((NodesWrapper) element).getNode());
+ // }
+ }
+ // try {
+ // return super.getImage();
+ // } catch (RepositoryException e) {
+ // return null;
+ // }
+ return super.getImage(element);
+ }
+
+ protected Image getImage(Node node) {
+ try {
+ if (node.getPrimaryNodeType().isNodeType(NodeType.NT_FILE))
+ return JcrImages.FILE;
+ else if (node.getPrimaryNodeType().isNodeType(NodeType.NT_FOLDER))
+ return JcrImages.FOLDER;
+ else if (node.getPrimaryNodeType().isNodeType(NodeType.NT_RESOURCE))
+ return JcrImages.BINARY;
+ try {
+ // TODO check workspace type?
+ if (node.getDepth() == 1 && node.hasProperty(Property.JCR_ID))
+ return JcrImages.HOME;
+
+ // optimizes
+// if (node.hasProperty(LdapAttrs.uid.property()) && node.isNodeType(NodeTypes.NODE_USER_HOME))
+// return JcrImages.HOME;
+ } catch (NamespaceException e) {
+ // node namespace is not registered in this repo
+ }
+ return JcrImages.NODE;
+ } catch (RepositoryException e) {
+ log.warn("Error while retrieving type for " + node + " in order to display corresponding image");
+ e.printStackTrace();
+ return null;
+ }
+ }
+}
--- /dev/null
+package org.argeo.cms.ui.jcr;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.jcr.Repository;
+
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.ServiceReference;
+import org.osgi.util.tracker.ServiceTracker;
+
+public class OsgiRepositoryRegister extends DefaultRepositoryRegister {
+ private final static BundleContext bc = FrameworkUtil.getBundle(OsgiRepositoryRegister.class).getBundleContext();
+ private final ServiceTracker<Repository, Repository> repositoryTracker;
+
+ public OsgiRepositoryRegister() {
+ repositoryTracker = new ServiceTracker<Repository, Repository>(bc, Repository.class, null) {
+
+ @Override
+ public Repository addingService(ServiceReference<Repository> reference) {
+
+ Repository repository = super.addingService(reference);
+ Map<String, Object> props = new HashMap<>();
+ for (String key : reference.getPropertyKeys()) {
+ props.put(key, reference.getProperty(key));
+ }
+ register(repository, props);
+ return repository;
+ }
+
+ @Override
+ public void removedService(ServiceReference<Repository> reference, Repository service) {
+ Map<String, Object> props = new HashMap<>();
+ for (String key : reference.getPropertyKeys()) {
+ props.put(key, reference.getProperty(key));
+ }
+ unregister(service, props);
+ super.removedService(reference, service);
+ }
+
+ };
+ }
+
+ public void init() {
+ repositoryTracker.open();
+ }
+
+ public void destroy() {
+ repositoryTracker.close();
+ }
+}
--- /dev/null
+package org.argeo.cms.ui.jcr;
+
+import java.util.Set;
+import java.util.TreeSet;
+
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.PropertyIterator;
+import javax.jcr.RepositoryException;
+
+import org.argeo.eclipse.ui.EclipseUiException;
+import org.argeo.eclipse.ui.jcr.util.JcrItemsComparator;
+import org.eclipse.jface.viewers.IStructuredContentProvider;
+import org.eclipse.jface.viewers.Viewer;
+
+/** Simple content provider that displays all properties of a given Node */
+public class PropertiesContentProvider implements IStructuredContentProvider {
+ private static final long serialVersionUID = 5227554668841613078L;
+ private JcrItemsComparator itemComparator = new JcrItemsComparator();
+
+ public void dispose() {
+ }
+
+ public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
+ }
+
+ public Object[] getElements(Object inputElement) {
+ try {
+ if (inputElement instanceof Node) {
+ Set<Property> props = new TreeSet<Property>(itemComparator);
+ PropertyIterator pit = ((Node) inputElement).getProperties();
+ while (pit.hasNext())
+ props.add(pit.nextProperty());
+ return props.toArray();
+ }
+ return new Object[] {};
+ } catch (RepositoryException e) {
+ throw new EclipseUiException("Cannot get element for "
+ + inputElement, e);
+ }
+ }
+}
--- /dev/null
+package org.argeo.cms.ui.jcr;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+
+import javax.jcr.Property;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import javax.jcr.Value;
+
+import org.argeo.eclipse.ui.EclipseUiException;
+import org.argeo.jcr.JcrUtils;
+import org.eclipse.jface.viewers.ColumnLabelProvider;
+import org.eclipse.jface.viewers.ViewerCell;
+
+/** Default basic label provider for a given JCR Node's properties */
+public class PropertyLabelProvider extends ColumnLabelProvider {
+ private static final long serialVersionUID = -5405794508731390147L;
+
+ // To be able to change column order easily
+ public static final int COLUMN_PROPERTY = 0;
+ public static final int COLUMN_VALUE = 1;
+ public static final int COLUMN_TYPE = 2;
+ public static final int COLUMN_ATTRIBUTES = 3;
+
+ private final static String DATE_TIME_FORMAT = "dd/MM/yyyy, HH:mm";
+
+ // Utils
+ protected DateFormat timeFormatter = new SimpleDateFormat(DATE_TIME_FORMAT);
+
+ public void update(ViewerCell cell) {
+ Object element = cell.getElement();
+ cell.setText(getColumnText(element, cell.getColumnIndex()));
+ }
+
+ public String getColumnText(Object element, int columnIndex) {
+ try {
+ if (element instanceof Property) {
+ Property prop = (Property) element;
+ if (prop.isMultiple()) {
+ switch (columnIndex) {
+ case COLUMN_PROPERTY:
+ return prop.getName();
+ case COLUMN_VALUE:
+ // Corresponding values are listed on children
+ return "";
+ case COLUMN_TYPE:
+ return JcrBrowserUtils.getPropertyTypeAsString(prop);
+ case COLUMN_ATTRIBUTES:
+ return JcrUtils.getPropertyDefinitionAsString(prop);
+ }
+ } else {
+ switch (columnIndex) {
+ case COLUMN_PROPERTY:
+ return prop.getName();
+ case COLUMN_VALUE:
+ return formatValueAsString(prop.getValue());
+ case COLUMN_TYPE:
+ return JcrBrowserUtils.getPropertyTypeAsString(prop);
+ case COLUMN_ATTRIBUTES:
+ return JcrUtils.getPropertyDefinitionAsString(prop);
+ }
+ }
+ } else if (element instanceof Value) {
+ Value val = (Value) element;
+ switch (columnIndex) {
+ case COLUMN_PROPERTY:
+ // Nothing to show
+ return "";
+ case COLUMN_VALUE:
+ return formatValueAsString(val);
+ case COLUMN_TYPE:
+ // listed on the parent
+ return "";
+ case COLUMN_ATTRIBUTES:
+ // Corresponding attributes are listed on the parent
+ return "";
+ }
+ }
+ } catch (RepositoryException re) {
+ throw new EclipseUiException("Cannot retrieve prop value on " + element, re);
+ }
+ return null;
+ }
+
+ private String formatValueAsString(Value value) {
+ // TODO enhance this method
+ try {
+ String strValue;
+
+ if (value.getType() == PropertyType.BINARY)
+ strValue = "<binary>";
+ else if (value.getType() == PropertyType.DATE)
+ strValue = timeFormatter.format(value.getDate().getTime());
+ else
+ strValue = value.getString();
+ return strValue;
+ } catch (RepositoryException e) {
+ throw new EclipseUiException("unexpected error while formatting value", e);
+ }
+ }
+}
--- /dev/null
+package org.argeo.cms.ui.jcr;
+
+import java.util.Map;
+
+import javax.jcr.Repository;
+import javax.jcr.RepositoryFactory;
+
+/** Allows to register repositories by name. */
+public interface RepositoryRegister extends RepositoryFactory {
+ /**
+ * The registered {@link Repository} as a read-only map. Note that this
+ * method should be called for each access in order to be sure to be up to
+ * date in case repositories have registered/unregistered
+ */
+ public Map<String, Repository> getRepositories();
+}
--- /dev/null
+package org.argeo.cms.ui.jcr;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.version.Version;
+
+import org.argeo.eclipse.ui.EclipseUiException;
+import org.eclipse.jface.viewers.ColumnLabelProvider;
+
+/**
+ * Simple wrapping of the ColumnLabelProvider class to provide text display in
+ * order to build a tree for version. The getText() method does not assume that
+ * {@link Version} extends {@link Node} class to respect JCR 2.0 specification
+ *
+ */
+public class VersionLabelProvider extends ColumnLabelProvider {
+ private static final long serialVersionUID = 5270739851193688238L;
+
+ public String getText(Object element) {
+ try {
+ if (element instanceof Version) {
+ Version version = (Version) element;
+ return version.getName();
+ } else if (element instanceof Node) {
+ return ((Node) element).getName();
+ }
+ } catch (RepositoryException re) {
+ throw new EclipseUiException(
+ "Unexpected error while getting element name", re);
+ }
+ return super.getText(element);
+ }
+}
--- /dev/null
+package org.argeo.cms.ui.jcr.model;
+
+import javax.jcr.Repository;
+
+import org.argeo.cms.ux.widgets.TreeParent;
+
+/** Wrap a MaintainedRepository */
+public class MaintainedRepositoryElem extends RepositoryElem {
+
+ public MaintainedRepositoryElem(String alias, Repository repository, TreeParent parent) {
+ super(alias, repository, parent);
+ // if (!(repository instanceof MaintainedRepository)) {
+ // throw new ArgeoException("Repository " + alias
+ // + " is not a maintained repository");
+ // }
+ }
+
+ // protected MaintainedRepository getMaintainedRepository() {
+ // return (MaintainedRepository) getRepository();
+ // }
+}
--- /dev/null
+package org.argeo.cms.ui.jcr.model;
+
+import java.util.Arrays;
+
+import javax.jcr.Node;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.RepositoryFactory;
+import javax.jcr.Session;
+import javax.jcr.SimpleCredentials;
+
+import org.argeo.cms.ArgeoNames;
+import org.argeo.cms.jcr.CmsJcrUtils;
+import org.argeo.cms.security.Keyring;
+import org.argeo.cms.ux.widgets.TreeParent;
+import org.argeo.eclipse.ui.EclipseUiException;
+
+/** Root of a remote repository */
+public class RemoteRepositoryElem extends RepositoryElem {
+ private final Keyring keyring;
+ /**
+ * A session of the logged in user on the default workspace of the node
+ * repository.
+ */
+ private final Session userSession;
+ private final String remoteNodePath;
+
+ private final RepositoryFactory repositoryFactory;
+ private final String uri;
+
+ public RemoteRepositoryElem(String alias, RepositoryFactory repositoryFactory, String uri, TreeParent parent,
+ Session userSession, Keyring keyring, String remoteNodePath) {
+ super(alias, null, parent);
+ this.repositoryFactory = repositoryFactory;
+ this.uri = uri;
+ this.keyring = keyring;
+ this.userSession = userSession;
+ this.remoteNodePath = remoteNodePath;
+ }
+
+ @Override
+ protected Session repositoryLogin(String workspaceName) throws RepositoryException {
+ Node remoteRepository = userSession.getNode(remoteNodePath);
+ String userID = remoteRepository.getProperty(ArgeoNames.ARGEO_USER_ID).getString();
+ if (userID.trim().equals("")) {
+ return getRepository().login(workspaceName);
+ } else {
+ String pwdPath = remoteRepository.getPath() + '/' + ArgeoNames.ARGEO_PASSWORD;
+ char[] password = keyring.getAsChars(pwdPath);
+ try {
+ SimpleCredentials credentials = new SimpleCredentials(userID, password);
+ return getRepository().login(credentials, workspaceName);
+ } finally {
+ Arrays.fill(password, 0, password.length, ' ');
+ }
+ }
+ }
+
+ @Override
+ public Repository getRepository() {
+ if (repository == null)
+ repository = CmsJcrUtils.getRepositoryByUri(repositoryFactory, uri);
+ return super.getRepository();
+ }
+
+ public void remove() {
+ try {
+ Node remoteNode = userSession.getNode(remoteNodePath);
+ remoteNode.remove();
+ remoteNode.getSession().save();
+ } catch (RepositoryException e) {
+ throw new EclipseUiException("Cannot remove " + remoteNodePath, e);
+ }
+ }
+
+}
--- /dev/null
+package org.argeo.cms.ui.jcr.model;
+
+import java.util.Map;
+
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.RepositoryFactory;
+import javax.jcr.Session;
+
+import org.argeo.cms.ArgeoNames;
+import org.argeo.cms.jcr.CmsJcrUtils;
+import org.argeo.cms.security.Keyring;
+import org.argeo.cms.ui.jcr.RepositoryRegister;
+import org.argeo.cms.ux.widgets.TreeParent;
+import org.argeo.eclipse.ui.EclipseUiException;
+import org.argeo.eclipse.ui.dialogs.ErrorFeedback;
+
+/**
+ * UI Tree component that implements the Argeo abstraction of a
+ * {@link RepositoryFactory} that enable a user to "mount" various repositories
+ * in a single Tree like View. It is usually meant to be at the root of the UI
+ * Tree and thus {@link #getParent()} method will return null.
+ *
+ * The {@link RepositoryFactory} is injected at instantiation time and must be
+ * use get or register new {@link Repository} objects upon which a reference is
+ * kept here.
+ */
+
+public class RepositoriesElem extends TreeParent implements ArgeoNames {
+ private final RepositoryRegister repositoryRegister;
+ private final RepositoryFactory repositoryFactory;
+
+ /**
+ * A session of the logged in user on the default workspace of the node
+ * repository.
+ */
+ private final Session userSession;
+ private final Keyring keyring;
+
+ public RepositoriesElem(String name, RepositoryRegister repositoryRegister, RepositoryFactory repositoryFactory,
+ TreeParent parent, Session userSession, Keyring keyring) {
+ super(name);
+ this.repositoryRegister = repositoryRegister;
+ this.repositoryFactory = repositoryFactory;
+ this.userSession = userSession;
+ this.keyring = keyring;
+ }
+
+ /**
+ * Override normal behavior to initialize the various repositories only at
+ * request time
+ */
+ @Override
+ public synchronized Object[] getChildren() {
+ if (isLoaded()) {
+ return super.getChildren();
+ } else {
+ // initialize current object
+ Map<String, Repository> refRepos = repositoryRegister.getRepositories();
+ for (String name : refRepos.keySet()) {
+ Repository repository = refRepos.get(name);
+ // if (repository instanceof MaintainedRepository)
+ // super.addChild(new MaintainedRepositoryElem(name,
+ // repository, this));
+ // else
+ super.addChild(new RepositoryElem(name, repository, this));
+ }
+
+ // remote
+ if (keyring != null) {
+ try {
+ addRemoteRepositories(keyring);
+ } catch (RepositoryException e) {
+ throw new EclipseUiException("Cannot browse remote repositories", e);
+ }
+ }
+ return super.getChildren();
+ }
+ }
+
+ protected void addRemoteRepositories(Keyring jcrKeyring) throws RepositoryException {
+ Node userHome = CmsJcrUtils.getUserHome(userSession);
+ if (userHome != null && userHome.hasNode(ARGEO_REMOTE)) {
+ NodeIterator it = userHome.getNode(ARGEO_REMOTE).getNodes();
+ while (it.hasNext()) {
+ Node remoteNode = it.nextNode();
+ String uri = remoteNode.getProperty(ARGEO_URI).getString();
+ try {
+ RemoteRepositoryElem remoteRepositoryNode = new RemoteRepositoryElem(remoteNode.getName(),
+ repositoryFactory, uri, this, userSession, jcrKeyring, remoteNode.getPath());
+ super.addChild(remoteRepositoryNode);
+ } catch (Exception e) {
+ ErrorFeedback.show("Cannot add remote repository " + remoteNode, e);
+ }
+ }
+ }
+ }
+
+ public void registerNewRepository(String alias, Repository repository) {
+ // TODO: implement this
+ // Create a new RepositoryNode Object
+ // add it
+ // super.addChild(new RepositoriesNode(...));
+ }
+
+ /** Returns the {@link RepositoryRegister} wrapped by this object. */
+ public RepositoryRegister getRepositoryRegister() {
+ return repositoryRegister;
+ }
+}
--- /dev/null
+package org.argeo.cms.ui.jcr.model;
+
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.cms.ux.widgets.TreeParent;
+import org.argeo.eclipse.ui.EclipseUiException;
+import org.argeo.jcr.JcrUtils;
+
+/**
+ * UI Tree component that wraps a JCR {@link Repository}. It also keeps a
+ * reference to its parent Tree Ui component; typically the unique
+ * {@link RepositoriesElem} object of the current view to enable bi-directionnal
+ * browsing in the tree.
+ */
+
+public class RepositoryElem extends TreeParent {
+ private String alias;
+ protected Repository repository;
+ private Session defaultSession = null;
+
+ /** Create a new repository with distinct name and alias */
+ public RepositoryElem(String alias, Repository repository, TreeParent parent) {
+ super(alias);
+ this.repository = repository;
+ setParent(parent);
+ this.alias = alias;
+ }
+
+ public void login() {
+ try {
+ defaultSession = repositoryLogin(CmsConstants.SYS_WORKSPACE);
+ String[] wkpNames = defaultSession.getWorkspace().getAccessibleWorkspaceNames();
+ for (String wkpName : wkpNames) {
+ if (wkpName.equals(defaultSession.getWorkspace().getName()))
+ addChild(new WorkspaceElem(this, wkpName, defaultSession));
+ else
+ addChild(new WorkspaceElem(this, wkpName));
+ }
+ } catch (RepositoryException e) {
+ throw new EclipseUiException("Cannot connect to repository " + alias, e);
+ }
+ }
+
+ public synchronized void logout() {
+ for (Object child : getChildren()) {
+ if (child instanceof WorkspaceElem)
+ ((WorkspaceElem) child).logout();
+ }
+ clearChildren();
+ JcrUtils.logoutQuietly(defaultSession);
+ defaultSession = null;
+ }
+
+ /**
+ * Actual call to the {@link Repository#login(javax.jcr.Credentials, String)}
+ * method. To be overridden.
+ */
+ protected Session repositoryLogin(String workspaceName) throws RepositoryException {
+ return repository.login(workspaceName);
+ }
+
+ public String[] getAccessibleWorkspaceNames() {
+ try {
+ return defaultSession.getWorkspace().getAccessibleWorkspaceNames();
+ } catch (RepositoryException e) {
+ throw new EclipseUiException("Cannot retrieve workspace names", e);
+ }
+ }
+
+ public void createWorkspace(String workspaceName) {
+ if (!isConnected())
+ login();
+ try {
+ defaultSession.getWorkspace().createWorkspace(workspaceName);
+ } catch (RepositoryException e) {
+ throw new EclipseUiException("Cannot create workspace", e);
+ }
+ }
+
+ /** returns the {@link Repository} referenced by the current UI Node */
+ public Repository getRepository() {
+ return repository;
+ }
+
+ public String getAlias() {
+ return alias;
+ }
+
+ public Boolean isConnected() {
+ if (defaultSession != null && defaultSession.isLive())
+ return true;
+ else
+ return false;
+ }
+}
--- /dev/null
+package org.argeo.cms.ui.jcr.model;
+
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.RepositoryException;
+import javax.jcr.Workspace;
+
+import org.argeo.cms.ux.widgets.TreeParent;
+import org.argeo.eclipse.ui.EclipseUiException;
+
+/**
+ * UI Tree component. Wraps a node of a JCR {@link Workspace}. It also keeps a
+ * reference to its parent node that can either be a {@link WorkspaceElem}, a
+ * {@link SingleJcrNodeElem} or null if the node is "mounted" as the root of the
+ * UI tree.
+ */
+public class SingleJcrNodeElem extends TreeParent {
+
+ private final Node node;
+ private String alias = null;
+
+ /** Creates a new UiNode in the UI Tree */
+ public SingleJcrNodeElem(TreeParent parent, Node node, String name) {
+ super(name);
+ setParent(parent);
+ this.node = node;
+ }
+
+ /**
+ * Creates a new UiNode in the UI Tree, keeping a reference to the alias of
+ * the corresponding repository in the current UI environment. It is useful
+ * to be able to mount nodes as roots of the UI tree.
+ */
+ public SingleJcrNodeElem(TreeParent parent, Node node, String name, String alias) {
+ super(name);
+ setParent(parent);
+ this.node = node;
+ this.alias = alias;
+ }
+
+ /** Returns the node wrapped by the current UI object */
+ public Node getNode() {
+ return node;
+ }
+
+ protected String getRepositoryAlias() {
+ return alias;
+ }
+
+ /**
+ * Overrides normal behaviour to initialise children only when first
+ * requested
+ */
+ @Override
+ public synchronized Object[] getChildren() {
+ if (isLoaded()) {
+ return super.getChildren();
+ } else {
+ // initialize current object
+ try {
+ NodeIterator ni = node.getNodes();
+ while (ni.hasNext()) {
+ Node curNode = ni.nextNode();
+ addChild(new SingleJcrNodeElem(this, curNode, curNode.getName()));
+ }
+ return super.getChildren();
+ } catch (RepositoryException re) {
+ throw new EclipseUiException("Cannot initialize SingleJcrNode children", re);
+ }
+ }
+ }
+
+ @Override
+ public boolean hasChildren() {
+ try {
+ if (node.getSession().isLive())
+ return node.hasNodes();
+ else
+ return false;
+ } catch (RepositoryException re) {
+ throw new EclipseUiException("Cannot check children node existence", re);
+ }
+ }
+}
--- /dev/null
+package org.argeo.cms.ui.jcr.model;
+
+import javax.jcr.AccessDeniedException;
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+// import javax.jcr.Workspace;
+import javax.jcr.Workspace;
+
+import org.argeo.cms.ux.widgets.TreeParent;
+import org.argeo.eclipse.ui.EclipseUiException;
+import org.argeo.jcr.JcrUtils;
+
+/**
+ * UI Tree component. Wraps the root node of a JCR {@link Workspace}. It also
+ * keeps a reference to its parent {@link RepositoryElem}, to be able to
+ * retrieve alias of the current used repository
+ */
+public class WorkspaceElem extends TreeParent {
+ private Session session = null;
+
+ public WorkspaceElem(RepositoryElem parent, String name) {
+ this(parent, name, null);
+ }
+
+ public WorkspaceElem(RepositoryElem parent, String name, Session session) {
+ super(name);
+ this.session = session;
+ setParent(parent);
+ }
+
+ public synchronized Session getSession() {
+ return session;
+ }
+
+ public synchronized Node getRootNode() {
+ try {
+ if (session != null)
+ return session.getRootNode();
+ else
+ return null;
+ } catch (RepositoryException e) {
+ throw new EclipseUiException("Cannot get root node of workspace " + getName(), e);
+ }
+ }
+
+ public synchronized void login() {
+ try {
+ session = ((RepositoryElem) getParent()).repositoryLogin(getName());
+ } catch (RepositoryException e) {
+ throw new EclipseUiException("Cannot connect to repository " + getName(), e);
+ }
+ }
+
+ public Boolean isConnected() {
+ if (session != null && session.isLive())
+ return true;
+ else
+ return false;
+ }
+
+ @Override
+ public synchronized void dispose() {
+ logout();
+ super.dispose();
+ }
+
+ /** Logouts the session, does not nothing if there is no live session. */
+ public synchronized void logout() {
+ clearChildren();
+ JcrUtils.logoutQuietly(session);
+ session = null;
+ }
+
+ @Override
+ public synchronized boolean hasChildren() {
+ try {
+ if (isConnected())
+ try {
+ return session.getRootNode().hasNodes();
+ } catch (AccessDeniedException e) {
+ // current user may not have access to the root node
+ return false;
+ }
+ else
+ return false;
+ } catch (RepositoryException re) {
+ throw new EclipseUiException("Unexpected error while checking children node existence", re);
+ }
+ }
+
+ /** Override normal behaviour to initialize display of the workspace */
+ @Override
+ public synchronized Object[] getChildren() {
+ if (isLoaded()) {
+ return super.getChildren();
+ } else {
+ // initialize current object
+ try {
+ Node rootNode;
+ if (session == null)
+ return null;
+ else
+ rootNode = session.getRootNode();
+ NodeIterator ni = rootNode.getNodes();
+ while (ni.hasNext()) {
+ Node node = ni.nextNode();
+ addChild(new SingleJcrNodeElem(this, node, node.getName()));
+ }
+ return super.getChildren();
+ } catch (RepositoryException e) {
+ throw new EclipseUiException("Cannot initialize WorkspaceNode UI object." + getName(), e);
+ }
+ }
+ }
+}
--- /dev/null
+/** Model for SWT/JFace JCR components. */
+package org.argeo.cms.ui.jcr.model;
\ No newline at end of file
--- /dev/null
+/** SWT/JFace JCR components. */
+package org.argeo.cms.ui.jcr;
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-17"/>
+ <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+ <classpathentry kind="src" path="src"/>
+ <classpathentry kind="output" path="bin"/>
+</classpath>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>org.argeo.tool.swt</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.eclipse.jdt.core.javabuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.pde.ManifestBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.pde.SchemaBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.eclipse.pde.PluginNature</nature>
+ <nature>org.eclipse.jdt.core.javanature</nature>
+ </natures>
+</projectDescription>
--- /dev/null
+Bundle-ActivationPolicy: lazy
--- /dev/null
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+ .
--- /dev/null
+package org.argeo.cms.ui.theme;
+
+import java.net.URL;
+
+import org.eclipse.jface.resource.ImageDescriptor;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.widgets.Display;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
+
+public class CmsImages {
+ private static BundleContext themeBc = FrameworkUtil.getBundle(CmsImages.class).getBundleContext();
+
+ final public static String ICONS_BASE = "icons/";
+ final public static String TYPES_BASE = ICONS_BASE + "types/";
+ final public static String ACTIONS_BASE = ICONS_BASE + "actions/";
+
+ public static Image createIcon(String name) {
+ return createImg(CmsImages.ICONS_BASE + name);
+ }
+
+ public static Image createAction(String name) {
+ return createImg(CmsImages.ACTIONS_BASE + name);
+ }
+
+ public static Image createType(String name) {
+ return createImg(CmsImages.TYPES_BASE + name);
+ }
+
+ public static Image createImg(String name) {
+ return CmsImages.createDesc(name).createImage(Display.getDefault());
+ }
+
+ public static ImageDescriptor createDesc(String name) {
+ return createDesc(themeBc, name);
+ }
+
+ public static ImageDescriptor createDesc(BundleContext bc, String name) {
+ URL url = bc.getBundle().getResource(name);
+ if (url == null)
+ return ImageDescriptor.getMissingImageDescriptor();
+ return ImageDescriptor.createFromURL(url);
+ }
+
+ public static Image createImg(BundleContext bc, String name) {
+ return createDesc(bc, name).createImage(Display.getDefault());
+ }
+
+}
--- /dev/null
+/** Argeo CMS core theme images. */
+package org.argeo.cms.ui.theme;
\ No newline at end of file