licensing issues with GPL.
rcp: base
$(JVM) -jar $(ECJ_JAR) -11 -nowarn -time -cp $(RCP_CLASSPATH) \
- $(SDK_SRC_BASE)/org.argeo.cms.servlet/src[-d $(SDK_SRC_BASE)/org.argeo.cms.servlet/bin] \
+ $(SDK_SRC_BASE)/eclipse/org.argeo.cms.servlet/src[-d $(SDK_SRC_BASE)/eclipse/org.argeo.cms.servlet/bin] \
$(SDK_SRC_BASE)/rcp/org.argeo.swt.minidesktop/src[-d $(SDK_SRC_BASE)/rcp/org.argeo.swt.minidesktop/bin] \
$(SDK_SRC_BASE)/rcp/org.argeo.swt.specific.rcp/src[-d $(SDK_SRC_BASE)/rcp/org.argeo.swt.specific.rcp/bin] \
- $(SDK_SRC_BASE)/org.argeo.cms.swt/src[-d $(SDK_SRC_BASE)/org.argeo.cms.swt/bin] \
+ $(SDK_SRC_BASE)/eclipse/org.argeo.cms.swt/src[-d $(SDK_SRC_BASE)/eclipse/org.argeo.cms.swt/bin] \
$(SDK_SRC_BASE)/rcp/org.argeo.cms.ui.rcp/src[-d $(SDK_SRC_BASE)/rcp/org.argeo.cms.ui.rcp/bin] \
--- /dev/null
+-include: ../../cnf/maven.bnd
\ 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-11"/>
+ <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.cms.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
+/MANIFEST.MF
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="Default CallbackHandler">
+ <implementation class="org.argeo.cms.swt.auth.DynamicCallbackHandler"/>
+ <service>
+ <provide interface="javax.security.auth.callback.CallbackHandler"/>
+ </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=home)"/>
+ <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.osgi.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
+Service-Component: OSGI-INF/homeRepository.xml,\
+OSGI-INF/userAdminWrapper.xml,\
+OSGI-INF/defaultCallbackHandler.xml
+Bundle-ActivationPolicy: lazy
+
+Import-Package: org.eclipse.swt,\
+org.eclipse.swt.widgets;version="0.0.0",\
+org.eclipse.e4.ui.model.application.ui,\
+org.eclipse.e4.ui.model.application,\
+javax.jcr.nodetype,\
+org.argeo.cms,\
+org.eclipse.core.commands.common,\
+org.eclipse.jface.window,\
+org.argeo.cms.swt.auth,\
+*
--- /dev/null
+output.. = bin/
+bin.includes = META-INF/,\
+ OSGI-INF/,\
+ .,\
+ OSGI-INF/homeRepository.xml,\
+ OSGI-INF/userAdminWrapper.xml,\
+ OSGI-INF/defaultCallbackHandler.xml,\
+ e4xmi/cms-demo.e4xmi
+source.. = src/
--- /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.cms.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.cms.e4/org.argeo.cms.e4.users.UsersView" label="Users" iconURI="platform:/plugin/org.argeo.cms.swt/icons/person.png">
+ <handlers xmi:id="_0mN68DvjEeiF1foPJZSZkw" elementId="org.argeo.cms.e4.handler.4" contributionURI="bundleclass://org.argeo.cms.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.cms.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.cms.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.cms.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.cms.e4/org.argeo.cms.e4.users.GroupsView" label="Groups" iconURI="platform:/plugin/org.argeo.cms.swt/icons/group.png">
+ <handlers xmi:id="_cmShoDvkEeiF1foPJZSZkw" elementId="org.argeo.cms.e4.handler.6" contributionURI="bundleclass://org.argeo.cms.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.cms.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.cms.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.cms.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.cms.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.cms.e4/org.argeo.cms.e4.jcr.JcrBrowserView" label="JCR" iconURI="platform:/plugin/org.argeo.cms.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.cms.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.cms.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.cms.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.cms.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.cms.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.cms.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.cms.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.cms.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.cms.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.cms.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.cms.e4/org.argeo.cms.e4.monitoring.OsgiConfigurationsView" label="OSGi Configurations" iconURI="platform:/plugin/org.argeo.cms.swt/icons/node.gif"/>
+ <children xsi:type="basic:Part" xmi:id="_8dM90FhJEeiknZQLx-vtnA" elementId="org.argeo.cms.e4.part.cmsSessions" contributionURI="bundleclass://org.argeo.cms.e4/org.argeo.cms.e4.monitoring.CmsSessionsView" label="CMS Sessions" iconURI="platform:/plugin/org.argeo.cms.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.cms.e4/org.argeo.cms.e4.monitoring.ModulesView" label="Modules" iconURI="platform:/plugin/org.argeo.cms.swt/icons/bundles.gif"/>
+ <children xsi:type="basic:Part" xmi:id="_dXtIoFhNEeiknZQLx-vtnA" elementId="org.argeo.cms.e4.part.bundles" contributionURI="bundleclass://org.argeo.cms.e4/org.argeo.cms.e4.monitoring.BundlesView" label="Bundles" iconURI="platform:/plugin/org.argeo.cms.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.cms.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.cms.e4/org.argeo.cms.e4.files.NodeFsBrowserView" label="Files" iconURI="platform:/plugin/org.argeo.cms.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.cms.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.cms.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.cms.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.cms.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.cms.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.cms.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.cms.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.cms.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.cms.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.cms.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.cms.swt/icons/node.gif" allowMultiple="true" category="dataExplorer" closeable="true" contributionURI="bundleclass://org.argeo.cms.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.cms.swt/icons/person.png" allowMultiple="true" category="usersEditorArea" closeable="true" dirtyable="true" contributionURI="bundleclass://org.argeo.cms.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.cms.swt/icons/group.png" allowMultiple="true" category="usersEditorArea" closeable="true" dirtyable="true" contributionURI="bundleclass://org.argeo.cms.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>
--- /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: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=user,ou=roles,ou=node</tags>
+ <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.cms.e4/org.argeo.cms.e4.jcr.JcrBrowserView" label="JCR" iconURI="platform:/plugin/org.argeo.cms.ui.theme/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.cms.ui.theme/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.cms.ui.theme/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.cms.ui.theme/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.cms.ui.theme/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.cms.ui.theme/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.cms.ui.theme/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.cms.ui.theme/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.cms.ui.theme/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.cms.ui.theme/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 xsi:type="basic:Part" xmi:id="_LyT80MKKEeqaPNgZ5fEKYw" elementId="org.argeo.cms.e4.part.egoDashboard" contributionURI="bundleclass://org.argeo.cms.e4/org.argeo.cms.e4.parts.EgoDashboard" label="" iconURI="platform:/plugin/org.argeo.cms.ui.theme/icons/home.png">
+ <toolbar xmi:id="_Ut8wMMKMEeqaPNgZ5fEKYw" elementId="org.argeo.cms.e4.toolbar.0">
+ <children xsi:type="menu:HandledToolItem" xmi:id="_nElwUMKMEeq1Ytjq4ALs6g" elementId="org.argeo.cms.e4.handledtoolitem.changepassword" label="Change password" iconURI="platform:/plugin/org.argeo.cms.ui.theme/icons/actions/edit.png" command="_jEjCUMKMEeq1Ytjq4ALs6g"/>
+ <children xsi:type="menu:HandledToolItem" xmi:id="_WAD4UMKMEeqaPNgZ5fEKYw" elementId="org.argeo.cms.e4.handledtoolitem.logout" label="Log out" iconURI="platform:/plugin/org.argeo.cms.ui.theme/icons/logout.png" command="_PsWd0FhLEeiknZQLx-vtnA"/>
+ </toolbar>
+ </children>
+ </children>
+ </children>
+ </children>
+ <handlers xmi:id="_Xp-P4CqREeidr6NYQH6GbQ" elementId="org.argeo.cms.e4.handler.0" contributionURI="bundleclass://org.argeo.cms.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.cms.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.cms.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.cms.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.cms.e4/org.argeo.cms.e4.jcr.handlers.AddRemoteRepository" command="_ZWpasFgQEeiknZQLx-vtnA"/>
+ <handlers xmi:id="_RQyFAFhLEeiknZQLx-vtnA" elementId="org.argeo.cms.e4.handler.logout" contributionURI="bundleclass://org.argeo.cms.e4/org.argeo.cms.e4.handlers.CloseWorkbench" command="_PsWd0FhLEeiknZQLx-vtnA"/>
+ <handlers xmi:id="_lN4GUMKMEeq1Ytjq4ALs6g" elementId="org.argeo.suite.e4.handler.changePassword" contributionURI="bundleclass://org.argeo.cms.e4/org.argeo.cms.e4.handlers.ChangePassword" command="_jEjCUMKMEeq1Ytjq4ALs6g"/>
+ <descriptors xmi:id="_XzfoMCqlEeidr6NYQH6GbQ" elementId="org.argeo.cms.e4.partdescriptor.nodeEditor" label="Node Editor" iconURI="platform:/plugin/org.argeo.cms.ui.theme/icons/node.gif" allowMultiple="true" category="dataExplorer" closeable="true" contributionURI="bundleclass://org.argeo.cms.e4/org.argeo.cms.e4.jcr.JcrNodeEditor"/>
+ <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"/>
+ <commands xmi:id="_jEjCUMKMEeq1Ytjq4ALs6g" elementId="org.argeo.cms.e4.command.changePassword" commandName="Change Password"/>
+ <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>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.argeo.commons</groupId>
+ <artifactId>eclipse</artifactId>
+ <version>2.3-SNAPSHOT</version>
+ <relativePath>..</relativePath>
+ </parent>
+ <artifactId>org.argeo.cms.e4</artifactId>
+ <name>CMS E4</name>
+ <packaging>jar</packaging>
+ <dependencies>
+ <dependency>
+ <groupId>org.argeo.commons</groupId>
+ <artifactId>org.argeo.cms.ui</artifactId>
+ <version>2.3-SNAPSHOT</version>
+ </dependency>
+
+ <!-- UI -->
+ <dependency>
+ <groupId>org.argeo.commons.rap</groupId>
+ <artifactId>org.argeo.swt.specific.rap</artifactId>
+ <version>2.3-SNAPSHOT</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.argeo.tp</groupId>
+ <artifactId>argeo-tp-rap-e4</artifactId>
+ <version>${version.argeo-tp}</version>
+ <type>pom</type>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+</project>
\ No newline at end of file
--- /dev/null
+package org.argeo.cms.e4;
+
+import java.util.List;
+
+import org.argeo.cms.CmsException;
+import org.eclipse.e4.ui.model.application.MApplication;
+import org.eclipse.e4.ui.model.application.commands.MCommand;
+import org.eclipse.e4.ui.model.application.ui.basic.MPart;
+import org.eclipse.e4.ui.model.application.ui.menu.MDirectMenuItem;
+import org.eclipse.e4.ui.model.application.ui.menu.MHandledMenuItem;
+import org.eclipse.e4.ui.workbench.modeling.EModelService;
+import org.eclipse.e4.ui.workbench.modeling.EPartService;
+import org.eclipse.e4.ui.workbench.modeling.EPartService.PartState;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.FrameworkUtil;
+
+/** Static utilities simplifying recurring Eclipse 4 patterns. */
+public class CmsE4Utils {
+ /** Open an editor based on its id. */
+ public static void openEditor(EPartService partService, String editorId, String key, String state) {
+ for (MPart part : partService.getParts()) {
+ String id = part.getPersistedState().get(key);
+ if (id != null && state.equals(id)) {
+ partService.showPart(part, PartState.ACTIVATE);
+ return;
+ }
+ }
+
+ // new part
+ MPart part = partService.createPart(editorId);
+ if (part == null)
+ throw new CmsException("No editor found with id " + editorId);
+ part.getPersistedState().put(key, state);
+ partService.showPart(part, PartState.ACTIVATE);
+ }
+
+ /** Dynamically creates an handled menu item from a command ID. */
+ public static MHandledMenuItem createHandledMenuItem(EModelService modelService, MApplication app,
+ String commandId) {
+ MCommand command = findCommand(modelService, app, commandId);
+ if (command == null)
+ return null;
+ MHandledMenuItem handledItem = modelService.createModelElement(MHandledMenuItem.class);
+ handledItem.setCommand(command);
+ return handledItem;
+
+ }
+
+ /**
+ * Finds a command by ID.
+ *
+ * @return the {@link MCommand} or <code>null</code> if not found.
+ */
+ public static MCommand findCommand(EModelService modelService, MApplication app, String commandId) {
+ List<MCommand> cmds = modelService.findElements(app, null, MCommand.class, null);
+ for (MCommand cmd : cmds) {
+ if (cmd.getElementId().equals(commandId)) {
+ return cmd;
+ }
+ }
+ return null;
+ }
+
+ /** Dynamically creates a direct menu item from a class. */
+ public static MDirectMenuItem createDirectMenuItem(EModelService modelService, Class<?> clss, String label) {
+ MDirectMenuItem dynamicItem = modelService.createModelElement(MDirectMenuItem.class);
+ dynamicItem.setLabel(label);
+ Bundle bundle = FrameworkUtil.getBundle(clss);
+ dynamicItem.setContributionURI("bundleclass://" + bundle.getSymbolicName() + "/" + clss.getName());
+ return dynamicItem;
+ }
+
+ /** Singleton. */
+ private CmsE4Utils() {
+ }
+
+}
--- /dev/null
+package org.argeo.cms.e4;
+
+import org.argeo.cms.CmsException;
+import org.eclipse.e4.core.contexts.ContextFunction;
+import org.eclipse.e4.core.contexts.IEclipseContext;
+import org.eclipse.e4.core.di.IInjector;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceReference;
+
+/** An Eclipse 4 {@link ContextFunction} based on an OSGi filter. */
+public class OsgiFilterContextFunction extends ContextFunction {
+
+ private BundleContext bc = FrameworkUtil.getBundle(OsgiFilterContextFunction.class).getBundleContext();
+
+ @Override
+ public Object compute(IEclipseContext context, String contextKey) {
+ ServiceReference<?>[] srs;
+ try {
+ srs = bc.getServiceReferences((String) null, contextKey);
+ } catch (InvalidSyntaxException e) {
+ throw new CmsException("Context key " + contextKey + " must be a valid osgi filter", e);
+ }
+ if (srs == null || srs.length == 0) {
+ return IInjector.NOT_A_VALUE;
+ } else {
+ // return the first one
+ return bc.getService(srs[0]);
+ }
+ }
+
+}
--- /dev/null
+package org.argeo.cms.e4;
+
+import java.security.AccessControlContext;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+
+import javax.security.auth.Subject;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.jobs.Job;
+
+/**
+ * Propagate authentication to an eclipse job. Typically to execute a privileged
+ * action outside the UI thread
+ */
+public abstract class PrivilegedJob extends Job {
+ private final Subject subject;
+
+ public PrivilegedJob(String jobName) {
+ this(jobName, AccessController.getContext());
+ }
+
+ public PrivilegedJob(String jobName,
+ AccessControlContext accessControlContext) {
+ super(jobName);
+ subject = Subject.getSubject(accessControlContext);
+
+ // Must be called *before* the job is scheduled,
+ // it is required for the progress window to appear
+ setUser(true);
+ }
+
+ @Override
+ protected IStatus run(final IProgressMonitor progressMonitor) {
+ PrivilegedAction<IStatus> privilegedAction = new PrivilegedAction<IStatus>() {
+ public IStatus run() {
+ return doRun(progressMonitor);
+ }
+ };
+ return Subject.doAs(subject, privilegedAction);
+ }
+
+ /**
+ * Implement here what should be executed with default context
+ * authentication
+ */
+ protected abstract IStatus doRun(IProgressMonitor progressMonitor);
+}
--- /dev/null
+package org.argeo.cms.e4.addons;
+
+import java.security.AccessController;
+import java.util.Iterator;
+
+import javax.annotation.PostConstruct;
+import javax.security.auth.Subject;
+import javax.servlet.http.HttpServletRequest;
+
+import org.argeo.api.cms.CmsLog;
+import org.argeo.cms.CmsException;
+import org.argeo.cms.auth.CurrentUser;
+import org.eclipse.e4.ui.model.application.MApplication;
+import org.eclipse.e4.ui.model.application.ui.MElementContainer;
+import org.eclipse.e4.ui.model.application.ui.MUIElement;
+import org.eclipse.e4.ui.model.application.ui.basic.MTrimBar;
+import org.eclipse.e4.ui.model.application.ui.basic.MTrimmedWindow;
+import org.eclipse.e4.ui.model.application.ui.basic.MWindow;
+
+public class AuthAddon {
+ private final static CmsLog log = CmsLog.getLog(AuthAddon.class);
+
+ public final static String AUTH = "auth.";
+
+ @PostConstruct
+ void init(MApplication application) {
+ Iterator<MWindow> windows = application.getChildren().iterator();
+ boolean atLeastOneTopLevelWindowVisible = false;
+ windows: while (windows.hasNext()) {
+ MWindow window = windows.next();
+ // main window
+ boolean windowVisible = process(window);
+ if (!windowVisible) {
+// windows.remove();
+ continue windows;
+ }
+ atLeastOneTopLevelWindowVisible = true;
+ // trim bars
+ if (window instanceof MTrimmedWindow) {
+ Iterator<MTrimBar> trimBars = ((MTrimmedWindow) window).getTrimBars().iterator();
+ while (trimBars.hasNext()) {
+ MTrimBar trimBar = trimBars.next();
+ if (!process(trimBar)) {
+ trimBars.remove();
+ }
+ }
+ }
+ }
+
+ if (!atLeastOneTopLevelWindowVisible) {
+ log.warn("No top-level window is authorized for user " + CurrentUser.getUsername() + ", logging out..");
+ logout();
+ }
+ }
+
+ protected boolean process(MUIElement element) {
+ for (String tag : element.getTags()) {
+ if (tag.startsWith(AUTH)) {
+ String role = tag.substring(AUTH.length(), tag.length());
+ if (!CurrentUser.isInRole(role)) {
+ element.setVisible(false);
+ element.setToBeRendered(false);
+ return false;
+ }
+ }
+ }
+
+ // children
+ if (element instanceof MElementContainer) {
+ @SuppressWarnings("unchecked")
+ MElementContainer<? extends MUIElement> container = (MElementContainer<? extends MUIElement>) element;
+ Iterator<? extends MUIElement> children = container.getChildren().iterator();
+ while (children.hasNext()) {
+ MUIElement child = children.next();
+ boolean visible = process(child);
+ if (!visible)
+ children.remove();
+ }
+
+ for (Object child : container.getChildren()) {
+ if (child instanceof MUIElement) {
+ boolean visible = process((MUIElement) child);
+ if (!visible)
+ container.getChildren().remove(child);
+ }
+ }
+ }
+
+ return true;
+ }
+
+ protected void logout() {
+ Subject subject = Subject.getSubject(AccessController.getContext());
+ try {
+ CurrentUser.logoutCmsSession(subject);
+ } catch (Exception e) {
+ throw new CmsException("Cannot log out", e);
+ }
+ HttpServletRequest request = org.argeo.eclipse.ui.specific.UiContext.getHttpRequest();
+ if (request != null)
+ request.getSession().setMaxInactiveInterval(0);
+ }
+
+}
--- /dev/null
+package org.argeo.cms.e4.addons;
+
+import java.security.AccessController;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+
+import javax.annotation.PostConstruct;
+import javax.security.auth.Subject;
+
+import org.argeo.eclipse.ui.specific.UiContext;
+import org.eclipse.e4.core.services.nls.ILocaleChangeService;
+import org.eclipse.e4.ui.model.application.MApplication;
+import org.eclipse.e4.ui.model.application.ui.basic.MWindow;
+import org.eclipse.e4.ui.workbench.modeling.EModelService;
+import org.eclipse.e4.ui.workbench.modeling.ElementMatcher;
+import org.eclipse.swt.SWT;
+
+/** Integrate workbench with the locale provided at log in. */
+public class LocaleAddon {
+ private final static String STYLE_OVERRIDE = "styleOverride";
+
+ // Right to left languages
+ private final static String ARABIC = "ar";
+ private final static String HEBREW = "he";
+
+ @PostConstruct
+ public void init(ILocaleChangeService localeChangeService, EModelService modelService, MApplication application) {
+ Subject subject = Subject.getSubject(AccessController.getContext());
+ Set<Locale> locales = subject.getPublicCredentials(Locale.class);
+ if (!locales.isEmpty()) {
+ Locale locale = locales.iterator().next();
+ localeChangeService.changeApplicationLocale(locale);
+ UiContext.setLocale(locale);
+
+ if (locale.getLanguage().equals(ARABIC) || locale.getLanguage().equals(HEBREW)) {
+ List<MWindow> windows = modelService.findElements(application, MWindow.class, EModelService.ANYWHERE,
+ new ElementMatcher(null, null, (String) null));
+ for (MWindow window : windows) {
+ String currentStyle = window.getPersistedState().get(STYLE_OVERRIDE);
+ int style = 0;
+ if (currentStyle != null) {
+ style = Integer.parseInt(currentStyle);
+ }
+ style = style | SWT.RIGHT_TO_LEFT;
+ window.getPersistedState().put(STYLE_OVERRIDE, Integer.toString(style));
+ }
+ }
+ }
+ }
+}
--- /dev/null
+/** Eclipse 4 addons to integrate with Argeo CMS. */
+package org.argeo.cms.e4.addons;
\ 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.cms.CmsException;
+import org.argeo.cms.jcr.CmsJcrUtils;
+import org.argeo.eclipse.ui.fs.AdvancedFsBrowser;
+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 CmsException("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.handlers;
+
+import java.util.Locale;
+
+import org.eclipse.e4.core.di.annotations.Execute;
+import org.eclipse.e4.core.services.nls.ILocaleChangeService;
+
+public class ChangeLanguage {
+ @Execute
+ public void execute(ILocaleChangeService localeChangeService) {
+ localeChangeService.changeApplicationLocale(Locale.FRENCH);
+ }
+}
--- /dev/null
+package org.argeo.cms.e4.handlers;
+
+import static org.argeo.cms.CmsMsg.changePassword;
+import static org.argeo.cms.CmsMsg.currentPassword;
+import static org.argeo.cms.CmsMsg.newPassword;
+import static org.argeo.cms.CmsMsg.passwordChanged;
+import static org.argeo.cms.CmsMsg.repeatNewPassword;
+
+import java.util.Arrays;
+
+import javax.inject.Inject;
+import javax.naming.InvalidNameException;
+import javax.naming.ldap.LdapName;
+
+import org.argeo.cms.CmsException;
+import org.argeo.cms.auth.CurrentUser;
+import org.argeo.cms.security.CryptoKeyring;
+import org.argeo.cms.swt.dialogs.CmsMessageDialog;
+import org.argeo.eclipse.ui.dialogs.ErrorFeedback;
+import org.argeo.osgi.transaction.WorkTransaction;
+import org.eclipse.e4.core.di.annotations.Execute;
+import org.eclipse.e4.core.di.annotations.Optional;
+import org.eclipse.jface.dialogs.Dialog;
+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.Display;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Text;
+import org.osgi.service.useradmin.User;
+import org.osgi.service.useradmin.UserAdmin;
+
+/** Change the password of the logged-in user. */
+public class ChangePassword {
+ @Inject
+ private UserAdmin userAdmin;
+ @Inject
+ private WorkTransaction userTransaction;
+ @Inject
+ @Optional
+ private CryptoKeyring keyring = null;
+
+ @Execute
+ public void execute() {
+ ChangePasswordDialog dialog = new ChangePasswordDialog(Display.getCurrent().getActiveShell(), userAdmin);
+ if (dialog.open() == Dialog.OK) {
+ new CmsMessageDialog(Display.getCurrent().getActiveShell(), passwordChanged.lead(),
+ CmsMessageDialog.INFORMATION).open();
+ }
+ }
+
+ protected void changePassword(char[] oldPassword, char[] newPassword) {
+ String name = CurrentUser.getUsername();
+ LdapName dn;
+ try {
+ dn = new LdapName(name);
+ } catch (InvalidNameException e) {
+ throw new CmsException("Invalid user dn " + name, e);
+ }
+ User user = (User) userAdmin.getRole(dn.toString());
+ if (!user.hasCredential(null, oldPassword))
+ throw new CmsException("Invalid password");
+ if (Arrays.equals(newPassword, new char[0]))
+ throw new CmsException("New password empty");
+ try {
+ userTransaction.begin();
+ user.getCredentials().put(null, newPassword);
+ if (keyring != null) {
+ keyring.changePassword(oldPassword, newPassword);
+ // TODO change secret keys in the CMS session
+ }
+ userTransaction.commit();
+ } catch (Exception e) {
+ try {
+ userTransaction.rollback();
+ } catch (Exception e1) {
+ e1.printStackTrace();
+ }
+ if (e instanceof RuntimeException)
+ throw (RuntimeException) e;
+ else
+ throw new CmsException("Cannot change password", e);
+ }
+ }
+
+ class ChangePasswordDialog extends CmsMessageDialog {
+ private Text oldPassword, newPassword1, newPassword2;
+
+ public ChangePasswordDialog(Shell parentShell, UserAdmin securityService) {
+ super(parentShell, changePassword.lead(), CONFIRM);
+ }
+
+// protected Point getInitialSize() {
+// return new Point(400, 450);
+// }
+
+ 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));
+ oldPassword = createLP(composite, currentPassword.lead());
+ newPassword1 = createLP(composite, newPassword.lead());
+ newPassword2 = createLP(composite, repeatNewPassword.lead());
+
+// parent.pack();
+ oldPassword.setFocus();
+ return composite;
+ }
+
+ @Override
+ protected void okPressed() {
+ try {
+ if (!newPassword1.getText().equals(newPassword2.getText()))
+ throw new CmsException("New passwords are different");
+ changePassword(oldPassword.getTextChars(), newPassword1.getTextChars());
+ closeShell(OK);
+ } catch (Exception e) {
+ ErrorFeedback.show("Cannot change password", e);
+ }
+ }
+
+ /** Creates label and password. */
+ protected Text createLP(Composite parent, String label) {
+ new Label(parent, SWT.NONE).setText(label);
+ Text text = new Text(parent, SWT.SINGLE | SWT.LEAD | SWT.PASSWORD | SWT.BORDER);
+ text.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
+ return text;
+ }
+
+ }
+
+}
--- /dev/null
+package org.argeo.cms.e4.handlers;
+
+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.workbench.modeling.EPartService;
+
+public class CloseAllParts {
+
+ @Execute
+ void execute(EPartService partService) {
+ for (MPart part : partService.getParts()) {
+ if (part.isCloseable()) {
+ if (part.isDirty()) {
+ if (partService.savePart(part, true)) {
+ partService.hidePart(part, true);
+ }
+ } else {
+ partService.hidePart(part, true);
+ }
+ }
+ }
+ }
+
+ @CanExecute
+ boolean canExecute(EPartService partService) {
+ boolean atLeastOnePart = false;
+ for (MPart part : partService.getParts()) {
+ if (part.isVisible() && part.isCloseable()) {
+ atLeastOnePart = true;
+ break;
+ }
+ }
+ return atLeastOnePart;
+ }
+
+}
\ No newline at end of file
--- /dev/null
+package org.argeo.cms.e4.handlers;
+
+import java.security.AccessController;
+
+import javax.security.auth.Subject;
+
+import org.argeo.cms.CmsException;
+import org.argeo.cms.auth.CurrentUser;
+import org.eclipse.e4.core.di.annotations.Execute;
+import org.eclipse.e4.ui.workbench.IWorkbench;
+
+public class CloseWorkbench {
+ @Execute
+ public void execute(IWorkbench workbench) {
+ logout();
+ workbench.close();
+ }
+
+ protected void logout() {
+ Subject subject = Subject.getSubject(AccessController.getContext());
+ try {
+ CurrentUser.logoutCmsSession(subject);
+ } catch (Exception e) {
+ throw new CmsException("Cannot log out", e);
+ }
+ }
+
+}
--- /dev/null
+package org.argeo.cms.e4.handlers;
+
+import org.eclipse.e4.core.di.annotations.Execute;
+
+public class DoNothing {
+ @Execute
+ public void execute() {
+
+ }
+}
--- /dev/null
+
+package org.argeo.cms.e4.handlers;
+
+import java.util.Date;
+import java.util.List;
+
+import org.eclipse.e4.ui.di.AboutToHide;
+import org.eclipse.e4.ui.di.AboutToShow;
+import org.eclipse.e4.ui.model.application.ui.menu.MDirectMenuItem;
+import org.eclipse.e4.ui.model.application.ui.menu.MMenuElement;
+import org.eclipse.e4.ui.workbench.modeling.EModelService;
+
+public class LanguageMenuContribution {
+ @AboutToShow
+ public void aboutToShow(List<MMenuElement> items, EModelService modelService) {
+ MDirectMenuItem dynamicItem = modelService.createModelElement(MDirectMenuItem.class);
+ dynamicItem.setLabel("Dynamic Menu Item (" + new Date() + ")");
+ //dynamicItem.setContributorURI("platform:/plugin/org.argeo.cms.e4");
+ //dynamicItem.setContributionURI("bundleclass://org.argeo.cms.e4/" + ChangeLanguage.class.getName());
+ dynamicItem.setEnabled(true);
+ dynamicItem.setContributionURI("bundleclass://org.argeo.cms.e4/org.argeo.cms.e4.handlers.ChangeLanguage");
+ items.add(dynamicItem);
+ }
+
+ @AboutToHide
+ public void aboutToHide() {
+
+ }
+}
\ No newline at end of file
--- /dev/null
+package org.argeo.cms.e4.handlers;
+
+import java.util.List;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+import org.eclipse.e4.core.di.annotations.Execute;
+import org.eclipse.e4.ui.model.application.MApplication;
+import org.eclipse.e4.ui.model.application.ui.advanced.MPerspective;
+import org.eclipse.e4.ui.workbench.modeling.EModelService;
+import org.eclipse.e4.ui.workbench.modeling.EPartService;
+
+public class OpenPerspective {
+ @Inject
+ MApplication application;
+ @Inject
+ EPartService partService;
+ @Inject
+ EModelService modelService;
+
+ @Execute
+ public void execute(@Named("perspectiveId") String perspectiveId) {
+ List<MPerspective> perspectives = modelService.findElements(application, perspectiveId, MPerspective.class,
+ null);
+ if (perspectives.size() == 0)
+ return;
+ MPerspective perspective = perspectives.get(0);
+ partService.switchPerspective(perspective);
+ }
+}
--- /dev/null
+package org.argeo.cms.e4.handlers;
+
+import org.eclipse.e4.core.di.annotations.CanExecute;
+import org.eclipse.e4.core.di.annotations.Execute;
+import org.eclipse.e4.ui.workbench.modeling.EPartService;
+
+public class SaveAllParts {
+
+ @Execute
+ void execute(EPartService partService) {
+ partService.saveAll(false);
+ }
+
+ @CanExecute
+ boolean canExecute(EPartService partService) {
+ return partService.getDirtyParts().size() > 0;
+ }
+
+}
\ No newline at end of file
--- /dev/null
+package org.argeo.cms.e4.handlers;
+
+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.workbench.modeling.EPartService;
+
+public class SavePart {
+ @Execute
+ void execute(EPartService partService, MPart part) {
+ partService.savePart(part, false);
+ }
+
+ @CanExecute
+ boolean canExecute(MPart part) {
+ return part.isDirty();
+ }
+}
\ No newline at end of file
--- /dev/null
+/** Generic Eclipse 4 handlers. */
+package org.argeo.cms.e4.handlers;
\ No newline at end of file
--- /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.CmsException;
+import org.argeo.cms.security.CryptoKeyring;
+import org.argeo.cms.security.Keyring;
+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.eclipse.ui.EclipseUiException;
+import org.argeo.eclipse.ui.TreeParent;
+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.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.eclipse.ui.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.eclipse.ui.EclipseUiException;
+import org.argeo.eclipse.ui.TreeParent;
+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.eclipse.ui.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.Cms2DSize;
+import org.argeo.cms.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 <br>" + nodeState.getHostname() + "</br>, 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("[" + "<b>" + nodeState.getHostname() + "</b>]# " + "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.eclipse.ui.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 javax.naming.ldap.LdapName;
+
+import org.argeo.api.cms.CmsSession;
+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) {
+ LdapName userDn = ((CmsSession) element).getUserDn();
+ return userDn.getRdn(userDn.size() - 1).getValue().toString();
+ }
+
+ 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.eclipse.ui.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.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.eclipse.ui.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
+/** Eclipse 4 user interfaces. */
+package org.argeo.cms.e4;
\ No newline at end of file
--- /dev/null
+package org.argeo.cms.e4.parts;
+
+import java.security.AccessController;
+import java.time.ZonedDateTime;
+
+import javax.annotation.PostConstruct;
+import javax.security.auth.Subject;
+
+import org.argeo.api.cms.CmsSession;
+import org.argeo.cms.auth.CurrentUser;
+import org.argeo.cms.osgi.CmsOsgiUtils;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
+
+/** 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 = CmsOsgiUtils.getCmsSession(bc, subject);
+ 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.ui.eclipse.forms.AbstractFormPart;
+import org.argeo.cms.ui.eclipse.forms.IManagedForm;
+import org.argeo.eclipse.ui.ColumnDefinition;
+import org.argeo.eclipse.ui.EclipseUiUtils;
+import org.argeo.eclipse.ui.parts.LdifUsersTable;
+import org.argeo.jcr.JcrException;
+import org.argeo.jcr.JcrUtils;
+import org.argeo.osgi.transaction.WorkTransaction;
+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.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.CmsException;
+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.ui.workbench.WorkbenchUiPlugin;
+//import org.argeo.cms.ui.workbench.internal.useradmin.UiUserAdminListener;
+//import org.argeo.cms.ui.workbench.internal.useradmin.UserAdminWrapper;
+//import org.argeo.cms.ui.workbench.internal.useradmin.providers.CommonNameLP;
+//import org.argeo.cms.ui.workbench.internal.useradmin.providers.DomainNameLP;
+//import org.argeo.cms.ui.workbench.internal.useradmin.providers.RoleIconLP;
+//import org.argeo.cms.ui.workbench.internal.useradmin.providers.UserDragListener;
+//import org.argeo.cms.ui.workbench.internal.useradmin.providers.UserTableDefaultDClickListener;
+import org.argeo.eclipse.ui.ColumnDefinition;
+import org.argeo.eclipse.ui.EclipseUiUtils;
+import org.argeo.eclipse.ui.parts.LdifUsersTable;
+import org.argeo.util.naming.LdapAttrs;
+import org.argeo.util.naming.LdapObjs;
+import org.eclipse.e4.ui.di.Focus;
+import org.eclipse.e4.ui.workbench.modeling.EPartService;
+import org.eclipse.e4.ui.workbench.modeling.ESelectionService;
+import org.eclipse.jface.viewers.ISelectionChangedListener;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.viewers.SelectionChangedEvent;
+import org.eclipse.jface.viewers.TableViewer;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.dnd.DND;
+import org.eclipse.swt.dnd.TextTransfer;
+import org.eclipse.swt.dnd.Transfer;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Display;
+//import org.eclipse.ui.part.ViewPart;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.service.useradmin.Role;
+import org.osgi.service.useradmin.User;
+import org.osgi.service.useradmin.UserAdminEvent;
+import org.osgi.service.useradmin.UserAdminListener;
+
+/** List all groups with filter */
+public class GroupsView {
+ private final static CmsLog log = CmsLog.getLog(GroupsView.class);
+ // public final static String ID = WorkbenchUiPlugin.PLUGIN_ID + ".groupsView";
+
+ @Inject
+ private EPartService partService;
+ @Inject
+ private UserAdminWrapper userAdminWrapper;
+
+ // UI Objects
+ private LdifUsersTable groupTableViewerCmp;
+ private TableViewer userViewer;
+ private List<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.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.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.osgi.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.CmsException;
+import org.argeo.osgi.transaction.WorkTransaction;
+import org.argeo.osgi.useradmin.UserAdminConf;
+import org.argeo.osgi.useradmin.UserDirectory;
+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.getBaseDn().toString();
+
+ if (onlyWritable && readOnly)
+ continue;
+ if (baseDn.equalsIgnoreCase(CmsConstants.ROLES_BASEDN))
+ continue;
+ if (baseDn.equalsIgnoreCase(CmsConstants.TOKENS_BASEDN))
+ continue;
+ dns.put(baseDn, UserAdminConf.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.CmsException;
+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.eclipse.ui.ColumnDefinition;
+import org.argeo.eclipse.ui.EclipseUiUtils;
+import org.argeo.eclipse.ui.parts.LdifUsersTable;
+import org.argeo.osgi.transaction.WorkTransaction;
+import org.argeo.util.naming.LdapAttrs;
+import org.argeo.util.naming.LdapObjs;
+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.ui.eclipse.forms.AbstractFormPart;
+//import org.argeo.cms.ui.eclipse.forms.FormToolkit;
+import org.argeo.cms.ui.eclipse.forms.IManagedForm;
+import org.argeo.eclipse.ui.ColumnDefinition;
+import org.argeo.eclipse.ui.EclipseUiUtils;
+import org.argeo.eclipse.ui.parts.LdifUsersTable;
+import org.argeo.util.naming.LdapAttrs;
+import org.eclipse.e4.ui.workbench.modeling.EPartService;
+import org.eclipse.jface.action.Action;
+import org.eclipse.jface.action.ToolBarManager;
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.jface.dialogs.TrayDialog;
+import org.eclipse.jface.resource.ImageDescriptor;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.viewers.TableViewer;
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.jface.viewers.ViewerDropAdapter;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.dnd.DND;
+import org.eclipse.swt.dnd.DropTargetEvent;
+import org.eclipse.swt.dnd.TextTransfer;
+import org.eclipse.swt.dnd.Transfer;
+import org.eclipse.swt.dnd.TransferData;
+import org.eclipse.swt.events.ModifyEvent;
+import org.eclipse.swt.events.ModifyListener;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Link;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Text;
+import org.eclipse.swt.widgets.ToolBar;
+import org.osgi.service.useradmin.Group;
+import org.osgi.service.useradmin.Role;
+import org.osgi.service.useradmin.User;
+import org.osgi.service.useradmin.UserAdmin;
+import org.osgi.service.useradmin.UserAdminEvent;
+
+/** Display/edit the properties of a given user */
+public class UserEditor extends AbstractRoleEditor {
+ // final static String ID = "UserEditor.mainPage";
+
+ @Inject
+ private EPartService partService;
+
+ // private final UserEditor editor;
+ // private UserAdminWrapper userAdminWrapper;
+
+ // Local configuration
+ // private final int PRE_TITLE_INDENT = 10;
+
+ // public UserMainPage(FormEditor editor, UserAdminWrapper userAdminWrapper) {
+ // super(editor, ID, "Main");
+ // this.editor = (UserEditor) editor;
+ // this.userAdminWrapper = userAdminWrapper;
+ // }
+
+ // protected void createFormContent(final IManagedForm mf) {
+ // ScrolledForm form = mf.getForm();
+ // Composite body = form.getBody();
+ // GridLayout mainLayout = new GridLayout();
+ // // mainLayout.marginRight = 10;
+ // body.setLayout(mainLayout);
+ // User user = editor.getDisplayedUser();
+ // appendOverviewPart(body, user);
+ // // Remove to ability to force the password for his own user. The user
+ // // must then use the change pwd feature
+ // appendMemberOfPart(body, user);
+ // }
+
+ @Override
+ protected void createUi(Composite body) {
+ // Composite body = new Composite(parent, SWT.BORDER);
+ GridLayout mainLayout = new GridLayout();
+ // mainLayout.marginRight = 10;
+ body.setLayout(mainLayout);
+ // body.getParent().setLayout(new GridLayout());
+ // body.setLayoutData(CmsUiUtils.fillAll());
+ User user = getDisplayedUser();
+ appendOverviewPart(body, user);
+ // Remove to ability to force the password for his own user. The user
+ // must then use the change pwd feature
+ appendMemberOfPart(body, user);
+ }
+
+ /** Creates the general section */
+ private void appendOverviewPart(final Composite parent, final User user) {
+ // FormToolkit tk = getManagedForm().getToolkit();
+
+ // Section section = tk.createSection(parent, SWT.NO_FOCUS);
+ // GridData gd = EclipseUiUtils.fillWidth();
+ // // gd.verticalAlignment = PRE_TITLE_INDENT;
+ // section.setLayoutData(gd);
+ Composite body = new Composite(parent, SWT.NONE);
+ body.setLayoutData(EclipseUiUtils.fillWidth());
+ // section.setClient(body);
+ // body.setLayout(new GridLayout(6, false));
+ body.setLayout(new GridLayout(2, false));
+
+ Text commonName = createReadOnlyLT(body, "Name", getProperty(user, cn));
+ Text distinguishedName = createReadOnlyLT(body, "Login", getProperty(user, uid));
+ Text firstName = createLT(body, "First name", getProperty(user, givenName));
+ Text lastName = createLT(body, "Last name", getProperty(user, sn));
+ Text email = createLT(body, "Email", getProperty(user, mail));
+
+ Link resetPwdLk = new Link(body, SWT.NONE);
+ if (!UserAdminUtils.isCurrentUser(user)) {
+ resetPwdLk.setText("<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.CmsException;
+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.eclipse.ui.ColumnDefinition;
+import org.argeo.eclipse.ui.EclipseUiUtils;
+import org.argeo.eclipse.ui.parts.LdifUsersTable;
+import org.argeo.util.naming.LdapAttrs;
+import org.argeo.util.naming.LdapObjs;
+import org.eclipse.e4.ui.di.Focus;
+import org.eclipse.e4.ui.workbench.modeling.EPartService;
+import org.eclipse.e4.ui.workbench.modeling.ESelectionService;
+import org.eclipse.jface.viewers.ISelectionChangedListener;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.viewers.SelectionChangedEvent;
+import org.eclipse.jface.viewers.TableViewer;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.dnd.DND;
+import org.eclipse.swt.dnd.TextTransfer;
+import org.eclipse.swt.dnd.Transfer;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Display;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.service.useradmin.Role;
+import org.osgi.service.useradmin.User;
+import org.osgi.service.useradmin.UserAdminEvent;
+import org.osgi.service.useradmin.UserAdminListener;
+
+/** List all users with filter - based on Ldif userAdmin */
+public class UsersView {
+ // private final static Log log = LogFactory.getLog(UsersView.class);
+
+ // public final static String ID = WorkbenchUiPlugin.PLUGIN_ID + ".usersView";
+
+ @Inject
+ private UserAdminWrapper userAdminWrapper;
+ @Inject
+ private EPartService partService;
+
+ // UI Objects
+ private LdifUsersTable userTableViewerCmp;
+ private TableViewer userViewer;
+ private List<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.CmsException;
+import org.argeo.cms.e4.users.UserAdminWrapper;
+import org.argeo.eclipse.ui.EclipseUiUtils;
+import org.argeo.eclipse.ui.dialogs.ErrorFeedback;
+import org.argeo.osgi.useradmin.UserAdminConf;
+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 = UserAdminConf.uriAsProperties(dns.get(bdn));
+ String dn = LdapAttrs.cn.name() + "=" + cn + "," + UserAdminConf.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.CmsException;
+import org.argeo.cms.auth.UserAdminUtils;
+import org.argeo.cms.e4.users.UiAdminUtils;
+import org.argeo.cms.e4.users.UserAdminWrapper;
+import org.argeo.eclipse.ui.EclipseUiUtils;
+import org.argeo.eclipse.ui.dialogs.ErrorFeedback;
+import org.argeo.osgi.useradmin.UserAdminConf;
+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 = UserAdminConf.uriAsProperties(dns.get(bdn));
+ String dn = LdapAttrs.uid.name() + "=" + uid + "," + UserAdminConf.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.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.CmsException;
+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.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.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
+<?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-11"/>
+ <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.cms.servlet</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" activate="start" deactivate="stop" name="Jetty Service Factory">
+ <implementation class="org.argeo.cms.servlet.internal.jetty.JettyServiceFactory"/>
+ <service>
+ <provide interface="org.osgi.service.cm.ManagedServiceFactory"/>
+ </service>
+ <property name="service.pid" type="String" value="org.argeo.equinox.jetty.config"/>
+</scr:component>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="org.argeo.cms.pkgServlet">
+ <implementation class="org.argeo.cms.servlet.internal.PkgServlet"/>
+ <service>
+ <provide interface="javax.servlet.Servlet"/>
+ </service>
+ <property name="osgi.http.whiteboard.servlet.pattern" type="String" value="/*"/>
+ <property name="osgi.http.whiteboard.context.select" type="String" value="(osgi.http.whiteboard.context.name=pkgServletContext)"/>
+</scr:component>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="init" deactivate="destroy" name="org.argeo.cms.pkgServletContext">
+ <implementation class="org.argeo.cms.servlet.CmsServletContext"/>
+ <service>
+ <provide interface="org.osgi.service.http.context.ServletContextHelper"/>
+ </service>
+ <property name="osgi.http.whiteboard.context.name" type="String" value="pkgServletContext"/>
+ <property name="osgi.http.whiteboard.context.path" type="String" value="/pkg"/>
+</scr:component>
--- /dev/null
+Import-Package:\
+org.osgi.service.http;version=0.0.0,\
+org.osgi.service.http.whiteboard;version=0.0.0,\
+org.osgi.framework.namespace;version=0.0.0,\
+org.argeo.cms.osgi,\
+*
+
+Service-Component:\
+OSGI-INF/jettyServiceFactory.xml,\
+OSGI-INF/pkgServletContext.xml,\
+OSGI-INF/pkgServlet.xml
--- /dev/null
+output.. = bin/
+bin.includes = META-INF/,\
+ .,\
+ OSGI-INF/jettyServiceFactory.xml
+source.. = src/
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.argeo.commons</groupId>
+ <version>2.3-SNAPSHOT</version>
+ <artifactId>eclipse</artifactId>
+ <relativePath>..</relativePath>
+ </parent>
+ <artifactId>org.argeo.cms.servlet</artifactId>
+ <packaging>jar</packaging>
+ <name>CMS Servlet</name>
+ <description>CMS components depending on the Servlet APIs</description>
+ <dependencies>
+ <dependency>
+ <groupId>org.argeo.commons</groupId>
+ <artifactId>org.argeo.cms</artifactId>
+ <version>2.3-SNAPSHOT</version>
+ </dependency>
+ </dependencies>
+</project>
\ No newline at end of file
--- /dev/null
+package org.argeo.cms.servlet;
+
+import java.io.IOException;
+import java.net.URL;
+import java.security.PrivilegedAction;
+import java.util.Map;
+
+import javax.security.auth.Subject;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.argeo.api.cms.CmsAuth;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.cms.auth.RemoteAuthCallbackHandler;
+import org.argeo.cms.auth.RemoteAuthUtils;
+import org.argeo.cms.servlet.internal.HttpUtils;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.service.http.context.ServletContextHelper;
+
+/**
+ * Default servlet context degrading to anonymous if the the session is not
+ * pre-authenticated.
+ */
+public class CmsServletContext extends ServletContextHelper {
+ private final static CmsLog log = CmsLog.getLog(CmsServletContext.class);
+ // use CMS bundle for resources
+ private Bundle bundle = FrameworkUtil.getBundle(getClass());
+
+ public void init(Map<String, String> properties) {
+
+ }
+
+ public void destroy() {
+
+ }
+
+ @Override
+ public boolean handleSecurity(HttpServletRequest request, HttpServletResponse response) throws IOException {
+ if (log.isTraceEnabled())
+ HttpUtils.logRequestHeaders(log, request);
+ LoginContext lc;
+ try {
+ lc = CmsAuth.USER.newLoginContext(
+ new RemoteAuthCallbackHandler(new ServletHttpRequest(request), new ServletHttpResponse(response)));
+ lc.login();
+ } catch (LoginException e) {
+ lc = processUnauthorized(request, response);
+ if (log.isTraceEnabled())
+ HttpUtils.logResponseHeaders(log, response);
+ if (lc == null)
+ return false;
+ }
+
+ Subject subject = lc.getSubject();
+ // log.debug("SERVLET CONTEXT: "+subject);
+ Subject.doAs(subject, new PrivilegedAction<Void>() {
+
+ @Override
+ public Void run() {
+ // TODO also set login context in order to log out ?
+ RemoteAuthUtils.configureRequestSecurity(new ServletHttpRequest(request));
+ return null;
+ }
+
+ });
+ return true;
+ }
+
+ @Override
+ public void finishSecurity(HttpServletRequest request, HttpServletResponse response) {
+ RemoteAuthUtils.clearRequestSecurity(new ServletHttpRequest(request));
+ }
+
+ protected LoginContext processUnauthorized(HttpServletRequest request, HttpServletResponse response) {
+ // anonymous
+ try {
+ LoginContext lc = new LoginContext(CmsAuth.LOGIN_CONTEXT_ANONYMOUS,
+ new RemoteAuthCallbackHandler(new ServletHttpRequest(request), new ServletHttpResponse(response)));
+ lc.login();
+ return lc;
+ } catch (LoginException e1) {
+ if (log.isDebugEnabled())
+ log.error("Cannot log in as anonymous", e1);
+ return null;
+ }
+ }
+
+ @Override
+ public URL getResource(String name) {
+ // TODO make it more robust and versatile
+ // if used directly it can only load from within this bundle
+ return bundle.getResource(name);
+ }
+
+}
--- /dev/null
+package org.argeo.cms.servlet;
+
+import javax.security.auth.login.LoginContext;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.argeo.cms.auth.SpnegoLoginModule;
+import org.argeo.cms.servlet.internal.HttpUtils;
+
+/** Servlet context forcing authentication. */
+public class PrivateWwwAuthServletContext extends CmsServletContext {
+ // TODO make it configurable
+ private final String httpAuthRealm = "Argeo";
+ private final boolean forceBasic = false;
+
+ @Override
+ protected LoginContext processUnauthorized(HttpServletRequest request, HttpServletResponse response) {
+ askForWwwAuth(request, response);
+ return null;
+ }
+
+ protected void askForWwwAuth(HttpServletRequest request, HttpServletResponse response) {
+ // response.setHeader(HttpUtils.HEADER_WWW_AUTHENTICATE, "basic
+ // realm=\"" + httpAuthRealm + "\"");
+ if (SpnegoLoginModule.hasAcceptorCredentials() && !forceBasic)// SPNEGO
+ response.setHeader(HttpUtils.HEADER_WWW_AUTHENTICATE, "Negotiate");
+ else
+ response.setHeader(HttpUtils.HEADER_WWW_AUTHENTICATE, "Basic realm=\"" + httpAuthRealm + "\"");
+
+ // response.setDateHeader("Date", System.currentTimeMillis());
+ // response.setDateHeader("Expires", System.currentTimeMillis() + (24 *
+ // 60 * 60 * 1000));
+ // response.setHeader("Accept-Ranges", "bytes");
+ // response.setHeader("Connection", "Keep-Alive");
+ // response.setHeader("Keep-Alive", "timeout=5, max=97");
+ // response.setContentType("text/html; charset=UTF-8");
+ response.setStatus(401);
+ }
+}
--- /dev/null
+package org.argeo.cms.servlet;
+
+import java.util.Locale;
+import java.util.Objects;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.argeo.cms.auth.RemoteAuthRequest;
+import org.argeo.cms.auth.RemoteAuthSession;
+
+public class ServletHttpRequest implements RemoteAuthRequest {
+ private final HttpServletRequest request;
+
+ public ServletHttpRequest(HttpServletRequest request) {
+ Objects.requireNonNull(request);
+ this.request = request;
+ }
+
+ @Override
+ public RemoteAuthSession getSession() {
+ return new ServletHttpSession(request.getSession(false));
+ }
+
+ @Override
+ public RemoteAuthSession createSession() {
+ return new ServletHttpSession(request.getSession(true));
+ }
+
+ @Override
+ public Locale getLocale() {
+ return request.getLocale();
+ }
+
+ @Override
+ public Object getAttribute(String key) {
+ return request.getAttribute(key);
+ }
+
+ @Override
+ public void setAttribute(String key, Object object) {
+ request.setAttribute(key, object);
+ }
+
+ @Override
+ public String getHeader(String key) {
+ return request.getHeader(key);
+ }
+
+ @Override
+ public String getRemoteAddr() {
+ return request.getRemoteAddr();
+ }
+
+ @Override
+ public int getLocalPort() {
+ return request.getLocalPort();
+ }
+
+ @Override
+ public int getRemotePort() {
+ return request.getRemotePort();
+ }
+}
--- /dev/null
+package org.argeo.cms.servlet;
+
+import java.util.Objects;
+
+import javax.servlet.http.HttpServletResponse;
+
+import org.argeo.cms.auth.RemoteAuthResponse;
+
+public class ServletHttpResponse implements RemoteAuthResponse {
+ private final HttpServletResponse response;
+
+ public ServletHttpResponse(HttpServletResponse response) {
+ Objects.requireNonNull(response);
+ this.response = response;
+ }
+
+ @Override
+ public void setHeader(String keys, String value) {
+ response.setHeader(keys, value);
+ }
+
+}
--- /dev/null
+package org.argeo.cms.servlet;
+
+import org.argeo.cms.auth.RemoteAuthSession;
+
+public class ServletHttpSession implements RemoteAuthSession {
+ private javax.servlet.http.HttpSession session;
+
+ public ServletHttpSession(javax.servlet.http.HttpSession session) {
+ super();
+ this.session = session;
+ }
+
+ @Override
+ public boolean isValid() {
+ try {// test http session
+ session.getCreationTime();
+ return true;
+ } catch (IllegalStateException ise) {
+ return false;
+ }
+ }
+
+ @Override
+ public String getId() {
+ return session.getId();
+ }
+
+}
--- /dev/null
+package org.argeo.cms.servlet.internal;
+
+import java.util.Enumeration;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.argeo.api.cms.CmsLog;
+
+public class HttpUtils {
+ public final static String HEADER_AUTHORIZATION = "Authorization";
+ public final static String HEADER_WWW_AUTHENTICATE = "WWW-Authenticate";
+
+ static boolean isBrowser(String userAgent) {
+ return userAgent.contains("webkit") || userAgent.contains("gecko") || userAgent.contains("firefox")
+ || userAgent.contains("msie") || userAgent.contains("chrome") || userAgent.contains("chromium")
+ || userAgent.contains("opera") || userAgent.contains("browser");
+ }
+
+ public static void logResponseHeaders(CmsLog log, HttpServletResponse response) {
+ if (!log.isDebugEnabled())
+ return;
+ for (String headerName : response.getHeaderNames()) {
+ Object headerValue = response.getHeader(headerName);
+ log.debug(headerName + ": " + headerValue);
+ }
+ }
+
+ public static void logRequestHeaders(CmsLog log, HttpServletRequest request) {
+ if (!log.isDebugEnabled())
+ return;
+ for (Enumeration<String> headerNames = request.getHeaderNames(); headerNames.hasMoreElements();) {
+ String headerName = headerNames.nextElement();
+ Object headerValue = request.getHeader(headerName);
+ log.debug(headerName + ": " + headerValue);
+ }
+ log.debug(request.getRequestURI() + "\n");
+ }
+
+ public static void logRequest(CmsLog log, HttpServletRequest request) {
+ log.debug("contextPath=" + request.getContextPath());
+ log.debug("servletPath=" + request.getServletPath());
+ log.debug("requestURI=" + request.getRequestURI());
+ log.debug("queryString=" + request.getQueryString());
+ StringBuilder buf = new StringBuilder();
+ // headers
+ Enumeration<String> en = request.getHeaderNames();
+ while (en.hasMoreElements()) {
+ String header = en.nextElement();
+ Enumeration<String> values = request.getHeaders(header);
+ while (values.hasMoreElements())
+ buf.append(" " + header + ": " + values.nextElement());
+ buf.append('\n');
+ }
+
+ // attributed
+ Enumeration<String> an = request.getAttributeNames();
+ while (an.hasMoreElements()) {
+ String attr = an.nextElement();
+ Object value = request.getAttribute(attr);
+ buf.append(" " + attr + ": " + value);
+ buf.append('\n');
+ }
+ log.debug("\n" + buf);
+ }
+
+ private HttpUtils() {
+
+ }
+}
--- /dev/null
+package org.argeo.cms.servlet.internal;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Collection;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.io.IOUtils;
+import org.argeo.cms.osgi.PublishNamespace;
+import org.argeo.osgi.util.FilterRequirement;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.Version;
+import org.osgi.framework.VersionRange;
+import org.osgi.framework.namespace.PackageNamespace;
+import org.osgi.framework.wiring.BundleCapability;
+import org.osgi.framework.wiring.BundleWiring;
+import org.osgi.framework.wiring.FrameworkWiring;
+import org.osgi.resource.Requirement;
+
+public class PkgServlet extends HttpServlet {
+ private static final long serialVersionUID = 7660824185145214324L;
+
+ private BundleContext bundleContext = FrameworkUtil.getBundle(PkgServlet.class).getBundleContext();
+
+ @Override
+ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+ String pathInfo = req.getPathInfo();
+
+ String pkg, versionStr, file;
+ String[] parts = pathInfo.split("/");
+ // first is always empty
+ if (parts.length == 4) {
+ pkg = parts[1];
+ versionStr = parts[2];
+ file = parts[3];
+ } else if (parts.length == 3) {
+ pkg = parts[1];
+ versionStr = null;
+ file = parts[2];
+ } else {
+ throw new IllegalArgumentException("Unsupported path length " + pathInfo);
+ }
+
+ FrameworkWiring frameworkWiring = bundleContext.getBundle(0).adapt(FrameworkWiring.class);
+ String filter;
+ if (versionStr == null) {
+ filter = "(" + PackageNamespace.PACKAGE_NAMESPACE + "=" + pkg + ")";
+ } else {
+ if (versionStr.startsWith("[") || versionStr.startsWith("(")) {// range
+ VersionRange versionRange = new VersionRange(versionStr);
+ filter = "(&(" + PackageNamespace.PACKAGE_NAMESPACE + "=" + pkg + ")"
+ + versionRange.toFilterString(PackageNamespace.CAPABILITY_VERSION_ATTRIBUTE) + ")";
+
+ } else {
+ Version version = new Version(versionStr);
+ filter = "(&(" + PackageNamespace.PACKAGE_NAMESPACE + "=" + pkg + ")("
+ + PackageNamespace.CAPABILITY_VERSION_ATTRIBUTE + "=" + version + "))";
+ }
+ }
+ Requirement requirement = new FilterRequirement(PackageNamespace.PACKAGE_NAMESPACE, filter);
+ Collection<BundleCapability> packages = frameworkWiring.findProviders(requirement);
+ if (packages.isEmpty()) {
+ resp.sendError(404);
+ return;
+ }
+
+ // TODO verify that it works with multiple versions
+ SortedMap<Version, BundleCapability> sorted = new TreeMap<>();
+ for (BundleCapability capability : packages) {
+ sorted.put(capability.getRevision().getVersion(), capability);
+ }
+
+ Bundle bundle = sorted.get(sorted.firstKey()).getRevision().getBundle();
+ String entryPath = '/' + pkg.replace('.', '/') + '/' + file;
+ URL internalURL = bundle.getResource(entryPath);
+ if (internalURL == null) {
+ resp.sendError(404);
+ return;
+ }
+
+ // Resource found, we now check whether it can be published
+ boolean publish = false;
+ BundleWiring bundleWiring = bundle.adapt(BundleWiring.class);
+ capabilities: for (BundleCapability bundleCapability : bundleWiring
+ .getCapabilities(PublishNamespace.CMS_PUBLISH_NAMESPACE)) {
+ Object publishedPkg = bundleCapability.getAttributes().get(PublishNamespace.PKG);
+ if (publishedPkg != null) {
+ if (publishedPkg.equals("*") || publishedPkg.equals(pkg)) {
+ Object publishedFile = bundleCapability.getAttributes().get(PublishNamespace.FILE);
+ if (publishedFile == null) {
+ publish = true;
+ break capabilities;
+ } else {
+ String[] publishedFiles = publishedFile.toString().split(",");
+ for (String pattern : publishedFiles) {
+ if (pattern.startsWith("*.")) {
+ String ext = pattern.substring(1);
+ if (file.endsWith(ext)) {
+ publish = true;
+ break capabilities;
+ }
+ } else {
+ if (publishedFile.equals(file)) {
+ publish = true;
+ break capabilities;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (!publish) {
+ resp.sendError(404);
+ return;
+ }
+
+ try (InputStream in = internalURL.openStream()) {
+ IOUtils.copy(in, resp.getOutputStream());
+ }
+ }
+
+}
--- /dev/null
+package org.argeo.cms.servlet.internal;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+public class RobotServlet extends HttpServlet {
+ private static final long serialVersionUID = 7935661175336419089L;
+
+ @Override
+ protected void service(HttpServletRequest request, HttpServletResponse response)
+ throws ServletException, IOException {
+ PrintWriter writer = response.getWriter();
+ writer.append("User-agent: *\n");
+ writer.append("Disallow:\n");
+ response.setHeader("Content-Type", "text/plain");
+ writer.flush();
+ }
+
+}
--- /dev/null
+package org.argeo.cms.servlet.internal.jetty;
+
+import java.util.Dictionary;
+
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.api.cms.CmsLog;
+import org.eclipse.equinox.http.jetty.JettyConfigurator;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.service.cm.ConfigurationException;
+import org.osgi.service.cm.ManagedServiceFactory;
+
+public class JettyServiceFactory implements ManagedServiceFactory {
+ private final CmsLog log = CmsLog.getLog(JettyServiceFactory.class);
+
+ public void start() {
+
+ }
+
+ @Override
+ public String getName() {
+ return "Jetty Service Factory";
+ }
+
+ @Override
+ public void updated(String pid, Dictionary<String, ?> properties) throws ConfigurationException {
+ // Explicitly configures Jetty so that the default server is not started by the
+ // activator of the Equinox Jetty bundle.
+
+// if (!webServerConfig.isEmpty()) {
+// webServerConfig.put("customizer.class", KernelConstants.CMS_JETTY_CUSTOMIZER_CLASS);
+//
+// // TODO centralise with Jetty extender
+// Object webSocketEnabled = webServerConfig.get(InternalHttpConstants.WEBSOCKET_ENABLED);
+// if (webSocketEnabled != null && webSocketEnabled.toString().equals("true")) {
+// bc.registerService(ServerEndpointConfig.Configurator.class, new CmsWebSocketConfigurator(), null);
+// webServerConfig.put(InternalHttpConstants.WEBSOCKET_ENABLED, "true");
+// }
+// }
+
+ int tryCount = 60;
+ try {
+ tryGettyJetty: while (tryCount > 0) {
+ try {
+ // FIXME deal with multiple ids
+ JettyConfigurator.startServer(CmsConstants.DEFAULT, properties);
+ // Explicitly starts Jetty OSGi HTTP bundle, so that it gets triggered if OSGi
+ // configuration is not cleaned
+ FrameworkUtil.getBundle(JettyConfigurator.class).start();
+ break tryGettyJetty;
+ } catch (IllegalStateException e) {
+ // Jetty may not be ready
+ try {
+ Thread.sleep(1000);
+ } catch (Exception e1) {
+ // silent
+ }
+ tryCount--;
+ }
+ }
+ } catch (Exception e) {
+ log.error("Cannot start default Jetty server with config " + properties, e);
+ }
+
+ }
+
+ @Override
+ public void deleted(String pid) {
+ }
+
+ public void stop() {
+ try {
+ JettyConfigurator.stopServer(CmsConstants.DEFAULT);
+ } catch (Exception e) {
+ log.error("Cannot stop default Jetty server.", e);
+ }
+
+ }
+
+}
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="src" path="src" />
+ <classpathentry kind="con"
+ path="org.eclipse.pde.core.requiredPlugins" />
+ <classpathentry kind="con"
+ path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11" />
+ <classpathentry kind="output" path="bin" />
+</classpath>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>org.argeo.cms.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
+/MANIFEST.MF
--- /dev/null
+Import-Package: org.eclipse.swt,\
+ org.eclipse.jface.window,\
+ org.eclipse.core.commands.common,\
+ *
+
+Bundle-ActivationPolicy: lazy
+
\ No newline at end of file
--- /dev/null
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+ .
+
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.argeo.commons</groupId>
+ <version>2.3-SNAPSHOT</version>
+ <artifactId>eclipse</artifactId>
+ <relativePath>..</relativePath>
+ </parent>
+ <artifactId>org.argeo.cms.swt</artifactId>
+ <name>CMS SWT</name>
+ <dependencies>
+<!-- <dependency> -->
+<!-- <groupId>org.argeo.commons</groupId> -->
+<!-- <artifactId>org.argeo.util</artifactId> -->
+<!-- <version>2.1.89-SNAPSHOT</version> -->
+<!-- </dependency> -->
+ <dependency>
+ <groupId>org.argeo.commons</groupId>
+ <artifactId>org.argeo.cms</artifactId>
+ <version>2.3-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>org.argeo.commons</groupId>
+ <artifactId>org.argeo.cms.servlet</artifactId>
+ <version>2.3-SNAPSHOT</version>
+ </dependency>
+
+ <!-- Specific -->
+ <dependency>
+ <groupId>org.argeo.commons.rap</groupId>
+ <artifactId>org.argeo.swt.specific.rap</artifactId>
+ <version>2.3-SNAPSHOT</version>
+ <scope>provided</scope>
+ </dependency>
+ <!-- UI -->
+ <dependency>
+ <groupId>org.argeo.tp.rap.e4</groupId>
+ <artifactId>org.eclipse.rap.rwt</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.argeo.tp.rap.e4</groupId>
+ <artifactId>org.eclipse.core.commands</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.argeo.tp.rap.e4</groupId>
+ <artifactId>org.eclipse.rap.jface</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+</project>
\ No newline at end of file
--- /dev/null
+package org.argeo.cms.swt;
+
+import org.argeo.api.cms.CmsTheme;
+import org.eclipse.swt.graphics.Image;
+
+/** Can be applied to {@link Enum}s in order to generated {@link Image}s. */
+public interface CmsIcon {
+ String name();
+
+ default Image getSmallIcon(CmsTheme theme) {
+ return ((CmsSwtTheme) theme).getIcon(name(), getSmallIconSize());
+ }
+
+ default Image getBigIcon(CmsTheme theme) {
+ return ((CmsSwtTheme) theme).getIcon(name(), getBigIconSize());
+ }
+
+ default Integer getSmallIconSize() {
+ return 16;
+ }
+
+ default Integer getBigIconSize() {
+ return 32;
+ }
+}
--- /dev/null
+package org.argeo.cms.swt;
+
+/** Styles references in the CSS. */
+@Deprecated
+public interface CmsStyles {
+ // General
+ public final static String CMS_SHELL = "cms_shell";
+ public final static String CMS_MENU_LINK = "cms_menu_link";
+
+ // Header
+ public final static String CMS_HEADER = "cms_header";
+ public final static String CMS_HEADER_LEAD = "cms_header-lead";
+ public final static String CMS_HEADER_CENTER = "cms_header-center";
+ public final static String CMS_HEADER_END = "cms_header-end";
+
+ public final static String CMS_LEAD = "cms_lead";
+ public final static String CMS_END = "cms_end";
+ public final static String CMS_FOOTER = "cms_footer";
+
+ public final static String CMS_USER_MENU = "cms_user_menu";
+ public final static String CMS_USER_MENU_LINK = "cms_user_menu-link";
+ public final static String CMS_USER_MENU_ITEM = "cms_user_menu-item";
+ public final static String CMS_LOGIN_DIALOG = "cms_login_dialog";
+ public final static String CMS_LOGIN_DIALOG_USERNAME = "cms_login_dialog-username";
+ public final static String CMS_LOGIN_DIALOG_PASSWORD = "cms_login_dialog-password";
+
+ // Body
+ public final static String CMS_SCROLLED_AREA = "cms_scrolled_area";
+ public final static String CMS_BODY = "cms_body";
+ public final static String CMS_STATIC_TEXT = "cms_static-text";
+ public final static String CMS_LINK = "cms_link";
+}
--- /dev/null
+package org.argeo.cms.swt;
+
+import org.argeo.api.cms.CmsTheme;
+import org.eclipse.swt.graphics.Image;
+
+/** SWT specific {@link CmsTheme}. */
+public interface CmsSwtTheme extends CmsTheme {
+ /** The image registered at this path, or <code>null</code> if not found. */
+ Image getImage(String path);
+
+ /**
+ * And icon with this file name (without the extension), with a best effort to
+ * find the appropriate size, or <code>null</code> if not found.
+ *
+ * @param name An icon file name without path and extension.
+ * @param preferredSize the preferred size, if <code>null</code>,
+ * {@link #getDefaultIconSize()} will be tried.
+ */
+ Image getIcon(String name, Integer preferredSize);
+
+}
--- /dev/null
+package org.argeo.cms.swt;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.argeo.api.cms.CmsStyle;
+import org.argeo.api.cms.CmsTheme;
+import org.argeo.api.cms.CmsView;
+import org.argeo.eclipse.ui.specific.EclipseUiSpecificUtils;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.layout.FormAttachment;
+import org.eclipse.swt.layout.FormData;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.layout.RowData;
+import org.eclipse.swt.layout.RowLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Text;
+import org.eclipse.swt.widgets.Widget;
+
+/** SWT utilities. */
+public class CmsSwtUtils {
+
+ /** Singleton. */
+ private CmsSwtUtils() {
+ }
+
+ public static CmsTheme getCmsTheme(Composite parent) {
+ CmsTheme theme = (CmsTheme) parent.getData(CmsTheme.class.getName());
+ if (theme == null) {
+ // find parent shell
+ Shell topShell = parent.getShell();
+ while (topShell.getParent() != null)
+ topShell = (Shell) topShell.getParent();
+ theme = (CmsTheme) topShell.getData(CmsTheme.class.getName());
+ parent.setData(CmsTheme.class.getName(), theme);
+ }
+ return theme;
+ }
+
+ public static void registerCmsTheme(Shell shell, CmsTheme theme) {
+ // find parent shell
+ Shell topShell = shell;
+ while (topShell.getParent() != null)
+ topShell = (Shell) topShell.getParent();
+ // check if already set
+ if (topShell.getData(CmsTheme.class.getName()) != null) {
+ CmsTheme registeredTheme = (CmsTheme) topShell.getData(CmsTheme.class.getName());
+ throw new IllegalArgumentException(
+ "Theme " + registeredTheme.getThemeId() + " already registered in this shell");
+ }
+ topShell.setData(CmsTheme.class.getName(), theme);
+ }
+
+ public static CmsView getCmsView(Control parent) {
+ // find parent shell
+ Shell topShell = parent.getShell();
+ while (topShell.getParent() != null)
+ topShell = (Shell) topShell.getParent();
+ return (CmsView) topShell.getData(CmsView.class.getName());
+ }
+
+ public static void registerCmsView(Shell shell, CmsView view) {
+ // find parent shell
+ Shell topShell = shell;
+ while (topShell.getParent() != null)
+ topShell = (Shell) topShell.getParent();
+ // check if already set
+ if (topShell.getData(CmsView.class.getName()) != null) {
+ CmsView registeredView = (CmsView) topShell.getData(CmsView.class.getName());
+ throw new IllegalArgumentException("Cms view " + registeredView + " already registered in this shell");
+ }
+ shell.setData(CmsView.class.getName(), view);
+ }
+
+ /** Sends an event via {@link CmsView#sendEvent(String, Map)}. */
+ public static void sendEventOnSelect(Control control, String topic, Map<String, Object> properties) {
+ SelectionListener listener = (Selected) (e) -> {
+ getCmsView(control.getParent()).sendEvent(topic, properties);
+ };
+ if (control instanceof Button) {
+ ((Button) control).addSelectionListener(listener);
+ } else
+ throw new UnsupportedOperationException("Control type " + control.getClass() + " is not supported.");
+ }
+
+ /**
+ * Convenience method to sends an event via
+ * {@link CmsView#sendEvent(String, Map)}.
+ */
+ public static void sendEventOnSelect(Control control, String topic, String key, Object value) {
+ Map<String, Object> properties = new HashMap<>();
+ properties.put(key, value);
+ sendEventOnSelect(control, topic, properties);
+ }
+
+ /*
+ * GRID LAYOUT
+ */
+ public static GridLayout noSpaceGridLayout() {
+ return noSpaceGridLayout(new GridLayout());
+ }
+
+ public static GridLayout noSpaceGridLayout(int columns) {
+ return noSpaceGridLayout(new GridLayout(columns, false));
+ }
+
+ /** @return the same layout, with spaces removed. */
+ public static GridLayout noSpaceGridLayout(GridLayout layout) {
+ layout.horizontalSpacing = 0;
+ layout.verticalSpacing = 0;
+ layout.marginWidth = 0;
+ layout.marginHeight = 0;
+ return layout;
+ }
+
+ public static GridData fillAll() {
+ return new GridData(SWT.FILL, SWT.FILL, true, true);
+ }
+
+ public static GridData fillWidth() {
+ return grabWidth(SWT.FILL, SWT.FILL);
+ }
+
+ public static GridData grabWidth(int horizontalAlignment, int verticalAlignment) {
+ return new GridData(horizontalAlignment, horizontalAlignment, true, false);
+ }
+
+ public static GridData fillHeight() {
+ return grabHeight(SWT.FILL, SWT.FILL);
+ }
+
+ public static GridData grabHeight(int horizontalAlignment, int verticalAlignment) {
+ return new GridData(horizontalAlignment, horizontalAlignment, false, true);
+ }
+
+ /*
+ * ROW LAYOUT
+ */
+ /** @return the same layout, with margins removed. */
+ public static RowLayout noMarginsRowLayout(RowLayout rowLayout) {
+ rowLayout.marginTop = 0;
+ rowLayout.marginBottom = 0;
+ rowLayout.marginLeft = 0;
+ rowLayout.marginRight = 0;
+ return rowLayout;
+ }
+
+ public static RowLayout noMarginsRowLayout(int type) {
+ return noMarginsRowLayout(new RowLayout(type));
+ }
+
+ public static RowData rowData16px() {
+ return new RowData(16, 16);
+ }
+
+ /*
+ * FORM LAYOUT
+ */
+ public static FormData coverAll() {
+ FormData fdLabel = new FormData();
+ fdLabel.top = new FormAttachment(0, 0);
+ fdLabel.left = new FormAttachment(0, 0);
+ fdLabel.right = new FormAttachment(100, 0);
+ fdLabel.bottom = new FormAttachment(100, 0);
+ return fdLabel;
+ }
+
+ /*
+ * STYLING
+ */
+
+ /** Style widget */
+ public static <T extends Widget> T style(T widget, String style) {
+ if (style == null)
+ return widget;// does nothing
+ EclipseUiSpecificUtils.setStyleData(widget, style);
+ if (widget instanceof Control) {
+ CmsView cmsView = getCmsView((Control) widget);
+ if (cmsView != null)
+ cmsView.applyStyles(widget);
+ }
+ return widget;
+ }
+
+ /** Style widget */
+ public static <T extends Widget> T style(T widget, CmsStyle style) {
+ return style(widget, style.style());
+ }
+
+ /** Enable markups on widget */
+ public static <T extends Widget> T markup(T widget) {
+ EclipseUiSpecificUtils.setMarkupData(widget);
+ return widget;
+ }
+
+ /** Disable markup validation. */
+ public static <T extends Widget> T disableMarkupValidation(T widget) {
+ EclipseUiSpecificUtils.setMarkupValidationDisabledData(widget);
+ return widget;
+ }
+
+ /**
+ * Apply markup and set text on {@link Label}, {@link Button}, {@link Text}.
+ *
+ * @param widget the widget to style and to use in order to display text
+ * @param txt the object to display via its <code>toString()</code> method.
+ * This argument should not be null, but if it is null and
+ * assertions are disabled "<null>" is displayed instead; if
+ * assertions are enabled the call will fail.
+ *
+ * @see markup
+ */
+ public static <T extends Widget> T text(T widget, Object txt) {
+ assert txt != null;
+ String str = txt != null ? txt.toString() : "<null>";
+ markup(widget);
+ if (widget instanceof Label)
+ ((Label) widget).setText(str);
+ else if (widget instanceof Button)
+ ((Button) widget).setText(str);
+ else if (widget instanceof Text)
+ ((Text) widget).setText(str);
+ else
+ throw new IllegalArgumentException("Unsupported widget type " + widget.getClass());
+ return widget;
+ }
+
+ /** A {@link Label} with markup activated. */
+ public static Label lbl(Composite parent, Object txt) {
+ return text(new Label(parent, SWT.NONE), txt);
+ }
+
+ /** A read-only {@link Text} whose content can be copy/pasted. */
+ public static Text txt(Composite parent, Object txt) {
+ return text(new Text(parent, SWT.NONE), txt);
+ }
+
+ /** Dispose all children of a Composite */
+ public static void clear(Composite composite) {
+ if (composite.isDisposed())
+ return;
+ for (Control child : composite.getChildren())
+ child.dispose();
+ }
+}
--- /dev/null
+package org.argeo.cms.swt;
+
+import org.eclipse.swt.events.MouseEvent;
+import org.eclipse.swt.events.MouseListener;
+
+/**
+ * {@link MouseListener#mouseDoubleClick(MouseEvent)} as a functional interface
+ * in order to use as a short lambda expression in UI code.
+ * {@link MouseListener#mouseDownouseEvent)} and
+ * {@link MouseListener#mouseUp(MouseEvent)} do nothing by default.
+ */
+@FunctionalInterface
+public interface MouseDoubleClick extends MouseListener {
+ @Override
+ void mouseDoubleClick(MouseEvent e);
+
+ @Override
+ default void mouseDown(MouseEvent e) {
+ // does nothing
+ }
+
+ @Override
+ default void mouseUp(MouseEvent e) {
+ // does nothing
+ }
+}
--- /dev/null
+package org.argeo.cms.swt;
+
+import org.eclipse.swt.events.MouseEvent;
+import org.eclipse.swt.events.MouseListener;
+
+/**
+ * {@link MouseListener#mouseDown(MouseEvent)} as a functional interface in
+ * order to use as a short lambda expression in UI code.
+ * {@link MouseListener#mouseDoubleClick(MouseEvent)} and
+ * {@link MouseListener#mouseUp(MouseEvent)} do nothing by default.
+ */
+@FunctionalInterface
+public interface MouseDown extends MouseListener {
+ @Override
+ void mouseDown(MouseEvent e);
+
+ @Override
+ default void mouseDoubleClick(MouseEvent e) {
+ // does nothing
+ }
+
+ @Override
+ default void mouseUp(MouseEvent e) {
+ // does nothing
+ }
+}
--- /dev/null
+package org.argeo.cms.swt;
+
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+
+/**
+ * {@link SelectionListener} as a functional interface in order to use as a
+ * short lambda expression in UI code.
+ * {@link SelectionListener#widgetDefaultSelected(SelectionEvent)} does nothing
+ * by default.
+ */
+@FunctionalInterface
+public interface Selected extends SelectionListener {
+ @Override
+ public void widgetSelected(SelectionEvent e);
+
+ default public void widgetDefaultSelected(SelectionEvent e) {
+ // does nothing
+ }
+
+}
--- /dev/null
+package org.argeo.cms.swt;
+
+import org.argeo.api.cms.UxContext;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.widgets.Display;
+
+public class SimpleSwtUxContext implements UxContext {
+ private Point size;
+ private Point small = new Point(400, 400);
+
+ public SimpleSwtUxContext() {
+ this(Display.getCurrent().getBounds());
+ }
+
+ public SimpleSwtUxContext(Rectangle rect) {
+ this.size = new Point(rect.width, rect.height);
+ }
+
+ public SimpleSwtUxContext(Point size) {
+ this.size = size;
+ }
+
+ @Override
+ public boolean isPortrait() {
+ return size.x >= size.y;
+ }
+
+ @Override
+ public boolean isLandscape() {
+ return size.x < size.y;
+ }
+
+ @Override
+ public boolean isSquare() {
+ return size.x == size.y;
+ }
+
+ @Override
+ public boolean isSmall() {
+ return size.x <= small.x || size.y <= small.y;
+ }
+
+ @Override
+ public boolean isMasterData() {
+ // TODO make it configurable
+ return true;
+ }
+
+}
--- /dev/null
+package org.argeo.cms.swt.auth;
+
+import static org.argeo.cms.CmsMsg.password;
+import static org.argeo.cms.CmsMsg.username;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Locale;
+
+import javax.security.auth.Subject;
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.LanguageCallback;
+import javax.security.auth.callback.NameCallback;
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+
+import org.argeo.api.cms.CmsAuth;
+import org.argeo.api.cms.CmsContext;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.api.cms.CmsView;
+import org.argeo.cms.CmsMsg;
+import org.argeo.cms.LocaleUtils;
+import org.argeo.cms.auth.RemoteAuthCallback;
+import org.argeo.cms.servlet.ServletHttpRequest;
+import org.argeo.cms.servlet.ServletHttpResponse;
+import org.argeo.cms.swt.CmsStyles;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.eclipse.ui.specific.UiContext;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.MouseAdapter;
+import org.eclipse.swt.events.MouseEvent;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.events.TraverseEvent;
+import org.eclipse.swt.events.TraverseListener;
+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.Label;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Text;
+
+public class CmsLogin implements CmsStyles, CallbackHandler {
+ private final static CmsLog log = CmsLog.getLog(CmsLogin.class);
+
+ private Composite parent;
+ private Text usernameT, passwordT;
+ private Composite credentialsBlock;
+ private final SelectionListener loginSelectionListener;
+
+ private final Locale defaultLocale;
+ private LocaleChoice localeChoice = null;
+
+ private final CmsView cmsView;
+
+ // optional subject to be set explicitly
+ private Subject subject = null;
+
+ public CmsLogin(CmsView cmsView) {
+ this.cmsView = cmsView;
+ CmsContext nodeState = null;// = Activator.getNodeState();
+ // FIXME reactivate locales
+ if (nodeState != null) {
+ defaultLocale = nodeState.getDefaultLocale();
+ List<Locale> locales = nodeState.getLocales();
+ if (locales != null)
+ localeChoice = new LocaleChoice(locales, defaultLocale);
+ } else {
+ defaultLocale = Locale.getDefault();
+ }
+ loginSelectionListener = new SelectionListener() {
+ private static final long serialVersionUID = -8832133363830973578L;
+
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ login();
+ }
+
+ @Override
+ public void widgetDefaultSelected(SelectionEvent e) {
+ }
+ };
+ }
+
+ protected boolean isAnonymous() {
+ return cmsView.isAnonymous();
+ }
+
+ public final void createUi(Composite parent) {
+ this.parent = parent;
+ createContents(parent);
+ }
+
+ protected void createContents(Composite parent) {
+ defaultCreateContents(parent);
+ }
+
+ public final void defaultCreateContents(Composite parent) {
+ parent.setLayout(CmsSwtUtils.noSpaceGridLayout());
+ Composite credentialsBlock = createCredentialsBlock(parent);
+ if (parent instanceof Shell) {
+ credentialsBlock.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, true, true));
+ }
+ }
+
+ public final Composite createCredentialsBlock(Composite parent) {
+ if (isAnonymous()) {
+ return anonymousUi(parent);
+ } else {
+ return userUi(parent);
+ }
+ }
+
+ public Composite getCredentialsBlock() {
+ return credentialsBlock;
+ }
+
+ protected Composite userUi(Composite parent) {
+ Locale locale = localeChoice == null ? this.defaultLocale : localeChoice.getSelectedLocale();
+ credentialsBlock = new Composite(parent, SWT.NONE);
+ credentialsBlock.setLayout(new GridLayout());
+ // credentialsBlock.setLayoutData(CmsUiUtils.fillAll());
+
+ specificUserUi(credentialsBlock);
+
+ Label l = new Label(credentialsBlock, SWT.NONE);
+ CmsSwtUtils.style(l, CMS_USER_MENU_ITEM);
+ l.setText(CmsMsg.logout.lead(locale));
+ GridData lData = CmsSwtUtils.fillWidth();
+ lData.widthHint = 120;
+ l.setLayoutData(lData);
+
+ l.addMouseListener(new MouseAdapter() {
+ private static final long serialVersionUID = 6444395812777413116L;
+
+ public void mouseDown(MouseEvent e) {
+ logout();
+ }
+ });
+ return credentialsBlock;
+ }
+
+ /** To be overridden */
+ protected void specificUserUi(Composite parent) {
+
+ }
+
+ protected Composite anonymousUi(Composite parent) {
+ Locale locale = localeChoice == null ? this.defaultLocale : localeChoice.getSelectedLocale();
+ // We need a composite for the traversal
+ credentialsBlock = new Composite(parent, SWT.NONE);
+ credentialsBlock.setLayout(new GridLayout());
+ // credentialsBlock.setLayoutData(CmsUiUtils.fillAll());
+ CmsSwtUtils.style(credentialsBlock, CMS_LOGIN_DIALOG);
+
+ Integer textWidth = 120;
+ if (parent instanceof Shell)
+ CmsSwtUtils.style(parent, CMS_USER_MENU);
+ // new Label(this, SWT.NONE).setText(CmsMsg.username.lead());
+ usernameT = new Text(credentialsBlock, SWT.BORDER);
+ usernameT.setMessage(username.lead(locale));
+ CmsSwtUtils.style(usernameT, CMS_LOGIN_DIALOG_USERNAME);
+ GridData gd = CmsSwtUtils.fillWidth();
+ gd.widthHint = textWidth;
+ usernameT.setLayoutData(gd);
+
+ // new Label(this, SWT.NONE).setText(CmsMsg.password.lead());
+ passwordT = new Text(credentialsBlock, SWT.BORDER | SWT.PASSWORD);
+ passwordT.setMessage(password.lead(locale));
+ CmsSwtUtils.style(passwordT, CMS_LOGIN_DIALOG_PASSWORD);
+ gd = CmsSwtUtils.fillWidth();
+ gd.widthHint = textWidth;
+ passwordT.setLayoutData(gd);
+
+ TraverseListener tl = new TraverseListener() {
+ private static final long serialVersionUID = -1158892811534971856L;
+
+ public void keyTraversed(TraverseEvent e) {
+ if (e.detail == SWT.TRAVERSE_RETURN)
+ login();
+ }
+ };
+ credentialsBlock.addTraverseListener(tl);
+ usernameT.addTraverseListener(tl);
+ passwordT.addTraverseListener(tl);
+ parent.setTabList(new Control[] { credentialsBlock });
+ credentialsBlock.setTabList(new Control[] { usernameT, passwordT });
+
+ // Button
+ Button loginButton = new Button(credentialsBlock, SWT.PUSH);
+ loginButton.setText(CmsMsg.login.lead(locale));
+ loginButton.setLayoutData(CmsSwtUtils.fillWidth());
+ loginButton.addSelectionListener(loginSelectionListener);
+
+ extendsCredentialsBlock(credentialsBlock, locale, loginSelectionListener);
+ if (localeChoice != null)
+ createLocalesBlock(credentialsBlock);
+ return credentialsBlock;
+ }
+
+ /**
+ * To be overridden in order to provide custom login button and other links.
+ */
+ protected void extendsCredentialsBlock(Composite credentialsBlock, Locale selectedLocale,
+ SelectionListener loginSelectionListener) {
+
+ }
+
+ protected void updateLocale(Locale selectedLocale) {
+ // save already entered values
+ String usernameStr = usernameT.getText();
+ char[] pwd = passwordT.getTextChars();
+
+ for (Control child : parent.getChildren())
+ child.dispose();
+ createContents(parent);
+ if (parent.getParent() != null)
+ parent.getParent().layout(true, true);
+ else
+ parent.layout();
+ usernameT.setText(usernameStr);
+ passwordT.setTextChars(pwd);
+ }
+
+ protected Composite createLocalesBlock(final Composite parent) {
+ Composite c = new Composite(parent, SWT.NONE);
+ CmsSwtUtils.style(c, CMS_USER_MENU_ITEM);
+ c.setLayout(CmsSwtUtils.noSpaceGridLayout());
+ c.setLayoutData(CmsSwtUtils.fillAll());
+
+ SelectionListener selectionListener = new SelectionAdapter() {
+ private static final long serialVersionUID = 4891637813567806762L;
+
+ public void widgetSelected(SelectionEvent event) {
+ Button button = (Button) event.widget;
+ if (button.getSelection()) {
+ localeChoice.setSelectedIndex((Integer) event.widget.getData());
+ updateLocale(localeChoice.getSelectedLocale());
+ }
+ };
+ };
+
+ List<Locale> locales = localeChoice.getLocales();
+ for (Integer i = 0; i < locales.size(); i++) {
+ Locale locale = locales.get(i);
+ Button button = new Button(c, SWT.RADIO);
+ CmsSwtUtils.style(button, CMS_USER_MENU_ITEM);
+ button.setData(i);
+ button.setText(LocaleUtils.toLead(locale.getDisplayName(locale), locale) + " (" + locale + ")");
+ // button.addListener(SWT.Selection, listener);
+ button.addSelectionListener(selectionListener);
+ if (i == localeChoice.getSelectedIndex())
+ button.setSelection(true);
+ }
+ return c;
+ }
+
+ protected boolean login() {
+ // TODO use CmsVie in order to retrieve subject?
+ // Subject subject = cmsView.getLoginContext().getSubject();
+ // LoginContext loginContext = cmsView.getLoginContext();
+ try {
+ //
+ // LOGIN
+ //
+ // loginContext.logout();
+ LoginContext loginContext;
+ if (subject == null)
+ loginContext = new LoginContext(CmsAuth.LOGIN_CONTEXT_USER, this);
+ else
+ loginContext = new LoginContext(CmsAuth.LOGIN_CONTEXT_USER, subject, this);
+ loginContext.login();
+ cmsView.authChange(loginContext);
+ return true;
+ } catch (LoginException e) {
+ if (log.isTraceEnabled())
+ log.warn("Login failed: " + e.getMessage(), e);
+ else
+ log.warn("Login failed: " + e.getMessage());
+
+ try {
+ Thread.sleep(3000);
+ } catch (InterruptedException e2) {
+ // silent
+ }
+ // ErrorFeedback.show("Login failed", e);
+ return false;
+ }
+ // catch (LoginException e) {
+ // log.error("Cannot login", e);
+ // return false;
+ // }
+ }
+
+ protected void logout() {
+ cmsView.logout();
+ cmsView.navigateTo("~");
+ }
+
+ @Override
+ public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
+ for (Callback callback : callbacks) {
+ if (callback instanceof NameCallback && usernameT != null)
+ ((NameCallback) callback).setName(usernameT.getText());
+ else if (callback instanceof PasswordCallback && passwordT != null)
+ ((PasswordCallback) callback).setPassword(passwordT.getTextChars());
+ else if (callback instanceof RemoteAuthCallback) {
+ ((RemoteAuthCallback) callback).setRequest(new ServletHttpRequest(UiContext.getHttpRequest()));
+ ((RemoteAuthCallback) callback).setResponse(new ServletHttpResponse(UiContext.getHttpResponse()));
+ } else if (callback instanceof LanguageCallback) {
+ Locale toUse = null;
+ if (localeChoice != null)
+ toUse = localeChoice.getSelectedLocale();
+ else if (defaultLocale != null)
+ toUse = defaultLocale;
+
+ if (toUse != null) {
+ ((LanguageCallback) callback).setLocale(toUse);
+ UiContext.setLocale(toUse);
+ }
+
+ }
+ }
+ }
+
+ public void setSubject(Subject subject) {
+ this.subject = subject;
+ }
+
+}
--- /dev/null
+package org.argeo.cms.swt.auth;
+
+import org.argeo.api.cms.CmsView;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Shell;
+
+/** The site-related user menu */
+public class CmsLoginShell extends CmsLogin {
+ private final Shell shell;
+
+ public CmsLoginShell(CmsView cmsView) {
+ super(cmsView);
+ shell = createShell();
+// createUi(shell);
+ }
+
+ /** To be overridden. */
+ protected Shell createShell() {
+ Shell shell = new Shell(Display.getCurrent(), SWT.NO_TRIM);
+ shell.setMaximized(true);
+ return shell;
+ }
+
+ /** To be overridden. */
+ public void open() {
+ CmsSwtUtils.style(shell, CMS_USER_MENU);
+ shell.open();
+ }
+
+ @Override
+ protected boolean login() {
+ boolean success = false;
+ try {
+ success = super.login();
+ return success;
+ } finally {
+ if (success)
+ closeShell();
+ else {
+ for (Control child : shell.getChildren())
+ child.dispose();
+ createUi(shell);
+ shell.layout();
+ // TODO error message
+ }
+ }
+ }
+
+ @Override
+ protected void logout() {
+ closeShell();
+ super.logout();
+ }
+
+ protected void closeShell() {
+ if (!shell.isDisposed()) {
+ shell.close();
+ shell.dispose();
+ }
+ }
+
+ public Shell getShell() {
+ return shell;
+ }
+
+ public void createUi(){
+ createUi(shell);
+ }
+}
--- /dev/null
+package org.argeo.cms.swt.auth;
+
+import java.io.IOException;
+import java.util.Arrays;
+
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.NameCallback;
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.callback.TextOutputCallback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+
+import org.eclipse.swt.SWT;
+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.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+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.Text;
+
+/**
+ * A composite that can populate itself based on {@link Callback}s. It can be
+ * used directly as a {@link CallbackHandler} or be used by one by calling the
+ * {@link #createCallbackHandlers(Callback[])}. Supported standard
+ * {@link Callback}s are:<br>
+ * <ul>
+ * <li>{@link PasswordCallback}</li>
+ * <li>{@link NameCallback}</li>
+ * <li>{@link TextOutputCallback}</li>
+ * </ul>
+ * Supported Argeo {@link Callback}s are:<br>
+ * <ul>
+ * <li>{@link LocaleChoice}</li>
+ * </ul>
+ */
+public class CompositeCallbackHandler extends Composite implements CallbackHandler {
+ private static final long serialVersionUID = -928223893722723777L;
+
+ private boolean wasUsedAlready = false;
+ private boolean isSubmitted = false;
+ private boolean isCanceled = false;
+
+ public CompositeCallbackHandler(Composite parent, int style) {
+ super(parent, style);
+ }
+
+ @Override
+ public synchronized void handle(final Callback[] callbacks) throws IOException, UnsupportedCallbackException {
+ // reset
+ if (wasUsedAlready && !isSubmitted() && !isCanceled()) {
+ cancel();
+ for (Control control : getChildren())
+ control.dispose();
+ isSubmitted = false;
+ isCanceled = false;
+ }
+
+ for (Callback callback : callbacks)
+ checkCallbackSupported(callback);
+ // create controls synchronously in the UI thread
+ getDisplay().syncExec(new Runnable() {
+
+ @Override
+ public void run() {
+ createCallbackHandlers(callbacks);
+ }
+ });
+
+ if (!wasUsedAlready)
+ wasUsedAlready = true;
+
+ // while (!isSubmitted() && !isCanceled()) {
+ // try {
+ // wait(1000l);
+ // } catch (InterruptedException e) {
+ // // silent
+ // }
+ // }
+
+ // cleanCallbacksAfterCancel(callbacks);
+ }
+
+ public void checkCallbackSupported(Callback callback) throws UnsupportedCallbackException {
+ if (callback instanceof TextOutputCallback || callback instanceof NameCallback
+ || callback instanceof PasswordCallback || callback instanceof LocaleChoice) {
+ return;
+ } else {
+ throw new UnsupportedCallbackException(callback);
+ }
+ }
+
+ /**
+ * Set writable callbacks to null if the handle is canceled (check is done
+ * by the method)
+ */
+ public void cleanCallbacksAfterCancel(Callback[] callbacks) {
+ if (isCanceled()) {
+ for (Callback callback : callbacks) {
+ if (callback instanceof NameCallback) {
+ ((NameCallback) callback).setName(null);
+ } else if (callback instanceof PasswordCallback) {
+ PasswordCallback pCallback = (PasswordCallback) callback;
+ char[] arr = pCallback.getPassword();
+ if (arr != null) {
+ Arrays.fill(arr, '*');
+ pCallback.setPassword(null);
+ }
+ }
+ }
+ }
+ }
+
+ public void createCallbackHandlers(Callback[] callbacks) {
+ Composite composite = this;
+ for (int i = 0; i < callbacks.length; i++) {
+ Callback callback = callbacks[i];
+ if (callback instanceof TextOutputCallback) {
+ createLabelTextoutputHandler(composite, (TextOutputCallback) callback);
+ } else if (callback instanceof NameCallback) {
+ createNameHandler(composite, (NameCallback) callback);
+ } else if (callback instanceof PasswordCallback) {
+ createPasswordHandler(composite, (PasswordCallback) callback);
+ } else if (callback instanceof LocaleChoice) {
+ createLocaleHandler(composite, (LocaleChoice) callback);
+ }
+ }
+ }
+
+ protected Text createNameHandler(Composite composite, final NameCallback callback) {
+ Label label = new Label(composite, SWT.NONE);
+ label.setText(callback.getPrompt());
+ final Text text = new Text(composite, SWT.SINGLE | SWT.LEAD | SWT.BORDER);
+ if (callback.getDefaultName() != null) {
+ // set default value, if provided
+ text.setText(callback.getDefaultName());
+ callback.setName(callback.getDefaultName());
+ }
+ text.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
+ text.addModifyListener(new ModifyListener() {
+ private static final long serialVersionUID = 7300032545287292973L;
+
+ public void modifyText(ModifyEvent event) {
+ callback.setName(text.getText());
+ }
+ });
+ text.addSelectionListener(new SelectionListener() {
+ private static final long serialVersionUID = 1820530045857665111L;
+
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ }
+
+ @Override
+ public void widgetDefaultSelected(SelectionEvent e) {
+ submit();
+ }
+ });
+
+ text.addKeyListener(new KeyListener() {
+ private static final long serialVersionUID = -8698107785092095713L;
+
+ @Override
+ public void keyReleased(KeyEvent e) {
+ }
+
+ @Override
+ public void keyPressed(KeyEvent e) {
+ }
+ });
+ return text;
+ }
+
+ protected Text createPasswordHandler(Composite composite, final PasswordCallback callback) {
+ Label label = new Label(composite, SWT.NONE);
+ label.setText(callback.getPrompt());
+ final Text passwordText = new Text(composite, SWT.SINGLE | SWT.LEAD | SWT.PASSWORD | SWT.BORDER);
+ passwordText.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
+ passwordText.addModifyListener(new ModifyListener() {
+ private static final long serialVersionUID = -7099363995047686732L;
+
+ public void modifyText(ModifyEvent event) {
+ callback.setPassword(passwordText.getTextChars());
+ }
+ });
+ passwordText.addSelectionListener(new SelectionListener() {
+ private static final long serialVersionUID = 1820530045857665111L;
+
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ }
+
+ @Override
+ public void widgetDefaultSelected(SelectionEvent e) {
+ submit();
+ }
+ });
+ return passwordText;
+ }
+
+ protected Combo createLocaleHandler(Composite composite, final LocaleChoice callback) {
+ String[] labels = callback.getSupportedLocalesLabels();
+ if (labels.length == 0)
+ return null;
+ Label label = new Label(composite, SWT.NONE);
+ label.setText("Language");
+
+ final Combo combo = new Combo(composite, SWT.READ_ONLY);
+ combo.setItems(labels);
+ combo.select(callback.getDefaultIndex());
+ combo.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
+ combo.addSelectionListener(new SelectionListener() {
+ private static final long serialVersionUID = 38678989091946277L;
+
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ callback.setSelectedIndex(combo.getSelectionIndex());
+ }
+
+ @Override
+ public void widgetDefaultSelected(SelectionEvent e) {
+ }
+ });
+ return combo;
+ }
+
+ protected Label createLabelTextoutputHandler(Composite composite, final TextOutputCallback callback) {
+ Label label = new Label(composite, SWT.NONE);
+ label.setText(callback.getMessage());
+ GridData data = new GridData(SWT.FILL, SWT.FILL, true, true);
+ data.horizontalSpan = 2;
+ label.setLayoutData(data);
+ return label;
+ // TODO: find a way to pass this information
+ // int messageType = callback.getMessageType();
+ // int dialogMessageType = IMessageProvider.NONE;
+ // switch (messageType) {
+ // case TextOutputCallback.INFORMATION:
+ // dialogMessageType = IMessageProvider.INFORMATION;
+ // break;
+ // case TextOutputCallback.WARNING:
+ // dialogMessageType = IMessageProvider.WARNING;
+ // break;
+ // case TextOutputCallback.ERROR:
+ // dialogMessageType = IMessageProvider.ERROR;
+ // break;
+ // }
+ // setMessage(callback.getMessage(), dialogMessageType);
+ }
+
+ synchronized boolean isSubmitted() {
+ return isSubmitted;
+ }
+
+ synchronized boolean isCanceled() {
+ return isCanceled;
+ }
+
+ protected synchronized void submit() {
+ isSubmitted = true;
+ notifyAll();
+ }
+
+ protected synchronized void cancel() {
+ isCanceled = true;
+ notifyAll();
+ }
+}
--- /dev/null
+package org.argeo.cms.swt.auth;
+
+import java.io.IOException;
+
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.UnsupportedCallbackException;
+
+import org.argeo.eclipse.ui.dialogs.LightweightDialog;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Shell;
+
+public class DynamicCallbackHandler implements CallbackHandler {
+
+ @Override
+ public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
+ Shell activeShell = Display.getCurrent().getActiveShell();
+ LightweightDialog dialog = new LightweightDialog(activeShell) {
+
+ @Override
+ protected Control createDialogArea(Composite parent) {
+ CompositeCallbackHandler cch = new CompositeCallbackHandler(parent, SWT.NONE);
+ cch.createCallbackHandlers(callbacks);
+ return cch;
+ }
+ };
+ dialog.setBlockOnOpen(true);
+ dialog.open();
+ }
+
+}
--- /dev/null
+package org.argeo.cms.swt.auth;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+
+import javax.security.auth.callback.LanguageCallback;
+
+import org.argeo.cms.CmsException;
+import org.argeo.cms.LocaleUtils;
+
+/** Choose in a list of locales. TODO: replace with {@link LanguageCallback} */
+public class LocaleChoice {
+ private final List<Locale> locales;
+
+ private Integer selectedIndex = null;
+ private final Integer defaultIndex;
+
+ public LocaleChoice(List<Locale> locales, Locale defaultLocale) {
+ Integer defaultIndex = null;
+ this.locales = Collections.unmodifiableList(locales);
+ for (int i = 0; i < locales.size(); i++)
+ if (locales.get(i).equals(defaultLocale))
+ defaultIndex = i;
+
+ // based on language only
+ if (defaultIndex == null)
+ for (int i = 0; i < locales.size(); i++)
+ if (locales.get(i).getLanguage().equals(defaultLocale.getLanguage()))
+ defaultIndex = i;
+
+ if (defaultIndex == null)
+ throw new CmsException("Default locale " + defaultLocale + " is not in available locales " + locales);
+ this.defaultIndex = defaultIndex;
+
+ this.selectedIndex = defaultIndex;
+ }
+
+ /**
+ * Convenience constructor based on a comma separated list of iso codes (en,
+ * en_US, fr_CA, etc.). Default selection is default locale.
+ */
+ public LocaleChoice(String locales, Locale defaultLocale) {
+ this(LocaleUtils.asLocaleList(locales), defaultLocale);
+ }
+
+ public String[] getSupportedLocalesLabels() {
+ String[] labels = new String[locales.size()];
+ for (int i = 0; i < locales.size(); i++) {
+ Locale locale = locales.get(i);
+ if (locale.getCountry().equals(""))
+ labels[i] = locale.getDisplayLanguage(locale) + " [" + locale.getLanguage() + "]";
+ else
+ labels[i] = locale.getDisplayLanguage(locale) + " (" + locale.getDisplayCountry(locale) + ") ["
+ + locale.getLanguage() + "_" + locale.getCountry() + "]";
+
+ }
+ return labels;
+ }
+
+ public Locale getSelectedLocale() {
+ if (selectedIndex == null)
+ return null;
+ return locales.get(selectedIndex);
+ }
+
+ public void setSelectedIndex(Integer selectedIndex) {
+ this.selectedIndex = selectedIndex;
+ }
+
+ public Integer getSelectedIndex() {
+ return selectedIndex;
+ }
+
+ public Integer getDefaultIndex() {
+ return defaultIndex;
+ }
+
+ public List<Locale> getLocales() {
+ return locales;
+ }
+
+ public Locale getDefaultLocale() {
+ return locales.get(getDefaultIndex());
+ }
+}
--- /dev/null
+/** Argeo CMS authentication widgets, based on SWT. */
+package org.argeo.cms.swt.auth;
\ No newline at end of file
--- /dev/null
+package org.argeo.cms.swt.dialogs;
+
+import java.security.PrivilegedAction;
+import java.util.Arrays;
+
+import org.argeo.api.cms.CmsView;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.cms.CmsMsg;
+import org.argeo.cms.CmsUserManager;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Text;
+
+/** Dialog to change a password. */
+public class ChangePasswordDialog extends CmsMessageDialog {
+ private final static CmsLog log = CmsLog.getLog(ChangePasswordDialog.class);
+
+ private CmsUserManager cmsUserManager;
+ private CmsView cmsView;
+
+ private PrivilegedAction<Integer> doIt;
+
+ public ChangePasswordDialog(Shell parentShell, String message, int kind, CmsUserManager cmsUserManager) {
+ super(parentShell, message, kind);
+ this.cmsUserManager = cmsUserManager;
+ cmsView = CmsSwtUtils.getCmsView(parentShell);
+ }
+
+ @Override
+ protected Control createInputArea(Composite userSection) {
+ addFormLabel(userSection, CmsMsg.currentPassword.lead());
+ Text previousPassword = new Text(userSection, SWT.BORDER | SWT.PASSWORD);
+ previousPassword.setLayoutData(CmsSwtUtils.fillWidth());
+ addFormLabel(userSection, CmsMsg.newPassword.lead());
+ Text newPassword = new Text(userSection, SWT.BORDER | SWT.PASSWORD);
+ newPassword.setLayoutData(CmsSwtUtils.fillWidth());
+ addFormLabel(userSection, CmsMsg.repeatNewPassword.lead());
+ Text confirmPassword = new Text(userSection, SWT.BORDER | SWT.PASSWORD);
+ confirmPassword.setLayoutData(CmsSwtUtils.fillWidth());
+
+ doIt = () -> {
+ if (Arrays.equals(newPassword.getTextChars(), confirmPassword.getTextChars())) {
+ try {
+ cmsUserManager.changeOwnPassword(previousPassword.getTextChars(), newPassword.getTextChars());
+ return OK;
+ } catch (Exception e1) {
+ log.error("Could not change password", e1);
+ cancel();
+ CmsMessageDialog.openError(CmsMsg.invalidPassword.lead());
+ return CANCEL;
+ }
+ } else {
+ cancel();
+ CmsMessageDialog.openError(CmsMsg.repeatNewPassword.lead());
+ return CANCEL;
+ }
+ };
+
+ pack();
+ return previousPassword;
+ }
+
+ @Override
+ protected void okPressed() {
+ Integer returnCode = cmsView.doAs(doIt);
+ if (returnCode.equals(OK)) {
+ super.okPressed();
+ CmsMessageDialog.openInformation(CmsMsg.passwordChanged.lead());
+ }
+ }
+
+ private static Label addFormLabel(Composite parent, String label) {
+ Label lbl = new Label(parent, SWT.WRAP);
+ lbl.setText(label);
+// CmsUiUtils.style(lbl, SuiteStyle.simpleLabel);
+ return lbl;
+ }
+
+}
--- /dev/null
+package org.argeo.cms.swt.dialogs;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+import org.argeo.api.cms.CmsLog;
+import org.argeo.cms.CmsMsg;
+import org.argeo.cms.swt.Selected;
+import org.eclipse.swt.SWT;
+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.Label;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Text;
+
+/** A dialog feedback based on a {@link LightweightDialog}. */
+public class CmsFeedback extends LightweightDialog {
+ private final static CmsLog log = CmsLog.getLog(CmsFeedback.class);
+
+ private String message;
+ private Throwable exception;
+
+ public CmsFeedback(Shell parentShell, String message, Throwable e) {
+ super(parentShell);
+ this.message = message;
+ this.exception = e;
+ log.error(message, e);
+ }
+
+ public static CmsFeedback show(String message, Throwable e) {
+ // rethrow ThreaDeath in order to make sure that RAP will properly clean
+ // up the UI thread
+ if (e instanceof ThreadDeath)
+ throw (ThreadDeath) e;
+
+ try {
+ CmsFeedback cmsFeedback = new CmsFeedback(null, message, e);
+ cmsFeedback.setBlockOnOpen(false);
+ cmsFeedback.open();
+ return cmsFeedback;
+ } catch (Throwable e1) {
+ log.error("Cannot open error feedback (" + e.getMessage() + "), original error below", e);
+ return null;
+ }
+ }
+
+ public static CmsFeedback show(String message) {
+ CmsFeedback cmsFeedback = new CmsFeedback(null, message, null);
+ cmsFeedback.open();
+ return cmsFeedback;
+ }
+
+ /** Tries to find a display */
+ // private static Display getDisplay() {
+ // try {
+ // Display display = Display.getCurrent();
+ // if (display != null)
+ // return display;
+ // else
+ // return Display.getDefault();
+ // } catch (Exception e) {
+ // return Display.getCurrent();
+ // }
+ // }
+
+ protected Control createDialogArea(Composite parent) {
+ parent.setLayout(new GridLayout(2, false));
+
+ Label messageLbl = new Label(parent, SWT.WRAP);
+ if (message != null)
+ messageLbl.setText(message);
+ else if (exception != null)
+ messageLbl.setText(exception.getLocalizedMessage());
+
+ Button close = new Button(parent, SWT.FLAT);
+ close.setText(CmsMsg.close.lead());
+ close.setLayoutData(new GridData(SWT.END, SWT.TOP, false, false));
+ close.addSelectionListener((Selected) (e) -> closeShell(OK));
+
+ // Composite composite = new Composite(dialogarea, SWT.NONE);
+ // composite.setLayout(new GridLayout(2, false));
+ // composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
+
+ if (exception != null) {
+ Text stack = new Text(parent, SWT.MULTI | SWT.LEAD | SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL);
+ stack.setEditable(false);
+ stack.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 2, 1));
+ StringWriter sw = new StringWriter();
+ exception.printStackTrace(new PrintWriter(sw));
+ stack.setText(sw.toString());
+ }
+
+ // parent.pack();
+ return messageLbl;
+ }
+
+}
--- /dev/null
+package org.argeo.cms.swt.dialogs;
+
+import org.argeo.cms.CmsMsg;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.swt.Selected;
+import org.argeo.eclipse.ui.EclipseUiUtils;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.TraverseEvent;
+import org.eclipse.swt.events.TraverseListener;
+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;
+
+/** Base class for dialogs displaying messages or small forms. */
+public class CmsMessageDialog extends LightweightDialog {
+ public final static int NONE = 0;
+ public final static int ERROR = 1;
+ public final static int INFORMATION = 2;
+ public final static int QUESTION = 3;
+ public final static int WARNING = 4;
+ public final static int CONFIRM = 5;
+ public final static int QUESTION_WITH_CANCEL = 6;
+
+ private int kind;
+ private String message;
+
+ public CmsMessageDialog(Shell parentShell, String message, int kind) {
+ super(parentShell);
+ this.kind = kind;
+ this.message = message;
+ }
+
+ protected Control createDialogArea(Composite parent) {
+ parent.setLayout(new GridLayout());
+
+ TraverseListener traverseListener = new TraverseListener() {
+ private static final long serialVersionUID = -1158892811534971856L;
+
+ public void keyTraversed(TraverseEvent e) {
+ if (e.detail == SWT.TRAVERSE_RETURN)
+ okPressed();
+ else if (e.detail == SWT.TRAVERSE_ESCAPE)
+ cancelPressed();
+ }
+ };
+
+ // message
+ Composite body = new Composite(parent, SWT.NONE);
+ body.addTraverseListener(traverseListener);
+ GridLayout bodyGridLayout = new GridLayout();
+ bodyGridLayout.marginHeight = 20;
+ bodyGridLayout.marginWidth = 20;
+ body.setLayout(bodyGridLayout);
+ body.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
+
+ if (message != null) {
+ Label messageLbl = new Label(body, SWT.WRAP);
+ CmsSwtUtils.markup(messageLbl);
+ messageLbl.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
+ messageLbl.setFont(EclipseUiUtils.getBoldFont(parent));
+ messageLbl.setText(message);
+ }
+
+ // buttons
+ Composite buttons = new Composite(parent, SWT.NONE);
+ buttons.addTraverseListener(traverseListener);
+ buttons.setLayoutData(new GridData(SWT.END, SWT.FILL, true, false));
+ if (kind == INFORMATION || kind == WARNING || kind == ERROR || kind == ERROR) {
+ GridLayout layout = new GridLayout(1, true);
+ layout.marginWidth = 0;
+ layout.marginHeight = 0;
+ buttons.setLayout(layout);
+
+ Button close = new Button(buttons, SWT.FLAT);
+ close.setText(CmsMsg.close.lead());
+ close.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
+ close.addSelectionListener((Selected) (e) -> closeShell(OK));
+ close.setFocus();
+ close.addTraverseListener(traverseListener);
+
+ buttons.setTabList(new Control[] { close });
+ } else if (kind == CONFIRM || kind == QUESTION || kind == QUESTION_WITH_CANCEL) {
+ Control input = createInputArea(body);
+ if (input != null) {
+ input.addTraverseListener(traverseListener);
+ body.setTabList(new Control[] { input });
+ }
+ GridLayout layout = new GridLayout(2, true);
+ layout.marginWidth = 0;
+ layout.marginHeight = 0;
+ buttons.setLayout(layout);
+
+ Button cancel = new Button(buttons, SWT.FLAT);
+ cancel.setText(CmsMsg.cancel.lead());
+ cancel.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
+ cancel.addSelectionListener((Selected) (e) -> cancelPressed());
+ cancel.addTraverseListener(traverseListener);
+
+ Button ok = new Button(buttons, SWT.FLAT);
+ ok.setText(CmsMsg.ok.lead());
+ ok.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
+ ok.addSelectionListener((Selected) (e) -> okPressed());
+ ok.addTraverseListener(traverseListener);
+ if (input == null)
+ ok.setFocus();
+ else
+ input.setFocus();
+
+ buttons.setTabList(new Control[] { ok, cancel });
+ }
+ // pack();
+ parent.setTabList(new Control[] { body, buttons });
+ return body;
+ }
+
+ protected Control createInputArea(Composite parent) {
+ return null;
+ }
+
+ protected void okPressed() {
+ closeShell(OK);
+ }
+
+ protected void cancelPressed() {
+ closeShell(CANCEL);
+ }
+
+ protected void cancel() {
+ closeShell(CANCEL);
+ }
+
+ protected Point getInitialSize() {
+ return new Point(400, 200);
+ }
+
+ public static boolean open(int kind, Shell parent, String message) {
+ CmsMessageDialog dialog = new CmsMessageDialog(parent, message, kind);
+ return dialog.open() == 0;
+ }
+
+ public static boolean openConfirm(String message) {
+ return open(CONFIRM, Display.getCurrent().getActiveShell(), message);
+ }
+
+ public static void openInformation(String message) {
+ open(INFORMATION, Display.getCurrent().getActiveShell(), message);
+ }
+
+ public static boolean openQuestion(String message) {
+ return open(QUESTION, Display.getCurrent().getActiveShell(), message);
+ }
+
+ public static void openWarning(String message) {
+ open(WARNING, Display.getCurrent().getActiveShell(), message);
+ }
+
+ public static void openError(String message) {
+ open(ERROR, Display.getCurrent().getActiveShell(), message);
+ }
+
+}
--- /dev/null
+package org.argeo.cms.swt.dialogs;
+
+import java.lang.reflect.InvocationTargetException;
+
+import org.argeo.cms.CmsMsg;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.swt.Selected;
+import org.argeo.eclipse.ui.EclipseUiUtils;
+import org.eclipse.jface.operation.IRunnableWithProgress;
+import org.eclipse.jface.wizard.IWizard;
+import org.eclipse.jface.wizard.IWizardContainer2;
+import org.eclipse.jface.wizard.IWizardPage;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.FormAttachment;
+import org.eclipse.swt.layout.FormData;
+import org.eclipse.swt.layout.FormLayout;
+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.Label;
+import org.eclipse.swt.widgets.Shell;
+
+/** A wizard dialog based on {@link LightweightDialog}. */
+public class CmsWizardDialog extends LightweightDialog implements IWizardContainer2 {
+ private static final long serialVersionUID = -2123153353654812154L;
+
+ private IWizard wizard;
+ private IWizardPage currentPage;
+ private int currentPageIndex;
+
+ private Label titleBar;
+ private Label message;
+ private Composite[] pageBodies;
+ private Composite buttons;
+ private Button back;
+ private Button next;
+ private Button finish;
+
+ public CmsWizardDialog(Shell parentShell, IWizard wizard) {
+ super(parentShell);
+ this.wizard = wizard;
+ wizard.setContainer(this);
+ // create the pages
+ wizard.addPages();
+ currentPage = wizard.getStartingPage();
+ if (currentPage == null)
+ throw new IllegalArgumentException("At least one wizard page is required");
+ }
+
+ @Override
+ protected Control createDialogArea(Composite parent) {
+ updateWindowTitle();
+
+ Composite messageArea = new Composite(parent, SWT.NONE);
+ messageArea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
+ {
+ messageArea.setLayout(CmsSwtUtils.noSpaceGridLayout(new GridLayout(2, false)));
+ titleBar = new Label(messageArea, SWT.WRAP);
+ titleBar.setFont(EclipseUiUtils.getBoldFont(parent));
+ titleBar.setLayoutData(new GridData(SWT.BEGINNING, SWT.FILL, true, false));
+ updateTitleBar();
+ Button cancelButton = new Button(messageArea, SWT.FLAT);
+ cancelButton.setText(CmsMsg.cancel.lead());
+ cancelButton.setLayoutData(new GridData(SWT.END, SWT.TOP, false, false, 1, 3));
+ cancelButton.addSelectionListener((Selected) (e) -> closeShell(CANCEL));
+ message = new Label(messageArea, SWT.WRAP);
+ message.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 1, 2));
+ updateMessage();
+ }
+
+ Composite body = new Composite(parent, SWT.BORDER);
+ body.setLayout(new FormLayout());
+ body.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
+ pageBodies = new Composite[wizard.getPageCount()];
+ IWizardPage[] pages = wizard.getPages();
+ for (int i = 0; i < pages.length; i++) {
+ pageBodies[i] = new Composite(body, SWT.NONE);
+ pageBodies[i].setLayout(CmsSwtUtils.noSpaceGridLayout());
+ setSwitchingFormData(pageBodies[i]);
+ pages[i].createControl(pageBodies[i]);
+ }
+ showPage(currentPage);
+
+ buttons = new Composite(parent, SWT.NONE);
+ buttons.setLayoutData(new GridData(SWT.END, SWT.FILL, true, false));
+ {
+ boolean singlePage = wizard.getPageCount() == 1;
+ // singlePage = false;// dev
+ GridLayout layout = new GridLayout(singlePage ? 1 : 3, true);
+ layout.marginWidth = 0;
+ layout.marginHeight = 0;
+ buttons.setLayout(layout);
+ // TODO revert order for right-to-left languages
+
+ if (!singlePage) {
+ back = new Button(buttons, SWT.PUSH);
+ back.setText(CmsMsg.wizardBack.lead());
+ back.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
+ back.addSelectionListener((Selected) (e) -> backPressed());
+
+ next = new Button(buttons, SWT.PUSH);
+ next.setText(CmsMsg.wizardNext.lead());
+ next.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
+ next.addSelectionListener((Selected) (e) -> nextPressed());
+ }
+ finish = new Button(buttons, SWT.PUSH);
+ finish.setText(CmsMsg.wizardFinish.lead());
+ finish.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
+ finish.addSelectionListener((Selected) (e) -> finishPressed());
+
+ updateButtons();
+ }
+ return body;
+ }
+
+ @Override
+ public IWizardPage getCurrentPage() {
+ return currentPage;
+ }
+
+ @Override
+ public Shell getShell() {
+ return getForegoundShell();
+ }
+
+ @Override
+ public void showPage(IWizardPage page) {
+ IWizardPage[] pages = wizard.getPages();
+ int index = -1;
+ for (int i = 0; i < pages.length; i++) {
+ if (page == pages[i]) {
+ index = i;
+ break;
+ }
+ }
+ if (index < 0)
+ throw new IllegalArgumentException("Cannot find index of wizard page " + page);
+ pageBodies[index].moveAbove(pageBodies[currentPageIndex]);
+
+ // // clear
+ // for (Control c : body.getChildren())
+ // c.dispose();
+ // page.createControl(body);
+ // body.layout(true, true);
+ currentPageIndex = index;
+ currentPage = page;
+ }
+
+ @Override
+ public void updateButtons() {
+ if (back != null)
+ back.setEnabled(wizard.getPreviousPage(currentPage) != null);
+ if (next != null)
+ next.setEnabled(wizard.getNextPage(currentPage) != null && currentPage.canFlipToNextPage());
+ if (finish != null) {
+ finish.setEnabled(wizard.canFinish());
+ }
+ }
+
+ @Override
+ public void updateMessage() {
+ if (currentPage.getMessage() != null)
+ message.setText(currentPage.getMessage());
+ }
+
+ @Override
+ public void updateTitleBar() {
+ if (currentPage.getTitle() != null)
+ titleBar.setText(currentPage.getTitle());
+ }
+
+ @Override
+ public void updateWindowTitle() {
+ setTitle(wizard.getWindowTitle());
+ }
+
+ @Override
+ public void run(boolean fork, boolean cancelable, IRunnableWithProgress runnable)
+ throws InvocationTargetException, InterruptedException {
+ runnable.run(null);
+ }
+
+ @Override
+ public void updateSize() {
+ // TODO pack?
+ }
+
+ protected boolean onCancel() {
+ return wizard.performCancel();
+ }
+
+ protected void nextPressed() {
+ IWizardPage page = wizard.getNextPage(currentPage);
+ showPage(page);
+ updateButtons();
+ }
+
+ protected void backPressed() {
+ IWizardPage page = wizard.getPreviousPage(currentPage);
+ showPage(page);
+ updateButtons();
+ }
+
+ protected void finishPressed() {
+ if (wizard.performFinish())
+ closeShell(OK);
+ }
+
+ private static void setSwitchingFormData(Composite composite) {
+ FormData fdLabel = new FormData();
+ fdLabel.top = new FormAttachment(0, 0);
+ fdLabel.left = new FormAttachment(0, 0);
+ fdLabel.right = new FormAttachment(100, 0);
+ fdLabel.bottom = new FormAttachment(100, 0);
+ composite.setLayoutData(fdLabel);
+ }
+
+}
--- /dev/null
+package org.argeo.cms.swt.dialogs;
+
+import org.argeo.api.cms.CmsLog;
+import org.argeo.eclipse.ui.EclipseUiException;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.FocusEvent;
+import org.eclipse.swt.events.FocusListener;
+import org.eclipse.swt.events.ShellAdapter;
+import org.eclipse.swt.events.ShellEvent;
+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.Display;
+import org.eclipse.swt.widgets.Shell;
+
+/** Generic lightweight dialog, not based on JFace. */
+public class LightweightDialog {
+ private final static CmsLog log = CmsLog.getLog(LightweightDialog.class);
+
+ // must be the same value as org.eclipse.jface.window.Window#OK
+ public final static int OK = 0;
+ // must be the same value as org.eclipse.jface.window.Window#CANCEL
+ public final static int CANCEL = 1;
+
+ private Shell parentShell;
+ private Shell backgroundShell;
+ private Shell foregoundShell;
+
+ private Integer returnCode = null;
+ private boolean block = true;
+
+ private String title;
+
+ /** Tries to find a display */
+ private static Display getDisplay() {
+ try {
+ Display display = Display.getCurrent();
+ if (display != null)
+ return display;
+ else
+ return Display.getDefault();
+ } catch (Exception e) {
+ return Display.getCurrent();
+ }
+ }
+
+ public LightweightDialog(Shell parentShell) {
+ this.parentShell = parentShell;
+ }
+
+ public int open() {
+ if (foregoundShell != null)
+ throw new EclipseUiException("There is already a shell");
+ backgroundShell = new Shell(parentShell, SWT.ON_TOP);
+ backgroundShell.setFullScreen(true);
+ // if (parentShell != null) {
+ // backgroundShell.setBounds(parentShell.getBounds());
+ // } else
+ // backgroundShell.setMaximized(true);
+ backgroundShell.setAlpha(128);
+ backgroundShell.setBackground(getDisplay().getSystemColor(SWT.COLOR_BLACK));
+ foregoundShell = new Shell(backgroundShell, SWT.NO_TRIM | SWT.ON_TOP);
+ if (title != null)
+ setTitle(title);
+ foregoundShell.setLayout(new GridLayout());
+ foregoundShell.setSize(getInitialSize());
+ createDialogArea(foregoundShell);
+ // shell.pack();
+ // shell.layout();
+
+ Rectangle shellBounds = parentShell != null ? parentShell.getBounds() : Display.getCurrent().getBounds();// RAP
+ Point dialogSize = foregoundShell.getSize();
+ int x = shellBounds.x + (shellBounds.width - dialogSize.x) / 2;
+ int y = shellBounds.y + (shellBounds.height - dialogSize.y) / 2;
+ foregoundShell.setLocation(x, y);
+
+ foregoundShell.addShellListener(new ShellAdapter() {
+ private static final long serialVersionUID = -2701270481953688763L;
+
+ @Override
+ public void shellDeactivated(ShellEvent e) {
+ if (hasChildShells())
+ return;
+ if (returnCode == null)// not yet closed
+ closeShell(CANCEL);
+ }
+
+ @Override
+ public void shellClosed(ShellEvent e) {
+ notifyClose();
+ }
+
+ });
+
+ backgroundShell.open();
+ foregoundShell.open();
+ // after the foreground shell has been opened
+ backgroundShell.addFocusListener(new FocusListener() {
+ private static final long serialVersionUID = 3137408447474661070L;
+
+ @Override
+ public void focusLost(FocusEvent event) {
+ }
+
+ @Override
+ public void focusGained(FocusEvent event) {
+ if (hasChildShells())
+ return;
+ if (returnCode == null)// not yet closed
+ closeShell(CANCEL);
+ }
+ });
+
+ if (block) {
+ block();
+ }
+ if (returnCode == null)
+ returnCode = OK;
+ return returnCode;
+ }
+
+ public void block() {
+ try {
+ runEventLoop(foregoundShell);
+ } catch (ThreadDeath t) {
+ returnCode = CANCEL;
+ if (log.isTraceEnabled())
+ log.error("Thread death, canceling dialog", t);
+ } catch (Throwable t) {
+ returnCode = CANCEL;
+ log.error("Cannot open blocking lightweight dialog", t);
+ }
+ }
+
+ private boolean hasChildShells() {
+ if (foregoundShell == null)
+ return false;
+ return foregoundShell.getShells().length != 0;
+ }
+
+ // public synchronized int openAndWait() {
+ // open();
+ // while (returnCode == null)
+ // try {
+ // wait(100);
+ // } catch (InterruptedException e) {
+ // // silent
+ // }
+ // return returnCode;
+ // }
+
+ private synchronized void notifyClose() {
+ if (returnCode == null)
+ returnCode = CANCEL;
+ notifyAll();
+ }
+
+ protected void closeShell(int returnCode) {
+ this.returnCode = returnCode;
+ if (CANCEL == returnCode)
+ onCancel();
+ if (foregoundShell != null && !foregoundShell.isDisposed()) {
+ foregoundShell.close();
+ foregoundShell.dispose();
+ foregoundShell = null;
+ }
+
+ if (backgroundShell != null && !backgroundShell.isDisposed()) {
+ backgroundShell.close();
+ backgroundShell.dispose();
+ }
+ }
+
+ protected Point getInitialSize() {
+ // if (exception != null)
+ // return new Point(800, 600);
+ // else
+ return new Point(600, 400);
+ }
+
+ protected Control createDialogArea(Composite parent) {
+ Composite dialogarea = new Composite(parent, SWT.NONE);
+ dialogarea.setLayout(new GridLayout());
+ dialogarea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
+ return dialogarea;
+ }
+
+ protected Shell getBackgroundShell() {
+ return backgroundShell;
+ }
+
+ protected Shell getForegoundShell() {
+ return foregoundShell;
+ }
+
+ public void setBlockOnOpen(boolean shouldBlock) {
+ block = shouldBlock;
+ }
+
+ public void pack() {
+ foregoundShell.pack();
+ }
+
+ private void runEventLoop(Shell loopShell) {
+ Display display;
+ if (foregoundShell == null) {
+ display = Display.getCurrent();
+ } else {
+ display = loopShell.getDisplay();
+ }
+
+ while (loopShell != null && !loopShell.isDisposed()) {
+ try {
+ if (!display.readAndDispatch()) {
+ display.sleep();
+ }
+ } catch (UnsupportedOperationException e) {
+ throw e;
+ } catch (Throwable e) {
+ handleException(e);
+ }
+ }
+ if (!display.isDisposed())
+ display.update();
+ }
+
+ protected void handleException(Throwable t) {
+ if (t instanceof ThreadDeath) {
+ // Don't catch ThreadDeath as this is a normal occurrence when
+ // the thread dies
+ throw (ThreadDeath) t;
+ }
+ // Try to keep running.
+ t.printStackTrace();
+ }
+
+ /** @return false, if the dialog should not be closed. */
+ protected boolean onCancel() {
+ return true;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ if (title != null && getForegoundShell() != null)
+ getForegoundShell().setText(title);
+ }
+
+ public Integer getReturnCode() {
+ return returnCode;
+ }
+
+}
\ No newline at end of file
--- /dev/null
+package org.argeo.cms.swt.dialogs;
+
+import org.eclipse.jface.window.Window;
+import org.eclipse.swt.SWT;
+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.Shell;
+import org.eclipse.swt.widgets.Text;
+
+/** A dialog asking a for a single value. */
+public class SingleValueDialog extends CmsMessageDialog {
+ private Text valueT;
+ private String value;
+ private String defaultValue;
+
+ public SingleValueDialog(Shell parentShell, String message) {
+ super(parentShell, message, QUESTION);
+ }
+
+ public SingleValueDialog(Shell parentShell, String message, String defaultValue) {
+ super(parentShell, message, QUESTION);
+ this.defaultValue = defaultValue;
+ }
+
+ @Override
+ protected Control createInputArea(Composite parent) {
+ valueT = new Text(parent, SWT.LEAD | SWT.BORDER | SWT.SINGLE);
+ valueT.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, true));
+ if (defaultValue != null)
+ valueT.setText(defaultValue);
+ return valueT;
+ }
+
+ @Override
+ protected void okPressed() {
+ value = valueT.getText();
+ super.okPressed();
+ }
+
+ public String getString() {
+ return value;
+ }
+
+ public Long getLong() {
+ return Long.valueOf(getString());
+ }
+
+ public Double getDouble() {
+ return Double.valueOf(getString());
+ }
+
+ public static String ask(String message) {
+ return ask(message, null);
+ }
+
+ public static String ask(String message, String defaultValue) {
+ SingleValueDialog svd = new SingleValueDialog(Display.getCurrent().getActiveShell(), message, defaultValue);
+ if (svd.open() == Window.OK)
+ return svd.getString();
+ else
+ return null;
+ }
+
+ public static Long askLong(String message) {
+ SingleValueDialog svd = new SingleValueDialog(Display.getCurrent().getActiveShell(), message);
+ if (svd.open() == Window.OK)
+ return svd.getLong();
+ else
+ return null;
+ }
+
+ public static Double askDouble(String message) {
+ SingleValueDialog svd = new SingleValueDialog(Display.getCurrent().getActiveShell(), message);
+ if (svd.open() == Window.OK)
+ return svd.getDouble();
+ else
+ return null;
+ }
+
+}
--- /dev/null
+/** SWT/JFace dialogs. */
+package org.argeo.cms.swt.dialogs;
\ No newline at end of file
--- /dev/null
+package org.argeo.cms.swt.gcr;
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import javax.xml.namespace.QName;
+
+import org.argeo.api.acr.Content;
+import org.argeo.cms.acr.fs.FsContentProvider;
+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.Display;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Table;
+import org.eclipse.swt.widgets.TableColumn;
+import org.eclipse.swt.widgets.TableItem;
+import org.eclipse.swt.widgets.Tree;
+import org.eclipse.swt.widgets.TreeItem;
+
+public class GcrContentTreeView extends Composite {
+ private Tree tree;
+ private Table table;
+ private Content rootContent;
+
+ private Content selected;
+
+ public GcrContentTreeView(Composite parent, int style, Content content) {
+ super(parent, style);
+ this.rootContent = content;
+ this.selected = rootContent;
+ setLayout(new GridLayout(2, false));
+ initTree();
+ GridData treeGd = CmsSwtUtils.fillHeight();
+ treeGd.widthHint = 300;
+ tree.setLayoutData(treeGd);
+ initTable();
+
+ table.setLayoutData(CmsSwtUtils.fillAll());
+ }
+
+ protected void initTree() {
+ tree = new Tree(this, 0);
+ for (Content c : rootContent) {
+ TreeItem root = new TreeItem(tree, 0);
+ root.setText(c.getName().toString());
+ root.setData(c);
+ new TreeItem(root, 0);
+ }
+ tree.addListener(SWT.Expand, event -> {
+ final TreeItem root = (TreeItem) event.item;
+ TreeItem[] items = root.getItems();
+ for (TreeItem item : items) {
+ if (item.getData() != null)
+ return;
+ item.dispose();
+ }
+ Content content = (Content) root.getData();
+ for (Content c : content) {
+ TreeItem item = new TreeItem(root, 0);
+ item.setText(c.getName().toString());
+ item.setData(c);
+ boolean hasChildren = true;
+ if (hasChildren) {
+ new TreeItem(item, 0);
+ }
+ }
+ });
+ tree.addListener(SWT.Selection, event -> {
+ TreeItem item = (TreeItem) event.item;
+ selected = (Content) item.getData();
+ refreshTable();
+ });
+ }
+
+ protected void initTable() {
+ table = new Table(this, 0);
+ table.setLinesVisible(true);
+ table.setHeaderVisible(true);
+ TableColumn keyCol = new TableColumn(table, SWT.NONE);
+ keyCol.setText("Attribute");
+ keyCol.setWidth(200);
+ TableColumn valueCol = new TableColumn(table, SWT.NONE);
+ valueCol.setText("Value");
+ keyCol.setWidth(300);
+ refreshTable();
+ }
+
+ protected void refreshTable() {
+ for (TableItem item : table.getItems()) {
+ item.dispose();
+ }
+ for (QName key : selected.keySet()) {
+ TableItem item = new TableItem(table, 0);
+ item.setText(0, key.toString());
+ Object value = selected.get(key);
+ item.setText(1, value.toString());
+ }
+ table.getColumn(0).pack();
+ table.getColumn(1).pack();
+ }
+
+ public static void main(String[] args) {
+ Path basePath;
+ if (args.length > 0) {
+ basePath = Paths.get(args[0]);
+ } else {
+ basePath = Paths.get(System.getProperty("user.home"));
+ }
+
+ final Display display = new Display();
+ final Shell shell = new Shell(display);
+ shell.setText(basePath.toString());
+ shell.setLayout(new FillLayout());
+
+ FsContentProvider contentSession = new FsContentProvider(basePath);
+// GcrContentTreeView treeView = new GcrContentTreeView(shell, 0, contentSession.get("/"));
+
+ shell.setSize(shell.computeSize(800, 600));
+ shell.open();
+ while (!shell.isDisposed()) {
+ if (!display.readAndDispatch())
+ display.sleep();
+ }
+ display.dispose();
+ }
+}
--- /dev/null
+package org.argeo.cms.swt.gcr;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.cms.MvcProvider;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+
+@FunctionalInterface
+public interface SwtUiProvider extends MvcProvider<Composite, Content, Control> {
+
+}
--- /dev/null
+package org.argeo.cms.swt.osgi;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.argeo.cms.osgi.BundleCmsTheme;
+import org.argeo.cms.swt.CmsSwtTheme;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.ImageData;
+import org.eclipse.swt.widgets.Display;
+
+/** Centralises some generic {@link CmsSwtTheme} patterns. */
+public class BundleCmsSwtTheme extends BundleCmsTheme implements CmsSwtTheme {
+ private Map<String, ImageData> imageCache = new HashMap<>();
+
+ private Map<String, Map<Integer, String>> iconPaths = new HashMap<>();
+
+ public Image getImage(String path) {
+ if (!imageCache.containsKey(path)) {
+ try (InputStream in = getResourceAsStream(path)) {
+ if (in == null)
+ return null;
+ ImageData imageData = new ImageData(in);
+ imageCache.put(path, imageData);
+ } catch (IOException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+ ImageData imageData = imageCache.get(path);
+ Image image = new Image(Display.getCurrent(), imageData);
+ return image;
+ }
+
+ /**
+ * And icon with this file name (without the extension), with a best effort to
+ * find the appropriate size, or <code>null</code> if not found.
+ *
+ * @param name An icon file name without path and extension.
+ * @param preferredSize the preferred size, if <code>null</code>,
+ * {@link #getDefaultIconSize()} will be tried.
+ */
+ public Image getIcon(String name, Integer preferredSize) {
+ if (preferredSize == null)
+ preferredSize = getDefaultIconSize();
+ Map<Integer, String> subCache;
+ if (!iconPaths.containsKey(name))
+ subCache = new HashMap<>();
+ else
+ subCache = iconPaths.get(name);
+ Image image = null;
+ if (!subCache.containsKey(preferredSize)) {
+ Image bestMatchSoFar = null;
+ paths: for (String p : getImagesPaths()) {
+ int lastSlash = p.lastIndexOf('/');
+ String fileName = p;
+ if (lastSlash >= 0)
+ fileName = p.substring(lastSlash + 1);
+ int lastDot = fileName.lastIndexOf('.');
+ if (lastDot >= 0)
+ fileName = fileName.substring(0, lastDot);
+ if (fileName.equals(name)) {// matched
+ Image img = getImage(p);
+ int width = img.getBounds().width;
+ if (width == preferredSize) {// perfect match
+ subCache.put(preferredSize, p);
+ image = img;
+ break paths;
+ }
+ if (bestMatchSoFar == null) {
+ bestMatchSoFar = img;
+ } else {
+ if (Math.abs(width - preferredSize) < Math
+ .abs(bestMatchSoFar.getBounds().width - preferredSize))
+ bestMatchSoFar = img;
+ }
+ }
+ }
+
+ if (image == null)
+ image = bestMatchSoFar;
+ } else {
+ image = getImage(subCache.get(preferredSize));
+ }
+
+ if (image != null && !iconPaths.containsKey(name))
+ iconPaths.put(name, subCache);
+
+ return image;
+ }
+
+}
--- /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.eclipse.ui.parts.LdifUsersTable;
+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.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.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
+/** SWT/JFace users management components. */
+package org.argeo.cms.swt.useradmin;
\ No newline at end of file
--- /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
--- /dev/null
+package org.argeo.eclipse.ui;
+
+import org.eclipse.jface.viewers.ITreeContentProvider;
+import org.eclipse.jface.viewers.Viewer;
+
+/**
+ * Tree content provider dealing with tree objects and providing reasonable
+ * defaults.
+ */
+public abstract class AbstractTreeContentProvider implements
+ ITreeContentProvider {
+ private static final long serialVersionUID = 8246126401957763868L;
+
+ /** Does nothing */
+ public void dispose() {
+ }
+
+ /** Does nothing */
+ public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
+ }
+
+ public Object[] getChildren(Object element) {
+ if (element instanceof TreeParent) {
+ return ((TreeParent) element).getChildren();
+ }
+ return new Object[0];
+ }
+
+ public Object getParent(Object element) {
+ if (element instanceof TreeParent) {
+ return ((TreeParent) element).getParent();
+ }
+ return null;
+ }
+
+ public boolean hasChildren(Object element) {
+ if (element instanceof TreeParent) {
+ return ((TreeParent) element).hasChildren();
+ }
+ return false;
+ }
+}
\ No newline at end of file
--- /dev/null
+package org.argeo.eclipse.ui;
+
+import org.eclipse.jface.viewers.ColumnLabelProvider;
+
+/**
+ * Wraps the definition of a column to be used in the various JFace viewers
+ * (typically tree and table). It enables definition of generic viewers which
+ * column can be then defined externally. Also used to generate export.
+ */
+public class ColumnDefinition {
+ private ColumnLabelProvider labelProvider;
+ private String label;
+ private int weight = 0;
+ private int minWidth = 120;
+
+ public ColumnDefinition(ColumnLabelProvider labelProvider, String label) {
+ this.labelProvider = labelProvider;
+ this.label = label;
+ }
+
+ public ColumnDefinition(ColumnLabelProvider labelProvider, String label,
+ int weight) {
+ this.labelProvider = labelProvider;
+ this.label = label;
+ this.weight = weight;
+ this.minWidth = weight;
+ }
+
+ public ColumnDefinition(ColumnLabelProvider labelProvider, String label,
+ int weight, int minimumWidth) {
+ this.labelProvider = labelProvider;
+ this.label = label;
+ this.weight = weight;
+ this.minWidth = minimumWidth;
+ }
+
+ public ColumnLabelProvider getLabelProvider() {
+ return labelProvider;
+ }
+
+ public void setLabelProvider(ColumnLabelProvider labelProvider) {
+ this.labelProvider = labelProvider;
+ }
+
+ public String getLabel() {
+ return label;
+ }
+
+ public void setLabel(String label) {
+ this.label = label;
+ }
+
+ public int getWeight() {
+ return weight;
+ }
+
+ public void setWeight(int weight) {
+ this.weight = weight;
+ }
+
+ public int getMinWidth() {
+ return minWidth;
+ }
+
+ public void setMinWidth(int minWidth) {
+ this.minWidth = minWidth;
+ }
+}
\ No newline at end of file
--- /dev/null
+package org.argeo.eclipse.ui;
+
+import org.eclipse.jface.viewers.ColumnViewer;
+import org.eclipse.jface.viewers.TableViewerColumn;
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.jface.viewers.ViewerComparator;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+
+/** Generic column viewer sorter */
+public class ColumnViewerComparator extends ViewerComparator {
+ private static final long serialVersionUID = -2266218906355859909L;
+
+ public static final int ASC = 1;
+
+ public static final int NONE = 0;
+
+ public static final int DESC = -1;
+
+ private int direction = 0;
+
+ private TableViewerColumn column;
+
+ private ColumnViewer viewer;
+
+ public ColumnViewerComparator(TableViewerColumn column) {
+ super(null);
+ this.column = column;
+ this.viewer = column.getViewer();
+ this.column.getColumn().addSelectionListener(new SelectionAdapter() {
+ private static final long serialVersionUID = 7586796298965472189L;
+
+ public void widgetSelected(SelectionEvent e) {
+ if (ColumnViewerComparator.this.viewer.getComparator() != null) {
+ if (ColumnViewerComparator.this.viewer.getComparator() == ColumnViewerComparator.this) {
+ int tdirection = ColumnViewerComparator.this.direction;
+
+ if (tdirection == ASC) {
+ setSortDirection(DESC);
+ } else if (tdirection == DESC) {
+ setSortDirection(NONE);
+ }
+ } else {
+ setSortDirection(ASC);
+ }
+ } else {
+ setSortDirection(ASC);
+ }
+ }
+ });
+ }
+
+ private void setSortDirection(int direction) {
+ if (direction == NONE) {
+ column.getColumn().getParent().setSortColumn(null);
+ column.getColumn().getParent().setSortDirection(SWT.NONE);
+ viewer.setComparator(null);
+ } else {
+ column.getColumn().getParent().setSortColumn(column.getColumn());
+ this.direction = direction;
+
+ if (direction == ASC) {
+ column.getColumn().getParent().setSortDirection(SWT.DOWN);
+ } else {
+ column.getColumn().getParent().setSortDirection(SWT.UP);
+ }
+
+ if (viewer.getComparator() == this) {
+ viewer.refresh();
+ } else {
+ viewer.setComparator(this);
+ }
+
+ }
+ }
+
+ public int compare(Viewer viewer, Object e1, Object e2) {
+ return direction * super.compare(viewer, e1, e2);
+ }
+}
--- /dev/null
+package org.argeo.eclipse.ui;
+
+/** CMS specific exceptions. */
+public class EclipseUiException extends RuntimeException {
+ private static final long serialVersionUID = -5341764743356771313L;
+
+ public EclipseUiException(String message) {
+ super(message);
+ }
+
+ public EclipseUiException(String message, Throwable e) {
+ super(message, e);
+ }
+
+}
--- /dev/null
+package org.argeo.eclipse.ui;
+
+import org.eclipse.jface.resource.JFaceResources;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.ModifyListener;
+import org.eclipse.swt.graphics.Font;
+import org.eclipse.swt.layout.FormAttachment;
+import org.eclipse.swt.layout.FormData;
+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.Text;
+
+/** Utilities to simplify UI development. */
+public class EclipseUiUtils {
+
+ /** Dispose all children of a Composite */
+ public static void clear(Composite composite) {
+ for (Control child : composite.getChildren())
+ child.dispose();
+ }
+
+ /**
+ * Enables efficient call to the layout method of a composite, refreshing only
+ * some of the children controls.
+ */
+ public static void layout(Composite parent, Control... toUpdateControls) {
+ parent.layout(toUpdateControls);
+ }
+
+ //
+ // FONTS
+ //
+ /** Shortcut to retrieve default italic font from display */
+ public static Font getItalicFont(Composite parent) {
+ return JFaceResources.getFontRegistry().defaultFontDescriptor().setStyle(SWT.ITALIC)
+ .createFont(parent.getDisplay());
+ }
+
+ /** Shortcut to retrieve default bold font from display */
+ public static Font getBoldFont(Composite parent) {
+ return JFaceResources.getFontRegistry().defaultFontDescriptor().setStyle(SWT.BOLD)
+ .createFont(parent.getDisplay());
+ }
+
+ /** Shortcut to retrieve default bold italic font from display */
+ public static Font getBoldItalicFont(Composite parent) {
+ return JFaceResources.getFontRegistry().defaultFontDescriptor().setStyle(SWT.BOLD | SWT.ITALIC)
+ .createFont(parent.getDisplay());
+ }
+
+ //
+ // Simplify grid layouts management
+ //
+ public static GridLayout noSpaceGridLayout() {
+ return noSpaceGridLayout(new GridLayout());
+ }
+
+ public static GridLayout noSpaceGridLayout(int columns) {
+ return noSpaceGridLayout(new GridLayout(columns, false));
+ }
+
+ public static GridLayout noSpaceGridLayout(GridLayout layout) {
+ layout.horizontalSpacing = 0;
+ layout.verticalSpacing = 0;
+ layout.marginWidth = 0;
+ layout.marginHeight = 0;
+ return layout;
+ }
+
+ public static GridData fillWidth() {
+ return grabWidth(SWT.FILL, SWT.FILL);
+ }
+
+ public static GridData fillWidth(int colSpan) {
+ GridData gd = grabWidth(SWT.FILL, SWT.FILL);
+ gd.horizontalSpan = colSpan;
+ return gd;
+ }
+
+ public static GridData fillAll() {
+ return new GridData(SWT.FILL, SWT.FILL, true, true);
+ }
+
+ public static GridData fillAll(int colSpan, int rowSpan) {
+ return new GridData(SWT.FILL, SWT.FILL, true, true, colSpan, rowSpan);
+ }
+
+ public static GridData grabWidth(int horizontalAlignment, int verticalAlignment) {
+ return new GridData(horizontalAlignment, horizontalAlignment, true, false);
+ }
+
+ //
+ // Simplify Form layout management
+ //
+
+ /**
+ * Creates a basic form data that is attached to the 4 corners of the parent
+ * composite
+ */
+ public static FormData fillFormData() {
+ FormData formData = new FormData();
+ formData.top = new FormAttachment(0, 0);
+ formData.left = new FormAttachment(0, 0);
+ formData.right = new FormAttachment(100, 0);
+ formData.bottom = new FormAttachment(100, 0);
+ return formData;
+ }
+
+ /**
+ * Create a label and a text field for a grid layout, the text field grabbing
+ * excess horizontal
+ *
+ * @param parent
+ * the parent composite
+ * @param label
+ * the label to display
+ * @param modifyListener
+ * a {@link ModifyListener} to listen on events on the text, can be
+ * null
+ * @return the created text
+ *
+ */
+ // FIXME why was this deprecated.
+ // * @ deprecated use { @ link #createGridLT(Composite, String)} instead
+ // @ Deprecated
+ public static Text createGridLT(Composite parent, String label, ModifyListener modifyListener) {
+ Label lbl = new Label(parent, SWT.LEAD);
+ lbl.setText(label);
+ lbl.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false));
+ Text txt = new Text(parent, SWT.LEAD | SWT.BORDER);
+ txt.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
+ if (modifyListener != null)
+ txt.addModifyListener(modifyListener);
+ return txt;
+ }
+
+ /**
+ * Create a label and a text field for a grid layout, the text field grabbing
+ * excess horizontal
+ */
+ public static Text createGridLT(Composite parent, String label) {
+ return createGridLT(parent, label, null);
+ }
+
+ /**
+ * Creates one label and a text field not editable with background colour of the
+ * parent (like a label but with selectable text)
+ */
+ public static Text createGridLL(Composite parent, String label, String text) {
+ Text txt = createGridLT(parent, label);
+ txt.setText(text);
+ txt.setEditable(false);
+ txt.setBackground(parent.getBackground());
+ return txt;
+ }
+
+ /**
+ * Create a label and a text field with password display for a grid layout, the
+ * text field grabbing excess horizontal
+ */
+ public static Text createGridLP(Composite parent, String label) {
+ return createGridLP(parent, label, null);
+ }
+
+ /**
+ * Create a label and a text field with password display for a grid layout, the
+ * text field grabbing excess horizontal. The given modify listener will be
+ * added to the newly created text field if not null.
+ */
+ public static Text createGridLP(Composite parent, String label, ModifyListener modifyListener) {
+ Label lbl = new Label(parent, SWT.LEAD);
+ lbl.setText(label);
+ lbl.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false));
+ Text txt = new Text(parent, SWT.LEAD | SWT.BORDER | SWT.PASSWORD);
+ txt.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
+ if (modifyListener != null)
+ txt.addModifyListener(modifyListener);
+ return txt;
+ }
+
+ // MISCELLANEOUS
+
+ /** Simply checks if a string is not null nor empty */
+ public static boolean notEmpty(String stringToTest) {
+ return !(stringToTest == null || "".equals(stringToTest.trim()));
+ }
+
+ /** Simply checks if a string is null or empty */
+ public static boolean isEmpty(String stringToTest) {
+ return stringToTest == null || "".equals(stringToTest.trim());
+ }
+}
\ No newline at end of file
--- /dev/null
+package org.argeo.eclipse.ui;
+
+import java.io.InputStream;
+
+/**
+ * Used for file download : subclasses must implement model specific methods to
+ * get a byte array representing a file given is ID.
+ */
+@Deprecated
+public interface FileProvider {
+
+ public byte[] getByteArrayFileFromId(String fileId);
+
+ public InputStream getInputStreamFromFileId(String fileId);
+
+}
--- /dev/null
+package org.argeo.eclipse.ui;
+
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.jface.viewers.ViewerComparator;
+
+public abstract class GenericTableComparator extends ViewerComparator {
+ private static final long serialVersionUID = -1175894935075325810L;
+ protected int propertyIndex;
+ public static final int ASCENDING = 0, DESCENDING = 1;
+ protected int direction = DESCENDING;
+
+ /**
+ * Creates an instance of a sorter for TableViewer.
+ *
+ * @param defaultColumnIndex
+ * the default sorter column
+ */
+
+ public GenericTableComparator(int defaultColumnIndex, int direction) {
+ propertyIndex = defaultColumnIndex;
+ this.direction = direction;
+ }
+
+ public void setColumn(int column) {
+ if (column == this.propertyIndex) {
+ // Same column as last sort; toggle the direction
+ direction = 1 - direction;
+ } else {
+ // New column; do a descending sort
+ this.propertyIndex = column;
+ direction = DESCENDING;
+ }
+ }
+
+ /**
+ * Must be Overriden in each view.
+ */
+ public abstract int compare(Viewer viewer, Object e1, Object e2);
+}
--- /dev/null
+package org.argeo.eclipse.ui;
+
+import java.util.List;
+
+/**
+ * Views and editors can implement this interface so that one of the list that
+ * is displayed in the part (For instance in a Table or a Tree Viewer) can be
+ * rebuilt externally. Typically to generate csv or calc extract.
+ */
+public interface IListProvider {
+ /**
+ * Returns an array of current and relevant elements
+ */
+ public Object[] getElements(String extractId);
+
+ /**
+ * Returns the column definition for passed ID
+ */
+ public List<ColumnDefinition> getColumnDefinition(String extractId);
+}
--- /dev/null
+package org.argeo.eclipse.ui;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/** Parent / children semantic to be used for simple UI Tree structure */
+public class TreeParent {
+ private String name;
+ private TreeParent parent;
+
+ private List<Object> children;
+
+ /**
+ * Unique id within the context of a tree display. If set, equals() and
+ * hashCode() methods will be based on it
+ */
+ private String path = null;
+
+ /** False until at least one child has been added, then true until cleared */
+ private boolean loaded = false;
+
+ public TreeParent(String name) {
+ this.name = name;
+ children = new ArrayList<Object>();
+ }
+
+ public synchronized void addChild(Object child) {
+ loaded = true;
+ children.add(child);
+ if (child instanceof TreeParent)
+ ((TreeParent) child).setParent(this);
+ }
+
+ /**
+ * Remove this child. The child is disposed.
+ */
+ public synchronized void removeChild(Object child) {
+ children.remove(child);
+ if (child instanceof TreeParent) {
+ ((TreeParent) child).dispose();
+ }
+ }
+
+ public synchronized void clearChildren() {
+ for (Object obj : children) {
+ if (obj instanceof TreeParent)
+ ((TreeParent) obj).dispose();
+ }
+ loaded = false;
+ children.clear();
+ }
+
+ /**
+ * If overridden, <code>super.dispose()</code> must be called, typically
+ * after custom cleaning.
+ */
+ public synchronized void dispose() {
+ clearChildren();
+ parent = null;
+ children = null;
+ }
+
+ public synchronized Object[] getChildren() {
+ return children.toArray(new Object[children.size()]);
+ }
+
+ @SuppressWarnings("unchecked")
+ public synchronized <T> List<T> getChildrenOfType(Class<T> clss) {
+ List<T> lst = new ArrayList<T>();
+ for (Object obj : children) {
+ if (clss.isAssignableFrom(obj.getClass()))
+ lst.add((T) obj);
+ }
+ return lst;
+ }
+
+ public synchronized boolean hasChildren() {
+ return children.size() > 0;
+ }
+
+ public Object getChildByName(String name) {
+ for (Object child : children) {
+ if (child.toString().equals(name))
+ return child;
+ }
+ return null;
+ }
+
+ public synchronized Boolean isLoaded() {
+ return loaded;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setParent(TreeParent parent) {
+ this.parent = parent;
+ if (parent != null && parent.path != null)
+ this.path = parent.path + '/' + name;
+ else
+ this.path = '/' + name;
+ }
+
+ public TreeParent getParent() {
+ return parent;
+ }
+
+ public String toString() {
+ return getName();
+ }
+
+ public int compareTo(TreeParent o) {
+ return name.compareTo(o.name);
+ }
+
+ @Override
+ public int hashCode() {
+ if (path != null)
+ return path.hashCode();
+ else
+ return name.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (path != null && obj instanceof TreeParent)
+ return path.equals(((TreeParent) obj).path);
+ else
+ return name.equals(obj.toString());
+ }
+
+}
--- /dev/null
+package org.argeo.eclipse.ui.dialogs;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+import org.argeo.api.cms.CmsLog;
+import org.eclipse.jface.dialogs.IMessageProvider;
+import org.eclipse.jface.dialogs.TitleAreaDialog;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Point;
+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.Display;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Text;
+
+/**
+ * Generic error dialog to be used in try/catch blocks.
+ *
+ * @deprecated Use CMS dialogs instead.
+ */
+@Deprecated
+public class ErrorFeedback extends TitleAreaDialog {
+ private static final long serialVersionUID = -8918084784628179044L;
+
+ private final static CmsLog log = CmsLog.getLog(ErrorFeedback.class);
+
+ private final String message;
+ private final Throwable exception;
+
+ public static void show(String message, Throwable e) {
+ // rethrow ThreaDeath in order to make sure that RAP will properly clean
+ // up the UI thread
+ if (e instanceof ThreadDeath)
+ throw (ThreadDeath) e;
+
+ new ErrorFeedback(newShell(), message, e).open();
+ }
+
+ public static void show(String message) {
+ new ErrorFeedback(newShell(), message, null).open();
+ }
+
+ private static Shell newShell() {
+ return new Shell(getDisplay(), SWT.NO_TRIM);
+ }
+
+ /** Tries to find a display */
+ private static Display getDisplay() {
+ try {
+ Display display = Display.getCurrent();
+ if (display != null)
+ return display;
+ else
+ return Display.getDefault();
+ } catch (Exception e) {
+ return Display.getCurrent();
+ }
+ }
+
+ public ErrorFeedback(Shell parentShell, String message, Throwable e) {
+ super(parentShell);
+ setShellStyle(SWT.NO_TRIM);
+ this.message = message;
+ this.exception = e;
+ log.error(message, e);
+ }
+
+ protected Point getInitialSize() {
+ if (exception != null)
+ return new Point(800, 600);
+ else
+ return new Point(400, 300);
+ }
+
+ @Override
+ 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, true));
+
+ setMessage(message != null ? message + (exception != null ? ": " + exception.getMessage() : "")
+ : exception != null ? exception.getMessage() : "Unkown Error", IMessageProvider.ERROR);
+
+ if (exception != null) {
+ Text stack = new Text(composite, SWT.MULTI | SWT.LEAD | SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL);
+ stack.setEditable(false);
+ stack.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
+ StringWriter sw = new StringWriter();
+ exception.printStackTrace(new PrintWriter(sw));
+ stack.setText(sw.toString());
+ }
+
+ parent.pack();
+ return composite;
+ }
+
+ protected void configureShell(Shell shell) {
+ super.configureShell(shell);
+ shell.setText("Error");
+ }
+}
\ No newline at end of file
--- /dev/null
+package org.argeo.eclipse.ui.dialogs;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+import org.argeo.api.cms.CmsLog;
+import org.argeo.eclipse.ui.EclipseUiException;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.ShellAdapter;
+import org.eclipse.swt.events.ShellEvent;
+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.Display;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Text;
+
+/**
+ * Generic lightweight dialog, not based on JFace.
+ *
+ * @deprecated Use CMS dialogs instead.
+ */
+@Deprecated
+public class FeedbackDialog extends LightweightDialog {
+ private final static CmsLog log = CmsLog.getLog(FeedbackDialog.class);
+
+ private String message;
+ private Throwable exception;
+
+// private Shell parentShell;
+ private Shell shell;
+
+ public static void show(String message, Throwable e) {
+ // rethrow ThreaDeath in order to make sure that RAP will properly clean
+ // up the UI thread
+ if (e instanceof ThreadDeath)
+ throw (ThreadDeath) e;
+
+ new FeedbackDialog(getDisplay().getActiveShell(), message, e).open();
+ }
+
+ public static void show(String message) {
+ new FeedbackDialog(getDisplay().getActiveShell(), message, null).open();
+ }
+
+ /** Tries to find a display */
+ private static Display getDisplay() {
+ try {
+ Display display = Display.getCurrent();
+ if (display != null)
+ return display;
+ else
+ return Display.getDefault();
+ } catch (Exception e) {
+ return Display.getCurrent();
+ }
+ }
+
+ public FeedbackDialog(Shell parentShell, String message, Throwable e) {
+ super(parentShell);
+ this.message = message;
+ this.exception = e;
+ log.error(message, e);
+ }
+
+ public int open() {
+ if (shell != null)
+ throw new EclipseUiException("There is already a shell");
+ shell = new Shell(getDisplay(), SWT.NO_TRIM | SWT.BORDER | SWT.ON_TOP);
+ shell.setLayout(new GridLayout());
+ // shell.setText("Error");
+ shell.setSize(getInitialSize());
+ createDialogArea(shell);
+ // shell.pack();
+ // shell.layout();
+
+ Rectangle shellBounds = Display.getCurrent().getBounds();// RAP
+ Point dialogSize = shell.getSize();
+ int x = shellBounds.x + (shellBounds.width - dialogSize.x) / 2;
+ int y = shellBounds.y + (shellBounds.height - dialogSize.y) / 2;
+ shell.setLocation(x, y);
+
+ shell.addShellListener(new ShellAdapter() {
+ private static final long serialVersionUID = -2701270481953688763L;
+
+ @Override
+ public void shellDeactivated(ShellEvent e) {
+ closeShell();
+ }
+ });
+
+ shell.open();
+ return OK;
+ }
+
+ protected void closeShell() {
+ shell.close();
+ shell.dispose();
+ shell = null;
+ }
+
+ protected Point getInitialSize() {
+ // if (exception != null)
+ // return new Point(800, 600);
+ // else
+ return new Point(400, 300);
+ }
+
+ protected Control createDialogArea(Composite parent) {
+ Composite dialogarea = new Composite(parent, SWT.NONE);
+ dialogarea.setLayout(new GridLayout());
+ // Composite dialogarea = (Composite) super.createDialogArea(parent);
+ dialogarea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
+
+ Label messageLbl = new Label(dialogarea, SWT.NONE);
+ if (message != null)
+ messageLbl.setText(message);
+ else if (exception != null)
+ messageLbl.setText(exception.getLocalizedMessage());
+
+ Composite composite = new Composite(dialogarea, SWT.NONE);
+ composite.setLayout(new GridLayout(2, false));
+ composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
+
+ if (exception != null) {
+ Text stack = new Text(composite, SWT.MULTI | SWT.LEAD | SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL);
+ stack.setEditable(false);
+ stack.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
+ StringWriter sw = new StringWriter();
+ exception.printStackTrace(new PrintWriter(sw));
+ stack.setText(sw.toString());
+ }
+
+ // parent.pack();
+ return composite;
+ }
+}
\ No newline at end of file
--- /dev/null
+package org.argeo.eclipse.ui.dialogs;
+
+import org.argeo.api.cms.CmsLog;
+import org.argeo.eclipse.ui.EclipseUiException;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.FocusEvent;
+import org.eclipse.swt.events.FocusListener;
+import org.eclipse.swt.events.ShellAdapter;
+import org.eclipse.swt.events.ShellEvent;
+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.Display;
+import org.eclipse.swt.widgets.Shell;
+
+/** Generic lightweight dialog, not based on JFace. */
+@Deprecated
+public class LightweightDialog {
+ private final static CmsLog log = CmsLog.getLog(LightweightDialog.class);
+
+ // must be the same value as org.eclipse.jface.window.Window#OK
+ public final static int OK = 0;
+ // must be the same value as org.eclipse.jface.window.Window#CANCEL
+ public final static int CANCEL = 1;
+
+ private Shell parentShell;
+ private Shell backgroundShell;
+ private Shell foregoundShell;
+
+ private Integer returnCode = null;
+ private boolean block = true;
+
+ private String title;
+
+ /** Tries to find a display */
+ private static Display getDisplay() {
+ try {
+ Display display = Display.getCurrent();
+ if (display != null)
+ return display;
+ else
+ return Display.getDefault();
+ } catch (Exception e) {
+ return Display.getCurrent();
+ }
+ }
+
+ public LightweightDialog(Shell parentShell) {
+ this.parentShell = parentShell;
+ }
+
+ public int open() {
+ if (foregoundShell != null)
+ throw new EclipseUiException("There is already a shell");
+ backgroundShell = new Shell(parentShell, SWT.ON_TOP);
+ backgroundShell.setFullScreen(true);
+ // if (parentShell != null) {
+ // backgroundShell.setBounds(parentShell.getBounds());
+ // } else
+ // backgroundShell.setMaximized(true);
+ backgroundShell.setAlpha(128);
+ backgroundShell.setBackground(getDisplay().getSystemColor(SWT.COLOR_BLACK));
+ foregoundShell = new Shell(backgroundShell, SWT.NO_TRIM | SWT.ON_TOP);
+ if (title != null)
+ setTitle(title);
+ foregoundShell.setLayout(new GridLayout());
+ foregoundShell.setSize(getInitialSize());
+ createDialogArea(foregoundShell);
+ // shell.pack();
+ // shell.layout();
+
+ Rectangle shellBounds = parentShell != null ? parentShell.getBounds() : Display.getCurrent().getBounds();// RAP
+ Point dialogSize = foregoundShell.getSize();
+ int x = shellBounds.x + (shellBounds.width - dialogSize.x) / 2;
+ int y = shellBounds.y + (shellBounds.height - dialogSize.y) / 2;
+ foregoundShell.setLocation(x, y);
+
+ foregoundShell.addShellListener(new ShellAdapter() {
+ private static final long serialVersionUID = -2701270481953688763L;
+
+ @Override
+ public void shellDeactivated(ShellEvent e) {
+ if (hasChildShells())
+ return;
+ if (returnCode == null)// not yet closed
+ closeShell(CANCEL);
+ }
+
+ @Override
+ public void shellClosed(ShellEvent e) {
+ notifyClose();
+ }
+
+ });
+
+ backgroundShell.open();
+ foregoundShell.open();
+ // after the foreground shell has been opened
+ backgroundShell.addFocusListener(new FocusListener() {
+ private static final long serialVersionUID = 3137408447474661070L;
+
+ @Override
+ public void focusLost(FocusEvent event) {
+ }
+
+ @Override
+ public void focusGained(FocusEvent event) {
+ if (hasChildShells())
+ return;
+ if (returnCode == null)// not yet closed
+ closeShell(CANCEL);
+ }
+ });
+
+ if (block) {
+ block();
+ }
+ if (returnCode == null)
+ returnCode = OK;
+ return returnCode;
+ }
+
+ public void block() {
+ try {
+ runEventLoop(foregoundShell);
+ } catch (ThreadDeath t) {
+ returnCode = CANCEL;
+ if (log.isTraceEnabled())
+ log.error("Thread death, canceling dialog", t);
+ } catch (Throwable t) {
+ returnCode = CANCEL;
+ log.error("Cannot open blocking lightweight dialog", t);
+ }
+ }
+
+ private boolean hasChildShells() {
+ if (foregoundShell == null)
+ return false;
+ return foregoundShell.getShells().length != 0;
+ }
+
+ // public synchronized int openAndWait() {
+ // open();
+ // while (returnCode == null)
+ // try {
+ // wait(100);
+ // } catch (InterruptedException e) {
+ // // silent
+ // }
+ // return returnCode;
+ // }
+
+ private synchronized void notifyClose() {
+ if (returnCode == null)
+ returnCode = CANCEL;
+ notifyAll();
+ }
+
+ protected void closeShell(int returnCode) {
+ this.returnCode = returnCode;
+ if (CANCEL == returnCode)
+ onCancel();
+ if (foregoundShell != null && !foregoundShell.isDisposed()) {
+ foregoundShell.close();
+ foregoundShell.dispose();
+ foregoundShell = null;
+ }
+
+ if (backgroundShell != null && !backgroundShell.isDisposed()) {
+ backgroundShell.close();
+ backgroundShell.dispose();
+ }
+ }
+
+ protected Point getInitialSize() {
+ // if (exception != null)
+ // return new Point(800, 600);
+ // else
+ return new Point(600, 400);
+ }
+
+ protected Control createDialogArea(Composite parent) {
+ Composite dialogarea = new Composite(parent, SWT.NONE);
+ dialogarea.setLayout(new GridLayout());
+ dialogarea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
+ return dialogarea;
+ }
+
+ protected Shell getBackgroundShell() {
+ return backgroundShell;
+ }
+
+ protected Shell getForegoundShell() {
+ return foregoundShell;
+ }
+
+ public void setBlockOnOpen(boolean shouldBlock) {
+ block = shouldBlock;
+ }
+
+ public void pack() {
+ foregoundShell.pack();
+ }
+
+ private void runEventLoop(Shell loopShell) {
+ Display display;
+ if (foregoundShell == null) {
+ display = Display.getCurrent();
+ } else {
+ display = loopShell.getDisplay();
+ }
+
+ while (loopShell != null && !loopShell.isDisposed()) {
+ try {
+ if (!display.readAndDispatch()) {
+ display.sleep();
+ }
+ } catch (UnsupportedOperationException e) {
+ throw e;
+ } catch (Throwable e) {
+ handleException(e);
+ }
+ }
+ if (!display.isDisposed())
+ display.update();
+ }
+
+ protected void handleException(Throwable t) {
+ if (t instanceof ThreadDeath) {
+ // Don't catch ThreadDeath as this is a normal occurrence when
+ // the thread dies
+ throw (ThreadDeath) t;
+ }
+ // Try to keep running.
+ t.printStackTrace();
+ }
+
+ /** @return false, if the dialog should not be closed. */
+ protected boolean onCancel() {
+ return true;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ if (title != null && getForegoundShell() != null)
+ getForegoundShell().setText(title);
+ }
+
+ public Integer getReturnCode() {
+ return returnCode;
+ }
+
+}
\ No newline at end of file
--- /dev/null
+package org.argeo.eclipse.ui.dialogs;
+
+import org.argeo.eclipse.ui.EclipseUiUtils;
+import org.eclipse.jface.dialogs.IMessageProvider;
+import org.eclipse.jface.dialogs.TitleAreaDialog;
+import org.eclipse.jface.window.Window;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Point;
+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.Display;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Text;
+
+/**
+ * Dialog to retrieve a single value.
+ *
+ * @deprecated Use CMS dialogs instead.
+ */
+@Deprecated
+public class SingleValue extends TitleAreaDialog {
+ private static final long serialVersionUID = 2843538207460082349L;
+
+ private Text valueT;
+ private String value;
+ private final String title, message, label;
+ private final Boolean multiline;
+
+ public static String ask(String label, String message) {
+ SingleValue svd = new SingleValue(label, message);
+ if (svd.open() == Window.OK)
+ return svd.getString();
+ else
+ return null;
+ }
+
+ public static Long askLong(String label, String message) {
+ SingleValue svd = new SingleValue(label, message);
+ if (svd.open() == Window.OK)
+ return svd.getLong();
+ else
+ return null;
+ }
+
+ public static Double askDouble(String label, String message) {
+ SingleValue svd = new SingleValue(label, message);
+ if (svd.open() == Window.OK)
+ return svd.getDouble();
+ else
+ return null;
+ }
+
+ public SingleValue(String label, String message) {
+ this(Display.getDefault().getActiveShell(), label, message, label, false);
+ }
+
+ public SingleValue(Shell parentShell, String title, String message, String label, Boolean multiline) {
+ super(parentShell);
+ this.title = title;
+ this.message = message;
+ this.label = label;
+ this.multiline = multiline;
+ }
+
+ protected Point getInitialSize() {
+ if (multiline)
+ return new Point(450, 350);
+
+ else
+ return new Point(400, 270);
+ }
+
+ 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.setLayoutData(EclipseUiUtils.fillAll());
+ GridLayout layout = new GridLayout(2, false);
+ layout.marginWidth = layout.marginHeight = 20;
+ composite.setLayout(layout);
+
+ valueT = createLT(composite, label);
+
+ setMessage(message, IMessageProvider.NONE);
+
+ parent.pack();
+ valueT.setFocus();
+ return composite;
+ }
+
+ @Override
+ protected void okPressed() {
+ value = valueT.getText();
+ super.okPressed();
+ }
+
+ /** Creates label and text. */
+ protected Text createLT(Composite parent, String label) {
+ new Label(parent, SWT.NONE).setText(label);
+ Text text;
+ if (multiline) {
+ text = new Text(parent, SWT.LEAD | SWT.BORDER | SWT.MULTI);
+ text.setLayoutData(EclipseUiUtils.fillAll());
+ } else {
+ text = new Text(parent, SWT.LEAD | SWT.BORDER | SWT.SINGLE);
+ text.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, true));
+ }
+ return text;
+ }
+
+ protected void configureShell(Shell shell) {
+ super.configureShell(shell);
+ shell.setText(title);
+ }
+
+ public String getString() {
+ return value;
+ }
+
+ public Long getLong() {
+ return Long.valueOf(getString());
+ }
+
+ public Double getDouble() {
+ return Double.valueOf(getString());
+ }
+}
--- /dev/null
+/** Generic SWT/JFace dialogs. */
+package org.argeo.eclipse.ui.dialogs;
\ No newline at end of file
--- /dev/null
+package org.argeo.eclipse.ui.fs;
+
+import java.io.IOException;
+import java.nio.file.DirectoryStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.LinkedHashMap;
+
+import org.argeo.api.cms.CmsLog;
+import org.argeo.eclipse.ui.EclipseUiUtils;
+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.swt.SWT;
+import org.eclipse.swt.custom.SashForm;
+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.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.Text;
+
+/** Simple UI provider that populates a composite parent given a NIO path */
+public class AdvancedFsBrowser {
+ private final static CmsLog log = CmsLog.getLog(AdvancedFsBrowser.class);
+
+ // Some local constants to experiment. should be cleaned
+ // private final static int THUMBNAIL_WIDTH = 400;
+ // private Point imageWidth = new Point(250, 0);
+ private final static int COLUMN_WIDTH = 160;
+
+ private Path initialPath;
+ private Path currEdited;
+ // Filter
+ private Composite displayBoxCmp;
+ private Text parentPathTxt;
+ private Text filterTxt;
+ // Browser columns
+ private ScrolledComposite scrolledCmp;
+ // Keep a cache of the opened directories
+ private LinkedHashMap<Path, FilterEntitiesVirtualTable> browserCols = new LinkedHashMap<>();
+ private Composite scrolledCmpBody;
+
+ public Control createUi(Composite parent, Path basePath) {
+ if (basePath == null)
+ throw new IllegalArgumentException("Context cannot be null");
+ parent.setLayout(new GridLayout());
+
+ // top filter
+ Composite filterCmp = new Composite(parent, SWT.NO_FOCUS);
+ filterCmp.setLayoutData(EclipseUiUtils.fillWidth());
+ addFilterPanel(filterCmp);
+
+ // Bottom part a sash with browser on the left
+ SashForm form = new SashForm(parent, SWT.HORIZONTAL);
+ // form.setLayout(new FillLayout());
+ form.setLayoutData(EclipseUiUtils.fillAll());
+ Composite leftCmp = new Composite(form, SWT.NO_FOCUS);
+ displayBoxCmp = new Composite(form, SWT.NONE);
+ form.setWeights(new int[] { 3, 1 });
+
+ createBrowserPart(leftCmp, basePath);
+ // leftCmp.addControlListener(new ControlAdapter() {
+ // @Override
+ // public void controlResized(ControlEvent e) {
+ // Rectangle r = leftCmp.getClientArea();
+ // log.warn("Browser resized: " + r.toString());
+ // scrolledCmp.setMinSize(browserCols.size() * (COLUMN_WIDTH + 2),
+ // SWT.DEFAULT);
+ // // scrolledCmp.setMinSize(scrolledCmpBody.computeSize(SWT.DEFAULT,
+ // // r.height));
+ // }
+ // });
+
+ populateCurrEditedDisplay(displayBoxCmp, basePath);
+
+ // INIT
+ setEdited(basePath);
+ initialPath = basePath;
+ // form.layout(true, true);
+ return parent;
+ }
+
+ private void createBrowserPart(Composite parent, Path context) {
+ parent.setLayout(EclipseUiUtils.noSpaceGridLayout());
+
+ // scrolled composite
+ scrolledCmp = new ScrolledComposite(parent, SWT.H_SCROLL | SWT.BORDER | SWT.NO_FOCUS);
+ scrolledCmp.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
+ scrolledCmp.setExpandVertical(true);
+ scrolledCmp.setExpandHorizontal(true);
+ scrolledCmp.setShowFocusedControl(true);
+
+ scrolledCmpBody = new Composite(scrolledCmp, SWT.NO_FOCUS);
+ scrolledCmp.setContent(scrolledCmpBody);
+ scrolledCmpBody.addControlListener(new ControlAdapter() {
+ private static final long serialVersionUID = 183238447102854553L;
+
+ @Override
+ public void controlResized(ControlEvent e) {
+ Rectangle r = scrolledCmp.getClientArea();
+ scrolledCmp.setMinSize(scrolledCmpBody.computeSize(SWT.DEFAULT, r.height));
+ }
+ });
+ initExplorer(scrolledCmpBody, context);
+ scrolledCmpBody.layout(true, true);
+ scrolledCmp.layout();
+
+ }
+
+ private Control initExplorer(Composite parent, Path context) {
+ parent.setLayout(EclipseUiUtils.noSpaceGridLayout());
+ return createBrowserColumn(parent, context);
+ }
+
+ private Control createBrowserColumn(Composite parent, Path context) {
+ // TODO style is not correctly managed.
+ FilterEntitiesVirtualTable table = new FilterEntitiesVirtualTable(parent, SWT.BORDER | SWT.NO_FOCUS, context);
+ // CmsUtils.style(table, ArgeoOrgStyle.browserColumn.style());
+ table.filterList("*");
+ table.setLayoutData(new GridData(SWT.LEFT, SWT.FILL, false, true));
+ browserCols.put(context, table);
+ parent.layout(true, true);
+ return table;
+ }
+
+ public void addFilterPanel(Composite parent) {
+ parent.setLayout(EclipseUiUtils.noSpaceGridLayout(new GridLayout(2, false)));
+
+ 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(EclipseUiUtils.fillWidth());
+ filterTxt.addModifyListener(new ModifyListener() {
+ private static final long serialVersionUID = 1L;
+
+ public void modifyText(ModifyEvent event) {
+ modifyFilter(false);
+ }
+ });
+ filterTxt.addKeyListener(new KeyListener() {
+ private static final long serialVersionUID = 2533535233583035527L;
+
+ @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(currEdited);
+ if (table != null && !table.isDisposed())
+ currTable = table;
+ }
+
+ if (e.keyCode == SWT.ARROW_DOWN)
+ currTable.setFocus();
+ else if (e.keyCode == SWT.BS) {
+ if (filterTxt.getText().equals("")
+ && !(currEdited.getNameCount() == 1 || currEdited.equals(initialPath))) {
+ Path oldEdited = currEdited;
+ Path parentPath = currEdited.getParent();
+ setEdited(parentPath);
+ if (browserCols.containsKey(parentPath))
+ browserCols.get(parentPath).setSelected(oldEdited);
+ filterTxt.setFocus();
+ e.doit = false;
+ }
+ } else if (e.keyCode == SWT.TAB && !shiftPressed) {
+ Path uniqueChild = getOnlyChild(currEdited, filterTxt.getText());
+ if (uniqueChild != null) {
+ // Highlight the unique chosen child
+ currTable.setSelected(uniqueChild);
+ setEdited(uniqueChild);
+ }
+ filterTxt.setFocus();
+ e.doit = false;
+ }
+ }
+ });
+ }
+
+ private Path getOnlyChild(Path parent, String filter) {
+ try (DirectoryStream<Path> stream = Files.newDirectoryStream(currEdited, filter + "*")) {
+ Path uniqueChild = null;
+ boolean moreThanOne = false;
+ loop: for (Path entry : stream) {
+ if (uniqueChild == null) {
+ uniqueChild = entry;
+ } else {
+ moreThanOne = true;
+ break loop;
+ }
+ }
+ if (!moreThanOne)
+ return uniqueChild;
+ return null;
+ } catch (IOException ioe) {
+ throw new FsUiException(
+ "Unable to determine unique child existence and get it under " + parent + " with filter " + filter,
+ ioe);
+ }
+ }
+
+ private void setEdited(Path path) {
+ currEdited = path;
+ EclipseUiUtils.clear(displayBoxCmp);
+ populateCurrEditedDisplay(displayBoxCmp, currEdited);
+ refreshFilters(path);
+ refreshBrowser(path);
+ }
+
+ private void refreshFilters(Path path) {
+ parentPathTxt.setText(path.toUri().toString());
+ filterTxt.setText("");
+ filterTxt.getParent().layout();
+ }
+
+ private void refreshBrowser(Path currPath) {
+ Path currParPath = currPath.getParent();
+ Object[][] colMatrix = new Object[browserCols.size()][2];
+
+ int i = 0, currPathIndex = -1, lastLeftOpenedIndex = -1;
+ for (Path path : browserCols.keySet()) {
+ colMatrix[i][0] = path;
+ colMatrix[i][1] = browserCols.get(path);
+ if (currPathIndex >= 0 && lastLeftOpenedIndex < 0 && currParPath != null) {
+ boolean leaveOpened = path.startsWith(currPath);
+ if (!leaveOpened)
+ lastLeftOpenedIndex = i;
+ }
+ if (currParPath.equals(path))
+ currPathIndex = i;
+ i++;
+ }
+
+ if (currPathIndex >= 0 && lastLeftOpenedIndex >= 0) {
+ // dispose and remove useless cols
+ for (int l = i - 1; l >= lastLeftOpenedIndex; l--) {
+ ((FilterEntitiesVirtualTable) colMatrix[l][1]).dispose();
+ browserCols.remove(colMatrix[l][0]);
+ }
+ }
+
+ if (browserCols.containsKey(currPath)) {
+ FilterEntitiesVirtualTable currCol = browserCols.get(currPath);
+ if (currCol.isDisposed()) {
+ // Does it still happen ?
+ log.warn(currPath + " browser column was disposed and still listed");
+ browserCols.remove(currPath);
+ }
+ }
+
+ if (!browserCols.containsKey(currPath) && Files.isDirectory(currPath))
+ createBrowserColumn(scrolledCmpBody, currPath);
+
+ scrolledCmpBody.setLayout(EclipseUiUtils.noSpaceGridLayout(new GridLayout(browserCols.size(), false)));
+ scrolledCmpBody.layout(true, true);
+ // also resize the scrolled composite
+ scrolledCmp.layout();
+ }
+
+ private void modifyFilter(boolean fromOutside) {
+ if (!fromOutside)
+ if (currEdited != null) {
+ String filter = filterTxt.getText() + "*";
+ FilterEntitiesVirtualTable table = browserCols.get(currEdited);
+ if (table != null && !table.isDisposed())
+ table.filterList(filter);
+ }
+ }
+
+ /**
+ * Recreates the content of the box that displays information about the current
+ * selected node.
+ */
+ private void populateCurrEditedDisplay(Composite parent, Path context) {
+ parent.setLayout(new GridLayout());
+
+ // if (isImg(context)) {
+ // EditableImage image = new Img(parent, RIGHT, context, imageWidth);
+ // image.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, true, false,
+ // 2, 1));
+ // }
+
+ try {
+ Label contextL = new Label(parent, SWT.NONE);
+ contextL.setText(context.getFileName().toString());
+ contextL.setFont(EclipseUiUtils.getBoldFont(parent));
+ addProperty(parent, "Last modified", Files.getLastModifiedTime(context).toString());
+ addProperty(parent, "Owner", Files.getOwner(context).getName());
+ if (Files.isDirectory(context)) {
+ addProperty(parent, "Type", "Folder");
+ } else {
+ String mimeType = Files.probeContentType(context);
+ if (EclipseUiUtils.isEmpty(mimeType))
+ mimeType = "<i>Unknown</i>";
+ addProperty(parent, "Type", mimeType);
+ addProperty(parent, "Size", FsUiUtils.humanReadableByteCount(Files.size(context), false));
+ }
+ parent.layout(true, true);
+ } catch (IOException e) {
+ throw new FsUiException("Cannot display details for " + context, e);
+ }
+ }
+
+ private void addProperty(Composite parent, String propName, String value) {
+ Label contextL = new Label(parent, SWT.NONE);
+ contextL.setText(propName + ": " + value);
+ }
+
+ /**
+ * Almost canonical implementation of a table that displays the content of a
+ * directory
+ */
+ private class FilterEntitiesVirtualTable extends Composite {
+ private static final long serialVersionUID = 2223410043691844875L;
+
+ // Context
+ private Path context;
+ private Path currSelected = null;
+
+ // UI Objects
+ private FsTableViewer viewer;
+
+ @Override
+ public boolean setFocus() {
+ if (viewer.getTable().isDisposed())
+ return false;
+ if (currSelected != null)
+ viewer.setSelection(new StructuredSelection(currSelected), true);
+ else if (viewer.getSelection().isEmpty()) {
+ Object first = viewer.getElementAt(0);
+ if (first != null)
+ viewer.setSelection(new StructuredSelection(first), true);
+ }
+ return viewer.getTable().setFocus();
+ }
+
+ /**
+ * Enable highlighting the correct element in the table when externally browsing
+ * (typically via the command-line-like Text field)
+ */
+ void setSelected(Path selected) {
+ // to prevent change selection event to be thrown
+ currSelected = selected;
+ viewer.setSelection(new StructuredSelection(currSelected), true);
+ }
+
+ void filterList(String filter) {
+ viewer.setInput(context, filter);
+ }
+
+ public FilterEntitiesVirtualTable(Composite parent, int style, Path context) {
+ super(parent, SWT.NO_FOCUS);
+ this.context = context;
+ createTableViewer(this);
+ }
+
+ private void createTableViewer(final Composite parent) {
+ parent.setLayout(EclipseUiUtils.noSpaceGridLayout());
+
+ // 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(EclipseUiUtils.noSpaceGridLayout());
+ // viewer = new TableViewer(listCmp, SWT.VIRTUAL | SWT.MULTI |
+ // SWT.V_SCROLL);
+ // Table table = viewer.getTable();
+ // table.setLayoutData(EclipseUiUtils.fillAll());
+
+ viewer = new FsTableViewer(parent, SWT.MULTI);
+ Table table = viewer.configureDefaultSingleColumnTable(COLUMN_WIDTH);
+
+ viewer.addSelectionChangedListener(new ISelectionChangedListener() {
+
+ @Override
+ public void selectionChanged(SelectionChangedEvent event) {
+ IStructuredSelection selection = (IStructuredSelection) viewer.getSelection();
+ if (selection.isEmpty())
+ return;
+ Object obj = selection.getFirstElement();
+ Path newSelected;
+ if (obj instanceof Path)
+ newSelected = (Path) obj;
+ else if (obj instanceof ParentDir)
+ newSelected = ((ParentDir) obj).getPath();
+ else
+ return;
+ if (newSelected.equals(currSelected))
+ return;
+ currSelected = newSelected;
+ setEdited(newSelected);
+
+ }
+ });
+
+ table.addKeyListener(new KeyListener() {
+ private static final long serialVersionUID = -8083424284436715709L;
+
+ @Override
+ public void keyReleased(KeyEvent e) {
+ }
+
+ @Override
+ public void keyPressed(KeyEvent e) {
+ IStructuredSelection selection = (IStructuredSelection) viewer.getSelection();
+ Path selected = null;
+ if (!selection.isEmpty())
+ selected = ((Path) selection.getFirstElement());
+ if (e.keyCode == SWT.ARROW_RIGHT) {
+ if (!Files.isDirectory(selected))
+ return;
+ if (selected != null) {
+ setEdited(selected);
+ browserCols.get(selected).setFocus();
+ }
+ } else if (e.keyCode == SWT.ARROW_LEFT) {
+ if (context.equals(initialPath))
+ return;
+ Path parent = context.getParent();
+ if (parent == null)
+ return;
+
+ setEdited(parent);
+ browserCols.get(parent).setFocus();
+ }
+ }
+ });
+ }
+ }
+}
--- /dev/null
+package org.argeo.eclipse.ui.fs;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import org.eclipse.jface.resource.ImageDescriptor;
+import org.eclipse.jface.viewers.ColumnLabelProvider;
+import org.eclipse.swt.graphics.Image;
+
+/** Basic label provider with icon for NIO file viewers */
+public class FileIconNameLabelProvider extends ColumnLabelProvider {
+ private static final long serialVersionUID = 8187902187946523148L;
+
+ private Image folderIcon;
+ private Image fileIcon;
+
+ public FileIconNameLabelProvider() {
+ // if (!PlatformUI.isWorkbenchRunning()) {
+ folderIcon = ImageDescriptor.createFromFile(getClass(), "folder.png").createImage();
+ fileIcon = ImageDescriptor.createFromFile(getClass(), "file.png").createImage();
+ // }
+ }
+
+ @Override
+ public void dispose() {
+ if (folderIcon != null)
+ folderIcon.dispose();
+ if (fileIcon != null)
+ fileIcon.dispose();
+ super.dispose();
+ }
+
+ @Override
+ public String getText(Object element) {
+ if (element instanceof Path) {
+ Path curr = ((Path) element);
+ Path name = curr.getFileName();
+ if (name == null)
+ return "[No name]";
+ else
+ return name.toString();
+ } else if (element instanceof ParentDir) {
+ return "..";
+ }
+ return null;
+ }
+
+ @Override
+ public Image getImage(Object element) {
+ if (element instanceof Path) {
+ Path curr = ((Path) element);
+ if (Files.isDirectory(curr))
+ // if (folderIcon != null)
+ return folderIcon;
+ // else
+ // return
+ // PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_OBJ_FOLDER);
+ // else if (fileIcon != null)
+ return fileIcon;
+ // else
+ // return
+ // PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_OBJ_FILE);
+ } else if (element instanceof ParentDir) {
+ return folderIcon;
+ }
+ return null;
+ }
+
+ @Override
+ public String getToolTipText(Object element) {
+ if (element instanceof Path) {
+ Path curr = ((Path) element);
+ Path name = curr.getFileName();
+ if (name == null)
+ return "[No name]";
+ else
+ return name.toAbsolutePath().toString();
+ } else if (element instanceof ParentDir) {
+ return ((ParentDir) element).getPath().toAbsolutePath().toString();
+ }
+ return null;
+ }
+
+}
\ No newline at end of file
--- /dev/null
+package org.argeo.eclipse.ui.fs;
+
+import java.nio.file.Path;
+import java.util.List;
+
+import org.argeo.eclipse.ui.ColumnDefinition;
+import org.eclipse.jface.viewers.CellLabelProvider;
+import org.eclipse.jface.viewers.ILazyContentProvider;
+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.layout.GridData;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Table;
+import org.eclipse.swt.widgets.TableColumn;
+
+/**
+ * Canonical implementation of a JFace table viewer to display the content of a
+ * file folder
+ */
+public class FsTableViewer extends TableViewer {
+ private static final long serialVersionUID = -5632407542678477234L;
+
+ private boolean showHiddenItems = false;
+ private boolean folderFirst = true;
+ private boolean reverseOrder = false;
+ private String orderProperty = FsUiConstants.PROPERTY_NAME;
+
+ private Path initialPath = null;
+
+ public FsTableViewer(Composite parent, int style) {
+ super(parent, style | SWT.VIRTUAL);
+ }
+
+ public Table configureDefaultSingleColumnTable(int tableWidthHint) {
+
+ return configureDefaultSingleColumnTable(tableWidthHint, new FileIconNameLabelProvider());
+ }
+
+ public Table configureDefaultSingleColumnTable(int tableWidthHint, CellLabelProvider labelProvider) {
+ Table table = this.getTable();
+ table.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
+ table.setLinesVisible(false);
+ table.setHeaderVisible(false);
+ // CmsUtils.markup(table);
+ // CmsUtils.style(table, MaintenanceStyles.BROWSER_COLUMN);
+
+ TableViewerColumn column = new TableViewerColumn(this, SWT.NONE);
+ TableColumn tcol = column.getColumn();
+ tcol.setWidth(tableWidthHint);
+ column.setLabelProvider(labelProvider);
+ this.setContentProvider(new MyLazyCP());
+ return table;
+ }
+
+ public Table configureDefaultTable(List<ColumnDefinition> columns) {
+ this.setContentProvider(new MyLazyCP());
+ Table table = this.getTable();
+ table.setLinesVisible(true);
+ table.setHeaderVisible(true);
+ // CmsUtils.markup(table);
+ // CmsUtils.style(table, MaintenanceStyles.BROWSER_COLUMN);
+ for (ColumnDefinition colDef : columns) {
+ TableViewerColumn column = new TableViewerColumn(this, SWT.NONE);
+ column.setLabelProvider(colDef.getLabelProvider());
+ TableColumn tcol = column.getColumn();
+ tcol.setResizable(true);
+ tcol.setText(colDef.getLabel());
+ tcol.setWidth(colDef.getMinWidth());
+ }
+ return table;
+ }
+
+ public void setInput(Path dir, String filter) {
+ Path[] rows = FsUiUtils.getChildren(dir, filter, showHiddenItems, folderFirst, orderProperty, reverseOrder);
+ if (rows == null) {
+ this.setInput(null);
+ this.setItemCount(0);
+ return;
+ }
+ boolean isRoot;
+ try {
+ isRoot = dir.getRoot().equals(dir);
+ } catch (Exception e) {
+ // FIXME Workaround for JCR root node access
+ isRoot = dir.toString().equals("/");
+ }
+ final Object[] res;
+ if (isRoot)
+ res = rows;
+ else if (initialPath != null && initialPath.equals(dir))
+ res = rows;
+ else {
+ res = new Object[rows.length + 1];
+ res[0] = new ParentDir(dir.getParent());
+ for (int i = 1; i < res.length; i++) {
+ res[i] = rows[i - 1];
+ }
+ }
+ this.setInput(res);
+ int length = res.length;
+ this.setItemCount(length);
+ this.refresh();
+ }
+
+ /** Directly displays bookmarks **/
+ public void setPathsInput(Path... paths) {
+ this.setInput((Object[]) paths);
+ this.setItemCount(paths.length);
+ this.refresh();
+ }
+
+ /**
+ * A path which is to be considered as root (and thus provide no link to a
+ * parent directory)
+ */
+ public void setInitialPath(Path initialPath) {
+ this.initialPath = initialPath;
+ }
+
+ private class MyLazyCP implements ILazyContentProvider {
+ private static final long serialVersionUID = 9096550041395433128L;
+ private Object[] elements;
+
+ 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) {
+ if (index < elements.length)
+ FsTableViewer.this.replace(elements[index], index);
+ }
+ }
+}
--- /dev/null
+package org.argeo.eclipse.ui.fs;
+
+import java.io.IOException;
+import java.nio.file.DirectoryIteratorException;
+import java.nio.file.DirectoryStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.argeo.eclipse.ui.ColumnDefinition;
+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.layout.GridData;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Tree;
+import org.eclipse.swt.widgets.TreeColumn;
+
+/**
+ * Canonical implementation of a JFace TreeViewer to display the content of a
+ * repository
+ */
+public class FsTreeViewer extends TreeViewer {
+ private static final long serialVersionUID = -5632407542678477234L;
+
+ private boolean showHiddenItems = false;
+ private boolean showDirectoryFirst = true;
+ private String orderingProperty = FsUiConstants.PROPERTY_NAME;
+
+ public FsTreeViewer(Composite parent, int style) {
+ super(parent, style | SWT.VIRTUAL);
+ }
+
+ public Tree configureDefaultSingleColumnTable(int tableWidthHint) {
+ Tree tree = this.getTree();
+ tree.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
+ tree.setLinesVisible(true);
+ tree.setHeaderVisible(false);
+// CmsUtils.markup(tree);
+
+ TreeViewerColumn column = new TreeViewerColumn(this, SWT.NONE);
+ TreeColumn tcol = column.getColumn();
+ tcol.setWidth(tableWidthHint);
+ column.setLabelProvider(new FileIconNameLabelProvider());
+
+ this.setContentProvider(new MyCP());
+ return tree;
+ }
+
+ public Tree configureDefaultTable(List<ColumnDefinition> columns) {
+ this.setContentProvider(new MyCP());
+ Tree tree = this.getTree();
+ tree.setLinesVisible(true);
+ tree.setHeaderVisible(true);
+// CmsUtils.markup(tree);
+// CmsUtils.style(tree, MaintenanceStyles.BROWSER_COLUMN);
+ for (ColumnDefinition colDef : columns) {
+ TreeViewerColumn column = new TreeViewerColumn(this, SWT.NONE);
+ column.setLabelProvider(colDef.getLabelProvider());
+ TreeColumn tcol = column.getColumn();
+ tcol.setResizable(true);
+ tcol.setText(colDef.getLabel());
+ tcol.setWidth(colDef.getMinWidth());
+ }
+ return tree;
+ }
+
+ public void setInput(Path dir, String filter) {
+ try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir, filter)) {
+ // TODO make this lazy
+ List<Path> paths = new ArrayList<>();
+ for (Path entry : stream) {
+ paths.add(entry);
+ }
+ Object[] rows = paths.toArray(new Object[0]);
+ this.setInput(rows);
+ // this.setItemCount(rows.length);
+ this.refresh();
+ } catch (IOException | DirectoryIteratorException e) {
+ throw new FsUiException("Unable to filter " + dir + " children with filter " + filter, e);
+ }
+ }
+
+ /** Directly displays bookmarks **/
+ public void setPathsInput(Path... paths) {
+ this.setInput((Object[]) paths);
+ // this.setItemCount(paths.length);
+ this.refresh();
+ }
+
+ private class MyCP implements ITreeContentProvider {
+ private static final long serialVersionUID = 9096550041395433128L;
+ private Object[] elements;
+
+ 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;
+ }
+
+ @Override
+ public Object[] getElements(Object inputElement) {
+ return elements;
+ }
+
+ @Override
+ public Object[] getChildren(Object parentElement) {
+ Path path = (Path) parentElement;
+ if (!Files.isDirectory(path))
+ return null;
+ else
+ return FsUiUtils.getChildren(path, "*", showHiddenItems, showDirectoryFirst, orderingProperty, false);
+ }
+
+ @Override
+ public Object getParent(Object element) {
+ Path path = (Path) element;
+ return path.getParent();
+ }
+
+ @Override
+ public boolean hasChildren(Object element) {
+ Path path = (Path) element;
+ try {
+ if (!Files.isDirectory(path))
+ return false;
+ else
+ try (DirectoryStream<Path> children = Files.newDirectoryStream(path, "*")) {
+ return children.iterator().hasNext();
+ }
+ } catch (IOException e) {
+ throw new FsUiException("Unable to check child existence on " + path, e);
+ }
+ }
+
+ }
+}
--- /dev/null
+package org.argeo.eclipse.ui.fs;
+
+/** Centralize constants used by the Nio FS UI parts */
+public interface FsUiConstants {
+
+ // TODO use standard properties
+ String PROPERTY_NAME = "name";
+ String PROPERTY_SIZE = "size";
+ String PROPERTY_LAST_MODIFIED = "last-modified";
+ String PROPERTY_TYPE = "type";
+}
--- /dev/null
+package org.argeo.eclipse.ui.fs;
+
+/** Files specific exception */
+public class FsUiException extends RuntimeException {
+ private static final long serialVersionUID = 1L;
+
+ public FsUiException(String message) {
+ super(message);
+ }
+
+ public FsUiException(String message, Throwable e) {
+ super(message, e);
+ }
+}
--- /dev/null
+package org.argeo.eclipse.ui.fs;
+
+import java.io.IOException;
+import java.nio.file.DirectoryIteratorException;
+import java.nio.file.DirectoryStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/** Centralise additional utilitary methods to manage Java7 NIO files */
+public class FsUiUtils {
+
+ /**
+ * thanks to
+ * http://programming.guide/java/formatting-byte-size-to-human-readable-format.html
+ */
+ public static String humanReadableByteCount(long bytes, boolean si) {
+ int unit = si ? 1000 : 1024;
+ if (bytes < unit)
+ return bytes + " B";
+ int exp = (int) (Math.log(bytes) / Math.log(unit));
+ String pre = (si ? "kMGTPE" : "KMGTPE").charAt(exp - 1) + (si ? "" : "i");
+ return String.format("%.1f %sB", bytes / Math.pow(unit, exp), pre);
+ }
+
+ public static Path[] getChildren(Path parent, String filter, boolean showHiddenItems, boolean folderFirst,
+ String orderProperty, boolean reverseOrder) {
+ if (!Files.isDirectory(parent))
+ return null;
+ List<Pair> pairs = new ArrayList<>();
+ try (DirectoryStream<Path> stream = Files.newDirectoryStream(parent, filter)) {
+ loop: for (Path entry : stream) {
+ if (!showHiddenItems)
+ if (Files.isHidden(entry))
+ continue loop;
+ switch (orderProperty) {
+ case FsUiConstants.PROPERTY_SIZE:
+ if (folderFirst)
+ pairs.add(new LPair(entry, Files.size(entry), Files.isDirectory(entry)));
+ else
+ pairs.add(new LPair(entry, Files.size(entry)));
+ break;
+ case FsUiConstants.PROPERTY_LAST_MODIFIED:
+ if (folderFirst)
+ pairs.add(new LPair(entry, Files.getLastModifiedTime(entry).toMillis(),
+ Files.isDirectory(entry)));
+ else
+ pairs.add(new LPair(entry, Files.getLastModifiedTime(entry).toMillis()));
+ break;
+ case FsUiConstants.PROPERTY_NAME:
+ if (folderFirst)
+ pairs.add(new SPair(entry, entry.getFileName().toString(), Files.isDirectory(entry)));
+ else
+ pairs.add(new SPair(entry, entry.getFileName().toString()));
+ break;
+ default:
+ throw new FsUiException("Unable to prepare sort for property " + orderProperty);
+ }
+ }
+ Pair[] rows = pairs.toArray(new Pair[0]);
+ Arrays.sort(rows);
+ Path[] results = new Path[rows.length];
+ if (reverseOrder) {
+ int j = rows.length - 1;
+ for (int i = 0; i < rows.length; i++)
+ results[i] = rows[j - i].p;
+ } else
+ for (int i = 0; i < rows.length; i++)
+ results[i] = rows[i].p;
+ return results;
+ } catch (IOException | DirectoryIteratorException e) {
+ throw new FsUiException("Unable to filter " + parent + " children with filter " + filter, e);
+ }
+ }
+
+ static abstract class Pair implements Comparable<Object> {
+ Path p;
+ Boolean i;
+ };
+
+ static class LPair extends Pair {
+ long v;
+
+ public LPair(Path path, long propValue) {
+ p = path;
+ v = propValue;
+ }
+
+ public LPair(Path path, long propValue, boolean isDir) {
+ p = path;
+ v = propValue;
+ i = isDir;
+ }
+
+ public int compareTo(Object o) {
+ if (i != null) {
+ Boolean j = ((LPair) o).i;
+ if (i.booleanValue() != j.booleanValue())
+ return i.booleanValue() ? -1 : 1;
+ }
+ long u = ((LPair) o).v;
+ return v < u ? -1 : v == u ? 0 : 1;
+ }
+ };
+
+ static class SPair extends Pair {
+ String v;
+
+ public SPair(Path path, String propValue) {
+ p = path;
+ v = propValue;
+ }
+
+ public SPair(Path path, String propValue, boolean isDir) {
+ p = path;
+ v = propValue;
+ i = isDir;
+ }
+
+ public int compareTo(Object o) {
+ if (i != null) {
+ Boolean j = ((SPair) o).i;
+ if (i.booleanValue() != j.booleanValue())
+ return i.booleanValue() ? -1 : 1;
+ }
+ String u = ((SPair) o).v;
+ return v.compareTo(u);
+ }
+ };
+}
--- /dev/null
+package org.argeo.eclipse.ui.fs;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.attribute.FileTime;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import org.argeo.eclipse.ui.EclipseUiUtils;
+import org.eclipse.jface.viewers.ColumnLabelProvider;
+
+/** Expect a {@link Path} as input element */
+public class NioFileLabelProvider extends ColumnLabelProvider {
+ private final static FileTime EPOCH = FileTime.fromMillis(0);
+ private static final long serialVersionUID = 2160026425187796930L;
+ private final String propName;
+ private DateFormat dateFormat = new SimpleDateFormat("YYYY-MM-dd HH:mm");
+
+ // TODO use new formatting
+ // DateTimeFormatter formatter =
+ // DateTimeFormatter.ofLocalizedDateTime( FormatStyle.SHORT )
+ // .withLocale( Locale.UK )
+ // .withZone( ZoneId.systemDefault() );
+ public NioFileLabelProvider(String propName) {
+ this.propName = propName;
+ }
+
+ @Override
+ public String getText(Object element) {
+ try {
+ Path path;
+ if (element instanceof ParentDir) {
+// switch (propName) {
+// case FsUiConstants.PROPERTY_SIZE:
+// return "-";
+// case FsUiConstants.PROPERTY_LAST_MODIFIED:
+// return "-";
+// // return Files.getLastModifiedTime(((ParentDir) element).getPath()).toString();
+// case FsUiConstants.PROPERTY_TYPE:
+// return "Folder";
+// }
+ path = ((ParentDir) element).getPath();
+ } else
+ path = (Path) element;
+ switch (propName) {
+ case FsUiConstants.PROPERTY_SIZE:
+ if (Files.isDirectory(path))
+ return "-";
+ else
+ return FsUiUtils.humanReadableByteCount(Files.size(path), false);
+ case FsUiConstants.PROPERTY_LAST_MODIFIED:
+ if (Files.isDirectory(path))
+ return "-";
+ FileTime time = Files.getLastModifiedTime(path);
+ if (time.equals(EPOCH))
+ return "-";
+ else
+ return dateFormat.format(new Date(time.toMillis()));
+ case FsUiConstants.PROPERTY_TYPE:
+ if (Files.isDirectory(path))
+ return "Folder";
+ else {
+ String mimeType = Files.probeContentType(path);
+ if (EclipseUiUtils.isEmpty(mimeType))
+ return "Unknown";
+ else
+ return mimeType;
+ }
+ default:
+ throw new IllegalArgumentException("Unsupported property " + propName);
+ }
+ } catch (IOException ioe) {
+ throw new FsUiException("Cannot get property " + propName + " on " + element);
+ }
+ }
+}
--- /dev/null
+package org.argeo.eclipse.ui.fs;
+
+import java.nio.file.Path;
+
+/** A parent directory (..) reference. */
+public class ParentDir {
+ Path path;
+
+ public ParentDir(Path path) {
+ super();
+ this.path = path;
+ }
+
+ public Path getPath() {
+ return path;
+ }
+
+ @Override
+ public int hashCode() {
+ return path.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return "Parent dir " + path;
+ }
+
+}
--- /dev/null
+package org.argeo.eclipse.ui.fs;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.argeo.api.cms.CmsLog;
+import org.argeo.eclipse.ui.ColumnDefinition;
+import org.argeo.eclipse.ui.EclipseUiUtils;
+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.Viewer;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.SashForm;
+import org.eclipse.swt.events.KeyEvent;
+import org.eclipse.swt.events.KeyListener;
+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.Table;
+
+/**
+ * Experimental UI upon Java 7 nio files api: SashForm layout with bookmarks on
+ * the left hand side and a simple table on the right hand side.
+ */
+public class SimpleFsBrowser extends Composite {
+ private final static CmsLog log = CmsLog.getLog(SimpleFsBrowser.class);
+ private static final long serialVersionUID = -40347919096946585L;
+
+ private Path currSelected;
+ private FsTableViewer bookmarksViewer;
+ private FsTableViewer directoryDisplayViewer;
+
+ public SimpleFsBrowser(Composite parent, int style) {
+ super(parent, style);
+ createContent(this);
+ // parent.layout(true, true);
+ }
+
+ public Viewer getViewer() {
+ return directoryDisplayViewer;
+ }
+
+ private void createContent(Composite parent) {
+ parent.setLayout(EclipseUiUtils.noSpaceGridLayout());
+
+ SashForm form = new SashForm(parent, SWT.HORIZONTAL);
+ Composite leftCmp = new Composite(form, SWT.NONE);
+ populateBookmarks(leftCmp);
+
+ Composite rightCmp = new Composite(form, SWT.BORDER);
+ populateDisplay(rightCmp);
+ form.setLayoutData(EclipseUiUtils.fillAll());
+ form.setWeights(new int[] { 1, 3 });
+ }
+
+ public void setInput(Path... paths) {
+ bookmarksViewer.setPathsInput(paths);
+ bookmarksViewer.getTable().getParent().layout(true, true);
+ }
+
+ private void populateBookmarks(final Composite parent) {
+ // GridLayout layout = EclipseUiUtils.noSpaceGridLayout();
+ // layout.verticalSpacing = 5;
+ parent.setLayout(new GridLayout());
+
+ ISelectionChangedListener selList = new MySelectionChangedListener();
+
+ appendTitle(parent, "My bookmarks");
+ bookmarksViewer = new FsTableViewer(parent, SWT.MULTI | SWT.NO_SCROLL);
+ Table table = bookmarksViewer.configureDefaultSingleColumnTable(500);
+ GridData gd = EclipseUiUtils.fillWidth();
+ gd.horizontalIndent = 10;
+ table.setLayoutData(gd);
+ bookmarksViewer.addSelectionChangedListener(selList);
+
+ appendTitle(parent, "Jcr + File");
+
+ FsTableViewer jcrFilesViewers = new FsTableViewer(parent, SWT.MULTI | SWT.NO_SCROLL);
+ table = jcrFilesViewers.configureDefaultSingleColumnTable(500);
+ gd = EclipseUiUtils.fillWidth();
+ gd.horizontalIndent = 10;
+ table.setLayoutData(gd);
+ jcrFilesViewers.addSelectionChangedListener(selList);
+
+ // FileSystemProvider fsProvider = new JackrabbitMemoryFsProvider();
+ // try {
+ // Path testPath = fsProvider.getPath(new URI("jcr+memory:/"));
+ // jcrFilesViewers.setPathsInput(testPath);
+ // } catch (URISyntaxException e) {
+ // // TODO Auto-generated catch block
+ // e.printStackTrace();
+ // }
+ }
+
+ private Label appendTitle(Composite parent, String value) {
+ Label titleLbl = new Label(parent, SWT.NONE);
+ titleLbl.setText(value);
+ titleLbl.setFont(EclipseUiUtils.getBoldFont(parent));
+ GridData gd = EclipseUiUtils.fillWidth();
+ gd.horizontalIndent = 5;
+ gd.verticalIndent = 5;
+ titleLbl.setLayoutData(gd);
+ return titleLbl;
+ }
+
+ private class MySelectionChangedListener implements ISelectionChangedListener {
+ @Override
+ public void selectionChanged(SelectionChangedEvent event) {
+ IStructuredSelection selection = (IStructuredSelection) bookmarksViewer.getSelection();
+ if (selection.isEmpty())
+ return;
+ else {
+ Path newSelected = (Path) selection.getFirstElement();
+ if (newSelected.equals(currSelected))
+ return;
+ currSelected = newSelected;
+ directoryDisplayViewer.setInput(currSelected, "*");
+ }
+ }
+ }
+
+ private void populateDisplay(final Composite parent) {
+ parent.setLayout(EclipseUiUtils.noSpaceGridLayout());
+ directoryDisplayViewer = new FsTableViewer(parent, SWT.MULTI);
+ List<ColumnDefinition> colDefs = new ArrayList<>();
+ colDefs.add(new ColumnDefinition(new FileIconNameLabelProvider(), "Name", 200));
+ colDefs.add(new ColumnDefinition(new NioFileLabelProvider(FsUiConstants.PROPERTY_SIZE), "Size", 100));
+ colDefs.add(new ColumnDefinition(new NioFileLabelProvider(FsUiConstants.PROPERTY_TYPE), "Type", 250));
+ colDefs.add(new ColumnDefinition(new NioFileLabelProvider(FsUiConstants.PROPERTY_LAST_MODIFIED),
+ "Last modified", 200));
+ Table table = directoryDisplayViewer.configureDefaultTable(colDefs);
+ table.setLayoutData(EclipseUiUtils.fillAll());
+
+ table.addKeyListener(new KeyListener() {
+ private static final long serialVersionUID = -8083424284436715709L;
+
+ @Override
+ public void keyReleased(KeyEvent e) {
+ }
+
+ @Override
+ public void keyPressed(KeyEvent e) {
+ log.debug("Key event received: " + e.keyCode);
+ IStructuredSelection selection = (IStructuredSelection) directoryDisplayViewer.getSelection();
+ Path selected = null;
+ if (!selection.isEmpty())
+ selected = ((Path) selection.getFirstElement());
+ if (e.keyCode == SWT.CR) {
+ if (!Files.isDirectory(selected))
+ return;
+ if (selected != null) {
+ currSelected = selected;
+ directoryDisplayViewer.setInput(currSelected, "*");
+ }
+ } else if (e.keyCode == SWT.BS) {
+ currSelected = currSelected.getParent();
+ directoryDisplayViewer.setInput(currSelected, "*");
+ directoryDisplayViewer.getTable().setFocus();
+ }
+ }
+ });
+
+// directoryDisplayViewer.addDoubleClickListener(new IDoubleClickListener() {
+// @Override
+// public void doubleClick(DoubleClickEvent event) {
+// IStructuredSelection selection = (IStructuredSelection) directoryDisplayViewer.getSelection();
+// Path selected = null;
+// if (!selection.isEmpty()) {
+// Object obj = selection.getFirstElement();
+// if (obj instanceof Path)
+// selected = (Path) obj;
+// else if (obj instanceof ParentDir)
+// selected = ((ParentDir) obj).getPath();
+// }
+// if (selected != null) {
+// if (!Files.isDirectory(selected))
+// return;
+// currSelected = selected;
+// directoryDisplayViewer.setInput(currSelected, "*");
+// }
+// }
+// });
+
+ directoryDisplayViewer.addDoubleClickListener(new IDoubleClickListener() {
+ @Override
+ public void doubleClick(DoubleClickEvent event) {
+ IStructuredSelection selection = (IStructuredSelection) directoryDisplayViewer.getSelection();
+ Path selected = null;
+ if (!selection.isEmpty()) {
+ Object obj = selection.getFirstElement();
+ if (obj instanceof Path)
+ selected = (Path) obj;
+ else if (obj instanceof ParentDir)
+ selected = ((ParentDir) obj).getPath();
+ }
+ if (selected != null) {
+ if (!Files.isDirectory(selected))
+ return;
+ currSelected = selected;
+ directoryDisplayViewer.setInput(currSelected, "*");
+ }
+ }
+ });
+ }
+}
--- /dev/null
+package org.argeo.eclipse.ui.fs;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.argeo.api.cms.CmsLog;
+import org.argeo.eclipse.ui.ColumnDefinition;
+import org.argeo.eclipse.ui.EclipseUiUtils;
+import org.eclipse.jface.viewers.ISelectionChangedListener;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.viewers.SelectionChangedEvent;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.SashForm;
+import org.eclipse.swt.events.KeyEvent;
+import org.eclipse.swt.events.KeyListener;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Table;
+import org.eclipse.swt.widgets.Tree;
+
+/** A simple Java 7 nio files browser with a tree */
+public class SimpleFsTreeBrowser extends Composite {
+ private final static CmsLog log = CmsLog.getLog(SimpleFsTreeBrowser.class);
+ private static final long serialVersionUID = -40347919096946585L;
+
+ private Path currSelected;
+ private FsTreeViewer treeViewer;
+ private FsTableViewer directoryDisplayViewer;
+
+ public SimpleFsTreeBrowser(Composite parent, int style) {
+ super(parent, style);
+ createContent(this);
+ // parent.layout(true, true);
+ }
+
+ private void createContent(Composite parent) {
+ parent.setLayout(EclipseUiUtils.noSpaceGridLayout());
+ SashForm form = new SashForm(parent, SWT.HORIZONTAL);
+ Composite child1 = new Composite(form, SWT.NONE);
+ populateTree(child1);
+ Composite child2 = new Composite(form, SWT.BORDER);
+ populateDisplay(child2);
+ form.setLayoutData(EclipseUiUtils.fillAll());
+ form.setWeights(new int[] { 1, 3 });
+ }
+
+ public void setInput(Path... paths) {
+ treeViewer.setPathsInput(paths);
+ treeViewer.getControl().getParent().layout(true, true);
+ }
+
+ private void populateTree(final Composite parent) {
+ // GridLayout layout = EclipseUiUtils.noSpaceGridLayout();
+ // layout.verticalSpacing = 5;
+ parent.setLayout(new GridLayout());
+
+ ISelectionChangedListener selList = new MySelectionChangedListener();
+
+ treeViewer = new FsTreeViewer(parent, SWT.MULTI);
+ Tree tree = treeViewer.configureDefaultSingleColumnTable(500);
+ GridData gd = EclipseUiUtils.fillAll();
+ // gd.horizontalIndent = 10;
+ tree.setLayoutData(gd);
+ treeViewer.addSelectionChangedListener(selList);
+ }
+
+ private class MySelectionChangedListener implements ISelectionChangedListener {
+ @Override
+ public void selectionChanged(SelectionChangedEvent event) {
+ IStructuredSelection selection = (IStructuredSelection) treeViewer.getSelection();
+ if (selection.isEmpty())
+ return;
+ else {
+ Path newSelected = (Path) selection.getFirstElement();
+ if (newSelected.equals(currSelected))
+ return;
+ currSelected = newSelected;
+ if (Files.isDirectory(currSelected))
+ directoryDisplayViewer.setInput(currSelected, "*");
+ }
+ }
+ }
+
+ private void populateDisplay(final Composite parent) {
+ parent.setLayout(EclipseUiUtils.noSpaceGridLayout());
+ directoryDisplayViewer = new FsTableViewer(parent, SWT.MULTI);
+ List<ColumnDefinition> colDefs = new ArrayList<>();
+ colDefs.add(new ColumnDefinition(new FileIconNameLabelProvider(), "Name", 200, 200));
+ colDefs.add(new ColumnDefinition(new NioFileLabelProvider(FsUiConstants.PROPERTY_SIZE), "Size", 100, 100));
+ colDefs.add(new ColumnDefinition(new NioFileLabelProvider(FsUiConstants.PROPERTY_TYPE), "Type", 300, 300));
+ colDefs.add(new ColumnDefinition(new NioFileLabelProvider(FsUiConstants.PROPERTY_LAST_MODIFIED),
+ "Last modified", 100, 100));
+ Table table = directoryDisplayViewer.configureDefaultTable(colDefs);
+ table.setLayoutData(EclipseUiUtils.fillAll());
+
+ table.addKeyListener(new KeyListener() {
+ private static final long serialVersionUID = -8083424284436715709L;
+
+ @Override
+ public void keyReleased(KeyEvent e) {
+ }
+
+ @Override
+ public void keyPressed(KeyEvent e) {
+ log.debug("Key event received: " + e.keyCode);
+ IStructuredSelection selection = (IStructuredSelection) directoryDisplayViewer.getSelection();
+ Path selected = null;
+ if (!selection.isEmpty())
+ selected = ((Path) selection.getFirstElement());
+ if (e.keyCode == SWT.CR) {
+ if (!Files.isDirectory(selected))
+ return;
+ if (selected != null) {
+ currSelected = selected;
+ directoryDisplayViewer.setInput(currSelected, "*");
+ }
+ } else if (e.keyCode == SWT.BS) {
+ currSelected = currSelected.getParent();
+ directoryDisplayViewer.setInput(currSelected, "*");
+ directoryDisplayViewer.getTable().setFocus();
+ }
+ }
+ });
+ }
+}
--- /dev/null
+/** Generic SWT/JFace file system utilities. */
+package org.argeo.eclipse.ui.fs;
\ No newline at end of file
--- /dev/null
+/** Generic SWT/JFace utilities. */
+package org.argeo.eclipse.ui;
\ No newline at end of file
--- /dev/null
+package org.argeo.eclipse.ui.parts;
+
+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.argeo.eclipse.ui.util.ViewerUtils;
+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
+/** Generic SWT/JFace composites. */
+package org.argeo.eclipse.ui.parts;
\ No newline at end of file
--- /dev/null
+package org.argeo.eclipse.ui.util;
+
+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
+/** Generic SWT/JFace JCR helpers. */
+package org.argeo.eclipse.ui.util;
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.argeo.commons</groupId>
+ <artifactId>argeo-commons</artifactId>
+ <version>2.3-SNAPSHOT</version>
+ <relativePath>..</relativePath>
+ </parent>
+ <groupId>org.argeo.commons</groupId>
+ <artifactId>eclipse</artifactId>
+ <name>Eclipse Specific</name>
+ <packaging>pom</packaging>
+ <modules>
+ <module>org.argeo.cms.servlet</module>
+ <module>org.argeo.cms.swt</module>
+ <module>org.argeo.cms.e4</module>
+ </modules>
+</project>
\ No newline at end of file
--- /dev/null
+-include: ../../cnf/maven.bnd
\ 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-11">
+ <attributes>
+ <attribute name="module" value="true"/>
+ </attributes>
+ </classpathentry>
+ <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.cms.jcr</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
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.annotation.inheritNullAnnotations=disabled
+org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=ignore
+org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jdt.annotation.NonNull
+org.eclipse.jdt.core.compiler.annotation.nonnull.secondary=
+org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jdt.annotation.NonNullByDefault
+org.eclipse.jdt.core.compiler.annotation.nonnullbydefault.secondary=
+org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable
+org.eclipse.jdt.core.compiler.annotation.nullable.secondary=
+org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled
+org.eclipse.jdt.core.compiler.problem.APILeak=warning
+org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning
+org.eclipse.jdt.core.compiler.problem.autoboxing=ignore
+org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning
+org.eclipse.jdt.core.compiler.problem.deadCode=warning
+org.eclipse.jdt.core.compiler.problem.deprecation=warning
+org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled
+org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled
+org.eclipse.jdt.core.compiler.problem.discouragedReference=warning
+org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore
+org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=ignore
+org.eclipse.jdt.core.compiler.problem.fallthroughCase=ignore
+org.eclipse.jdt.core.compiler.problem.fatalOptionalError=disabled
+org.eclipse.jdt.core.compiler.problem.fieldHiding=ignore
+org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning
+org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning
+org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
+org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning
+org.eclipse.jdt.core.compiler.problem.includeNullInfoFromAsserts=disabled
+org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning
+org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=warning
+org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore
+org.eclipse.jdt.core.compiler.problem.localVariableHiding=ignore
+org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning
+org.eclipse.jdt.core.compiler.problem.missingDefaultCase=ignore
+org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=ignore
+org.eclipse.jdt.core.compiler.problem.missingEnumCaseDespiteDefault=disabled
+org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=ignore
+org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=ignore
+org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled
+org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning
+org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore
+org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning
+org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning
+org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore
+org.eclipse.jdt.core.compiler.problem.nonnullParameterAnnotationDropped=warning
+org.eclipse.jdt.core.compiler.problem.nonnullTypeVariableFromLegacyInvocation=warning
+org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=error
+org.eclipse.jdt.core.compiler.problem.nullReference=warning
+org.eclipse.jdt.core.compiler.problem.nullSpecViolation=error
+org.eclipse.jdt.core.compiler.problem.nullUncheckedConversion=warning
+org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning
+org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore
+org.eclipse.jdt.core.compiler.problem.pessimisticNullAnalysisForFreeTypeVariables=warning
+org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=ignore
+org.eclipse.jdt.core.compiler.problem.potentialNullReference=ignore
+org.eclipse.jdt.core.compiler.problem.potentiallyUnclosedCloseable=ignore
+org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning
+org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning
+org.eclipse.jdt.core.compiler.problem.redundantNullCheck=ignore
+org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=ignore
+org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=ignore
+org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore
+org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore
+org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled
+org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning
+org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled
+org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled
+org.eclipse.jdt.core.compiler.problem.syntacticNullAnalysisForFields=disabled
+org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore
+org.eclipse.jdt.core.compiler.problem.terminalDeprecation=warning
+org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning
+org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=enabled
+org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning
+org.eclipse.jdt.core.compiler.problem.unclosedCloseable=warning
+org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore
+org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning
+org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentType=warning
+org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentTypeStrict=disabled
+org.eclipse.jdt.core.compiler.problem.unlikelyEqualsArgumentType=info
+org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore
+org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=ignore
+org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore
+org.eclipse.jdt.core.compiler.problem.unstableAutoModuleName=warning
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=ignore
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled
+org.eclipse.jdt.core.compiler.problem.unusedExceptionParameter=ignore
+org.eclipse.jdt.core.compiler.problem.unusedImport=warning
+org.eclipse.jdt.core.compiler.problem.unusedLabel=warning
+org.eclipse.jdt.core.compiler.problem.unusedLocal=warning
+org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=ignore
+org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore
+org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled
+org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled
+org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled
+org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning
+org.eclipse.jdt.core.compiler.problem.unusedTypeParameter=ignore
+org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning
+org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="init" deactivate="destroy" name="org.argeo.cms.dataServletContext">
+ <implementation class="org.argeo.cms.jcr.internal.servlet.DataServletContext"/>
+ <service>
+ <provide interface="org.osgi.service.http.context.ServletContextHelper"/>
+ </service>
+ <property name="osgi.http.whiteboard.context.name" type="String" value="dataServletContext"/>
+ <property name="osgi.http.whiteboard.context.path" type="String" value="/data"/>
+</scr:component>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="org.argeo.cms.filesServlet">
+ <implementation class="org.argeo.cms.jcr.internal.servlet.CmsWebDavServlet"/>
+ <service>
+ <provide interface="javax.servlet.Servlet"/>
+ </service>
+ <property name="osgi.http.whiteboard.servlet.pattern" type="String" value="/*"/>
+ <property name="osgi.http.whiteboard.context.select" type="String" value="(osgi.http.whiteboard.context.name=filesServletContext)"/>
+ <property name="servlet.init.resource-config" type="String" value="/org/argeo/cms/jcr/internal/servlet/webdav-config.xml"/>
+ <property name="servlet.init.resource-path-prefix" type="String" value="/files"/>
+ <reference bind="setRepository" cardinality="1..1" interface="javax.jcr.Repository" policy="static" target="(cn=ego)"/>
+</scr:component>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="init" deactivate="destroy" name="org.argeo.cms.filesServletContext">
+ <implementation class="org.argeo.cms.jcr.internal.servlet.JcrServletContext"/>
+ <service>
+ <provide interface="org.osgi.service.http.context.ServletContextHelper"/>
+ </service>
+ <property name="osgi.http.whiteboard.context.name" type="String" value="filesServletContext"/>
+ <property name="osgi.http.whiteboard.context.path" type="String" value="/files"/>
+</scr:component>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="start" deactivate="stop" name="JCR Deployment">
+ <implementation class="org.argeo.cms.jcr.internal.CmsJcrDeployment"/>
+ <reference bind="setCmsDeployment" cardinality="1..1" interface="org.argeo.api.cms.CmsDeployment" policy="static"/>
+</scr:component>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="JCR FS Provider">
+ <implementation class="org.argeo.cms.jcr.internal.CmsJcrFsProvider"/>
+ <property name="service.pid" type="String" value="org.argeo.api.fsProvider"/>
+ <service>
+ <provide interface="java.nio.file.spi.FileSystemProvider"/>
+ </service>
+ <reference bind="setRepositoryFactory" cardinality="1..1" interface="javax.jcr.RepositoryFactory" name="RepositoryFactory" policy="static"/>
+ <reference bind="setRepository" cardinality="1..1" interface="javax.jcr.Repository" name="Repository" policy="static"/>
+</scr:component>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="JCR Repository Factory">
+ <implementation class="org.argeo.cms.jcr.internal.JcrRepositoryFactory"/>
+ <service>
+ <provide interface="javax.jcr.RepositoryFactory"/>
+ </service>
+ <reference cardinality="1..1" interface="org.argeo.api.cms.CmsState" name="CmsState" policy="static"/>
+</scr:component>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="init" deactivate="destroy" name="org.argeo.cms.jcrServletContext">
+ <implementation class="org.argeo.cms.jcr.internal.servlet.JcrServletContext"/>
+ <service>
+ <provide interface="org.osgi.service.http.context.ServletContextHelper"/>
+ </service>
+ <property name="osgi.http.whiteboard.context.name" type="String" value="jcrServletContext"/>
+ <property name="osgi.http.whiteboard.context.path" type="String" value="/jcr"/>
+</scr:component>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="init" deactivate="destroy" name="Jackrabbit Repository Contexts Factory">
+ <implementation class="org.argeo.cms.jcr.internal.RepositoryContextsFactory"/>
+ <property name="service.pid" type="String" value="org.argeo.api.repos"/>
+ <service>
+ <provide interface="org.osgi.service.cm.ManagedServiceFactory"/>
+ </service>
+ <reference cardinality="1..1" interface="org.argeo.api.cms.CmsState" name="CmsState" policy="static"/>
+</scr:component>
--- /dev/null
+Bundle-Activator: org.argeo.cms.jcr.internal.osgi.CmsJcrActivator
+
+Provide-Capability:\
+cms.datamodel; name=jcrx; cnd=/org/argeo/jcr/jcrx.cnd; abstract=true,\
+cms.datamodel; name=argeo; cnd=/org/argeo/cms/jcr/argeo.cnd; abstract=true,\
+cms.datamodel;name=ldap; cnd=/org/argeo/cms/jcr/ldap.cnd; abstract=true,\
+osgi.service;objectClass="javax.jcr.Repository"
+
+Import-Package:\
+org.argeo.cms.servlet,\
+javax.jcr.security,\
+org.h2;resolution:=optional,\
+org.postgresql;version="[42,43)";resolution:=optional,\
+org.apache.jackrabbit.webdav.server,\
+org.apache.jackrabbit.webdav.jcr,\
+org.apache.commons.httpclient.cookie;resolution:=optional,\
+org.osgi.framework.namespace;version=0.0.0,\
+org.osgi.*;version=0.0.0,\
+org.osgi.service.http.whiteboard,\
+org.apache.jackrabbit.api,\
+org.apache.jackrabbit.commons,\
+org.apache.jackrabbit.spi,\
+org.apache.jackrabbit.spi2dav,\
+org.apache.jackrabbit.spi2davex,\
+org.apache.jackrabbit.webdav,\
+junit.*;resolution:=optional,\
+*
+
+Service-Component:\
+OSGI-INF/repositoryContextsFactory.xml,\
+OSGI-INF/jcrRepositoryFactory.xml,\
+OSGI-INF/jcrFsProvider.xml,\
+OSGI-INF/jcrDeployment.xml,\
+OSGI-INF/jcrServletContext.xml,\
+OSGI-INF/dataServletContext.xml,\
+OSGI-INF/filesServletContext.xml,\
+OSGI-INF/filesServlet.xml
--- /dev/null
+output.. = bin/
+bin.includes = META-INF/,\
+ .,\
+ OSGI-INF/jcrDeployment.xml,\
+ OSGI-INF/repositoryContextsFactory.xml,\
+ OSGI-INF/jcrRepositoryFactory.xml,\
+ OSGI-INF/jcrFsProvider.xml
+source.. = src/
+additional.bundles = org.apache.jackrabbit.core,\
+ javax.jcr,\
+ org.apache.jackrabbit.api,\
+ org.apache.jackrabbit.data,\
+ org.apache.jackrabbit.jcr.commons,\
+ org.apache.jackrabbit.spi,\
+ org.apache.jackrabbit.spi.commons,\
+ org.slf4j.api,\
+ org.apache.commons.collections,\
+ EDU.oswego.cs.dl.util.concurrent,\
+ org.apache.lucene,\
+ org.apache.tika.core,\
+ org.apache.commons.dbcp,\
+ org.apache.commons.pool,\
+ com.google.guava,\
+ org.apache.jackrabbit.jcr2spi,\
+ org.apache.jackrabbit.spi2dav,\
+ org.apache.httpcomponents.httpclient,\
+ org.apache.httpcomponents.httpcore,\
+ org.apache.tika.parsers
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.argeo.commons</groupId>
+ <artifactId>jcr</artifactId>
+ <version>2.3-SNAPSHOT</version>
+ <relativePath>..</relativePath>
+ </parent>
+ <artifactId>org.argeo.cms.jcr</artifactId>
+ <name>CMS JCR</name>
+ <dependencies>
+ <dependency>
+ <groupId>org.argeo.commons</groupId>
+ <artifactId>org.argeo.cms</artifactId>
+ <version>2.3-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>org.argeo.commons</groupId>
+ <artifactId>org.argeo.cms.servlet</artifactId>
+ <version>2.3-SNAPSHOT</version>
+ </dependency>
+ </dependencies>
+</project>
\ No newline at end of file
--- /dev/null
+package org.argeo.cms.fs;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.file.FileSystem;
+import java.nio.file.Path;
+import java.nio.file.spi.FileSystemProvider;
+
+import javax.jcr.NoSuchWorkspaceException;
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.query.Query;
+import javax.jcr.query.QueryManager;
+
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.jcr.Jcr;
+
+/** Utilities around documents. */
+public class CmsFsUtils {
+ // TODO make it more robust and configurable
+ private static String baseWorkspaceName = CmsConstants.SYS_WORKSPACE;
+
+ public static Node getNode(Repository repository, Path path) {
+ String workspaceName = path.getNameCount() == 0 ? baseWorkspaceName : path.getName(0).toString();
+ String jcrPath = '/' + path.subpath(1, path.getNameCount()).toString();
+ try {
+ Session newSession;
+ try {
+ newSession = repository.login(workspaceName);
+ } catch (NoSuchWorkspaceException e) {
+ // base workspace
+ newSession = repository.login(baseWorkspaceName);
+ jcrPath = path.toString();
+ }
+ return newSession.getNode(jcrPath);
+ } catch (RepositoryException e) {
+ throw new IllegalStateException("Cannot get node from path " + path, e);
+ }
+ }
+
+ public static NodeIterator getLastUpdatedDocuments(Session session) {
+ try {
+ String qStr = "//element(*, nt:file)";
+ qStr += " order by @jcr:lastModified descending";
+ QueryManager queryManager = session.getWorkspace().getQueryManager();
+ @SuppressWarnings("deprecation")
+ Query xpathQuery = queryManager.createQuery(qStr, Query.XPATH);
+ xpathQuery.setLimit(8);
+ NodeIterator nit = xpathQuery.execute().getNodes();
+ return nit;
+ } catch (RepositoryException e) {
+ throw new IllegalStateException("Unable to retrieve last updated documents", e);
+ }
+ }
+
+ public static Path getPath(FileSystemProvider nodeFileSystemProvider, URI uri) {
+ try {
+ FileSystem fileSystem = nodeFileSystemProvider.getFileSystem(uri);
+ if (fileSystem == null)
+ fileSystem = nodeFileSystemProvider.newFileSystem(uri, null);
+ String path = uri.getPath();
+ return fileSystem.getPath(path);
+ } catch (IOException e) {
+ throw new IllegalStateException("Unable to initialise file system for " + uri, e);
+ }
+ }
+
+ public static Path getPath(FileSystemProvider nodeFileSystemProvider, Node node) {
+ String workspaceName = Jcr.getWorkspaceName(node);
+ String fullPath = baseWorkspaceName.equals(workspaceName) ? Jcr.getPath(node)
+ : '/' + workspaceName + Jcr.getPath(node);
+ URI uri;
+ try {
+ uri = new URI(CmsConstants.SCHEME_NODE, null, fullPath, null);
+ } catch (URISyntaxException e) {
+ throw new IllegalArgumentException("Cannot interpret " + fullPath + " as an URI", e);
+ }
+ return getPath(nodeFileSystemProvider, uri);
+ }
+
+ /** Singleton. */
+ private CmsFsUtils() {
+ }
+}
--- /dev/null
+package org.argeo.cms.internal.jcr;
+
+import java.util.Properties;
+
+import org.apache.jackrabbit.core.config.BeanConfig;
+import org.apache.jackrabbit.core.config.ConfigurationException;
+import org.apache.jackrabbit.core.config.RepositoryConfigurationParser;
+import org.apache.jackrabbit.core.config.WorkspaceSecurityConfig;
+import org.apache.jackrabbit.core.util.db.ConnectionFactory;
+import org.w3c.dom.Element;
+
+/**
+ * A {@link RepositoryConfigurationParser} providing more flexibility with
+ * classloaders.
+ */
+@SuppressWarnings("restriction")
+class CustomRepositoryConfigurationParser extends RepositoryConfigurationParser {
+ private ClassLoader classLoader = null;
+
+ public CustomRepositoryConfigurationParser(Properties variables) {
+ super(variables);
+ }
+
+ public CustomRepositoryConfigurationParser(Properties variables, ConnectionFactory connectionFactory) {
+ super(variables, connectionFactory);
+ }
+
+ @Override
+ protected RepositoryConfigurationParser createSubParser(Properties variables) {
+ Properties props = new Properties(getVariables());
+ props.putAll(variables);
+ CustomRepositoryConfigurationParser subParser = new CustomRepositoryConfigurationParser(props,
+ connectionFactory);
+ subParser.setClassLoader(classLoader);
+ return subParser;
+ }
+
+ @Override
+ public WorkspaceSecurityConfig parseWorkspaceSecurityConfig(Element parent) throws ConfigurationException {
+ WorkspaceSecurityConfig workspaceSecurityConfig = super.parseWorkspaceSecurityConfig(parent);
+ workspaceSecurityConfig.getAccessControlProviderConfig().setClassLoader(classLoader);
+ return workspaceSecurityConfig;
+ }
+
+ @Override
+ protected BeanConfig parseBeanConfig(Element parent, String name) throws ConfigurationException {
+ BeanConfig beanConfig = super.parseBeanConfig(parent, name);
+ if (beanConfig.getClassName().startsWith("org.argeo")) {
+ beanConfig.setClassLoader(classLoader);
+ }
+ return beanConfig;
+ }
+
+ public void setClassLoader(ClassLoader classLoader) {
+ this.classLoader = classLoader;
+ }
+
+}
--- /dev/null
+package org.argeo.cms.internal.jcr;
+
+/** Pre-defined Jackrabbit repository configurations. */
+enum JackrabbitType {
+ /** Local file system */
+ localfs,
+ /** Embedded Java H2 database */
+ h2,
+ /** Embedded Java H2 database in PostgreSQL compatibility mode */
+ h2_postgresql,
+ /** PostgreSQL */
+ postgresql,
+ /** PostgreSQL with datastore */
+ postgresql_ds,
+ /** PostgreSQL with cluster */
+ postgresql_cluster,
+ /** PostgreSQL with cluster and datastore */
+ postgresql_cluster_ds,
+ /** Memory */
+ memory;
+}
--- /dev/null
+package org.argeo.cms.internal.jcr;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Dictionary;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Map;
+
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.RepositoryFactory;
+
+import org.argeo.api.cms.CmsDeployment;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.jackrabbit.client.ClientDavexRepositoryFactory;
+import org.argeo.jcr.JcrException;
+import org.argeo.util.naming.LdapAttrs;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.FrameworkUtil;
+
+/** JCR specific init utilities. */
+public class JcrInitUtils {
+ private final static CmsLog log = CmsLog.getLog(JcrInitUtils.class);
+ private final static BundleContext bundleContext = FrameworkUtil.getBundle(JcrInitUtils.class).getBundleContext();
+
+ public static void addToDeployment(CmsDeployment nodeDeployment) {
+ // node repository
+// Dictionary<String, Object> provided = null;
+ Dictionary<String, Object> provided = nodeDeployment.getProps(CmsConstants.NODE_REPOS_FACTORY_PID,
+ CmsConstants.NODE);
+ Dictionary<String, Object> nodeConfig = JcrInitUtils.getNodeRepositoryConfig(provided);
+ // node repository is mandatory
+ nodeDeployment.addFactoryDeployConfig(CmsConstants.NODE_REPOS_FACTORY_PID, nodeConfig);
+
+ // additional repositories
+// dataModels: for (DataModels.DataModel dataModel : dataModels.getNonAbstractDataModels()) {
+// if (NodeConstants.NODE_REPOSITORY.equals(dataModel.getName()))
+// continue dataModels;
+// Dictionary<String, Object> config = JcrInitUtils.getRepositoryConfig(dataModel.getName(),
+// getProps(NodeConstants.NODE_REPOS_FACTORY_PID, dataModel.getName()));
+// if (config.size() != 0)
+// putFactoryDeployConfig(NodeConstants.NODE_REPOS_FACTORY_PID, config);
+// }
+
+ }
+
+ /** Override the provided config with the framework properties */
+ public static Dictionary<String, Object> getNodeRepositoryConfig(Dictionary<String, Object> provided) {
+ Dictionary<String, Object> props = provided != null ? provided : new Hashtable<String, Object>();
+ for (RepoConf repoConf : RepoConf.values()) {
+ Object value = getFrameworkProp(CmsConstants.NODE_REPO_PROP_PREFIX + repoConf.name());
+ if (value != null) {
+ props.put(repoConf.name(), value);
+ if (log.isDebugEnabled())
+ log.debug("Set node repo configuration " + repoConf.name() + " to " + value);
+ }
+ }
+ props.put(CmsConstants.CN, CmsConstants.NODE_REPOSITORY);
+ return props;
+ }
+
+ public static Dictionary<String, Object> getRepositoryConfig(String dataModelName,
+ Dictionary<String, Object> provided) {
+ if (dataModelName.equals(CmsConstants.NODE_REPOSITORY) || dataModelName.equals(CmsConstants.EGO_REPOSITORY))
+ throw new IllegalArgumentException("Data model '" + dataModelName + "' is reserved.");
+ Dictionary<String, Object> props = provided != null ? provided : new Hashtable<String, Object>();
+ for (RepoConf repoConf : RepoConf.values()) {
+ Object value = getFrameworkProp(
+ CmsConstants.NODE_REPOS_PROP_PREFIX + dataModelName + '.' + repoConf.name());
+ if (value != null) {
+ props.put(repoConf.name(), value);
+ if (log.isDebugEnabled())
+ log.debug("Set " + dataModelName + " repo configuration " + repoConf.name() + " to " + value);
+ }
+ }
+ if (props.size() != 0)
+ props.put(CmsConstants.CN, dataModelName);
+ return props;
+ }
+
+ private static void registerRemoteInit(String uri) {
+ try {
+ Repository repository = createRemoteRepository(new URI(uri));
+ Hashtable<String, Object> properties = new Hashtable<>();
+ properties.put(CmsConstants.CN, CmsConstants.NODE_INIT);
+ properties.put(LdapAttrs.labeledURI.name(), uri);
+ properties.put(Constants.SERVICE_RANKING, -1000);
+ bundleContext.registerService(Repository.class, repository, properties);
+ } catch (RepositoryException e) {
+ throw new JcrException(e);
+ } catch (URISyntaxException e) {
+ throw new IllegalArgumentException(e);
+ }
+ }
+
+ private static Repository createRemoteRepository(URI uri) throws RepositoryException {
+ RepositoryFactory repositoryFactory = new ClientDavexRepositoryFactory();
+ Map<String, String> params = new HashMap<String, String>();
+ params.put(ClientDavexRepositoryFactory.JACKRABBIT_DAVEX_URI, uri.toString());
+ // TODO make it configurable
+ params.put(ClientDavexRepositoryFactory.JACKRABBIT_REMOTE_DEFAULT_WORKSPACE, CmsConstants.SYS_WORKSPACE);
+ return repositoryFactory.getRepository(params);
+ }
+
+ private static String getFrameworkProp(String key, String def) {
+ String value;
+ if (bundleContext != null)
+ value = bundleContext.getProperty(key);
+ else
+ value = System.getProperty(key);
+ if (value == null)
+ return def;
+ return value;
+ }
+
+ private static String getFrameworkProp(String key) {
+ return getFrameworkProp(key, null);
+ }
+
+}
--- /dev/null
+package org.argeo.cms.internal.jcr;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+
+import org.apache.jackrabbit.core.data.DataIdentifier;
+import org.apache.jackrabbit.core.data.DataRecord;
+import org.apache.jackrabbit.core.data.DataStoreException;
+import org.apache.jackrabbit.core.data.FileDataStore;
+
+/**
+ * <b>experimental</b> Duplicate added entries in another directory (typically a
+ * remote mount).
+ */
+@SuppressWarnings("restriction")
+public class LocalFsDataStore extends FileDataStore {
+ String redundantPath;
+ FileDataStore redundantStore;
+
+ @Override
+ public void init(String homeDir) {
+ // init primary first
+ super.init(homeDir);
+
+ if (redundantPath != null) {
+ // redundant directory must be created first
+ // TODO implement some polling?
+ if (Files.exists(Paths.get(redundantPath))) {
+ redundantStore = new FileDataStore();
+ redundantStore.setPath(redundantPath);
+ redundantStore.init(homeDir);
+ }
+ }
+ }
+
+ @Override
+ public DataRecord addRecord(InputStream input) throws DataStoreException {
+ DataRecord dataRecord = super.addRecord(input);
+ syncRedundantRecord(dataRecord);
+ return dataRecord;
+ }
+
+ @Override
+ public DataRecord getRecord(DataIdentifier identifier) throws DataStoreException {
+ DataRecord dataRecord = super.getRecord(identifier);
+ syncRedundantRecord(dataRecord);
+ return dataRecord;
+ }
+
+ protected void syncRedundantRecord(DataRecord dataRecord) throws DataStoreException {
+ if (redundantStore == null)
+ return;
+ if (redundantStore.getRecordIfStored(dataRecord.getIdentifier()) == null) {
+ try (InputStream redundant = dataRecord.getStream()) {
+ redundantStore.addRecord(redundant);
+ } catch (IOException e) {
+ throw new DataStoreException("Cannot add redundant record.", e);
+ }
+ }
+ }
+
+ public void setRedundantPath(String redundantPath) {
+ this.redundantPath = redundantPath;
+ }
+
+}
--- /dev/null
+package org.argeo.cms.internal.jcr;
+
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.osgi.metatype.EnumAD;
+import org.argeo.osgi.metatype.EnumOCD;
+
+/** JCR repository configuration */
+public enum RepoConf implements EnumAD {
+ /** Repository type */
+ type("h2"),
+ /** Default workspace */
+ defaultWorkspace(CmsConstants.SYS_WORKSPACE),
+ /** Database URL */
+ dburl(null),
+ /** Database user */
+ dbuser(null),
+ /** Database password */
+ dbpassword(null),
+
+ /** The identifier (can be an URL locating the repo) */
+ labeledUri(null),
+ //
+ // JACKRABBIT SPECIFIC
+ //
+ /** Maximum database pool size */
+ maxPoolSize(10),
+ /** Maximum cache size in MB */
+ maxCacheMB(null),
+ /** Bundle cache size in MB */
+ bundleCacheMB(8),
+ /** Extractor pool size */
+ extractorPoolSize(0),
+ /** Search cache size */
+ searchCacheSize(1000),
+ /** Max volatile index size */
+ maxVolatileIndexSize(1048576),
+ /** Cluster id (if appropriate configuration) */
+ clusterId("default"),
+ /** Indexes base path */
+ indexesBase(null);
+
+ /** The default value. */
+ private Object def;
+ private String oid;
+
+ RepoConf(String oid, Object def) {
+ this.oid = oid;
+ this.def = def;
+ }
+
+ RepoConf(Object def) {
+ this.def = def;
+ }
+
+ public Object getDefault() {
+ return def;
+ }
+
+ @Override
+ public String getID() {
+ if (oid != null)
+ return oid;
+ return EnumAD.super.getID();
+ }
+
+ public static class OCD extends EnumOCD<RepoConf> {
+ public OCD(String locale) {
+ super(RepoConf.class, locale);
+ }
+ }
+
+}
--- /dev/null
+package org.argeo.cms.internal.jcr;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.Properties;
+import java.util.UUID;
+
+import javax.jcr.RepositoryException;
+
+import org.apache.jackrabbit.core.RepositoryContext;
+import org.apache.jackrabbit.core.RepositoryImpl;
+import org.apache.jackrabbit.core.cache.CacheManager;
+import org.apache.jackrabbit.core.config.RepositoryConfig;
+import org.apache.jackrabbit.core.config.RepositoryConfigurationParser;
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.cms.jcr.internal.CmsPaths;
+import org.xml.sax.InputSource;
+
+/** Can interpret properties in order to create an actual JCR repository. */
+public class RepositoryBuilder {
+ private final static CmsLog log = CmsLog.getLog(RepositoryBuilder.class);
+
+ public RepositoryContext createRepositoryContext(Dictionary<String, ?> properties)
+ throws RepositoryException, IOException {
+ RepositoryConfig repositoryConfig = createRepositoryConfig(properties);
+ RepositoryContext repositoryContext = createJackrabbitRepository(repositoryConfig);
+ RepositoryImpl repository = repositoryContext.getRepository();
+
+ // cache
+ Object maxCacheMbStr = prop(properties, RepoConf.maxCacheMB);
+ if (maxCacheMbStr != null) {
+ Integer maxCacheMB = Integer.parseInt(maxCacheMbStr.toString());
+ CacheManager cacheManager = repository.getCacheManager();
+ cacheManager.setMaxMemory(maxCacheMB * 1024l * 1024l);
+ cacheManager.setMaxMemoryPerCache((maxCacheMB / 4) * 1024l * 1024l);
+ }
+
+ return repositoryContext;
+ }
+
+ RepositoryConfig createRepositoryConfig(Dictionary<String, ?> properties) throws RepositoryException, IOException {
+ JackrabbitType type = JackrabbitType.valueOf(prop(properties, RepoConf.type).toString());
+ ClassLoader cl = getClass().getClassLoader();
+ final String base = "/org/argeo/cms/internal/jcr";
+ try (InputStream in = cl.getResourceAsStream(base + "/repository-" + type.name() + ".xml")) {
+ if (in == null)
+ throw new IllegalArgumentException("Repository configuration not found");
+ InputSource config = new InputSource(in);
+ Properties jackrabbitVars = getConfigurationProperties(type, properties);
+ // RepositoryConfig repositoryConfig = RepositoryConfig.create(config,
+ // jackrabbitVars);
+
+ // custom configuration parser
+ CustomRepositoryConfigurationParser parser = new CustomRepositoryConfigurationParser(jackrabbitVars);
+ parser.setClassLoader(cl);
+ RepositoryConfig repositoryConfig = parser.parseRepositoryConfig(config);
+ repositoryConfig.init();
+
+ // set the proper classloaders
+ repositoryConfig.getSecurityConfig().getSecurityManagerConfig().setClassLoader(cl);
+ repositoryConfig.getSecurityConfig().getAccessManagerConfig().setClassLoader(cl);
+// for (WorkspaceConfig workspaceConfig : repositoryConfig.getWorkspaceConfigs()) {
+// workspaceConfig.getSecurityConfig().getAccessControlProviderConfig().setClassLoader(cl);
+// }
+ return repositoryConfig;
+ }
+ }
+
+ private Properties getConfigurationProperties(JackrabbitType type, Dictionary<String, ?> properties) {
+ Properties props = new Properties();
+ for (Enumeration<String> keys = properties.keys(); keys.hasMoreElements();) {
+ String key = keys.nextElement();
+ props.put(key, properties.get(key));
+ }
+
+ // cluster id
+ // cf. https://wiki.apache.org/jackrabbit/Clustering
+ // TODO deal with multiple repos
+ String clusterId = System.getProperty("org.apache.jackrabbit.core.cluster.node_id");
+ String clusterIdProp = props.getProperty(RepoConf.clusterId.name());
+ if (clusterId != null) {
+ if (clusterIdProp != null)
+ throw new IllegalArgumentException("Cluster id defined as System properties and in deploy config");
+ props.put(RepoConf.clusterId.name(), clusterId);
+ } else {
+ clusterId = clusterIdProp;
+ }
+
+ // home
+ String homeUri = props.getProperty(RepoConf.labeledUri.name());
+ Path homePath;
+ if (homeUri == null) {
+ String cn = props.getProperty(CmsConstants.CN);
+ assert cn != null;
+ if (clusterId != null) {
+ homePath = CmsPaths.getRepoDirPath(cn + '/' + clusterId);
+ } else {
+ homePath = CmsPaths.getRepoDirPath(cn);
+ }
+ } else {
+ try {
+ URI uri = new URI(homeUri);
+ String host = uri.getHost();
+ if (host == null || host.trim().equals("")) {
+ homePath = Paths.get(uri).toAbsolutePath();
+ } else {
+ // TODO remote at this stage?
+ throw new IllegalArgumentException("Cannot manage repository path for host " + host);
+ }
+ } catch (URISyntaxException e) {
+ throw new IllegalArgumentException("Invalid repository home URI", e);
+ }
+ }
+ // TODO use Jackrabbit API (?)
+ Path rootUuidPath = homePath.resolve("repository/meta/rootUUID");
+ try {
+ if (!Files.exists(rootUuidPath)) {
+ Files.createDirectories(rootUuidPath.getParent());
+ Files.write(rootUuidPath, UUID.randomUUID().toString().getBytes());
+ }
+ // File homeDir = homePath.toFile();
+ // homeDir.mkdirs();
+ } catch (IOException e) {
+ throw new RuntimeException("Cannot set up repository home " + homePath, e);
+ }
+ // home cannot be overridden
+ props.put(RepositoryConfigurationParser.REPOSITORY_HOME_VARIABLE, homePath.toString());
+
+ setProp(props, RepoConf.indexesBase, CmsPaths.getRepoIndexesBase().toString());
+ // common
+ setProp(props, RepoConf.defaultWorkspace);
+ setProp(props, RepoConf.maxPoolSize);
+ // Jackrabbit defaults
+ setProp(props, RepoConf.bundleCacheMB);
+ // See http://wiki.apache.org/jackrabbit/Search
+ setProp(props, RepoConf.extractorPoolSize);
+ setProp(props, RepoConf.searchCacheSize);
+ setProp(props, RepoConf.maxVolatileIndexSize);
+
+ // specific
+ String dburl;
+ switch (type) {
+ case h2:
+ dburl = "jdbc:h2:" + homePath.toAbsolutePath() + "/h2/repository";
+ setProp(props, RepoConf.dburl, dburl);
+ setProp(props, RepoConf.dbuser, "sa");
+ setProp(props, RepoConf.dbpassword, "");
+ break;
+ case h2_postgresql:
+ dburl = "jdbc:h2:" + homePath.toAbsolutePath() + "/h2/repository;MODE=PostgreSQL;DATABASE_TO_LOWER=TRUE";
+ setProp(props, RepoConf.dburl, dburl);
+ setProp(props, RepoConf.dbuser, "sa");
+ setProp(props, RepoConf.dbpassword, "");
+ break;
+ case postgresql:
+ case postgresql_ds:
+ case postgresql_cluster:
+ case postgresql_cluster_ds:
+ dburl = "jdbc:postgresql://localhost/demo";
+ setProp(props, RepoConf.dburl, dburl);
+ setProp(props, RepoConf.dbuser, "argeo");
+ setProp(props, RepoConf.dbpassword, "argeo");
+ break;
+ case memory:
+ break;
+ case localfs:
+ break;
+ default:
+ throw new IllegalArgumentException("Unsupported node type " + type);
+ }
+ return props;
+ }
+
+ private void setProp(Properties props, RepoConf key, String def) {
+ Object value = props.get(key.name());
+ if (value == null)
+ value = def;
+ if (value == null)
+ value = key.getDefault();
+ if (value != null)
+ props.put(key.name(), value.toString());
+ }
+
+ private void setProp(Properties props, RepoConf key) {
+ setProp(props, key, null);
+ }
+
+ private String prop(Dictionary<String, ?> properties, RepoConf key) {
+ Object value = properties.get(key.name());
+ if (value == null)
+ return key.getDefault() != null ? key.getDefault().toString() : null;
+ else
+ return value.toString();
+ }
+
+ private RepositoryContext createJackrabbitRepository(RepositoryConfig repositoryConfig) throws RepositoryException {
+ ClassLoader currentContextCl = Thread.currentThread().getContextClassLoader();
+ Thread.currentThread().setContextClassLoader(RepositoryBuilder.class.getClassLoader());
+ try {
+ long begin = System.currentTimeMillis();
+ //
+ // Actual repository creation
+ //
+ RepositoryContext repositoryContext = RepositoryContext.create(repositoryConfig);
+
+ double duration = ((double) (System.currentTimeMillis() - begin)) / 1000;
+ if (log.isDebugEnabled())
+ log.debug(
+ "Created Jackrabbit repository in " + duration + " s, home: " + repositoryConfig.getHomeDir());
+
+ return repositoryContext;
+ } finally {
+ Thread.currentThread().setContextClassLoader(currentContextCl);
+ }
+ }
+
+}
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE Repository PUBLIC "Jackrabbit 2.6" "http://jackrabbit.apache.org/dtd/repository-2.6.dtd">
+<Repository>
+ <!-- Shared datasource -->
+ <DataSources>
+ <DataSource name="dataSource">
+ <param name="driver" value="org.h2.Driver" />
+ <param name="url" value="${dburl}" />
+ <param name="user" value="${dbuser}" />
+ <param name="password" value="${dbpassword}" />
+ <param name="databaseType" value="h2" />
+ <param name="maxPoolSize" value="${maxPoolSize}" />
+ </DataSource>
+ </DataSources>
+
+ <!-- File system and datastore -->
+ <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
+ <param name="dataSourceName" value="dataSource" />
+ <param name="schema" value="default" />
+ <param name="schemaObjectPrefix" value="fs_" />
+ </FileSystem>
+ <DataStore class="org.apache.jackrabbit.core.data.FileDataStore">
+ <param name="path" value="${rep.home}/datastore" />
+ </DataStore>
+
+ <!-- Workspace templates -->
+ <Workspaces rootPath="${rep.home}/workspaces"
+ defaultWorkspace="${defaultWorkspace}" />
+ <Workspace name="${wsp.name}">
+ <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
+ <param name="dataSourceName" value="dataSource" />
+ <param name="schema" value="default" />
+ <param name="schemaObjectPrefix" value="${wsp.name}_fs_" />
+ </FileSystem>
+ <PersistenceManager
+ class="org.apache.jackrabbit.core.persistence.pool.H2PersistenceManager">
+ <param name="dataSourceName" value="dataSource" />
+ <param name="schemaObjectPrefix" value="${wsp.name}_pm_" />
+ <param name="bundleCacheSize" value="${bundleCacheMB}" />
+ </PersistenceManager>
+ <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+ <param name="path" value="${indexesBase}/${cn}/${wsp.name}/index" />
+ <param name="extractorPoolSize" value="${extractorPoolSize}" />
+ <param name="cacheSize" value="${searchCacheSize}" />
+ <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
+<!-- <param name="tikaConfigPath" value="${indexesBase}/${cn}/tika-config.xml" /> -->
+ <param name="supportHighlighting" value="true" />
+ </SearchIndex>
+ <WorkspaceSecurity>
+ <AccessControlProvider
+ class="org.argeo.security.jackrabbit.ArgeoAccessControlProvider" />
+ </WorkspaceSecurity>
+ </Workspace>
+
+ <!-- Versioning -->
+ <Versioning rootPath="${rep.home}/version">
+ <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
+ <param name="dataSourceName" value="dataSource" />
+ <param name="schema" value="default" />
+ <param name="schemaObjectPrefix" value="fs_ver_" />
+ </FileSystem>
+ <PersistenceManager
+ class="org.apache.jackrabbit.core.persistence.pool.H2PersistenceManager">
+ <param name="dataSourceName" value="dataSource" />
+ <param name="schemaObjectPrefix" value="pm_ver_" />
+ <param name="bundleCacheSize" value="${bundleCacheMB}" />
+ </PersistenceManager>
+ </Versioning>
+
+ <!-- Indexing -->
+ <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+ <param name="path" value="${indexesBase}/${cn}/index" />
+ <param name="extractorPoolSize" value="${extractorPoolSize}" />
+ <param name="cacheSize" value="${searchCacheSize}" />
+ <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
+<!-- <param name="tikaConfigPath" value="${indexesBase}/${cn}/tika-config.xml" /> -->
+ <param name="supportHighlighting" value="true" />
+ </SearchIndex>
+
+ <!-- Security -->
+ <Security appName="Jackrabbit">
+ <SecurityManager class="org.argeo.security.jackrabbit.ArgeoSecurityManager"
+ workspaceName="security" />
+ <AccessManager class="org.argeo.security.jackrabbit.ArgeoAccessManager" />
+ </Security>
+</Repository>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE Repository PUBLIC "Jackrabbit 2.6" "http://jackrabbit.apache.org/dtd/repository-2.6.dtd">
+<Repository>
+ <!-- Shared datasource -->
+ <DataSources>
+ <DataSource name="dataSource">
+ <param name="driver" value="org.h2.Driver" />
+ <param name="url" value="${dburl}" />
+ <param name="user" value="${dbuser}" />
+ <param name="password" value="${dbpassword}" />
+ <param name="databaseType" value="postgresql" />
+ <param name="maxPoolSize" value="${maxPoolSize}" />
+ </DataSource>
+ </DataSources>
+
+ <!-- File system and datastore -->
+ <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
+ <param name="dataSourceName" value="dataSource" />
+ <param name="schema" value="default" />
+ <param name="schemaObjectPrefix" value="fs_" />
+ </FileSystem>
+ <DataStore class="org.apache.jackrabbit.core.data.FileDataStore">
+ <param name="path" value="${rep.home}/datastore" />
+ </DataStore>
+
+ <!-- Workspace templates -->
+ <Workspaces rootPath="${rep.home}/workspaces"
+ defaultWorkspace="${defaultWorkspace}" />
+ <Workspace name="${wsp.name}">
+ <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
+ <param name="dataSourceName" value="dataSource" />
+ <param name="schema" value="default" />
+ <param name="schemaObjectPrefix" value="${wsp.name}_fs_" />
+ </FileSystem>
+ <PersistenceManager
+ class="org.apache.jackrabbit.core.persistence.pool.PostgreSQLPersistenceManager">
+ <param name="dataSourceName" value="dataSource" />
+ <param name="schemaObjectPrefix" value="${wsp.name}_pm_" />
+ <param name="bundleCacheSize" value="${bundleCacheMB}" />
+ </PersistenceManager>
+ <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+ <param name="path" value="${indexesBase}/${cn}/${wsp.name}/index" />
+ <param name="extractorPoolSize" value="${extractorPoolSize}" />
+ <param name="cacheSize" value="${searchCacheSize}" />
+ <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
+<!-- <param name="tikaConfigPath" value="${indexesBase}/${cn}/tika-config.xml" /> -->
+ <param name="supportHighlighting" value="true" />
+ </SearchIndex>
+ <WorkspaceSecurity>
+ <AccessControlProvider
+ class="org.argeo.security.jackrabbit.ArgeoAccessControlProvider" />
+ </WorkspaceSecurity>
+ </Workspace>
+
+ <!-- Versioning -->
+ <Versioning rootPath="${rep.home}/version">
+ <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
+ <param name="dataSourceName" value="dataSource" />
+ <param name="schema" value="default" />
+ <param name="schemaObjectPrefix" value="fs_ver_" />
+ </FileSystem>
+ <PersistenceManager
+ class="org.apache.jackrabbit.core.persistence.pool.PostgreSQLPersistenceManager">
+ <param name="dataSourceName" value="dataSource" />
+ <param name="schemaObjectPrefix" value="pm_ver_" />
+ <param name="bundleCacheSize" value="${bundleCacheMB}" />
+ </PersistenceManager>
+ </Versioning>
+
+ <!-- Indexing -->
+ <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+ <param name="path" value="${indexesBase}/${cn}/index" />
+ <param name="extractorPoolSize" value="${extractorPoolSize}" />
+ <param name="cacheSize" value="${searchCacheSize}" />
+ <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
+<!-- <param name="tikaConfigPath" value="${indexesBase}/${cn}/tika-config.xml" /> -->
+ <param name="supportHighlighting" value="true" />
+ </SearchIndex>
+
+ <!-- Security -->
+ <Security appName="Jackrabbit">
+ <SecurityManager class="org.argeo.security.jackrabbit.ArgeoSecurityManager"
+ workspaceName="security" />
+ <AccessManager class="org.argeo.security.jackrabbit.ArgeoAccessManager" />
+ </Security>
+</Repository>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE Repository PUBLIC "Jackrabbit 2.6" "http://jackrabbit.apache.org/dtd/repository-2.6.dtd">
+<Repository>
+ <!-- File system and datastore -->
+ <FileSystem class="org.apache.jackrabbit.core.fs.local.LocalFileSystem">
+ <param name="path" value="${rep.home}/repository" />
+ </FileSystem>
+ <DataStore class="org.apache.jackrabbit.core.data.FileDataStore">
+ <param name="path" value="${rep.home}/datastore" />
+ </DataStore>
+
+ <!-- Workspace templates -->
+ <Workspaces rootPath="${rep.home}/workspaces"
+ defaultWorkspace="${defaultWorkspace}" />
+ <Workspace name="${wsp.name}">
+ <FileSystem class="org.apache.jackrabbit.core.fs.local.LocalFileSystem">
+ <param name="path" value="${wsp.home}" />
+ </FileSystem>
+ <PersistenceManager
+ class="org.apache.jackrabbit.core.persistence.bundle.BundleFsPersistenceManager">
+ <param name="bundleCacheSize" value="${bundleCacheMB}" />
+ </PersistenceManager>
+ <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+ <param name="path" value="${indexesBase}/${cn}/${wsp.name}/index" />
+ <param name="extractorPoolSize" value="${extractorPoolSize}" />
+ <param name="cacheSize" value="${searchCacheSize}" />
+ <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
+ </SearchIndex>
+ <WorkspaceSecurity>
+ <AccessControlProvider
+ class="org.argeo.security.jackrabbit.ArgeoAccessControlProvider" />
+ </WorkspaceSecurity>
+ </Workspace>
+
+ <!-- Versioning -->
+ <Versioning rootPath="${rep.home}/version">
+ <FileSystem class="org.apache.jackrabbit.core.fs.local.LocalFileSystem">
+ <param name="path" value="${rep.home}/version" />
+ </FileSystem>
+ <PersistenceManager
+ class="org.apache.jackrabbit.core.persistence.bundle.BundleFsPersistenceManager">
+ <param name="bundleCacheSize" value="${bundleCacheMB}" />
+ </PersistenceManager>
+ </Versioning>
+
+ <!-- Indexing -->
+ <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+ <param name="path" value="${indexesBase}/${cn}/index" />
+ <param name="extractorPoolSize" value="${extractorPoolSize}" />
+ <param name="cacheSize" value="${searchCacheSize}" />
+ <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
+ </SearchIndex>
+
+ <!-- Security -->
+ <Security appName="Jackrabbit">
+ <SecurityManager class="org.argeo.security.jackrabbit.ArgeoSecurityManager"
+ workspaceName="security" />
+ <AccessManager class="org.argeo.security.jackrabbit.ArgeoAccessManager" />
+ </Security>
+</Repository>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0"?>
+<!DOCTYPE Repository PUBLIC "Jackrabbit 2.6" "http://jackrabbit.apache.org/dtd/repository-2.6.dtd">
+<Repository>
+ <!-- File system and datastore -->
+ <FileSystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
+
+ <!-- Workspace templates -->
+ <Workspaces rootPath="${rep.home}/workspaces"
+ defaultWorkspace="${defaultWorkspace}" configRootPath="/workspaces" />
+ <Workspace name="${wsp.name}">
+ <FileSystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
+ <PersistenceManager
+ class="org.apache.jackrabbit.core.persistence.bundle.BundleFsPersistenceManager">
+ <param name="blobFSBlockSize" value="1" />
+ <param name="bundleCacheSize" value="${bundleCacheMB}" />
+ </PersistenceManager>
+ <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+ <param name="path" value="${indexesBase}/${cn}/${wsp.name}/index" />
+ <param name="directoryManagerClass"
+ value="org.apache.jackrabbit.core.query.lucene.directory.RAMDirectoryManager" />
+ <param name="extractorPoolSize" value="${extractorPoolSize}" />
+ <param name="cacheSize" value="${searchCacheSize}" />
+ <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
+ <FileSystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
+ </SearchIndex>
+ <WorkspaceSecurity>
+ <AccessControlProvider
+ class="org.argeo.security.jackrabbit.ArgeoAccessControlProvider" />
+ </WorkspaceSecurity>
+ </Workspace>
+
+ <!-- Versioning -->
+ <Versioning rootPath="${rep.home}/version">
+ <FileSystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
+ <PersistenceManager
+ class="org.apache.jackrabbit.core.persistence.bundle.BundleFsPersistenceManager">
+ <param name="blobFSBlockSize" value="1" />
+ <param name="bundleCacheSize" value="${bundleCacheMB}" />
+ </PersistenceManager>
+ </Versioning>
+
+ <!-- Indexing -->
+ <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+ <param name="path" value="${indexesBase}/${cn}/index" />
+ <param name="directoryManagerClass"
+ value="org.apache.jackrabbit.core.query.lucene.directory.RAMDirectoryManager" />
+ <param name="extractorPoolSize" value="${extractorPoolSize}" />
+ <param name="cacheSize" value="${searchCacheSize}" />
+ <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
+ <FileSystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
+ </SearchIndex>
+
+ <!-- Security -->
+ <Security appName="Jackrabbit">
+ <SecurityManager class="org.argeo.security.jackrabbit.ArgeoSecurityManager"
+ workspaceName="security" />
+ <AccessManager class="org.argeo.security.jackrabbit.ArgeoAccessManager" />
+ </Security>
+</Repository>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0"?>
+<!DOCTYPE Repository PUBLIC "Jackrabbit 2.6" "http://jackrabbit.apache.org/dtd/repository-2.6.dtd">
+<Repository>
+ <!-- Shared datasource -->
+ <DataSources>
+ <DataSource name="dataSource">
+ <param name="driver" value="org.postgresql.Driver" />
+ <param name="url" value="${dburl}" />
+ <param name="user" value="${dbuser}" />
+ <param name="password" value="${dbpassword}" />
+ <param name="databaseType" value="postgresql" />
+ <param name="maxPoolSize" value="${maxPoolSize}" />
+ </DataSource>
+ </DataSources>
+
+ <!-- File system and datastore -->
+ <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
+ <param name="dataSourceName" value="dataSource" />
+ <param name="schema" value="postgresql" />
+ <param name="schemaObjectPrefix" value="fs_" />
+ </FileSystem>
+
+ <!-- Workspace templates -->
+ <Workspaces rootPath="${rep.home}/workspaces"
+ defaultWorkspace="${defaultWorkspace}" />
+ <Workspace name="${wsp.name}">
+ <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
+ <param name="dataSourceName" value="dataSource" />
+ <param name="schema" value="postgresql" />
+ <param name="schemaObjectPrefix" value="${wsp.name}_fs_" />
+ </FileSystem>
+ <PersistenceManager
+ class="org.apache.jackrabbit.core.persistence.pool.PostgreSQLPersistenceManager">
+ <param name="dataSourceName" value="dataSource" />
+ <param name="schemaObjectPrefix" value="${wsp.name}_pm_" />
+ <param name="bundleCacheSize" value="${bundleCacheMB}" />
+ </PersistenceManager>
+ <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+ <param name="path" value="${indexesBase}/${cn}/${wsp.name}/index" />
+ <param name="extractorPoolSize" value="${extractorPoolSize}" />
+ <param name="cacheSize" value="${searchCacheSize}" />
+ <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
+ </SearchIndex>
+ <WorkspaceSecurity>
+ <AccessControlProvider
+ class="org.argeo.security.jackrabbit.ArgeoAccessControlProvider" />
+ </WorkspaceSecurity>
+ </Workspace>
+
+ <!-- Versioning -->
+ <Versioning rootPath="${rep.home}/version">
+ <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
+ <param name="dataSourceName" value="dataSource" />
+ <param name="schema" value="postgresql" />
+ <param name="schemaObjectPrefix" value="fs_ver_" />
+ </FileSystem>
+ <PersistenceManager
+ class="org.apache.jackrabbit.core.persistence.pool.PostgreSQLPersistenceManager">
+ <param name="dataSourceName" value="dataSource" />
+ <param name="schemaObjectPrefix" value="pm_ver_" />
+ <param name="bundleCacheSize" value="${bundleCacheMB}" />
+ </PersistenceManager>
+ </Versioning>
+
+ <!-- Indexing -->
+ <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+ <param name="path" value="${indexesBase}/${cn}/index" />
+ <param name="extractorPoolSize" value="${extractorPoolSize}" />
+ <param name="cacheSize" value="${searchCacheSize}" />
+ <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
+ </SearchIndex>
+
+ <!-- Security -->
+ <Security appName="Jackrabbit">
+ <SecurityManager class="org.argeo.security.jackrabbit.ArgeoSecurityManager"
+ workspaceName="security" />
+ <AccessManager class="org.argeo.security.jackrabbit.ArgeoAccessManager" />
+ </Security>
+</Repository>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0"?>
+<!DOCTYPE Repository PUBLIC "Jackrabbit 2.6" "http://jackrabbit.apache.org/dtd/repository-2.6.dtd">
+<Repository>
+ <!-- Shared datasource -->
+ <DataSources>
+ <DataSource name="dataSource">
+ <param name="driver" value="org.postgresql.Driver" />
+ <param name="url" value="${dburl}" />
+ <param name="user" value="${dbuser}" />
+ <param name="password" value="${dbpassword}" />
+ <param name="databaseType" value="postgresql" />
+ <param name="maxPoolSize" value="${maxPoolSize}" />
+ </DataSource>
+ </DataSources>
+
+ <!-- File system and datastore -->
+ <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
+ <param name="dataSourceName" value="dataSource" />
+ <param name="schema" value="postgresql" />
+ <param name="schemaObjectPrefix" value="fs_" />
+ </FileSystem>
+
+ <!-- Workspace templates -->
+ <Workspaces rootPath="${rep.home}/workspaces"
+ defaultWorkspace="${defaultWorkspace}" />
+ <Workspace name="${wsp.name}">
+ <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
+ <param name="dataSourceName" value="dataSource" />
+ <param name="schema" value="postgresql" />
+ <param name="schemaObjectPrefix" value="${wsp.name}_fs_" />
+ </FileSystem>
+ <PersistenceManager
+ class="org.apache.jackrabbit.core.persistence.pool.PostgreSQLPersistenceManager">
+ <param name="dataSourceName" value="dataSource" />
+ <param name="schemaObjectPrefix" value="${wsp.name}_pm_" />
+ <param name="bundleCacheSize" value="${bundleCacheMB}" />
+ </PersistenceManager>
+ <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+ <param name="path" value="${indexesBase}/${cn}/${wsp.name}/index" />
+ <param name="extractorPoolSize" value="${extractorPoolSize}" />
+ <param name="cacheSize" value="${searchCacheSize}" />
+ <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
+ </SearchIndex>
+ <WorkspaceSecurity>
+ <AccessControlProvider
+ class="org.argeo.security.jackrabbit.ArgeoAccessControlProvider" />
+ </WorkspaceSecurity>
+ </Workspace>
+
+ <!-- Versioning -->
+ <Versioning rootPath="${rep.home}/version">
+ <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
+ <param name="dataSourceName" value="dataSource" />
+ <param name="schema" value="postgresql" />
+ <param name="schemaObjectPrefix" value="fs_ver_" />
+ </FileSystem>
+ <PersistenceManager
+ class="org.apache.jackrabbit.core.persistence.pool.PostgreSQLPersistenceManager">
+ <param name="dataSourceName" value="dataSource" />
+ <param name="schemaObjectPrefix" value="pm_ver_" />
+ <param name="bundleCacheSize" value="${bundleCacheMB}" />
+ </PersistenceManager>
+ </Versioning>
+
+ <!-- Indexing -->
+ <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+ <param name="path" value="${indexesBase}/${cn}/index" />
+ <param name="extractorPoolSize" value="${extractorPoolSize}" />
+ <param name="cacheSize" value="${searchCacheSize}" />
+ <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
+ </SearchIndex>
+
+ <!-- Security -->
+ <Security appName="Jackrabbit">
+ <SecurityManager class="org.argeo.security.jackrabbit.ArgeoSecurityManager"
+ workspaceName="security" />
+ <AccessManager class="org.argeo.security.jackrabbit.ArgeoAccessManager" />
+ </Security>
+
+ <!-- Clustering -->
+ <Cluster id="${clusterId}">
+ <Journal class="org.apache.jackrabbit.core.journal.DatabaseJournal">
+ <param name="dataSourceName" value="dataSource" />
+ <param name="schemaObjectPrefix" value="journal_" />
+ </Journal>
+ </Cluster>
+</Repository>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0"?>
+<!DOCTYPE Repository PUBLIC "Jackrabbit 2.6" "http://jackrabbit.apache.org/dtd/repository-2.6.dtd">
+<Repository>
+ <!-- Shared datasource -->
+ <DataSources>
+ <DataSource name="dataSource">
+ <param name="driver" value="org.postgresql.Driver" />
+ <param name="url" value="${dburl}" />
+ <param name="user" value="${dbuser}" />
+ <param name="password" value="${dbpassword}" />
+ <param name="databaseType" value="postgresql" />
+ <param name="maxPoolSize" value="${maxPoolSize}" />
+ </DataSource>
+ </DataSources>
+
+ <!-- File system and datastore -->
+ <FileSystem
+ class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
+ <param name="dataSourceName" value="dataSource" />
+ <param name="schema" value="postgresql" />
+ <param name="schemaObjectPrefix" value="fs_" />
+ </FileSystem>
+ <DataStore
+ class="org.argeo.cms.internal.jcr.LocalFsDataStore">
+ <param name="path" value="${rep.home}/../datastore" />
+ <param name="redundantPath" value="${rep.home}/../datastorer" />
+ </DataStore>
+
+ <!-- Workspace templates -->
+ <Workspaces rootPath="${rep.home}/workspaces"
+ defaultWorkspace="${defaultWorkspace}" />
+ <Workspace name="${wsp.name}">
+ <FileSystem
+ class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
+ <param name="dataSourceName" value="dataSource" />
+ <param name="schema" value="postgresql" />
+ <param name="schemaObjectPrefix" value="${wsp.name}_fs_" />
+ </FileSystem>
+ <PersistenceManager
+ class="org.apache.jackrabbit.core.persistence.pool.PostgreSQLPersistenceManager">
+ <param name="dataSourceName" value="dataSource" />
+ <param name="schemaObjectPrefix" value="${wsp.name}_pm_" />
+ <param name="bundleCacheSize" value="${bundleCacheMB}" />
+ </PersistenceManager>
+ <SearchIndex
+ class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+ <param name="path"
+ value="${indexesBase}/${cn}/${wsp.name}/index" />
+ <param name="extractorPoolSize" value="${extractorPoolSize}" />
+ <param name="cacheSize" value="${searchCacheSize}" />
+ <param name="maxVolatileIndexSize"
+ value="${maxVolatileIndexSize}" />
+ </SearchIndex>
+ <WorkspaceSecurity>
+ <AccessControlProvider
+ class="org.argeo.security.jackrabbit.ArgeoAccessControlProvider" />
+ </WorkspaceSecurity>
+ </Workspace>
+
+ <!-- Versioning -->
+ <Versioning rootPath="${rep.home}/version">
+ <FileSystem
+ class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
+ <param name="dataSourceName" value="dataSource" />
+ <param name="schema" value="postgresql" />
+ <param name="schemaObjectPrefix" value="fs_ver_" />
+ </FileSystem>
+ <PersistenceManager
+ class="org.apache.jackrabbit.core.persistence.pool.PostgreSQLPersistenceManager">
+ <param name="dataSourceName" value="dataSource" />
+ <param name="schemaObjectPrefix" value="pm_ver_" />
+ <param name="bundleCacheSize" value="${bundleCacheMB}" />
+ </PersistenceManager>
+ </Versioning>
+
+ <!-- Indexing -->
+ <SearchIndex
+ class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+ <param name="path" value="${indexesBase}/${cn}/index" />
+ <param name="extractorPoolSize" value="${extractorPoolSize}" />
+ <param name="cacheSize" value="${searchCacheSize}" />
+ <param name="maxVolatileIndexSize"
+ value="${maxVolatileIndexSize}" />
+ </SearchIndex>
+
+ <!-- Security -->
+ <Security appName="Jackrabbit">
+ <SecurityManager
+ class="org.argeo.security.jackrabbit.ArgeoSecurityManager"
+ workspaceName="security" />
+ <AccessManager
+ class="org.argeo.security.jackrabbit.ArgeoAccessManager" />
+ </Security>
+
+ <!-- Clustering -->
+ <Cluster id="${clusterId}" syncDelay="100">
+ <Journal
+ class="org.apache.jackrabbit.core.journal.DatabaseJournal">
+ <param name="dataSourceName" value="dataSource" />
+ <param name="schemaObjectPrefix" value="journal_" />
+ </Journal>
+ </Cluster>
+</Repository>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0"?>
+<!DOCTYPE Repository PUBLIC "Jackrabbit 2.6" "http://jackrabbit.apache.org/dtd/repository-2.6.dtd">
+<Repository>
+ <!-- Shared datasource -->
+ <DataSources>
+ <DataSource name="dataSource">
+ <param name="driver" value="org.postgresql.Driver" />
+ <param name="url" value="${dburl}" />
+ <param name="user" value="${dbuser}" />
+ <param name="password" value="${dbpassword}" />
+ <param name="databaseType" value="postgresql" />
+ <param name="maxPoolSize" value="${maxPoolSize}" />
+ </DataSource>
+ </DataSources>
+
+ <!-- File system and datastore -->
+ <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
+ <param name="dataSourceName" value="dataSource" />
+ <param name="schema" value="postgresql" />
+ <param name="schemaObjectPrefix" value="fs_" />
+ </FileSystem>
+ <DataStore class="org.apache.jackrabbit.core.data.FileDataStore">
+ <param name="path" value="${rep.home}/datastore" />
+ </DataStore>
+
+ <!-- Workspace templates -->
+ <Workspaces rootPath="${rep.home}/workspaces"
+ defaultWorkspace="${defaultWorkspace}" />
+ <Workspace name="${wsp.name}">
+ <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
+ <param name="dataSourceName" value="dataSource" />
+ <param name="schema" value="postgresql" />
+ <param name="schemaObjectPrefix" value="${wsp.name}_fs_" />
+ </FileSystem>
+ <PersistenceManager
+ class="org.apache.jackrabbit.core.persistence.pool.PostgreSQLPersistenceManager">
+ <param name="dataSourceName" value="dataSource" />
+ <param name="schemaObjectPrefix" value="${wsp.name}_pm_" />
+ <param name="bundleCacheSize" value="${bundleCacheMB}" />
+ </PersistenceManager>
+ <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+ <param name="path" value="${indexesBase}/${cn}/${wsp.name}/index" />
+ <param name="extractorPoolSize" value="${extractorPoolSize}" />
+ <param name="cacheSize" value="${searchCacheSize}" />
+ <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
+ </SearchIndex>
+ <WorkspaceSecurity>
+ <AccessControlProvider
+ class="org.argeo.security.jackrabbit.ArgeoAccessControlProvider" />
+ </WorkspaceSecurity>
+ </Workspace>
+
+ <!-- Versioning -->
+ <Versioning rootPath="${rep.home}/version">
+ <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
+ <param name="dataSourceName" value="dataSource" />
+ <param name="schema" value="postgresql" />
+ <param name="schemaObjectPrefix" value="fs_ver_" />
+ </FileSystem>
+ <PersistenceManager
+ class="org.apache.jackrabbit.core.persistence.pool.PostgreSQLPersistenceManager">
+ <param name="dataSourceName" value="dataSource" />
+ <param name="schemaObjectPrefix" value="pm_ver_" />
+ <param name="bundleCacheSize" value="${bundleCacheMB}" />
+ </PersistenceManager>
+ </Versioning>
+
+ <!-- Indexing -->
+ <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+ <param name="path" value="${indexesBase}/${cn}/index" />
+ <param name="extractorPoolSize" value="${extractorPoolSize}" />
+ <param name="cacheSize" value="${searchCacheSize}" />
+ <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
+ </SearchIndex>
+
+ <!-- Security -->
+ <Security appName="Jackrabbit">
+ <SecurityManager class="org.argeo.security.jackrabbit.ArgeoSecurityManager"
+ workspaceName="security" />
+ <AccessManager class="org.argeo.security.jackrabbit.ArgeoAccessManager" />
+ </Security>
+</Repository>
\ No newline at end of file
--- /dev/null
+package org.argeo.cms.jcr;
+
+import java.security.PrivilegedAction;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.jcr.NoSuchWorkspaceException;
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.RepositoryFactory;
+import javax.jcr.Session;
+import javax.naming.InvalidNameException;
+import javax.naming.ldap.LdapName;
+import javax.security.auth.AuthPermission;
+import javax.security.auth.Subject;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+
+import org.argeo.api.cms.CmsAuth;
+import org.argeo.api.cms.CmsConstants;
+
+/** Utilities related to Argeo model in JCR */
+public class CmsJcrUtils {
+ /**
+ * Wraps the call to the repository factory based on parameter
+ * {@link CmsConstants#CN} in order to simplify it and protect against future
+ * API changes.
+ */
+ public static Repository getRepositoryByAlias(RepositoryFactory repositoryFactory, String alias) {
+ try {
+ Map<String, String> parameters = new HashMap<String, String>();
+ parameters.put(CmsConstants.CN, alias);
+ return repositoryFactory.getRepository(parameters);
+ } catch (RepositoryException e) {
+ throw new RuntimeException("Unexpected exception when trying to retrieve repository with alias " + alias,
+ e);
+ }
+ }
+
+ /**
+ * Wraps the call to the repository factory based on parameter
+ * {@link CmsConstants#LABELED_URI} in order to simplify it and protect against
+ * future API changes.
+ */
+ public static Repository getRepositoryByUri(RepositoryFactory repositoryFactory, String uri) {
+ return getRepositoryByUri(repositoryFactory, uri, null);
+ }
+
+ /**
+ * Wraps the call to the repository factory based on parameter
+ * {@link CmsConstants#LABELED_URI} in order to simplify it and protect against
+ * future API changes.
+ */
+ public static Repository getRepositoryByUri(RepositoryFactory repositoryFactory, String uri, String alias) {
+ try {
+ Map<String, String> parameters = new HashMap<String, String>();
+ parameters.put(CmsConstants.LABELED_URI, uri);
+ if (alias != null)
+ parameters.put(CmsConstants.CN, alias);
+ return repositoryFactory.getRepository(parameters);
+ } catch (RepositoryException e) {
+ throw new RuntimeException("Unexpected exception when trying to retrieve repository with uri " + uri, e);
+ }
+ }
+
+ /**
+ * Returns the home node of the user or null if none was found.
+ *
+ * @param session the session to use in order to perform the search, this can
+ * be a session with a different user ID than the one searched,
+ * typically when a system or admin session is used.
+ * @param username the username of the user
+ */
+ public static Node getUserHome(Session session, String username) {
+// try {
+// QueryObjectModelFactory qomf = session.getWorkspace().getQueryManager().getQOMFactory();
+// Selector sel = qomf.selector(NodeTypes.NODE_USER_HOME, "sel");
+// DynamicOperand dop = qomf.propertyValue(sel.getSelectorName(), NodeNames.LDAP_UID);
+// StaticOperand sop = qomf.literal(session.getValueFactory().createValue(username));
+// Constraint constraint = qomf.comparison(dop, QueryObjectModelFactory.JCR_OPERATOR_EQUAL_TO, sop);
+// Query query = qomf.createQuery(sel, constraint, null, null);
+// return querySingleNode(query);
+// } catch (RepositoryException e) {
+// throw new RuntimeException("Cannot find home for user " + username, e);
+// }
+
+ try {
+ checkUserWorkspace(session, username);
+ String homePath = getHomePath(username);
+ if (session.itemExists(homePath))
+ return session.getNode(homePath);
+ // legacy
+ homePath = "/home/" + username;
+ if (session.itemExists(homePath))
+ return session.getNode(homePath);
+ return null;
+ } catch (RepositoryException e) {
+ throw new RuntimeException("Cannot find home for user " + username, e);
+ }
+ }
+
+ private static String getHomePath(String username) {
+ LdapName dn;
+ try {
+ dn = new LdapName(username);
+ } catch (InvalidNameException e) {
+ throw new IllegalArgumentException("Invalid name " + username, e);
+ }
+ String userId = dn.getRdn(dn.size() - 1).getValue().toString();
+ return '/' + userId;
+ }
+
+ private static void checkUserWorkspace(Session session, String username) {
+ String workspaceName = session.getWorkspace().getName();
+ if (!CmsConstants.HOME_WORKSPACE.equals(workspaceName))
+ throw new IllegalArgumentException(workspaceName + " is not the home workspace for user " + username);
+ }
+
+ /**
+ * Returns the home node of the user or null if none was found.
+ *
+ * @param session the session to use in order to perform the search, this can
+ * be a session with a different user ID than the one searched,
+ * typically when a system or admin session is used.
+ * @param groupname the name of the group
+ */
+ public static Node getGroupHome(Session session, String groupname) {
+// try {
+// QueryObjectModelFactory qomf = session.getWorkspace().getQueryManager().getQOMFactory();
+// Selector sel = qomf.selector(NodeTypes.NODE_GROUP_HOME, "sel");
+// DynamicOperand dop = qomf.propertyValue(sel.getSelectorName(), NodeNames.LDAP_CN);
+// StaticOperand sop = qomf.literal(session.getValueFactory().createValue(cn));
+// Constraint constraint = qomf.comparison(dop, QueryObjectModelFactory.JCR_OPERATOR_EQUAL_TO, sop);
+// Query query = qomf.createQuery(sel, constraint, null, null);
+// return querySingleNode(query);
+// } catch (RepositoryException e) {
+// throw new RuntimeException("Cannot find home for group " + cn, e);
+// }
+
+ try {
+ checkGroupWorkspace(session, groupname);
+ String homePath = getGroupPath(groupname);
+ if (session.itemExists(homePath))
+ return session.getNode(homePath);
+ // legacy
+ homePath = "/groups/" + groupname;
+ if (session.itemExists(homePath))
+ return session.getNode(homePath);
+ return null;
+ } catch (RepositoryException e) {
+ throw new RuntimeException("Cannot find home for group " + groupname, e);
+ }
+
+ }
+
+ private static String getGroupPath(String groupname) {
+ String cn;
+ try {
+ LdapName dn = new LdapName(groupname);
+ cn = dn.getRdn(dn.size() - 1).getValue().toString();
+ } catch (InvalidNameException e) {
+ cn = groupname;
+ }
+ return '/' + cn;
+ }
+
+ private static void checkGroupWorkspace(Session session, String groupname) {
+ String workspaceName = session.getWorkspace().getName();
+ if (!CmsConstants.SRV_WORKSPACE.equals(workspaceName))
+ throw new IllegalArgumentException(workspaceName + " is not the group workspace for group " + groupname);
+ }
+
+ /**
+ * Queries one single node.
+ *
+ * @return one single node or null if none was found
+ * @throws ArgeoJcrException if more than one node was found
+ */
+// private static Node querySingleNode(Query query) {
+// NodeIterator nodeIterator;
+// try {
+// QueryResult queryResult = query.execute();
+// nodeIterator = queryResult.getNodes();
+// } catch (RepositoryException e) {
+// throw new RuntimeException("Cannot execute query " + query, e);
+// }
+// Node node;
+// if (nodeIterator.hasNext())
+// node = nodeIterator.nextNode();
+// else
+// return null;
+//
+// if (nodeIterator.hasNext())
+// throw new RuntimeException("Query returned more than one node.");
+// return node;
+// }
+
+ /** Returns the home node of the session user or null if none was found. */
+ public static Node getUserHome(Session session) {
+ String userID = session.getUserID();
+ return getUserHome(session, userID);
+ }
+
+ /** Whether this node is the home of the user of the underlying session. */
+ public static boolean isUserHome(Node node) {
+ try {
+ String userID = node.getSession().getUserID();
+ return node.hasProperty(Property.JCR_ID) && node.getProperty(Property.JCR_ID).getString().equals(userID);
+ } catch (RepositoryException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ /**
+ * Translate the path to this node into a path containing the name of the
+ * repository and the name of the workspace.
+ */
+ public static String getDataPath(String cn, Node node) {
+ assert node != null;
+ StringBuilder buf = new StringBuilder(CmsConstants.PATH_DATA);
+ try {
+ return buf.append('/').append(cn).append('/').append(node.getSession().getWorkspace().getName())
+ .append(node.getPath()).toString();
+ } catch (RepositoryException e) {
+ throw new IllegalStateException("Cannot get data path for " + node + " in repository " + cn, e);
+ }
+ }
+
+ /**
+ * Translate the path to this node into a path containing the name of the
+ * repository and the name of the workspace.
+ */
+ public static String getDataPath(Node node) {
+ return getDataPath(CmsConstants.NODE, node);
+ }
+
+ /**
+ * Open a JCR session with full read/write rights on the data, as
+ * {@link CmsConstants#ROLE_USER_ADMIN}, using the
+ * {@link CmsAuth#LOGIN_CONTEXT_DATA_ADMIN} login context. For security
+ * hardened deployement, use {@link AuthPermission} on this login context.
+ */
+ public static Session openDataAdminSession(Repository repository, String workspaceName) {
+ ClassLoader currentCl = Thread.currentThread().getContextClassLoader();
+ LoginContext loginContext;
+ try {
+ loginContext = new LoginContext(CmsAuth.LOGIN_CONTEXT_DATA_ADMIN);
+ loginContext.login();
+ } catch (LoginException e1) {
+ throw new RuntimeException("Could not login as data admin", e1);
+ } finally {
+ Thread.currentThread().setContextClassLoader(currentCl);
+ }
+ return Subject.doAs(loginContext.getSubject(), new PrivilegedAction<Session>() {
+
+ @Override
+ public Session run() {
+ try {
+ return repository.login(workspaceName);
+ } catch (NoSuchWorkspaceException e) {
+ throw new IllegalArgumentException("No workspace " + workspaceName + " available", e);
+ } catch (RepositoryException e) {
+ throw new RuntimeException("Cannot open data admin session", e);
+ }
+ }
+
+ });
+ }
+
+ /** Singleton. */
+ private CmsJcrUtils() {
+ }
+
+}
--- /dev/null
+package org.argeo.cms.jcr.acr;
+
+import java.util.Calendar;
+import java.util.Iterator;
+import java.util.Optional;
+
+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 javax.jcr.nodetype.NodeType;
+import javax.xml.namespace.QName;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.acr.spi.AbstractContent;
+import org.argeo.api.acr.spi.ProvidedSession;
+import org.argeo.jcr.Jcr;
+import org.argeo.jcr.JcrException;
+
+public class JcrContent extends AbstractContent {
+ private Node jcrNode;
+
+ private JcrContentProvider provider;
+ private ProvidedSession session;
+
+ protected JcrContent(ProvidedSession session, JcrContentProvider provider, Node node) {
+ this.session = session;
+ this.provider = provider;
+ this.jcrNode = node;
+ }
+
+ @Override
+ public QName getName() {
+ return session.parsePrefixedName(Jcr.getName(jcrNode));
+ }
+
+ @Override
+ public <A> Optional<A> get(QName key, Class<A> clss) {
+ if (isDefaultAttrTypeRequested(clss)) {
+ return Optional.of((A) get(jcrNode, key.toString()));
+ }
+ return Optional.of((A) Jcr.get(jcrNode, key.toString()));
+ }
+
+ @Override
+ public Iterator<Content> iterator() {
+ try {
+ return new JcrContentIterator(jcrNode.getNodes());
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot list children of " + jcrNode, e);
+ }
+ }
+
+ @Override
+ protected Iterable<QName> keys() {
+ return new Iterable<QName>() {
+
+ @Override
+ public Iterator<QName> iterator() {
+ try {
+ PropertyIterator propertyIterator = jcrNode.getProperties();
+ return new JcrKeyIterator(provider, propertyIterator);
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot retrive properties from " + jcrNode, e);
+ }
+ }
+ };
+ }
+
+ public Node getJcrNode() {
+ return jcrNode;
+ }
+
+ /** Cast to a standard Java object. */
+ static Object get(Node node, String property) {
+ try {
+ Value value = node.getProperty(property).getValue();
+ switch (value.getType()) {
+ case PropertyType.STRING:
+ return value.getString();
+ case PropertyType.DOUBLE:
+ return (Double) value.getDouble();
+ case PropertyType.LONG:
+ return (Long) value.getLong();
+ case PropertyType.BOOLEAN:
+ return (Boolean) value.getBoolean();
+ case PropertyType.DATE:
+ Calendar calendar = value.getDate();
+ return calendar.toInstant();
+ case PropertyType.BINARY:
+ throw new IllegalArgumentException("Binary is not supported as an attribute");
+ default:
+ return value.getString();
+ }
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot cast value from " + property + " of node " + node, e);
+ }
+ }
+
+ class JcrContentIterator implements Iterator<Content> {
+ private final NodeIterator nodeIterator;
+ // we keep track in order to be able to delete it
+ private JcrContent current = null;
+
+ protected JcrContentIterator(NodeIterator nodeIterator) {
+ this.nodeIterator = nodeIterator;
+ }
+
+ @Override
+ public boolean hasNext() {
+ return nodeIterator.hasNext();
+ }
+
+ @Override
+ public Content next() {
+ current = new JcrContent(session, provider, nodeIterator.nextNode());
+ return current;
+ }
+
+ @Override
+ public void remove() {
+ if (current != null) {
+ Jcr.remove(current.getJcrNode());
+ }
+ }
+
+ }
+
+ @Override
+ public Content getParent() {
+ return new JcrContent(session, provider, Jcr.getParent(getJcrNode()));
+ }
+
+ @Override
+ public Content add(QName name, QName... classes) {
+ if (classes.length > 0) {
+ QName primaryType = classes[0];
+ Node child = Jcr.addNode(getJcrNode(), name.toString(), primaryType.toString());
+ for (int i = 1; i < classes.length; i++) {
+ try {
+ child.addMixin(classes[i].toString());
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot add child to " + getJcrNode(), e);
+ }
+ }
+
+ } else {
+ Jcr.addNode(getJcrNode(), name.toString(), NodeType.NT_UNSTRUCTURED);
+ }
+ return null;
+ }
+
+ @Override
+ public void remove() {
+ Jcr.remove(getJcrNode());
+ }
+
+ @Override
+ protected void removeAttr(QName key) {
+ Property property = Jcr.getProperty(getJcrNode(), key.toString());
+ if (property != null) {
+ try {
+ property.remove();
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot remove property " + key + " from " + getJcrNode(), e);
+ }
+ }
+
+ }
+
+ class JcrKeyIterator implements Iterator<QName> {
+ private final JcrContentProvider contentSession;
+ private final PropertyIterator propertyIterator;
+
+ protected JcrKeyIterator(JcrContentProvider contentSession, PropertyIterator propertyIterator) {
+ this.contentSession = contentSession;
+ this.propertyIterator = propertyIterator;
+ }
+
+ @Override
+ public boolean hasNext() {
+ return propertyIterator.hasNext();
+ }
+
+ @Override
+ public QName next() {
+ Property property = null;
+ try {
+ property = propertyIterator.nextProperty();
+ // TODO map standard property names
+ return session.parsePrefixedName(property.getName());
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot retrieve property " + property, null);
+ }
+ }
+
+ }
+}
--- /dev/null
+package org.argeo.cms.jcr.acr;
+
+import java.util.Arrays;
+import java.util.Iterator;
+
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.xml.namespace.NamespaceContext;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.acr.spi.ContentProvider;
+import org.argeo.api.acr.spi.ProvidedSession;
+import org.argeo.cms.jcr.CmsJcrUtils;
+import org.argeo.jcr.JcrException;
+import org.argeo.jcr.JcrUtils;
+
+public class JcrContentProvider implements ContentProvider, NamespaceContext {
+ private Repository jcrRepository;
+ private Session adminSession;
+
+ public void init() {
+ adminSession = CmsJcrUtils.openDataAdminSession(jcrRepository, null);
+ }
+
+ public void destroy() {
+ JcrUtils.logoutQuietly(adminSession);
+ }
+
+ public void setJcrRepository(Repository jcrRepository) {
+ this.jcrRepository = jcrRepository;
+ }
+
+ @Override
+ public Content get(ProvidedSession session, String mountPath, String relativePath) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ /*
+ * NAMESPACE CONTEXT
+ */
+ @Override
+ public String getNamespaceURI(String prefix) {
+ try {
+ return adminSession.getNamespaceURI(prefix);
+ } catch (RepositoryException e) {
+ throw new JcrException(e);
+ }
+ }
+
+ @Override
+ public String getPrefix(String namespaceURI) {
+ try {
+ return adminSession.getNamespacePrefix(namespaceURI);
+ } catch (RepositoryException e) {
+ throw new JcrException(e);
+ }
+ }
+
+ @Override
+ public Iterator<String> getPrefixes(String namespaceURI) {
+ try {
+ return Arrays.asList(adminSession.getNamespacePrefix(namespaceURI)).iterator();
+ } catch (RepositoryException e) {
+ throw new JcrException(e);
+ }
+ }
+
+}
--- /dev/null
+<argeo = 'http://www.argeo.org/ns/argeo'>
+
+// GENERIC TYPES
+[argeo:remoteRepository] > nt:unstructured
+- argeo:uri (STRING)
+- argeo:userID (STRING)
++ argeo:password (argeo:encrypted)
+
+// TABULAR CONTENT
+[argeo:table] > nt:file
++ * (argeo:column) *
+
+[argeo:column] > mix:title
+- jcr:requiredType (STRING) = 'STRING'
+
+[argeo:csv] > nt:resource
+
+// CRYPTO
+[argeo:encrypted]
+mixin
+// initialization vector used by some algorithms
+- argeo:iv (BINARY)
+
+[argeo:pbeKeySpec]
+mixin
+- argeo:secretKeyFactory (STRING)
+- argeo:salt (BINARY)
+- argeo:iterationCount (LONG)
+- argeo:keyLength (LONG)
+- argeo:secretKeyEncryption (STRING)
+
+[argeo:pbeSpec] > argeo:pbeKeySpec
+mixin
+- argeo:cipher (STRING)
--- /dev/null
+// DN (see https://tools.ietf.org/html/rfc4514)
+<cn = 'http://www.argeo.org/ns/rfc4514/cn'>
+<l = 'http://www.argeo.org/ns/rfc4514/l'>
+<st = 'http://www.argeo.org/ns/rfc4514/st'>
+<o = 'http://www.argeo.org/ns/rfc4514/o'>
+<ou = 'http://www.argeo.org/ns/rfc4514/ou'>
+<c = 'http://www.argeo.org/ns/rfc4514/c'>
+<street = 'http://www.argeo.org/ns/rfc4514/street'>
+<dc = 'http://www.argeo.org/ns/rfc4514/dc'>
+<uid = 'http://www.argeo.org/ns/rfc4514/uid'>
--- /dev/null
+package org.argeo.cms.jcr.internal;
+
+import static org.argeo.cms.osgi.DataModelNamespace.CMS_DATA_MODEL_NAMESPACE;
+import static org.osgi.service.http.whiteboard.HttpWhiteboardConstants.HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.security.auth.callback.CallbackHandler;
+import javax.servlet.Servlet;
+
+import org.apache.jackrabbit.commons.cnd.CndImporter;
+import org.apache.jackrabbit.core.RepositoryContext;
+import org.apache.jackrabbit.core.RepositoryImpl;
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.api.cms.CmsDeployment;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.cms.ArgeoNames;
+import org.argeo.cms.internal.jcr.JcrInitUtils;
+import org.argeo.cms.jcr.CmsJcrUtils;
+import org.argeo.cms.jcr.internal.servlet.CmsRemotingServlet;
+import org.argeo.cms.jcr.internal.servlet.CmsWebDavServlet;
+import org.argeo.cms.jcr.internal.servlet.JcrHttpUtils;
+import org.argeo.cms.osgi.DataModelNamespace;
+import org.argeo.cms.security.CryptoKeyring;
+import org.argeo.cms.security.Keyring;
+import org.argeo.jcr.Jcr;
+import org.argeo.jcr.JcrException;
+import org.argeo.jcr.JcrUtils;
+import org.argeo.util.LangUtils;
+import org.argeo.util.naming.LdapAttrs;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.wiring.BundleCapability;
+import org.osgi.framework.wiring.BundleWire;
+import org.osgi.framework.wiring.BundleWiring;
+import org.osgi.service.cm.ManagedService;
+import org.osgi.service.http.whiteboard.HttpWhiteboardConstants;
+import org.osgi.util.tracker.ServiceTracker;
+
+/** Implementation of a CMS deployment. */
+public class CmsJcrDeployment {
+ private final CmsLog log = CmsLog.getLog(getClass());
+ private final BundleContext bc = FrameworkUtil.getBundle(getClass()).getBundleContext();
+
+ private DataModels dataModels;
+ private String webDavConfig = JcrHttpUtils.WEBDAV_CONFIG;
+
+ private boolean argeoDataModelExtensionsAvailable = false;
+
+ // Readiness
+ private boolean nodeAvailable = false;
+
+ CmsDeployment cmsDeployment;
+
+ public CmsJcrDeployment() {
+// initTrackers();
+ }
+
+ public void start() {
+ dataModels = new DataModels(bc);
+
+ ServiceTracker<?, ?> repoContextSt = new RepositoryContextStc();
+ repoContextSt.open();
+ //KernelUtils.asyncOpen(repoContextSt);
+
+// nodeDeployment = CmsJcrActivator.getService(NodeDeployment.class);
+
+ JcrInitUtils.addToDeployment(cmsDeployment);
+
+ }
+
+ public void stop() {
+// if (nodeHttp != null)
+// nodeHttp.destroy();
+
+ try {
+ for (ServiceReference<JackrabbitLocalRepository> sr : bc
+ .getServiceReferences(JackrabbitLocalRepository.class, null)) {
+ bc.getService(sr).destroy();
+ }
+ } catch (InvalidSyntaxException e1) {
+ log.error("Cannot clean repositories", e1);
+ }
+
+ }
+
+ public void setCmsDeployment(CmsDeployment cmsDeployment) {
+ this.cmsDeployment = cmsDeployment;
+ }
+
+ /**
+ * Checks whether the deployment is available according to expectations, and
+ * mark it as available.
+ */
+// private synchronized void checkReadiness() {
+// if (isAvailable())
+// return;
+// if (nodeAvailable && userAdminAvailable && (httpExpected ? httpAvailable : true)) {
+// String data = KernelUtils.getFrameworkProp(KernelUtils.OSGI_INSTANCE_AREA);
+// String state = KernelUtils.getFrameworkProp(KernelUtils.OSGI_CONFIGURATION_AREA);
+// availableSince = System.currentTimeMillis();
+// long jvmUptime = ManagementFactory.getRuntimeMXBean().getUptime();
+// String jvmUptimeStr = " in " + (jvmUptime / 1000) + "." + (jvmUptime % 1000) + "s";
+// log.info("## ARGEO NODE AVAILABLE" + (log.isDebugEnabled() ? jvmUptimeStr : "") + " ##");
+// if (log.isDebugEnabled()) {
+// log.debug("## state: " + state);
+// if (data != null)
+// log.debug("## data: " + data);
+// }
+// long begin = bc.getService(bc.getServiceReference(NodeState.class)).getAvailableSince();
+// long initDuration = System.currentTimeMillis() - begin;
+// if (log.isTraceEnabled())
+// log.trace("Kernel initialization took " + initDuration + "ms");
+// tributeToFreeSoftware(initDuration);
+// }
+// }
+
+ private void prepareNodeRepository(Repository deployedNodeRepository, List<String> publishAsLocalRepo) {
+// if (availableSince != null) {
+// throw new IllegalStateException("Deployment is already available");
+// }
+
+ // home
+ prepareDataModel(CmsConstants.NODE_REPOSITORY, deployedNodeRepository, publishAsLocalRepo);
+
+ // init from backup
+// if (deployConfig.isFirstInit()) {
+// Path restorePath = Paths.get(System.getProperty("user.dir"), "restore");
+// if (Files.exists(restorePath)) {
+// if (log.isDebugEnabled())
+// log.debug("Found backup " + restorePath + ", restoring it...");
+// LogicalRestore logicalRestore = new LogicalRestore(bc, deployedNodeRepository, restorePath);
+// KernelUtils.doAsDataAdmin(logicalRestore);
+// log.info("Restored backup from " + restorePath);
+// }
+// }
+
+ // init from repository
+ Collection<ServiceReference<Repository>> initRepositorySr;
+ try {
+ initRepositorySr = bc.getServiceReferences(Repository.class,
+ "(" + CmsConstants.CN + "=" + CmsConstants.NODE_INIT + ")");
+ } catch (InvalidSyntaxException e1) {
+ throw new IllegalArgumentException(e1);
+ }
+ Iterator<ServiceReference<Repository>> it = initRepositorySr.iterator();
+ while (it.hasNext()) {
+ ServiceReference<Repository> sr = it.next();
+ Object labeledUri = sr.getProperties().get(LdapAttrs.labeledURI.name());
+ Repository initRepository = bc.getService(sr);
+ if (log.isDebugEnabled())
+ log.debug("Found init repository " + labeledUri + ", copying it...");
+ initFromRepository(deployedNodeRepository, initRepository);
+ log.info("Node repository initialised from " + labeledUri);
+ }
+ }
+
+ /** Init from a (typically remote) repository. */
+ private void initFromRepository(Repository deployedNodeRepository, Repository initRepository) {
+ Session initSession = null;
+ try {
+ initSession = initRepository.login();
+ workspaces: for (String workspaceName : initSession.getWorkspace().getAccessibleWorkspaceNames()) {
+ if ("security".equals(workspaceName))
+ continue workspaces;
+ if (log.isDebugEnabled())
+ log.debug("Copying workspace " + workspaceName + " from init repository...");
+ long begin = System.currentTimeMillis();
+ Session targetSession = null;
+ Session sourceSession = null;
+ try {
+ try {
+ targetSession = CmsJcrUtils.openDataAdminSession(deployedNodeRepository, workspaceName);
+ } catch (IllegalArgumentException e) {// no such workspace
+ Session adminSession = CmsJcrUtils.openDataAdminSession(deployedNodeRepository, null);
+ try {
+ adminSession.getWorkspace().createWorkspace(workspaceName);
+ } finally {
+ Jcr.logout(adminSession);
+ }
+ targetSession = CmsJcrUtils.openDataAdminSession(deployedNodeRepository, workspaceName);
+ }
+ sourceSession = initRepository.login(workspaceName);
+// JcrUtils.copyWorkspaceXml(sourceSession, targetSession);
+ // TODO deal with referenceable nodes
+ JcrUtils.copy(sourceSession.getRootNode(), targetSession.getRootNode());
+ targetSession.save();
+ long duration = System.currentTimeMillis() - begin;
+ if (log.isDebugEnabled())
+ log.debug("Copied workspace " + workspaceName + " from init repository in " + (duration / 1000)
+ + " s");
+ } catch (Exception e) {
+ log.error("Cannot copy workspace " + workspaceName + " from init repository.", e);
+ } finally {
+ Jcr.logout(sourceSession);
+ Jcr.logout(targetSession);
+ }
+ }
+ } catch (RepositoryException e) {
+ throw new JcrException(e);
+ } finally {
+ Jcr.logout(initSession);
+ }
+ }
+
+ private void prepareHomeRepository(RepositoryImpl deployedRepository) {
+ Session adminSession = KernelUtils.openAdminSession(deployedRepository);
+ try {
+ argeoDataModelExtensionsAvailable = Arrays
+ .asList(adminSession.getWorkspace().getNamespaceRegistry().getURIs())
+ .contains(ArgeoNames.ARGEO_NAMESPACE);
+ } catch (RepositoryException e) {
+ log.warn("Cannot check whether Argeo namespace is registered assuming it isn't.", e);
+ argeoDataModelExtensionsAvailable = false;
+ } finally {
+ JcrUtils.logoutQuietly(adminSession);
+ }
+
+ // Publish home with the highest service ranking
+ Hashtable<String, Object> regProps = new Hashtable<>();
+ regProps.put(CmsConstants.CN, CmsConstants.EGO_REPOSITORY);
+ regProps.put(Constants.SERVICE_RANKING, Integer.MAX_VALUE);
+ Repository egoRepository = new EgoRepository(deployedRepository, false);
+ bc.registerService(Repository.class, egoRepository, regProps);
+ registerRepositoryServlets(CmsConstants.EGO_REPOSITORY, egoRepository);
+
+ // Keyring only if Argeo extensions are available
+ if (argeoDataModelExtensionsAvailable) {
+ new ServiceTracker<CallbackHandler, CallbackHandler>(bc, CallbackHandler.class, null) {
+
+ @Override
+ public CallbackHandler addingService(ServiceReference<CallbackHandler> reference) {
+ NodeKeyRing nodeKeyring = new NodeKeyRing(egoRepository);
+ CallbackHandler callbackHandler = bc.getService(reference);
+ nodeKeyring.setDefaultCallbackHandler(callbackHandler);
+ bc.registerService(LangUtils.names(Keyring.class, CryptoKeyring.class, ManagedService.class),
+ nodeKeyring, LangUtils.dict(Constants.SERVICE_PID, CmsConstants.NODE_KEYRING_PID));
+ return callbackHandler;
+ }
+
+ }.open();
+ }
+ }
+
+ /** Session is logged out. */
+ private void prepareDataModel(String cn, Repository repository, List<String> publishAsLocalRepo) {
+ Session adminSession = KernelUtils.openAdminSession(repository);
+ try {
+ Set<String> processed = new HashSet<String>();
+ bundles: for (Bundle bundle : bc.getBundles()) {
+ BundleWiring wiring = bundle.adapt(BundleWiring.class);
+ if (wiring == null)
+ continue bundles;
+ if (CmsConstants.NODE_REPOSITORY.equals(cn))// process all data models
+ processWiring(cn, adminSession, wiring, processed, false, publishAsLocalRepo);
+ else {
+ List<BundleCapability> capabilities = wiring.getCapabilities(CMS_DATA_MODEL_NAMESPACE);
+ for (BundleCapability capability : capabilities) {
+ String dataModelName = (String) capability.getAttributes().get(DataModelNamespace.NAME);
+ if (dataModelName.equals(cn))// process only own data model
+ processWiring(cn, adminSession, wiring, processed, false, publishAsLocalRepo);
+ }
+ }
+ }
+ } finally {
+ JcrUtils.logoutQuietly(adminSession);
+ }
+ }
+
+ private void processWiring(String cn, Session adminSession, BundleWiring wiring, Set<String> processed,
+ boolean importListedAbstractModels, List<String> publishAsLocalRepo) {
+ // recursively process requirements first
+ List<BundleWire> requiredWires = wiring.getRequiredWires(CMS_DATA_MODEL_NAMESPACE);
+ for (BundleWire wire : requiredWires) {
+ processWiring(cn, adminSession, wire.getProviderWiring(), processed, true, publishAsLocalRepo);
+ }
+
+ List<BundleCapability> capabilities = wiring.getCapabilities(CMS_DATA_MODEL_NAMESPACE);
+ capabilities: for (BundleCapability capability : capabilities) {
+ if (!importListedAbstractModels
+ && KernelUtils.asBoolean((String) capability.getAttributes().get(DataModelNamespace.ABSTRACT))) {
+ continue capabilities;
+ }
+ boolean publish = registerDataModelCapability(cn, adminSession, capability, processed);
+ if (publish)
+ publishAsLocalRepo.add((String) capability.getAttributes().get(DataModelNamespace.NAME));
+ }
+ }
+
+ private boolean registerDataModelCapability(String cn, Session adminSession, BundleCapability capability,
+ Set<String> processed) {
+ Map<String, Object> attrs = capability.getAttributes();
+ String name = (String) attrs.get(DataModelNamespace.NAME);
+ if (processed.contains(name)) {
+ if (log.isTraceEnabled())
+ log.trace("Data model " + name + " has already been processed");
+ return false;
+ }
+
+ // CND
+ String path = (String) attrs.get(DataModelNamespace.CND);
+ if (path != null) {
+ File dataModel = bc.getBundle().getDataFile("dataModels/" + path);
+ if (!dataModel.exists()) {
+ URL url = capability.getRevision().getBundle().getResource(path);
+ if (url == null)
+ throw new IllegalArgumentException("No data model '" + name + "' found under path " + path);
+ try (Reader reader = new InputStreamReader(url.openStream())) {
+ CndImporter.registerNodeTypes(reader, adminSession, true);
+ processed.add(name);
+ dataModel.getParentFile().mkdirs();
+ dataModel.createNewFile();
+ if (log.isDebugEnabled())
+ log.debug("Registered CND " + url);
+ } catch (Exception e) {
+ log.error("Cannot import CND " + url, e);
+ }
+ }
+ }
+
+ if (KernelUtils.asBoolean((String) attrs.get(DataModelNamespace.ABSTRACT)))
+ return false;
+ // Non abstract
+ boolean isStandalone = isStandalone(name);
+ boolean publishLocalRepo;
+ if (isStandalone && name.equals(cn))// includes the node itself
+ publishLocalRepo = true;
+ else if (!isStandalone && cn.equals(CmsConstants.NODE_REPOSITORY))
+ publishLocalRepo = true;
+ else
+ publishLocalRepo = false;
+
+ return publishLocalRepo;
+ }
+
+ boolean isStandalone(String dataModelName) {
+ return cmsDeployment.getProps(CmsConstants.NODE_REPOS_FACTORY_PID, dataModelName) != null;
+ }
+
+ private void publishLocalRepo(String dataModelName, Repository repository) {
+ Hashtable<String, Object> properties = new Hashtable<>();
+ properties.put(CmsConstants.CN, dataModelName);
+ LocalRepository localRepository;
+ String[] classes;
+ if (repository instanceof RepositoryImpl) {
+ localRepository = new JackrabbitLocalRepository((RepositoryImpl) repository, dataModelName);
+ classes = new String[] { Repository.class.getName(), LocalRepository.class.getName(),
+ JackrabbitLocalRepository.class.getName() };
+ } else {
+ localRepository = new LocalRepository(repository, dataModelName);
+ classes = new String[] { Repository.class.getName(), LocalRepository.class.getName() };
+ }
+ bc.registerService(classes, localRepository, properties);
+
+ // TODO make it configurable
+ registerRepositoryServlets(dataModelName, localRepository);
+ if (log.isTraceEnabled())
+ log.trace("Published data model " + dataModelName);
+ }
+
+// @Override
+// public synchronized Long getAvailableSince() {
+// return availableSince;
+// }
+//
+// public synchronized boolean isAvailable() {
+// return availableSince != null;
+// }
+
+ protected void registerRepositoryServlets(String alias, Repository repository) {
+ // FIXME re-enable it with a proper class loader
+// registerRemotingServlet(alias, repository);
+// registerWebdavServlet(alias, repository);
+ }
+
+ protected void registerWebdavServlet(String alias, Repository repository) {
+ CmsWebDavServlet webdavServlet = new CmsWebDavServlet(alias, repository);
+ Hashtable<String, String> ip = new Hashtable<>();
+ ip.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX + CmsWebDavServlet.INIT_PARAM_RESOURCE_CONFIG, webDavConfig);
+ ip.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX + CmsWebDavServlet.INIT_PARAM_RESOURCE_PATH_PREFIX,
+ "/" + alias);
+
+ ip.put(HttpWhiteboardConstants.HTTP_WHITEBOARD_SERVLET_PATTERN, "/" + alias + "/*");
+ ip.put(HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_SELECT,
+ "(" + HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_PATH + "=" + CmsConstants.PATH_DATA + ")");
+ bc.registerService(Servlet.class, webdavServlet, ip);
+ }
+
+ protected void registerRemotingServlet(String alias, Repository repository) {
+ CmsRemotingServlet remotingServlet = new CmsRemotingServlet(alias, repository);
+ Hashtable<String, String> ip = new Hashtable<>();
+ ip.put(CmsConstants.CN, alias);
+ // Properties ip = new Properties();
+ ip.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX + CmsRemotingServlet.INIT_PARAM_RESOURCE_PATH_PREFIX,
+ "/" + alias);
+ ip.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX + CmsRemotingServlet.INIT_PARAM_AUTHENTICATE_HEADER,
+ "Negotiate");
+
+ // Looks like a bug in Jackrabbit remoting init
+ Path tmpDir;
+ try {
+ tmpDir = Files.createTempDirectory("remoting_" + alias);
+ } catch (IOException e) {
+ throw new RuntimeException("Cannot create temp directory for remoting servlet", e);
+ }
+ ip.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX + CmsRemotingServlet.INIT_PARAM_HOME, tmpDir.toString());
+ ip.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX + CmsRemotingServlet.INIT_PARAM_TMP_DIRECTORY,
+ "remoting_" + alias);
+ ip.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX + CmsRemotingServlet.INIT_PARAM_PROTECTED_HANDLERS_CONFIG,
+ JcrHttpUtils.DEFAULT_PROTECTED_HANDLERS);
+ ip.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX + CmsRemotingServlet.INIT_PARAM_CREATE_ABSOLUTE_URI, "false");
+
+ ip.put(HttpWhiteboardConstants.HTTP_WHITEBOARD_SERVLET_PATTERN, "/" + alias + "/*");
+ ip.put(HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_SELECT,
+ "(" + HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_PATH + "=" + CmsConstants.PATH_JCR + ")");
+ bc.registerService(Servlet.class, remotingServlet, ip);
+ }
+
+ private class RepositoryContextStc extends ServiceTracker<RepositoryContext, RepositoryContext> {
+
+ public RepositoryContextStc() {
+ super(bc, RepositoryContext.class, null);
+ }
+
+ @Override
+ public RepositoryContext addingService(ServiceReference<RepositoryContext> reference) {
+ RepositoryContext repoContext = bc.getService(reference);
+ String cn = (String) reference.getProperty(CmsConstants.CN);
+ if (cn != null) {
+ List<String> publishAsLocalRepo = new ArrayList<>();
+ if (cn.equals(CmsConstants.NODE_REPOSITORY)) {
+// JackrabbitDataModelMigration.clearRepositoryCaches(repoContext.getRepositoryConfig());
+ prepareNodeRepository(repoContext.getRepository(), publishAsLocalRepo);
+ // TODO separate home repository
+ prepareHomeRepository(repoContext.getRepository());
+ registerRepositoryServlets(cn, repoContext.getRepository());
+ nodeAvailable = true;
+// checkReadiness();
+ } else {
+ prepareDataModel(cn, repoContext.getRepository(), publishAsLocalRepo);
+ }
+ // Publish all at once, so that bundles with multiple CNDs are consistent
+ for (String dataModelName : publishAsLocalRepo)
+ publishLocalRepo(dataModelName, repoContext.getRepository());
+ }
+ return repoContext;
+ }
+
+ @Override
+ public void modifiedService(ServiceReference<RepositoryContext> reference, RepositoryContext service) {
+ }
+
+ @Override
+ public void removedService(ServiceReference<RepositoryContext> reference, RepositoryContext service) {
+ }
+
+ }
+
+}
--- /dev/null
+package org.argeo.cms.jcr.internal;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystemAlreadyExistsException;
+import java.nio.file.Path;
+import java.nio.file.spi.FileSystemProvider;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.Repository;
+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.auth.CurrentUser;
+import org.argeo.cms.jcr.CmsJcrUtils;
+import org.argeo.jackrabbit.fs.AbstractJackrabbitFsProvider;
+import org.argeo.jcr.fs.JcrFileSystem;
+import org.argeo.jcr.fs.JcrFileSystemProvider;
+import org.argeo.jcr.fs.JcrFsException;
+
+/** Implementation of an {@link FileSystemProvider} based on Jackrabbit. */
+public class CmsJcrFsProvider extends AbstractJackrabbitFsProvider {
+ private Map<String, CmsFileSystem> fileSystems = new HashMap<>();
+
+ private RepositoryFactory repositoryFactory;
+ private Repository repository;
+
+ @Override
+ public String getScheme() {
+ return CmsConstants.SCHEME_NODE;
+ }
+
+ @Override
+ public FileSystem newFileSystem(URI uri, Map<String, ?> env) throws IOException {
+// BundleContext bc = FrameworkUtil.getBundle(CmsJcrFsProvider.class).getBundleContext();
+ String username = CurrentUser.getUsername();
+ if (username == null) {
+ // TODO deal with anonymous
+ return null;
+ }
+ if (fileSystems.containsKey(username))
+ throw new FileSystemAlreadyExistsException("CMS file system already exists for user " + username);
+
+ try {
+ String host = uri.getHost();
+ if (host != null && !host.trim().equals("")) {
+ URI repoUri = new URI("http", uri.getUserInfo(), uri.getHost(), uri.getPort(), "/jcr/node", null, null);
+// RepositoryFactory repositoryFactory = bc.getService(bc.getServiceReference(RepositoryFactory.class));
+ Repository repository = CmsJcrUtils.getRepositoryByUri(repositoryFactory, repoUri.toString());
+ CmsFileSystem fileSystem = new CmsFileSystem(this, repository);
+ fileSystems.put(username, fileSystem);
+ return fileSystem;
+ } else {
+// Repository repository = bc.getService(
+// bc.getServiceReferences(Repository.class, "(cn=" + CmsConstants.EGO_REPOSITORY + ")")
+// .iterator().next());
+
+ // Session session = repository.login();
+ CmsFileSystem fileSystem = new CmsFileSystem(this, repository);
+ fileSystems.put(username, fileSystem);
+ return fileSystem;
+ }
+ } catch (URISyntaxException e) {
+ throw new IllegalArgumentException("Cannot open file system " + uri + " for user " + username, e);
+ }
+ }
+
+ @Override
+ public FileSystem getFileSystem(URI uri) {
+ return currentUserFileSystem();
+ }
+
+ @Override
+ public Path getPath(URI uri) {
+ JcrFileSystem fileSystem = currentUserFileSystem();
+ String path = uri.getPath();
+ if (fileSystem == null)
+ try {
+ fileSystem = (JcrFileSystem) newFileSystem(uri, new HashMap<String, Object>());
+ } catch (IOException e) {
+ throw new JcrFsException("Could not autocreate file system", e);
+ }
+ return fileSystem.getPath(path);
+ }
+
+ protected JcrFileSystem currentUserFileSystem() {
+ String username = CurrentUser.getUsername();
+ return fileSystems.get(username);
+ }
+
+ public Node getUserHome(Repository repository) {
+ try {
+ Session session = repository.login(CmsConstants.HOME_WORKSPACE);
+ return CmsJcrUtils.getUserHome(session);
+ } catch (RepositoryException e) {
+ throw new IllegalStateException("Cannot get user home", e);
+ }
+ }
+
+ public void setRepositoryFactory(RepositoryFactory repositoryFactory) {
+ this.repositoryFactory = repositoryFactory;
+ }
+
+ public void setRepository(Repository repository) {
+ this.repository = repository;
+ }
+
+ static class CmsFileSystem extends JcrFileSystem {
+ public CmsFileSystem(JcrFileSystemProvider provider, Repository repository) throws IOException {
+ super(provider, repository);
+ }
+
+ public boolean skipNode(Node node) throws RepositoryException {
+// if (node.isNodeType(NodeType.NT_HIERARCHY_NODE) || node.isNodeType(NodeTypes.NODE_USER_HOME)
+// || node.isNodeType(NodeTypes.NODE_GROUP_HOME))
+ if (node.isNodeType(NodeType.NT_HIERARCHY_NODE))
+ return false;
+ // FIXME Better identifies home
+ if (node.hasProperty(Property.JCR_ID))
+ return false;
+ return true;
+ }
+
+ }
+}
--- /dev/null
+package org.argeo.cms.jcr.internal;
+
+import java.nio.file.Path;
+
+/** Centralises access to the default node deployment directories. */
+public class CmsPaths {
+ public static Path getRepoDirPath(String cn) {
+ return KernelUtils.getOsgiInstancePath(KernelConstants.DIR_REPOS + '/' + cn);
+ }
+
+ public static Path getRepoIndexesBase() {
+ return KernelUtils.getOsgiInstancePath(KernelConstants.DIR_INDEXES);
+ }
+
+ /** Singleton. */
+ private CmsPaths() {
+ }
+}
--- /dev/null
+package org.argeo.cms.jcr.internal;
+
+import java.util.GregorianCalendar;
+import java.util.concurrent.LinkedBlockingDeque;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.Value;
+import javax.jcr.nodetype.NodeType;
+import javax.jcr.observation.Event;
+import javax.jcr.observation.EventIterator;
+import javax.jcr.observation.EventListener;
+import javax.jcr.version.VersionManager;
+
+import org.apache.jackrabbit.api.JackrabbitValue;
+import org.apache.jackrabbit.core.RepositoryImpl;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.jcr.JcrUtils;
+
+/** Ensure consistency of files, folder and last modified nodes. */
+class CmsWorkspaceIndexer implements EventListener {
+ private final static CmsLog log = CmsLog.getLog(CmsWorkspaceIndexer.class);
+
+// private final static String MIX_ETAG = "mix:etag";
+ private final static String JCR_ETAG = "jcr:etag";
+// private final static String JCR_LAST_MODIFIED = "jcr:lastModified";
+// private final static String JCR_LAST_MODIFIED_BY = "jcr:lastModifiedBy";
+// private final static String JCR_MIXIN_TYPES = "jcr:mixinTypes";
+ private final static String JCR_DATA = "jcr:data";
+ private final static String JCR_CONTENT = "jcr:data";
+
+ private String cn;
+ private String workspaceName;
+ private RepositoryImpl repositoryImpl;
+ private Session session;
+ private VersionManager versionManager;
+
+ private LinkedBlockingDeque<Event> toProcess = new LinkedBlockingDeque<>();
+ private IndexingThread indexingThread;
+ private AtomicBoolean stopping = new AtomicBoolean(false);
+
+ public CmsWorkspaceIndexer(RepositoryImpl repositoryImpl, String cn, String workspaceName)
+ throws RepositoryException {
+ this.cn = cn;
+ this.workspaceName = workspaceName;
+ this.repositoryImpl = repositoryImpl;
+ }
+
+ public void init() {
+ session = KernelUtils.openAdminSession(repositoryImpl, workspaceName);
+ try {
+ String[] nodeTypes = { NodeType.NT_FILE, NodeType.MIX_LAST_MODIFIED };
+ session.getWorkspace().getObservationManager().addEventListener(this,
+ Event.NODE_ADDED | Event.PROPERTY_CHANGED, "/", true, null, nodeTypes, true);
+ versionManager = session.getWorkspace().getVersionManager();
+
+ indexingThread = new IndexingThread();
+ indexingThread.start();
+ } catch (RepositoryException e1) {
+ throw new IllegalStateException(e1);
+ }
+ }
+
+ public void destroy() {
+ stopping.set(true);
+ indexingThread.interrupt();
+ // TODO make it configurable
+ try {
+ indexingThread.join(10 * 60 * 1000);
+ } catch (InterruptedException e1) {
+ log.warn("Indexing thread interrupted. Will log out session.");
+ }
+
+ try {
+ session.getWorkspace().getObservationManager().removeEventListener(this);
+ } catch (RepositoryException e) {
+ if (log.isTraceEnabled())
+ log.warn("Cannot unregistered JCR event listener", e);
+ } finally {
+ JcrUtils.logoutQuietly(session);
+ }
+ }
+
+ private synchronized void processEvents(EventIterator events) {
+ long begin = System.currentTimeMillis();
+ long count = 0;
+ while (events.hasNext()) {
+ Event event = events.nextEvent();
+ try {
+ toProcess.put(event);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+// processEvent(event);
+ count++;
+ }
+ long duration = System.currentTimeMillis() - begin;
+ if (log.isTraceEnabled())
+ log.trace("Processed " + count + " events in " + duration + " ms");
+ notifyAll();
+ }
+
+ protected synchronized void processEvent(Event event) {
+ try {
+ String eventPath = event.getPath();
+ if (event.getType() == Event.NODE_ADDED) {
+ if (!versionManager.isCheckedOut(eventPath))
+ return;// ignore checked-in nodes
+ if (log.isTraceEnabled())
+ log.trace("NODE_ADDED " + eventPath);
+// session.refresh(true);
+ session.refresh(false);
+ Node node = session.getNode(eventPath);
+ Node parentNode = node.getParent();
+ if (parentNode.isNodeType(NodeType.NT_FILE)) {
+ if (node.isNodeType(NodeType.NT_UNSTRUCTURED)) {
+ if (!node.isNodeType(NodeType.MIX_LAST_MODIFIED))
+ node.addMixin(NodeType.MIX_LAST_MODIFIED);
+ Property property = node.getProperty(Property.JCR_DATA);
+ String etag = toEtag(property.getValue());
+ session.save();
+ node.setProperty(JCR_ETAG, etag);
+ if (log.isTraceEnabled())
+ log.trace("ETag and last modified added to new " + node);
+ } else if (node.isNodeType(NodeType.NT_RESOURCE)) {
+// if (!node.isNodeType(MIX_ETAG))
+// node.addMixin(MIX_ETAG);
+// session.save();
+// Property property = node.getProperty(Property.JCR_DATA);
+// String etag = toEtag(property.getValue());
+// node.setProperty(JCR_ETAG, etag);
+// session.save();
+ }
+// setLastModifiedRecursive(parentNode, event);
+// session.save();
+// if (log.isTraceEnabled())
+// log.trace("ETag and last modified added to new " + node);
+ }
+
+// if (node.isNodeType(NodeType.NT_FOLDER)) {
+// setLastModifiedRecursive(node, event);
+// session.save();
+// if (log.isTraceEnabled())
+// log.trace("Last modified added to new " + node);
+// }
+ } else if (event.getType() == Event.PROPERTY_CHANGED) {
+ String propertyName = extractItemName(eventPath);
+ // skip if last modified properties are explicitly set
+ if (!propertyName.equals(JCR_DATA))
+ return;
+// if (propertyName.equals(JCR_LAST_MODIFIED))
+// return;
+// if (propertyName.equals(JCR_LAST_MODIFIED_BY))
+// return;
+// if (propertyName.equals(JCR_MIXIN_TYPES))
+// return;
+// if (propertyName.equals(JCR_ETAG))
+// return;
+
+ if (log.isTraceEnabled())
+ log.trace("PROPERTY_CHANGED " + eventPath);
+
+ if (!session.propertyExists(eventPath))
+ return;
+ session.refresh(false);
+ Property property = session.getProperty(eventPath);
+ Node node = property.getParent();
+ if (property.getType() == PropertyType.BINARY && propertyName.equals(JCR_DATA)
+ && node.isNodeType(NodeType.NT_UNSTRUCTURED)) {
+ String etag = toEtag(property.getValue());
+ node.setProperty(JCR_ETAG, etag);
+ Node parentNode = node.getParent();
+ if (parentNode.isNodeType(NodeType.MIX_LAST_MODIFIED)) {
+ setLastModified(parentNode, event);
+ }
+ if (log.isTraceEnabled())
+ log.trace("ETag and last modified updated for " + node);
+ }
+// setLastModified(node, event);
+// session.save();
+// if (log.isTraceEnabled())
+// log.trace("ETag and last modified updated for " + node);
+ } else if (event.getType() == Event.NODE_REMOVED) {
+ String removeNodePath = eventPath;
+ String nodeName = extractItemName(eventPath);
+ if (JCR_CONTENT.equals(nodeName)) // parent is a file, deleted anyhow
+ return;
+ if (log.isTraceEnabled())
+ log.trace("NODE_REMOVED " + eventPath);
+// String parentPath = JcrUtils.parentPath(removeNodePath);
+// session.refresh(true);
+// setLastModified(parentPath, event);
+// session.save();
+ if (log.isTraceEnabled())
+ log.trace("Last modified updated for parents of removed " + removeNodePath);
+ }
+ } catch (Exception e) {
+ if (log.isTraceEnabled())
+ log.warn("Cannot process event " + event, e);
+ } finally {
+// try {
+// session.refresh(true);
+// if (session.hasPendingChanges())
+// session.save();
+//// session.refresh(false);
+// } catch (RepositoryException e) {
+// if (log.isTraceEnabled())
+// log.warn("Cannot refresh JCR session", e);
+// }
+ }
+
+ }
+
+ private String extractItemName(String path) {
+ if (path == null || path.length() <= 1)
+ return null;
+ int lastIndex = path.lastIndexOf('/');
+ if (lastIndex >= 0) {
+ return path.substring(lastIndex + 1);
+ } else {
+ return path;
+ }
+ }
+
+ @Override
+ public void onEvent(EventIterator events) {
+ processEvents(events);
+// Runnable toRun = new Runnable() {
+//
+// @Override
+// public void run() {
+// processEvents(events);
+// }
+// };
+// Future<?> future = Activator.getInternalExecutorService().submit(toRun);
+// try {
+// // make the call synchronous
+// future.get(60, TimeUnit.SECONDS);
+// } catch (TimeoutException | ExecutionException | InterruptedException e) {
+// // silent
+// }
+ }
+
+ static String toEtag(Value v) {
+ if (v instanceof JackrabbitValue) {
+ JackrabbitValue value = (JackrabbitValue) v;
+ return '\"' + value.getContentIdentity() + '\"';
+ } else {
+ return null;
+ }
+
+ }
+
+ protected synchronized void setLastModified(Node node, Event event) throws RepositoryException {
+ GregorianCalendar calendar = new GregorianCalendar();
+ calendar.setTimeInMillis(event.getDate());
+ node.setProperty(Property.JCR_LAST_MODIFIED, calendar);
+ node.setProperty(Property.JCR_LAST_MODIFIED_BY, event.getUserID());
+ if (log.isTraceEnabled())
+ log.trace("Last modified set on " + node);
+ }
+
+ /** Recursively set the last updated time on parents. */
+ protected synchronized void setLastModifiedRecursive(Node node, Event event) throws RepositoryException {
+ if (versionManager.isCheckedOut(node.getPath())) {
+ if (node.isNodeType(NodeType.MIX_LAST_MODIFIED)) {
+ setLastModified(node, event);
+ }
+ if (node.isNodeType(NodeType.NT_FOLDER) && !node.isNodeType(NodeType.MIX_LAST_MODIFIED)) {
+ node.addMixin(NodeType.MIX_LAST_MODIFIED);
+ if (log.isTraceEnabled())
+ log.trace("Last modified mix-in added to " + node);
+ }
+
+ }
+
+ // end condition
+ if (node.getDepth() == 0) {
+// try {
+// node.getSession().save();
+// } catch (RepositoryException e) {
+// log.warn("Cannot index workspace", e);
+// }
+ return;
+ } else {
+ Node parent = node.getParent();
+ setLastModifiedRecursive(parent, event);
+ }
+ }
+
+ /**
+ * Recursively set the last updated time on parents. Useful to use paths when
+ * dealing with deletions.
+ */
+ protected synchronized void setLastModifiedRecursive(String path, Event event) throws RepositoryException {
+ // root node will always exist, so end condition is delegated to the other
+ // recursive setLastModified method
+ if (session.nodeExists(path)) {
+ setLastModifiedRecursive(session.getNode(path), event);
+ } else {
+ setLastModifiedRecursive(JcrUtils.parentPath(path), event);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "Indexer for workspace " + workspaceName + " of repository " + cn;
+ }
+
+ class IndexingThread extends Thread {
+
+ public IndexingThread() {
+ super(CmsWorkspaceIndexer.this.toString());
+ // TODO Auto-generated constructor stub
+ }
+
+ @Override
+ public void run() {
+ life: while (session != null && session.isLive()) {
+ try {
+ Event nextEvent = toProcess.take();
+ processEvent(nextEvent);
+ } catch (InterruptedException e) {
+ // silent
+ interrupted();
+ }
+
+ if (stopping.get() && toProcess.isEmpty()) {
+ break life;
+ }
+ }
+ if (log.isDebugEnabled())
+ log.debug(CmsWorkspaceIndexer.this.toString() + " has shut down.");
+ }
+
+ }
+
+}
\ No newline at end of file
--- /dev/null
+package org.argeo.cms.jcr.internal;
+
+import static org.argeo.cms.osgi.DataModelNamespace.CMS_DATA_MODEL_NAMESPACE;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+import org.argeo.api.cms.CmsLog;
+import org.argeo.cms.osgi.DataModelNamespace;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleEvent;
+import org.osgi.framework.BundleListener;
+import org.osgi.framework.wiring.BundleCapability;
+import org.osgi.framework.wiring.BundleWire;
+import org.osgi.framework.wiring.BundleWiring;
+
+class DataModels implements BundleListener {
+ private final static CmsLog log = CmsLog.getLog(DataModels.class);
+
+ private Map<String, DataModel> dataModels = new TreeMap<>();
+
+ public DataModels(BundleContext bc) {
+ for (Bundle bundle : bc.getBundles())
+ processBundle(bundle, null);
+ bc.addBundleListener(this);
+ }
+
+ public List<DataModel> getNonAbstractDataModels() {
+ List<DataModel> res = new ArrayList<>();
+ for (String name : dataModels.keySet()) {
+ DataModel dataModel = dataModels.get(name);
+ if (!dataModel.isAbstract())
+ res.add(dataModel);
+ }
+ // TODO reorder?
+ return res;
+ }
+
+ @Override
+ public void bundleChanged(BundleEvent event) {
+ if (event.getType() == Bundle.RESOLVED) {
+ processBundle(event.getBundle(), null);
+ } else if (event.getType() == Bundle.UNINSTALLED) {
+ BundleWiring wiring = event.getBundle().adapt(BundleWiring.class);
+ List<BundleCapability> providedDataModels = wiring.getCapabilities(CMS_DATA_MODEL_NAMESPACE);
+ if (providedDataModels.size() == 0)
+ return;
+ for (BundleCapability bundleCapability : providedDataModels) {
+ dataModels.remove(bundleCapability.getAttributes().get(DataModelNamespace.NAME));
+ }
+ }
+
+ }
+
+ protected void processBundle(Bundle bundle, List<Bundle> scannedBundles) {
+ if (scannedBundles != null && scannedBundles.contains(bundle))
+ throw new IllegalStateException("Cycle in CMS data model requirements for " + bundle);
+ BundleWiring wiring = bundle.adapt(BundleWiring.class);
+ if (wiring == null) {
+ int bundleState = bundle.getState();
+ if (bundleState != Bundle.INSTALLED && bundleState != Bundle.UNINSTALLED) {// ignore unresolved bundles
+ log.warn("Bundle " + bundle.getSymbolicName() + " #" + bundle.getBundleId() + " ("
+ + bundle.getLocation() + ") cannot be adapted to a wiring");
+ } else {
+ if (log.isTraceEnabled())
+ log.warn("Bundle " + bundle.getSymbolicName() + " is not resolved.");
+ }
+ return;
+ }
+ List<BundleCapability> providedDataModels = wiring.getCapabilities(CMS_DATA_MODEL_NAMESPACE);
+ if (providedDataModels.size() == 0)
+ return;
+ List<BundleWire> requiredDataModels = wiring.getRequiredWires(CMS_DATA_MODEL_NAMESPACE);
+ // process requirements first
+ for (BundleWire bundleWire : requiredDataModels) {
+ List<Bundle> nextScannedBundles = new ArrayList<>();
+ if (scannedBundles != null)
+ nextScannedBundles.addAll(scannedBundles);
+ nextScannedBundles.add(bundle);
+ Bundle providerBundle = bundleWire.getProvider().getBundle();
+ processBundle(providerBundle, nextScannedBundles);
+ }
+ for (BundleCapability bundleCapability : providedDataModels) {
+ String name = (String) bundleCapability.getAttributes().get(DataModelNamespace.NAME);
+ assert name != null;
+ if (!dataModels.containsKey(name)) {
+ DataModel dataModel = new DataModel(name, bundleCapability, requiredDataModels);
+ dataModels.put(dataModel.getName(), dataModel);
+ }
+ }
+ }
+
+ /** Return a negative depth if dataModel is required by ref, 0 otherwise. */
+ static int required(DataModel ref, DataModel dataModel, int depth) {
+ for (DataModel dm : ref.getRequired()) {
+ if (dm.equals(dataModel))// found here
+ return depth - 1;
+ int d = required(dm, dataModel, depth - 1);
+ if (d != 0)// found deeper
+ return d;
+ }
+ return 0;// not found
+ }
+
+ class DataModel {
+ private final String name;
+ private final boolean abstrct;
+ // private final boolean standalone;
+ private final String cnd;
+ private final List<DataModel> required;
+
+ private DataModel(String name, BundleCapability bundleCapability, List<BundleWire> requiredDataModels) {
+ assert CMS_DATA_MODEL_NAMESPACE.equals(bundleCapability.getNamespace());
+ this.name = name;
+ Map<String, Object> attrs = bundleCapability.getAttributes();
+ abstrct = KernelUtils.asBoolean((String) attrs.get(DataModelNamespace.ABSTRACT));
+ // standalone = KernelUtils.asBoolean((String)
+ // attrs.get(DataModelNamespace.CAPABILITY_STANDALONE_ATTRIBUTE));
+ cnd = (String) attrs.get(DataModelNamespace.CND);
+ List<DataModel> req = new ArrayList<>();
+ for (BundleWire wire : requiredDataModels) {
+ String requiredDataModelName = (String) wire.getCapability().getAttributes()
+ .get(DataModelNamespace.NAME);
+ assert requiredDataModelName != null;
+ DataModel requiredDataModel = dataModels.get(requiredDataModelName);
+ if (requiredDataModel == null)
+ throw new IllegalStateException("No required data model " + requiredDataModelName);
+ req.add(requiredDataModel);
+ }
+ required = Collections.unmodifiableList(req);
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public boolean isAbstract() {
+ return abstrct;
+ }
+
+ // public boolean isStandalone() {
+ // return !isAbstract();
+ // }
+
+ public String getCnd() {
+ return cnd;
+ }
+
+ public List<DataModel> getRequired() {
+ return required;
+ }
+
+ // @Override
+ // public int compareTo(DataModel o) {
+ // if (equals(o))
+ // return 0;
+ // int res = required(this, o, 0);
+ // if (res != 0)
+ // return res;
+ // // the other way round
+ // res = required(o, this, 0);
+ // if (res != 0)
+ // return -res;
+ // return 0;
+ // }
+
+ @Override
+ public int hashCode() {
+ return name.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof DataModel)
+ return ((DataModel) obj).name.equals(name);
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return "Data model " + name;
+ }
+
+ }
+
+}
--- /dev/null
+package org.argeo.cms.jcr.internal;
+
+import java.security.PrivilegedAction;
+import java.text.SimpleDateFormat;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.nodetype.NodeType;
+import javax.jcr.security.Privilege;
+import javax.naming.InvalidNameException;
+import javax.naming.ldap.LdapName;
+import javax.security.auth.Subject;
+import javax.security.auth.login.LoginContext;
+
+import org.argeo.api.cms.CmsAuth;
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.cms.CmsException;
+import org.argeo.cms.jcr.CmsJcrUtils;
+import org.argeo.jcr.JcrException;
+import org.argeo.jcr.JcrRepositoryWrapper;
+import org.argeo.jcr.JcrUtils;
+
+/**
+ * Make sure each user has a home directory available.
+ */
+class EgoRepository extends JcrRepositoryWrapper implements KernelConstants {
+
+ /** The home base path. */
+// private String homeBasePath = KernelConstants.DEFAULT_HOME_BASE_PATH;
+// private String usersBasePath = KernelConstants.DEFAULT_USERS_BASE_PATH;
+// private String groupsBasePath = KernelConstants.DEFAULT_GROUPS_BASE_PATH;
+
+ private Set<String> checkedUsers = new HashSet<String>();
+
+ private SimpleDateFormat usersDatePath = new SimpleDateFormat("YYYY/MM");
+
+ private String defaultHomeWorkspace = CmsConstants.HOME_WORKSPACE;
+ private String defaultGroupsWorkspace = CmsConstants.SRV_WORKSPACE;
+// private String defaultGuestsWorkspace = NodeConstants.GUESTS_WORKSPACE;
+ private final boolean remote;
+
+ public EgoRepository(Repository repository, boolean remote) {
+ super(repository);
+ this.remote = remote;
+ putDescriptor(CmsConstants.CN, CmsConstants.EGO_REPOSITORY);
+ if (!remote) {
+ LoginContext lc;
+ try {
+ lc = new LoginContext(CmsAuth.LOGIN_CONTEXT_DATA_ADMIN);
+ lc.login();
+ } catch (javax.security.auth.login.LoginException e1) {
+ throw new IllegalStateException("Cannot login as system", e1);
+ }
+ Subject.doAs(lc.getSubject(), new PrivilegedAction<Void>() {
+
+ @Override
+ public Void run() {
+ loginOrCreateWorkspace(defaultHomeWorkspace);
+ loginOrCreateWorkspace(defaultGroupsWorkspace);
+ return null;
+ }
+
+ });
+ }
+ }
+
+ private void loginOrCreateWorkspace(String workspace) {
+ Session adminSession = null;
+ try {
+ adminSession = JcrUtils.loginOrCreateWorkspace(getRepository(workspace), workspace);
+// JcrUtils.addPrivilege(adminSession, "/", NodeConstants.ROLE_USER, Privilege.JCR_READ);
+
+// initJcr(adminSession);
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot init JCR home", e);
+ } finally {
+ JcrUtils.logoutQuietly(adminSession);
+ }
+ }
+
+// @Override
+// public Session login(Credentials credentials, String workspaceName)
+// throws LoginException, NoSuchWorkspaceException, RepositoryException {
+// if (workspaceName == null) {
+// return super.login(credentials, getUserHomeWorkspace());
+// } else {
+// return super.login(credentials, workspaceName);
+// }
+// }
+
+ protected String getUserHomeWorkspace() {
+ // TODO base on JAAS Subject metadata
+ return defaultHomeWorkspace;
+ }
+
+ protected String getGroupsWorkspace() {
+ // TODO base on JAAS Subject metadata
+ return defaultGroupsWorkspace;
+ }
+
+// protected String getGuestsWorkspace() {
+// // TODO base on JAAS Subject metadata
+// return defaultGuestsWorkspace;
+// }
+
+ @Override
+ protected void processNewSession(Session session, String workspaceName) {
+ String username = session.getUserID();
+ if (username == null || username.toString().equals(""))
+ return;
+ if (session.getUserID().equals(CmsConstants.ROLE_ANONYMOUS))
+ return;
+
+ String userHomeWorkspace = getUserHomeWorkspace();
+ if (workspaceName == null || !workspaceName.equals(userHomeWorkspace))
+ return;
+
+ if (checkedUsers.contains(username))
+ return;
+ Session adminSession = KernelUtils.openAdminSession(getRepository(workspaceName), workspaceName);
+ try {
+ syncJcr(adminSession, username);
+ checkedUsers.add(username);
+ } finally {
+ JcrUtils.logoutQuietly(adminSession);
+ }
+ }
+
+ /*
+ * JCR
+ */
+ /** Session is logged out. */
+ private void initJcr(Session adminSession) {
+ try {
+// JcrUtils.mkdirs(adminSession, homeBasePath);
+// JcrUtils.mkdirs(adminSession, groupsBasePath);
+ adminSession.save();
+
+// JcrUtils.addPrivilege(adminSession, homeBasePath, NodeConstants.ROLE_USER_ADMIN, Privilege.JCR_READ);
+// JcrUtils.addPrivilege(adminSession, groupsBasePath, NodeConstants.ROLE_USER_ADMIN, Privilege.JCR_READ);
+ adminSession.save();
+ } catch (RepositoryException e) {
+ throw new CmsException("Cannot initialize home repository", e);
+ } finally {
+ JcrUtils.logoutQuietly(adminSession);
+ }
+ }
+
+ protected synchronized void syncJcr(Session adminSession, String username) {
+ // only in the default workspace
+// if (workspaceName != null)
+// return;
+ // skip system users
+ if (username.endsWith(CmsConstants.ROLES_BASEDN))
+ return;
+
+ try {
+ Node userHome = CmsJcrUtils.getUserHome(adminSession, username);
+ if (userHome == null) {
+// String homePath = generateUserPath(username);
+ String userId = extractUserId(username);
+// if (adminSession.itemExists(homePath))// duplicate user id
+// userHome = adminSession.getNode(homePath).getParent().addNode(JcrUtils.lastPathElement(homePath));
+// else
+// userHome = JcrUtils.mkdirs(adminSession, homePath);
+ userHome = adminSession.getRootNode().addNode(userId);
+// userHome.addMixin(NodeTypes.NODE_USER_HOME);
+ userHome.addMixin(NodeType.MIX_CREATED);
+ userHome.addMixin(NodeType.MIX_TITLE);
+ userHome.setProperty(Property.JCR_ID, username);
+ // TODO use display name
+ userHome.setProperty(Property.JCR_TITLE, userId);
+// userHome.setProperty(NodeNames.LDAP_UID, username);
+ adminSession.save();
+
+ JcrUtils.clearAccessControList(adminSession, userHome.getPath(), username);
+ JcrUtils.addPrivilege(adminSession, userHome.getPath(), username, Privilege.JCR_ALL);
+// JackrabbitSecurityUtils.denyPrivilege(adminSession, userHome.getPath(), NodeConstants.ROLE_USER,
+// Privilege.JCR_READ);
+ }
+ if (adminSession.hasPendingChanges())
+ adminSession.save();
+ } catch (RepositoryException e) {
+ JcrUtils.discardQuietly(adminSession);
+ throw new JcrException("Cannot sync node security model for " + username, e);
+ }
+ }
+
+ /** Generate path for a new user home */
+ private String generateUserPath(String username) {
+ LdapName dn;
+ try {
+ dn = new LdapName(username);
+ } catch (InvalidNameException e) {
+ throw new CmsException("Invalid name " + username, e);
+ }
+ String userId = dn.getRdn(dn.size() - 1).getValue().toString();
+ return '/' + userId;
+// int atIndex = userId.indexOf('@');
+// if (atIndex < 0) {
+// return homeBasePath+'/' + userId;
+// } else {
+// return usersBasePath + '/' + usersDatePath.format(new Date()) + '/' + userId;
+// }
+ }
+
+ private String extractUserId(String username) {
+ LdapName dn;
+ try {
+ dn = new LdapName(username);
+ } catch (InvalidNameException e) {
+ throw new CmsException("Invalid name " + username, e);
+ }
+ String userId = dn.getRdn(dn.size() - 1).getValue().toString();
+ return userId;
+// int atIndex = userId.indexOf('@');
+// if (atIndex < 0) {
+// return homeBasePath+'/' + userId;
+// } else {
+// return usersBasePath + '/' + usersDatePath.format(new Date()) + '/' + userId;
+// }
+ }
+
+ public void createWorkgroup(LdapName dn) {
+ String groupsWorkspace = getGroupsWorkspace();
+ Session adminSession = KernelUtils.openAdminSession(getRepository(groupsWorkspace), groupsWorkspace);
+ String cn = dn.getRdn(dn.size() - 1).getValue().toString();
+ Node newWorkgroup = CmsJcrUtils.getGroupHome(adminSession, cn);
+ if (newWorkgroup != null) {
+ JcrUtils.logoutQuietly(adminSession);
+ throw new CmsException("Workgroup " + newWorkgroup + " already exists for " + dn);
+ }
+ try {
+ // TODO enhance transformation of cn to a valid node name
+ // String relPath = cn.replaceAll("[^a-zA-Z0-9]", "_");
+ String relPath = JcrUtils.replaceInvalidChars(cn);
+ newWorkgroup = adminSession.getRootNode().addNode(relPath, NodeType.NT_UNSTRUCTURED);
+// newWorkgroup = JcrUtils.mkdirs(adminSession.getNode(groupsBasePath), relPath, NodeType.NT_UNSTRUCTURED);
+// newWorkgroup.addMixin(NodeTypes.NODE_GROUP_HOME);
+ newWorkgroup.addMixin(NodeType.MIX_CREATED);
+ newWorkgroup.addMixin(NodeType.MIX_TITLE);
+ newWorkgroup.setProperty(Property.JCR_ID, dn.toString());
+ newWorkgroup.setProperty(Property.JCR_TITLE, cn);
+// newWorkgroup.setProperty(NodeNames.LDAP_CN, cn);
+ adminSession.save();
+ JcrUtils.addPrivilege(adminSession, newWorkgroup.getPath(), dn.toString(), Privilege.JCR_ALL);
+ adminSession.save();
+ } catch (RepositoryException e) {
+ throw new CmsException("Cannot create workgroup", e);
+ } finally {
+ JcrUtils.logoutQuietly(adminSession);
+ }
+
+ }
+
+ public boolean isRemote() {
+ return remote;
+ }
+
+}
--- /dev/null
+package org.argeo.cms.jcr.internal;
+
+import java.util.Map;
+import java.util.TreeMap;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.apache.jackrabbit.core.RepositoryImpl;
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.api.cms.CmsLog;
+
+class JackrabbitLocalRepository extends LocalRepository {
+ private final static CmsLog log = CmsLog.getLog(JackrabbitLocalRepository.class);
+ final String SECURITY_WORKSPACE = "security";
+
+ private Map<String, CmsWorkspaceIndexer> workspaceMonitors = new TreeMap<>();
+
+ public JackrabbitLocalRepository(RepositoryImpl repository, String cn) {
+ super(repository, cn);
+// Session session = KernelUtils.openAdminSession(repository);
+// try {
+// if (NodeConstants.NODE.equals(cn))
+// for (String workspaceName : session.getWorkspace().getAccessibleWorkspaceNames()) {
+// addMonitor(workspaceName);
+// }
+// } catch (RepositoryException e) {
+// throw new IllegalStateException(e);
+// } finally {
+// JcrUtils.logoutQuietly(session);
+// }
+ }
+
+ protected RepositoryImpl getJackrabbitrepository(String workspaceName) {
+ return (RepositoryImpl) getRepository(workspaceName);
+ }
+
+ @Override
+ protected synchronized void processNewSession(Session session, String workspaceName) {
+// String realWorkspaceName = session.getWorkspace().getName();
+// addMonitor(realWorkspaceName);
+ }
+
+ private void addMonitor(String realWorkspaceName) {
+ if (realWorkspaceName.equals(SECURITY_WORKSPACE))
+ return;
+ if (!CmsConstants.NODE_REPOSITORY.equals(getCn()))
+ return;
+
+ if (!workspaceMonitors.containsKey(realWorkspaceName)) {
+ try {
+ CmsWorkspaceIndexer workspaceMonitor = new CmsWorkspaceIndexer(
+ getJackrabbitrepository(realWorkspaceName), getCn(), realWorkspaceName);
+ workspaceMonitors.put(realWorkspaceName, workspaceMonitor);
+ workspaceMonitor.init();
+ if (log.isDebugEnabled())
+ log.debug("Registered " + workspaceMonitor);
+ } catch (RepositoryException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+ }
+
+ public void destroy() {
+ for (String workspaceName : workspaceMonitors.keySet()) {
+ workspaceMonitors.get(workspaceName).destroy();
+ }
+ }
+
+}
--- /dev/null
+package org.argeo.cms.jcr.internal;
+
+import java.io.ByteArrayInputStream;
+import java.io.CharArrayReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.nio.charset.StandardCharsets;
+import java.security.GeneralSecurityException;
+import java.security.NoSuchAlgorithmException;
+import java.security.Provider;
+import java.security.SecureRandom;
+
+import javax.crypto.Cipher;
+import javax.crypto.CipherInputStream;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.IvParameterSpec;
+import javax.jcr.Binary;
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.Property;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.query.Query;
+
+import org.apache.commons.io.IOUtils;
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.cms.ArgeoNames;
+import org.argeo.cms.ArgeoTypes;
+import org.argeo.cms.jcr.CmsJcrUtils;
+import org.argeo.cms.security.AbstractKeyring;
+import org.argeo.cms.security.PBEKeySpecCallback;
+import org.argeo.jcr.JcrException;
+import org.argeo.jcr.JcrUtils;
+
+/** JCR based implementation of a keyring */
+public class JcrKeyring extends AbstractKeyring implements ArgeoNames {
+ private final static CmsLog log = CmsLog.getLog(JcrKeyring.class);
+ /**
+ * Stronger with 256, but causes problem with Oracle JVM, force 128 in this case
+ */
+ public final static Long DEFAULT_SECRETE_KEY_LENGTH = 256l;
+ public final static String DEFAULT_SECRETE_KEY_FACTORY = "PBKDF2WithHmacSHA1";
+ public final static String DEFAULT_SECRETE_KEY_ENCRYPTION = "AES";
+ public final static String DEFAULT_CIPHER_NAME = "AES/CBC/PKCS5Padding";
+
+ private Integer iterationCountFactor = 200;
+ private Long secretKeyLength = DEFAULT_SECRETE_KEY_LENGTH;
+ private String secretKeyFactoryName = DEFAULT_SECRETE_KEY_FACTORY;
+ private String secretKeyEncryption = DEFAULT_SECRETE_KEY_ENCRYPTION;
+ private String cipherName = DEFAULT_CIPHER_NAME;
+
+ private final Repository repository;
+ // TODO remove thread local session ; open a session each time
+ private ThreadLocal<Session> sessionThreadLocal = new ThreadLocal<Session>() {
+
+ @Override
+ protected Session initialValue() {
+ return login();
+ }
+
+ };
+
+ // FIXME is it really still needed?
+ /**
+ * When setup is called the session has not yet been saved and we don't want to
+ * save it since there maybe other data which would be inconsistent. So we keep
+ * a reference to this node which will then be used (an reset to null) when
+ * handling the PBE callback. We keep one per thread in case multiple users are
+ * accessing the same instance of a keyring.
+ */
+ // private ThreadLocal<Node> notYetSavedKeyring = new ThreadLocal<Node>() {
+ //
+ // @Override
+ // protected Node initialValue() {
+ // return null;
+ // }
+ // };
+
+ public JcrKeyring(Repository repository) {
+ this.repository = repository;
+ }
+
+ private Session session() {
+ Session session = this.sessionThreadLocal.get();
+ if (!session.isLive()) {
+ session = login();
+ sessionThreadLocal.set(session);
+ }
+ return session;
+ }
+
+ private Session login() {
+ try {
+ return repository.login(CmsConstants.HOME_WORKSPACE);
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot login key ring session", e);
+ }
+ }
+
+ @Override
+ protected synchronized Boolean isSetup() {
+ Session session = null;
+ try {
+ // if (notYetSavedKeyring.get() != null)
+ // return true;
+ session = session();
+ session.refresh(true);
+ Node userHome = CmsJcrUtils.getUserHome(session);
+ return userHome.hasNode(ARGEO_KEYRING);
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot check whether keyring is setup", e);
+ } finally {
+ JcrUtils.logoutQuietly(session);
+ }
+ }
+
+ @Override
+ protected synchronized void setup(char[] password) {
+ Binary binary = null;
+ // InputStream in = null;
+ try {
+ session().refresh(true);
+ Node userHome = CmsJcrUtils.getUserHome(session());
+ Node keyring;
+ if (userHome.hasNode(ARGEO_KEYRING)) {
+ throw new IllegalArgumentException("Keyring already set up");
+ } else {
+ keyring = userHome.addNode(ARGEO_KEYRING);
+ }
+ keyring.addMixin(ArgeoTypes.ARGEO_PBE_SPEC);
+
+ // deterministic salt and iteration count based on username
+ String username = session().getUserID();
+ byte[] salt = new byte[8];
+ byte[] usernameBytes = username.getBytes(StandardCharsets.UTF_8);
+ for (int i = 0; i < salt.length; i++) {
+ if (i < usernameBytes.length)
+ salt[i] = usernameBytes[i];
+ else
+ salt[i] = 0;
+ }
+ try (InputStream in = new ByteArrayInputStream(salt);) {
+ binary = session().getValueFactory().createBinary(in);
+ keyring.setProperty(ARGEO_SALT, binary);
+ } catch (IOException e) {
+ throw new RuntimeException("Cannot set keyring salt", e);
+ }
+
+ Integer iterationCount = username.length() * iterationCountFactor;
+ keyring.setProperty(ARGEO_ITERATION_COUNT, iterationCount);
+
+ // default algo
+ // TODO check if algo and key length are available, use DES if not
+ keyring.setProperty(ARGEO_SECRET_KEY_FACTORY, secretKeyFactoryName);
+ keyring.setProperty(ARGEO_KEY_LENGTH, secretKeyLength);
+ keyring.setProperty(ARGEO_SECRET_KEY_ENCRYPTION, secretKeyEncryption);
+ keyring.setProperty(ARGEO_CIPHER, cipherName);
+
+ keyring.getSession().save();
+
+ // encrypted password hash
+ // IOUtils.closeQuietly(in);
+ // JcrUtils.closeQuietly(binary);
+ // byte[] btPass = hash(password, salt, iterationCount);
+ // in = new ByteArrayInputStream(btPass);
+ // binary = session().getValueFactory().createBinary(in);
+ // keyring.setProperty(ARGEO_PASSWORD, binary);
+
+ // notYetSavedKeyring.set(keyring);
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot setup keyring", e);
+ } finally {
+ JcrUtils.closeQuietly(binary);
+ // IOUtils.closeQuietly(in);
+ // JcrUtils.discardQuietly(session());
+ }
+ }
+
+ @Override
+ protected synchronized void handleKeySpecCallback(PBEKeySpecCallback pbeCallback) {
+ Session session = null;
+ try {
+ session = session();
+ session.refresh(true);
+ Node userHome = CmsJcrUtils.getUserHome(session);
+ Node keyring;
+ if (userHome.hasNode(ARGEO_KEYRING))
+ keyring = userHome.getNode(ARGEO_KEYRING);
+ // else if (notYetSavedKeyring.get() != null)
+ // keyring = notYetSavedKeyring.get();
+ else
+ throw new IllegalStateException("Keyring not setup");
+
+ pbeCallback.set(keyring.getProperty(ARGEO_SECRET_KEY_FACTORY).getString(),
+ JcrUtils.getBinaryAsBytes(keyring.getProperty(ARGEO_SALT)),
+ (int) keyring.getProperty(ARGEO_ITERATION_COUNT).getLong(),
+ (int) keyring.getProperty(ARGEO_KEY_LENGTH).getLong(),
+ keyring.getProperty(ARGEO_SECRET_KEY_ENCRYPTION).getString());
+
+ // if (notYetSavedKeyring.get() != null)
+ // notYetSavedKeyring.remove();
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot handle key spec callback", e);
+ } finally {
+ JcrUtils.logoutQuietly(session);
+ }
+ }
+
+ /** The parent node must already exist at this path. */
+ @Override
+ protected synchronized void encrypt(String path, InputStream unencrypted) {
+ // should be called first for lazy initialization
+ SecretKey secretKey = getSecretKey(null);
+ Cipher cipher = createCipher();
+
+ // Binary binary = null;
+ // InputStream in = null;
+ try {
+ session().refresh(true);
+ Node node;
+ if (!session().nodeExists(path)) {
+ String parentPath = JcrUtils.parentPath(path);
+ if (!session().nodeExists(parentPath))
+ throw new IllegalStateException("No parent node of " + path);
+ Node parentNode = session().getNode(parentPath);
+ node = parentNode.addNode(JcrUtils.nodeNameFromPath(path));
+ } else {
+ node = session().getNode(path);
+ }
+ encrypt(secretKey, cipher, node, unencrypted);
+ // node.addMixin(ArgeoTypes.ARGEO_ENCRYPTED);
+ // SecureRandom random = new SecureRandom();
+ // byte[] iv = new byte[16];
+ // random.nextBytes(iv);
+ // cipher.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(iv));
+ // JcrUtils.setBinaryAsBytes(node, ARGEO_IV, iv);
+ //
+ // try (InputStream in = new CipherInputStream(unencrypted, cipher);) {
+ // binary = session().getValueFactory().createBinary(in);
+ // node.setProperty(Property.JCR_DATA, binary);
+ // session().save();
+ // }
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot encrypt", e);
+ } finally {
+ try {
+ unencrypted.close();
+ } catch (IOException e) {
+ // silent
+ }
+ // IOUtils.closeQuietly(unencrypted);
+ // IOUtils.closeQuietly(in);
+ // JcrUtils.closeQuietly(binary);
+ JcrUtils.logoutQuietly(session());
+ }
+ }
+
+ protected synchronized void encrypt(SecretKey secretKey, Cipher cipher, Node node, InputStream unencrypted) {
+ try {
+ node.addMixin(ArgeoTypes.ARGEO_ENCRYPTED);
+ SecureRandom random = new SecureRandom();
+ byte[] iv = new byte[16];
+ random.nextBytes(iv);
+ cipher.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(iv));
+ JcrUtils.setBinaryAsBytes(node, ARGEO_IV, iv);
+
+ Binary binary = null;
+ try (InputStream in = new CipherInputStream(unencrypted, cipher);) {
+ binary = session().getValueFactory().createBinary(in);
+ node.setProperty(Property.JCR_DATA, binary);
+ session().save();
+ } finally {
+ JcrUtils.closeQuietly(binary);
+ }
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot encrypt", e);
+ } catch (GeneralSecurityException | IOException e) {
+ throw new RuntimeException("Cannot encrypt", e);
+ }
+ }
+
+ @Override
+ protected synchronized InputStream decrypt(String path) {
+ Binary binary = null;
+ try {
+ session().refresh(true);
+ if (!session().nodeExists(path)) {
+ char[] password = ask();
+ Reader reader = new CharArrayReader(password);
+ return new ByteArrayInputStream(IOUtils.toByteArray(reader, StandardCharsets.UTF_8));
+ } else {
+ // should be called first for lazy initialisation
+ SecretKey secretKey = getSecretKey(null);
+ Cipher cipher = createCipher();
+ Node node = session().getNode(path);
+ return decrypt(secretKey, cipher, node);
+ }
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot decrypt", e);
+ } catch (GeneralSecurityException | IOException e) {
+ throw new RuntimeException("Cannot decrypt", e);
+ } finally {
+ JcrUtils.closeQuietly(binary);
+ JcrUtils.logoutQuietly(session());
+ }
+ }
+
+ protected synchronized InputStream decrypt(SecretKey secretKey, Cipher cipher, Node node)
+ throws RepositoryException, GeneralSecurityException {
+ if (node.hasProperty(ARGEO_IV)) {
+ byte[] iv = JcrUtils.getBinaryAsBytes(node.getProperty(ARGEO_IV));
+ cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(iv));
+ } else {
+ cipher.init(Cipher.DECRYPT_MODE, secretKey);
+ }
+
+ Binary binary = node.getProperty(Property.JCR_DATA).getBinary();
+ InputStream encrypted = binary.getStream();
+ return new CipherInputStream(encrypted, cipher);
+ }
+
+ protected Cipher createCipher() {
+ try {
+ Node userHome = CmsJcrUtils.getUserHome(session());
+ if (!userHome.hasNode(ARGEO_KEYRING))
+ throw new IllegalArgumentException("Keyring not setup");
+ Node keyring = userHome.getNode(ARGEO_KEYRING);
+ String cipherName = keyring.getProperty(ARGEO_CIPHER).getString();
+ Provider securityProvider = getSecurityProvider();
+ Cipher cipher;
+ if (securityProvider == null)// TODO use BC?
+ cipher = Cipher.getInstance(cipherName);
+ else
+ cipher = Cipher.getInstance(cipherName, securityProvider);
+ return cipher;
+ } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
+ throw new IllegalArgumentException("Cannot get cipher", e);
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot get cipher", e);
+ } finally {
+
+ }
+ }
+
+ public synchronized void changePassword(char[] oldPassword, char[] newPassword) {
+ // TODO make it XA compatible
+ SecretKey oldSecretKey = getSecretKey(oldPassword);
+ SecretKey newSecretKey = getSecretKey(newPassword);
+ Session session = session();
+ try {
+ NodeIterator encryptedNodes = session.getWorkspace().getQueryManager()
+ .createQuery("select * from [argeo:encrypted]", Query.JCR_SQL2).execute().getNodes();
+ while (encryptedNodes.hasNext()) {
+ Node node = encryptedNodes.nextNode();
+ InputStream in = decrypt(oldSecretKey, createCipher(), node);
+ encrypt(newSecretKey, createCipher(), node, in);
+ if (log.isDebugEnabled())
+ log.debug("Converted keyring encrypted value of " + node.getPath());
+ }
+ } catch (GeneralSecurityException e) {
+ throw new RuntimeException("Cannot change JCR keyring password", e);
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot change JCR keyring password", e);
+ } finally {
+ JcrUtils.logoutQuietly(session);
+ }
+ }
+
+ // public synchronized void setSession(Session session) {
+ // this.session = session;
+ // }
+
+ public void setIterationCountFactor(Integer iterationCountFactor) {
+ this.iterationCountFactor = iterationCountFactor;
+ }
+
+ public void setSecretKeyLength(Long keyLength) {
+ this.secretKeyLength = keyLength;
+ }
+
+ public void setSecretKeyFactoryName(String secreteKeyFactoryName) {
+ this.secretKeyFactoryName = secreteKeyFactoryName;
+ }
+
+ public void setSecretKeyEncryption(String secreteKeyEncryption) {
+ this.secretKeyEncryption = secreteKeyEncryption;
+ }
+
+ public void setCipherName(String cipherName) {
+ this.cipherName = cipherName;
+ }
+
+}
\ No newline at end of file
--- /dev/null
+package org.argeo.cms.jcr.internal;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.RepositoryFactory;
+
+import org.apache.jackrabbit.jcr2dav.Jcr2davRepositoryFactory;
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.cms.internal.jcr.RepoConf;
+import org.argeo.cms.jcr.internal.osgi.CmsJcrActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceReference;
+
+/**
+ * OSGi-aware Jackrabbit repository factory which can retrieve/publish
+ * {@link Repository} as OSGi services.
+ */
+public class JcrRepositoryFactory implements RepositoryFactory {
+ private final CmsLog log = CmsLog.getLog(getClass());
+// private final BundleContext bundleContext = FrameworkUtil.getBundle(getClass()).getBundleContext();
+
+ // private Resource fileRepositoryConfiguration = new ClassPathResource(
+ // "/org/argeo/cms/internal/kernel/repository-localfs.xml");
+
+ protected Repository getRepositoryByAlias(String alias) {
+ BundleContext bundleContext = CmsJcrActivator.getBundleContext();
+ if (bundleContext != null) {
+ try {
+ Collection<ServiceReference<Repository>> srs = bundleContext.getServiceReferences(Repository.class,
+ "(" + CmsConstants.CN + "=" + alias + ")");
+ if (srs.size() == 0)
+ throw new IllegalArgumentException("No repository with alias " + alias + " found in OSGi registry");
+ else if (srs.size() > 1)
+ throw new IllegalArgumentException(
+ srs.size() + " repositories with alias " + alias + " found in OSGi registry");
+ return bundleContext.getService(srs.iterator().next());
+ } catch (InvalidSyntaxException e) {
+ throw new IllegalArgumentException("Cannot find repository with alias " + alias, e);
+ }
+ } else {
+ // TODO ability to filter static services
+ return null;
+ }
+ }
+
+ // private void publish(String alias, Repository repository, Properties
+ // properties) {
+ // if (bundleContext != null) {
+ // // do not modify reference
+ // Hashtable<String, String> props = new Hashtable<String, String>();
+ // props.putAll(props);
+ // props.put(JCR_REPOSITORY_ALIAS, alias);
+ // bundleContext.registerService(Repository.class.getName(), repository,
+ // props);
+ // }
+ // }
+
+ @SuppressWarnings({ "rawtypes" })
+ public Repository getRepository(Map parameters) throws RepositoryException {
+ // // check if can be found by alias
+ // Repository repository = super.getRepository(parameters);
+ // if (repository != null)
+ // return repository;
+
+ // check if remote
+ Repository repository;
+ String uri = null;
+ if (parameters.containsKey(RepoConf.labeledUri.name()))
+ uri = parameters.get(CmsConstants.LABELED_URI).toString();
+ else if (parameters.containsKey(KernelConstants.JACKRABBIT_REPOSITORY_URI))
+ uri = parameters.get(KernelConstants.JACKRABBIT_REPOSITORY_URI).toString();
+
+ if (uri != null) {
+ if (uri.startsWith("http")) {// http, https
+ Object defaultWorkspace = parameters.get(RepoConf.defaultWorkspace.name());
+ repository = createRemoteRepository(uri, defaultWorkspace != null ? defaultWorkspace.toString() : null);
+ } else if (uri.startsWith("file"))// http, https
+ repository = createFileRepository(uri, parameters);
+ else if (uri.startsWith("vm")) {
+ // log.warn("URI " + uri + " should have been managed by generic
+ // JCR repository factory");
+ repository = getRepositoryByAlias(getAliasFromURI(uri));
+ } else
+ throw new IllegalArgumentException("Unrecognized URI format " + uri);
+
+ }
+
+ else if (parameters.containsKey(CmsConstants.CN)) {
+ // Properties properties = new Properties();
+ // properties.putAll(parameters);
+ String alias = parameters.get(CmsConstants.CN).toString();
+ // publish(alias, repository, properties);
+ // log.info("Registered JCR repository under alias '" + alias + "'
+ // with properties " + properties);
+ repository = getRepositoryByAlias(alias);
+ } else
+ throw new IllegalArgumentException("Not enough information in " + parameters);
+
+ if (repository == null)
+ throw new IllegalArgumentException("Repository not found " + parameters);
+
+ return repository;
+ }
+
+ protected Repository createRemoteRepository(String uri, String defaultWorkspace) throws RepositoryException {
+ Map<String, String> params = new HashMap<String, String>();
+ params.put(KernelConstants.JACKRABBIT_REPOSITORY_URI, uri);
+ if (defaultWorkspace != null)
+ params.put(KernelConstants.JACKRABBIT_REMOTE_DEFAULT_WORKSPACE, defaultWorkspace);
+ Repository repository = new Jcr2davRepositoryFactory().getRepository(params);
+ if (repository == null)
+ throw new IllegalArgumentException("Remote Davex repository " + uri + " not found");
+ log.info("Initialized remote Jackrabbit repository from uri " + uri);
+ return repository;
+ }
+
+ @SuppressWarnings({ "rawtypes" })
+ protected Repository createFileRepository(final String uri, Map parameters) throws RepositoryException {
+ throw new UnsupportedOperationException();
+ // InputStream configurationIn = null;
+ // try {
+ // Properties vars = new Properties();
+ // vars.putAll(parameters);
+ // String dirPath = uri.substring("file:".length());
+ // File homeDir = new File(dirPath);
+ // if (homeDir.exists() && !homeDir.isDirectory())
+ // throw new ArgeoJcrException("Repository home " + dirPath + " is not a
+ // directory");
+ // if (!homeDir.exists())
+ // homeDir.mkdirs();
+ // configurationIn = fileRepositoryConfiguration.getInputStream();
+ // vars.put(RepositoryConfigurationParser.REPOSITORY_HOME_VARIABLE,
+ // homeDir.getCanonicalPath());
+ // RepositoryConfig repositoryConfig = RepositoryConfig.create(new
+ // InputSource(configurationIn), vars);
+ //
+ // // TransientRepository repository = new
+ // // TransientRepository(repositoryConfig);
+ // final RepositoryImpl repository =
+ // RepositoryImpl.create(repositoryConfig);
+ // Session session = repository.login();
+ // // FIXME make it generic
+ // org.argeo.jcr.JcrUtils.addPrivilege(session, "/", "ROLE_ADMIN",
+ // "jcr:all");
+ // org.argeo.jcr.JcrUtils.logoutQuietly(session);
+ // Runtime.getRuntime().addShutdownHook(new Thread("Clean JCR repository
+ // " + uri) {
+ // public void run() {
+ // repository.shutdown();
+ // log.info("Destroyed repository " + uri);
+ // }
+ // });
+ // log.info("Initialized file Jackrabbit repository from uri " + uri);
+ // return repository;
+ // } catch (Exception e) {
+ // throw new ArgeoJcrException("Cannot create repository " + uri, e);
+ // } finally {
+ // IOUtils.closeQuietly(configurationIn);
+ // }
+ }
+
+ protected String getAliasFromURI(String uri) {
+ try {
+ URI uriObj = new URI(uri);
+ String alias = uriObj.getPath();
+ if (alias.charAt(0) == '/')
+ alias = alias.substring(1);
+ if (alias.charAt(alias.length() - 1) == '/')
+ alias = alias.substring(0, alias.length() - 1);
+ return alias;
+ } catch (URISyntaxException e) {
+ throw new IllegalArgumentException("Cannot interpret URI " + uri, e);
+ }
+ }
+
+ /**
+ * Called after the repository has been initialised. Does nothing by default.
+ */
+ @SuppressWarnings("rawtypes")
+ protected void postInitialization(Repository repository, Map parameters) {
+
+ }
+}
--- /dev/null
+package org.argeo.cms.jcr.internal;
+
+import org.argeo.api.cms.CmsConstants;
+
+/** Internal CMS constants. */
+@Deprecated
+public interface KernelConstants {
+ // Directories
+ String DIR_NODE = "node";
+ String DIR_REPOS = "repos";
+ String DIR_INDEXES = "indexes";
+ String DIR_TRANSACTIONS = "transactions";
+
+ // Files
+ String DEPLOY_CONFIG_PATH = DIR_NODE + '/' + CmsConstants.DEPLOY_BASEDN + ".ldif";
+ String DEFAULT_KEYSTORE_PATH = DIR_NODE + '/' + CmsConstants.NODE + ".p12";
+ String DEFAULT_PEM_KEY_PATH = DIR_NODE + '/' + CmsConstants.NODE + ".key";
+ String DEFAULT_PEM_CERT_PATH = DIR_NODE + '/' + CmsConstants.NODE + ".crt";
+ String NODE_KEY_TAB_PATH = DIR_NODE + "/krb5.keytab";
+
+ // Security
+ String JAAS_CONFIG = "/org/argeo/cms/internal/kernel/jaas.cfg";
+ String JAAS_CONFIG_IPA = "/org/argeo/cms/internal/kernel/jaas-ipa.cfg";
+
+ // Java
+ String JAAS_CONFIG_PROP = "java.security.auth.login.config";
+
+ // DEFAULTS JCR PATH
+ String DEFAULT_HOME_BASE_PATH = "/home";
+ String DEFAULT_USERS_BASE_PATH = "/users";
+ String DEFAULT_GROUPS_BASE_PATH = "/groups";
+
+ // KERBEROS
+ String DEFAULT_KERBEROS_SERVICE = "HTTP";
+
+ // HTTP client
+ String COOKIE_POLICY_BROWSER_COMPATIBILITY = "compatibility";
+
+ // RWT / RAP
+ // String PATH_WORKBENCH = "/ui";
+ // String PATH_WORKBENCH_PUBLIC = PATH_WORKBENCH + "/public";
+
+ String JETTY_FACTORY_PID = "org.eclipse.equinox.http.jetty.config";
+ String WHITEBOARD_PATTERN_PROP = "osgi.http.whiteboard.servlet.pattern";
+ // default Jetty server configured via JettyConfigurator
+ String DEFAULT_JETTY_SERVER = "default";
+ String CMS_JETTY_CUSTOMIZER_CLASS = "org.argeo.equinox.jetty.CmsJettyCustomizer";
+
+ // avoid dependencies
+ String CONTEXT_NAME_PROP = "contextName";
+ String JACKRABBIT_REPOSITORY_URI = "org.apache.jackrabbit.repository.uri";
+ String JACKRABBIT_REMOTE_DEFAULT_WORKSPACE = "org.apache.jackrabbit.spi2davex.WorkspaceNameDefault";
+}
--- /dev/null
+package org.argeo.cms.jcr.internal;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.security.PrivilegedAction;
+import java.security.URIParameter;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.Properties;
+import java.util.TreeMap;
+import java.util.TreeSet;
+
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.security.auth.Subject;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+
+import org.argeo.api.cms.CmsAuth;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.cms.jcr.internal.osgi.CmsJcrActivator;
+import org.argeo.cms.osgi.DataModelNamespace;
+import org.osgi.framework.BundleContext;
+import org.osgi.util.tracker.ServiceTracker;
+
+/** Package utilities */
+class KernelUtils implements KernelConstants {
+ final static String OSGI_INSTANCE_AREA = "osgi.instance.area";
+ final static String OSGI_CONFIGURATION_AREA = "osgi.configuration.area";
+
+ static void setJaasConfiguration(URL jaasConfigurationUrl) {
+ try {
+ URIParameter uriParameter = new URIParameter(jaasConfigurationUrl.toURI());
+ javax.security.auth.login.Configuration jaasConfiguration = javax.security.auth.login.Configuration
+ .getInstance("JavaLoginConfig", uriParameter);
+ javax.security.auth.login.Configuration.setConfiguration(jaasConfiguration);
+ } catch (Exception e) {
+ throw new IllegalArgumentException("Cannot set configuration " + jaasConfigurationUrl, e);
+ }
+ }
+
+ static Dictionary<String, ?> asDictionary(Properties props) {
+ Hashtable<String, Object> hashtable = new Hashtable<String, Object>();
+ for (Object key : props.keySet()) {
+ hashtable.put(key.toString(), props.get(key));
+ }
+ return hashtable;
+ }
+
+ static Dictionary<String, ?> asDictionary(ClassLoader cl, String resource) {
+ Properties props = new Properties();
+ try {
+ props.load(cl.getResourceAsStream(resource));
+ } catch (IOException e) {
+ throw new IllegalArgumentException("Cannot load " + resource + " from classpath", e);
+ }
+ return asDictionary(props);
+ }
+
+ static File getExecutionDir(String relativePath) {
+ File executionDir = new File(getFrameworkProp("user.dir"));
+ if (relativePath == null)
+ return executionDir;
+ try {
+ return new File(executionDir, relativePath).getCanonicalFile();
+ } catch (IOException e) {
+ throw new IllegalArgumentException("Cannot get canonical file", e);
+ }
+ }
+
+ static File getOsgiInstanceDir() {
+ return new File(getBundleContext().getProperty(OSGI_INSTANCE_AREA).substring("file:".length()))
+ .getAbsoluteFile();
+ }
+
+ static Path getOsgiInstancePath(String relativePath) {
+ return Paths.get(getOsgiInstanceUri(relativePath));
+ }
+
+ static URI getOsgiInstanceUri(String relativePath) {
+ String osgiInstanceBaseUri = getFrameworkProp(OSGI_INSTANCE_AREA);
+ if (osgiInstanceBaseUri != null)
+ return safeUri(osgiInstanceBaseUri + (relativePath != null ? relativePath : ""));
+ else
+ return Paths.get(System.getProperty("user.dir")).toUri();
+ }
+
+ static File getOsgiConfigurationFile(String relativePath) {
+ try {
+ return new File(new URI(getBundleContext().getProperty(OSGI_CONFIGURATION_AREA) + relativePath))
+ .getCanonicalFile();
+ } catch (Exception e) {
+ throw new IllegalArgumentException("Cannot get configuration file for " + relativePath, e);
+ }
+ }
+
+ static String getFrameworkProp(String key, String def) {
+ BundleContext bundleContext = CmsJcrActivator.getBundleContext();
+ String value;
+ if (bundleContext != null)
+ value = bundleContext.getProperty(key);
+ else
+ value = System.getProperty(key);
+ if (value == null)
+ return def;
+ return value;
+ }
+
+ static String getFrameworkProp(String key) {
+ return getFrameworkProp(key, null);
+ }
+
+ // Security
+ // static Subject anonymousLogin() {
+ // Subject subject = new Subject();
+ // LoginContext lc;
+ // try {
+ // lc = new LoginContext(NodeConstants.LOGIN_CONTEXT_USER, subject);
+ // lc.login();
+ // return subject;
+ // } catch (LoginException e) {
+ // throw new CmsException("Cannot login as anonymous", e);
+ // }
+ // }
+
+ static void logFrameworkProperties(CmsLog log) {
+ BundleContext bc = getBundleContext();
+ for (Object sysProp : new TreeSet<Object>(System.getProperties().keySet())) {
+ log.debug(sysProp + "=" + bc.getProperty(sysProp.toString()));
+ }
+ // String[] keys = { Constants.FRAMEWORK_STORAGE,
+ // Constants.FRAMEWORK_OS_NAME, Constants.FRAMEWORK_OS_VERSION,
+ // Constants.FRAMEWORK_PROCESSOR, Constants.FRAMEWORK_SECURITY,
+ // Constants.FRAMEWORK_TRUST_REPOSITORIES,
+ // Constants.FRAMEWORK_WINDOWSYSTEM, Constants.FRAMEWORK_VENDOR,
+ // Constants.FRAMEWORK_VERSION, Constants.FRAMEWORK_STORAGE_CLEAN,
+ // Constants.FRAMEWORK_LANGUAGE, Constants.FRAMEWORK_UUID };
+ // for (String key : keys)
+ // log.debug(key + "=" + bc.getProperty(key));
+ }
+
+ static void printSystemProperties(PrintStream out) {
+ TreeMap<String, String> display = new TreeMap<>();
+ for (Object key : System.getProperties().keySet())
+ display.put(key.toString(), System.getProperty(key.toString()));
+ for (String key : display.keySet())
+ out.println(key + "=" + display.get(key));
+ }
+
+ static Session openAdminSession(Repository repository) {
+ return openAdminSession(repository, null);
+ }
+
+ static Session openAdminSession(final Repository repository, final String workspaceName) {
+ LoginContext loginContext = loginAsDataAdmin();
+ return Subject.doAs(loginContext.getSubject(), new PrivilegedAction<Session>() {
+
+ @Override
+ public Session run() {
+ try {
+ return repository.login(workspaceName);
+ } catch (RepositoryException e) {
+ throw new IllegalStateException("Cannot open admin session", e);
+ } finally {
+ try {
+ loginContext.logout();
+ } catch (LoginException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+ }
+
+ });
+ }
+
+ static LoginContext loginAsDataAdmin() {
+ ClassLoader currentCl = Thread.currentThread().getContextClassLoader();
+ Thread.currentThread().setContextClassLoader(KernelUtils.class.getClassLoader());
+ LoginContext loginContext;
+ try {
+ loginContext = new LoginContext(CmsAuth.LOGIN_CONTEXT_DATA_ADMIN);
+ loginContext.login();
+ } catch (LoginException e1) {
+ throw new IllegalStateException("Could not login as data admin", e1);
+ } finally {
+ Thread.currentThread().setContextClassLoader(currentCl);
+ }
+ return loginContext;
+ }
+
+ static void doAsDataAdmin(Runnable action) {
+ LoginContext loginContext = loginAsDataAdmin();
+ Subject.doAs(loginContext.getSubject(), new PrivilegedAction<Void>() {
+
+ @Override
+ public Void run() {
+ try {
+ action.run();
+ return null;
+ } finally {
+ try {
+ loginContext.logout();
+ } catch (LoginException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+ }
+
+ });
+ }
+
+ static void asyncOpen(ServiceTracker<?, ?> st) {
+ Runnable run = new Runnable() {
+
+ @Override
+ public void run() {
+ st.open();
+ }
+ };
+// Activator.getInternalExecutorService().execute(run);
+ new Thread(run, "Open service tracker " + st).start();
+ }
+
+ static BundleContext getBundleContext() {
+ return CmsJcrActivator.getBundleContext();
+ }
+
+ static boolean asBoolean(String value) {
+ if (value == null)
+ return false;
+ switch (value) {
+ case "true":
+ return true;
+ case "false":
+ return false;
+ default:
+ throw new IllegalArgumentException(
+ "Unsupported value for attribute " + DataModelNamespace.ABSTRACT + ": " + value);
+ }
+ }
+
+ private static URI safeUri(String uri) {
+ if (uri == null)
+ throw new IllegalArgumentException("URI cannot be null");
+ try {
+ return new URI(uri);
+ } catch (URISyntaxException e) {
+ throw new IllegalArgumentException("Badly formatted URI " + uri, e);
+ }
+ }
+
+ private KernelUtils() {
+
+ }
+}
--- /dev/null
+package org.argeo.cms.jcr.internal;
+
+import javax.jcr.Repository;
+
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.jcr.JcrRepositoryWrapper;
+
+class LocalRepository extends JcrRepositoryWrapper {
+ private final String cn;
+
+ public LocalRepository(Repository repository, String cn) {
+ super(repository);
+ this.cn = cn;
+ // Map<String, Object> attrs = dataModelCapability.getAttributes();
+ // cn = (String) attrs.get(DataModelNamespace.NAME);
+ putDescriptor(CmsConstants.CN, cn);
+ }
+
+ String getCn() {
+ return cn;
+ }
+
+}
--- /dev/null
+package org.argeo.cms.jcr.internal;
+
+import java.util.Dictionary;
+
+import javax.jcr.Repository;
+
+import org.osgi.service.cm.ConfigurationException;
+import org.osgi.service.cm.ManagedService;
+
+class NodeKeyRing extends JcrKeyring implements ManagedService{
+
+ public NodeKeyRing(Repository repository) {
+ super(repository);
+ }
+
+ @Override
+ public void updated(Dictionary<String, ?> properties) throws ConfigurationException {
+ }
+}
--- /dev/null
+package org.argeo.cms.jcr.internal;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Dictionary;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.RepositoryFactory;
+
+import org.apache.jackrabbit.core.RepositoryContext;
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.cms.internal.jcr.RepoConf;
+import org.argeo.cms.internal.jcr.RepositoryBuilder;
+import org.argeo.cms.jcr.internal.osgi.CmsJcrActivator;
+import org.argeo.util.LangUtils;
+import org.osgi.framework.Constants;
+import org.osgi.service.cm.ConfigurationException;
+import org.osgi.service.cm.ManagedServiceFactory;
+
+/** A {@link ManagedServiceFactory} creating or referencing JCR repositories. */
+public class RepositoryContextsFactory implements ManagedServiceFactory {
+ private final static CmsLog log = CmsLog.getLog(RepositoryContextsFactory.class);
+// private final BundleContext bc = FrameworkUtil.getBundle(RepositoryServiceFactory.class).getBundleContext();
+
+ private Map<String, RepositoryContext> repositories = new HashMap<String, RepositoryContext>();
+ private Map<String, Object> pidToCn = new HashMap<String, Object>();
+
+ public void init() {
+
+ }
+
+ public void destroy() {
+ for (String pid : repositories.keySet()) {
+ try {
+ RepositoryContext repositoryContext = repositories.get(pid);
+ // Must start in another thread otherwise shutdown is interrupted
+ // TODO use an executor?
+ new Thread(() -> {
+ repositoryContext.getRepository().shutdown();
+ if (log.isDebugEnabled())
+ log.debug("Shut down repository " + pid
+ + (pidToCn.containsKey(pid) ? " (" + pidToCn.get(pid) + ")" : ""));
+ }, "Shutdown JCR repository " + pid).start();
+ } catch (Exception e) {
+ log.error("Error when shutting down Jackrabbit repository " + pid, e);
+ }
+ }
+ }
+
+ @Override
+ public String getName() {
+ return "Jackrabbit repository service factory";
+ }
+
+ @Override
+ public void updated(String pid, Dictionary<String, ?> properties) throws ConfigurationException {
+ if (repositories.containsKey(pid))
+ throw new IllegalArgumentException("Already a repository registered for " + pid);
+
+ if (properties == null)
+ return;
+
+ Object cn = properties.get(CmsConstants.CN);
+ if (cn != null)
+ for (String otherPid : pidToCn.keySet()) {
+ Object o = pidToCn.get(otherPid);
+ if (cn.equals(o)) {
+ RepositoryContext repositoryContext = repositories.remove(otherPid);
+ repositories.put(pid, repositoryContext);
+ if (log.isDebugEnabled())
+ log.debug("Ignoring update of Jackrabbit repository " + cn);
+ // FIXME perform a proper update (also of the OSGi service)
+ return;
+ }
+ }
+
+ try {
+ Object labeledUri = properties.get(RepoConf.labeledUri.name());
+ if (labeledUri == null) {
+ RepositoryBuilder repositoryBuilder = new RepositoryBuilder();
+ RepositoryContext repositoryContext = repositoryBuilder.createRepositoryContext(properties);
+ repositories.put(pid, repositoryContext);
+ Dictionary<String, Object> props = LangUtils.dict(Constants.SERVICE_PID, pid);
+ // props.put(ArgeoJcrConstants.JCR_REPOSITORY_URI,
+ // properties.get(RepoConf.labeledUri.name()));
+ if (cn != null) {
+ props.put(CmsConstants.CN, cn);
+ // props.put(NodeConstants.JCR_REPOSITORY_ALIAS, cn);
+ pidToCn.put(pid, cn);
+ }
+ CmsJcrActivator.registerService(RepositoryContext.class, repositoryContext, props);
+ } else {
+ Object defaultWorkspace = properties.get(RepoConf.defaultWorkspace.name());
+ if (defaultWorkspace == null)
+ defaultWorkspace = RepoConf.defaultWorkspace.getDefault();
+ URI uri = new URI(labeledUri.toString());
+// RepositoryFactory repositoryFactory = bc
+// .getService(bc.getServiceReference(RepositoryFactory.class));
+ RepositoryFactory repositoryFactory = CmsJcrActivator.getService(RepositoryFactory.class);
+ Map<String, String> parameters = new HashMap<String, String>();
+ parameters.put(RepoConf.labeledUri.name(), uri.toString());
+ parameters.put(RepoConf.defaultWorkspace.name(), defaultWorkspace.toString());
+ Repository repository = repositoryFactory.getRepository(parameters);
+ // Repository repository = NodeUtils.getRepositoryByUri(repositoryFactory,
+ // uri.toString());
+ Dictionary<String, Object> props = LangUtils.dict(Constants.SERVICE_PID, pid);
+ props.put(RepoConf.labeledUri.name(),
+ new URI(uri.getScheme(), null, uri.getHost(), uri.getPort(), uri.getPath(), null, null)
+ .toString());
+ if (cn != null) {
+ props.put(CmsConstants.CN, cn);
+ // props.put(NodeConstants.JCR_REPOSITORY_ALIAS, cn);
+ pidToCn.put(pid, cn);
+ }
+ CmsJcrActivator.registerService(Repository.class, repository, props);
+
+ // home
+ if (cn.equals(CmsConstants.NODE_REPOSITORY)) {
+ Dictionary<String, Object> homeProps = LangUtils.dict(CmsConstants.CN, CmsConstants.EGO_REPOSITORY);
+ EgoRepository homeRepository = new EgoRepository(repository, true);
+ CmsJcrActivator.registerService(Repository.class, homeRepository, homeProps);
+ }
+ }
+ } catch (RepositoryException | URISyntaxException | IOException e) {
+ throw new IllegalStateException("Cannot create Jackrabbit repository " + pid, e);
+ }
+
+ }
+
+ @Override
+ public void deleted(String pid) {
+ RepositoryContext repositoryContext = repositories.remove(pid);
+ repositoryContext.getRepository().shutdown();
+ if (log.isDebugEnabled())
+ log.debug("Deleted repository " + pid);
+ }
+
+}
--- /dev/null
+package org.argeo.cms.jcr.internal;
+
+import java.io.File;
+import java.lang.management.ManagementFactory;
+
+import org.apache.jackrabbit.api.stats.RepositoryStatistics;
+import org.apache.jackrabbit.stats.RepositoryStatisticsImpl;
+import org.argeo.api.cms.CmsLog;
+
+/**
+ * Background thread started by the kernel, which gather statistics and
+ * monitor/control other processes.
+ */
+public class StatisticsThread extends Thread {
+ private final static CmsLog log = CmsLog.getLog(StatisticsThread.class);
+
+ private RepositoryStatisticsImpl repoStats;
+
+ /** The smallest period of operation, in ms */
+ private final long PERIOD = 60 * 1000l;
+ /** One ms in ns */
+ private final static long m = 1000l * 1000l;
+ private final static long M = 1024l * 1024l;
+
+ private boolean running = true;
+
+ private CmsLog kernelStatsLog = CmsLog.getLog("argeo.stats.kernel");
+ private CmsLog nodeStatsLog = CmsLog.getLog("argeo.stats.node");
+
+ @SuppressWarnings("unused")
+ private long cycle = 0l;
+
+ public StatisticsThread(String name) {
+ super(name);
+ }
+
+ private void doSmallestPeriod() {
+ // Clean expired sessions
+ // FIXME re-enable it in CMS
+ //CmsSessionImpl.closeInvalidSessions();
+
+ if (kernelStatsLog.isDebugEnabled()) {
+ StringBuilder line = new StringBuilder(64);
+ line.append("§\t");
+ long freeMem = Runtime.getRuntime().freeMemory() / M;
+ long totalMem = Runtime.getRuntime().totalMemory() / M;
+ long maxMem = Runtime.getRuntime().maxMemory() / M;
+ double loadAvg = ManagementFactory.getOperatingSystemMXBean().getSystemLoadAverage();
+ // in min
+ boolean min = true;
+ long uptime = ManagementFactory.getRuntimeMXBean().getUptime() / (1000 * 60);
+ if (uptime > 24 * 60) {
+ min = false;
+ uptime = uptime / 60;
+ }
+ line.append(uptime).append(min ? " min" : " h").append('\t');
+ line.append(loadAvg).append('\t').append(maxMem).append('\t').append(totalMem).append('\t').append(freeMem)
+ .append('\t');
+ kernelStatsLog.debug(line);
+ }
+
+ if (nodeStatsLog.isDebugEnabled()) {
+ File dataDir = KernelUtils.getOsgiInstanceDir();
+ long freeSpace = dataDir.getUsableSpace() / M;
+ // File currentRoot = null;
+ // for (File root : File.listRoots()) {
+ // String rootPath = root.getAbsolutePath();
+ // if (dataDir.getAbsolutePath().startsWith(rootPath)) {
+ // if (currentRoot == null
+ // || (rootPath.length() > currentRoot.getPath()
+ // .length())) {
+ // currentRoot = root;
+ // }
+ // }
+ // }
+ // long totalSpace = currentRoot.getTotalSpace();
+ StringBuilder line = new StringBuilder(128);
+ line.append("§\t").append(freeSpace).append(" MB left in " + dataDir);
+ line.append('\n');
+ if (repoStats != null)
+ for (RepositoryStatistics.Type type : RepositoryStatistics.Type.values()) {
+ long[] vals = repoStats.getTimeSeries(type).getValuePerMinute();
+ long val = vals[vals.length - 1];
+ line.append(type.name()).append('\t').append(val).append('\n');
+ }
+ nodeStatsLog.debug(line);
+ }
+ }
+
+ @Override
+ public void run() {
+ if (log.isTraceEnabled())
+ log.trace("Kernel thread started.");
+ final long periodNs = PERIOD * m;
+ while (running) {
+ long beginNs = System.nanoTime();
+ doSmallestPeriod();
+
+ long waitNs = periodNs - (System.nanoTime() - beginNs);
+ if (waitNs < 0)
+ continue;
+ // wait
+ try {
+ sleep(waitNs / m, (int) (waitNs % m));
+ } catch (InterruptedException e) {
+ // silent
+ }
+ cycle++;
+ }
+ }
+
+ public synchronized void destroyAndJoin() {
+ running = false;
+ notifyAll();
+// interrupt();
+// try {
+// join(PERIOD * 2);
+// } catch (InterruptedException e) {
+// // throw new CmsException("Kernel thread destruction was interrupted");
+// log.error("Kernel thread destruction was interrupted", e);
+// }
+ }
+}
--- /dev/null
+package org.argeo.cms.jcr.internal.osgi;
+
+import java.util.Dictionary;
+
+import org.argeo.cms.jcr.internal.StatisticsThread;
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+
+public class CmsJcrActivator implements BundleActivator {
+ private static BundleContext bundleContext;
+
+// private List<Runnable> stopHooks = new ArrayList<>();
+ private StatisticsThread kernelThread;
+
+// private JackrabbitRepositoryContextsFactory repositoryServiceFactory;
+// private CmsJcrDeployment jcrDeployment;
+
+ @Override
+ public void start(BundleContext context) throws Exception {
+ bundleContext = context;
+
+ // kernel thread
+ kernelThread = new StatisticsThread("Kernel Thread");
+ kernelThread.setContextClassLoader(getClass().getClassLoader());
+ kernelThread.start();
+
+ // JCR
+// repositoryServiceFactory = new JackrabbitRepositoryContextsFactory();
+//// stopHooks.add(() -> repositoryServiceFactory.shutdown());
+// registerService(ManagedServiceFactory.class, repositoryServiceFactory,
+// LangUtils.dict(Constants.SERVICE_PID, CmsConstants.NODE_REPOS_FACTORY_PID));
+
+// JcrRepositoryFactory repositoryFactory = new JcrRepositoryFactory();
+// registerService(RepositoryFactory.class, repositoryFactory, null);
+
+ // File System
+// CmsJcrFsProvider cmsFsProvider = new CmsJcrFsProvider();
+// ServiceLoader<FileSystemProvider> fspSl = ServiceLoader.load(FileSystemProvider.class);
+// for (FileSystemProvider fsp : fspSl) {
+// log.debug("FileSystemProvider " + fsp);
+// if (fsp instanceof CmsFsProvider) {
+// cmsFsProvider = (CmsFsProvider) fsp;
+// }
+// }
+// for (FileSystemProvider fsp : FileSystemProvider.installedProviders()) {
+// log.debug("Installed FileSystemProvider " + fsp);
+// }
+// registerService(FileSystemProvider.class, cmsFsProvider,
+// LangUtils.dict(Constants.SERVICE_PID, CmsConstants.NODE_FS_PROVIDER_PID));
+
+// jcrDeployment = new CmsJcrDeployment();
+// jcrDeployment.init();
+ }
+
+ @Override
+ public void stop(BundleContext context) throws Exception {
+// if (jcrDeployment != null)
+// jcrDeployment.destroy();
+
+// if (repositoryServiceFactory != null)
+// repositoryServiceFactory.shutdown();
+
+ if (kernelThread != null)
+ kernelThread.destroyAndJoin();
+
+ bundleContext = null;
+ }
+
+ @Deprecated
+ public static <T> void registerService(Class<T> clss, T service, Dictionary<String, ?> properties) {
+ if (bundleContext != null) {
+ bundleContext.registerService(clss, service, properties);
+ }
+
+ }
+
+ @Deprecated
+ public static BundleContext getBundleContext() {
+ return bundleContext;
+ }
+
+ @Deprecated
+ public static <T> T getService(Class<T> clss) {
+ if (bundleContext != null) {
+ return bundleContext.getService(bundleContext.getServiceReference(clss));
+ } else {
+ return null;
+ }
+ }
+
+}
--- /dev/null
+package org.argeo.cms.jcr.internal.servlet;
+
+import java.util.Map;
+
+import javax.jcr.Repository;
+
+import org.apache.jackrabbit.server.SessionProvider;
+import org.apache.jackrabbit.server.remoting.davex.JcrRemotingServlet;
+import org.argeo.api.cms.CmsConstants;
+
+/** A {@link JcrRemotingServlet} based on {@link CmsSessionProvider}. */
+public class CmsRemotingServlet extends JcrRemotingServlet {
+ private static final long serialVersionUID = 6459455509684213633L;
+ private Repository repository;
+ private SessionProvider sessionProvider;
+
+ public CmsRemotingServlet() {
+ }
+
+ public CmsRemotingServlet(String alias, Repository repository) {
+ this.repository = repository;
+ this.sessionProvider = new CmsSessionProvider(alias);
+ }
+
+ @Override
+ public Repository getRepository() {
+ return repository;
+ }
+
+ public void setRepository(Repository repository, Map<String, String> properties) {
+ this.repository = repository;
+ String alias = properties.get(CmsConstants.CN);
+ if (alias != null)
+ sessionProvider = new CmsSessionProvider(alias);
+ else
+ throw new IllegalArgumentException("Only aliased repositories are supported");
+ }
+
+ @Override
+ protected SessionProvider getSessionProvider() {
+ return sessionProvider;
+ }
+
+}
--- /dev/null
+package org.argeo.cms.jcr.internal.servlet;
+
+import java.io.Serializable;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.security.auth.Subject;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.jackrabbit.server.SessionProvider;
+import org.argeo.api.cms.CmsSession;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.jcr.JcrUtils;
+
+/**
+ * Implements an open session in view patter: a new JCR session is created for
+ * each request
+ */
+public class CmsSessionProvider implements SessionProvider, Serializable {
+ private static final long serialVersionUID = -1358136599534938466L;
+
+ private final static CmsLog log = CmsLog.getLog(CmsSessionProvider.class);
+
+ private final String alias;
+
+ private LinkedHashMap<Session, CmsDataSession> cmsSessions = new LinkedHashMap<>();
+
+ public CmsSessionProvider(String alias) {
+ this.alias = alias;
+ }
+
+ public Session getSession(HttpServletRequest request, Repository rep, String workspace)
+ throws javax.jcr.LoginException, ServletException, RepositoryException {
+
+ // a client is scanning parent URLs.
+// if (workspace == null)
+// return null;
+
+// CmsSessionImpl cmsSession = WebCmsSessionImpl.getCmsSession(request);
+ // FIXME retrieve CMS session
+ CmsSession cmsSession = null;
+ if (log.isTraceEnabled()) {
+ log.trace("Get JCR session from " + cmsSession);
+ }
+ if (cmsSession == null)
+ throw new IllegalStateException("Cannot find a session for request " + request.getRequestURI());
+ CmsDataSession cmsDataSession = new CmsDataSession(cmsSession);
+ Session session = cmsDataSession.getDataSession(alias, workspace, rep);
+ cmsSessions.put(session, cmsDataSession);
+ return session;
+ }
+
+ public void releaseSession(Session session) {
+// JcrUtils.logoutQuietly(session);
+ if (cmsSessions.containsKey(session)) {
+ CmsDataSession cmsDataSession = cmsSessions.get(session);
+ cmsDataSession.releaseDataSession(alias, session);
+ } else {
+ log.warn("JCR session " + session + " not found in CMS session list. Logging it out...");
+ JcrUtils.logoutQuietly(session);
+ }
+ }
+
+ static class CmsDataSession {
+ private CmsSession cmsSession;
+
+ private Map<String, Session> dataSessions = new HashMap<>();
+ private Set<String> dataSessionsInUse = new HashSet<>();
+ private Set<Session> additionalDataSessions = new HashSet<>();
+
+ private CmsDataSession(CmsSession cmsSession) {
+ this.cmsSession = cmsSession;
+ }
+
+ public Session newDataSession(String cn, String workspace, Repository repository) {
+ checkValid();
+ return login(repository, workspace);
+ }
+
+ public synchronized Session getDataSession(String cn, String workspace, Repository repository) {
+ checkValid();
+ // FIXME make it more robust
+ if (workspace == null)
+ workspace = CmsConstants.SYS_WORKSPACE;
+ String path = cn + '/' + workspace;
+ if (dataSessionsInUse.contains(path)) {
+ try {
+ wait(1000);
+ if (dataSessionsInUse.contains(path)) {
+ Session session = login(repository, workspace);
+ additionalDataSessions.add(session);
+ if (log.isTraceEnabled())
+ log.trace("Additional data session " + path + " for " + cmsSession.getUserDn());
+ return session;
+ }
+ } catch (InterruptedException e) {
+ // silent
+ }
+ }
+
+ Session session = null;
+ if (dataSessions.containsKey(path)) {
+ session = dataSessions.get(path);
+ } else {
+ session = login(repository, workspace);
+ dataSessions.put(path, session);
+ if (log.isTraceEnabled())
+ log.trace("New data session " + path + " for " + cmsSession.getUserDn());
+ }
+ dataSessionsInUse.add(path);
+ return session;
+ }
+
+ private Session login(Repository repository, String workspace) {
+ try {
+ return Subject.doAs(cmsSession.getSubject(), new PrivilegedExceptionAction<Session>() {
+ @Override
+ public Session run() throws Exception {
+ return repository.login(workspace);
+ }
+ });
+ } catch (PrivilegedActionException e) {
+ throw new IllegalStateException("Cannot log in " + cmsSession.getUserDn() + " to JCR", e);
+ }
+ }
+
+ public synchronized void releaseDataSession(String cn, Session session) {
+ if (additionalDataSessions.contains(session)) {
+ JcrUtils.logoutQuietly(session);
+ additionalDataSessions.remove(session);
+ if (log.isTraceEnabled())
+ log.trace("Remove additional data session " + session);
+ return;
+ }
+ String path = cn + '/' + session.getWorkspace().getName();
+ if (!dataSessionsInUse.contains(path))
+ log.warn("Data session " + path + " was not in use for " + cmsSession.getUserDn());
+ dataSessionsInUse.remove(path);
+ Session registeredSession = dataSessions.get(path);
+ if (session != registeredSession)
+ log.warn("Data session " + path + " not consistent for " + cmsSession.getUserDn());
+ if (log.isTraceEnabled())
+ log.trace("Released data session " + session + " for " + path);
+ notifyAll();
+ }
+
+ private void checkValid() {
+ if (!cmsSession.isValid())
+ throw new IllegalStateException(
+ "CMS session " + cmsSession.getUuid() + " is not valid since " + cmsSession.getEnd());
+ }
+
+ private void close() {
+ // FIXME class this when CMS session is closed
+ synchronized (this) {
+ // TODO check data session in use ?
+ for (String path : dataSessions.keySet())
+ JcrUtils.logoutQuietly(dataSessions.get(path));
+ for (Session session : additionalDataSessions)
+ JcrUtils.logoutQuietly(session);
+ }
+ }
+ }
+}
--- /dev/null
+package org.argeo.cms.jcr.internal.servlet;
+
+import java.util.Map;
+
+import javax.jcr.Repository;
+
+import org.apache.jackrabbit.webdav.simple.SimpleWebdavServlet;
+import org.argeo.api.cms.CmsConstants;
+
+/** A {@link SimpleWebdavServlet} based on {@link CmsSessionProvider}. */
+public class CmsWebDavServlet extends SimpleWebdavServlet {
+ private static final long serialVersionUID = 7485800288686328063L;
+ private Repository repository;
+
+ public CmsWebDavServlet() {
+ }
+
+ public CmsWebDavServlet(String alias, Repository repository) {
+ this.repository = repository;
+ setSessionProvider(new CmsSessionProvider(alias));
+ }
+
+ @Override
+ public Repository getRepository() {
+ return repository;
+ }
+
+ public void setRepository(Repository repository, Map<String, String> properties) {
+ this.repository = repository;
+ String alias = properties.get(CmsConstants.CN);
+ if (alias != null)
+ setSessionProvider(new CmsSessionProvider(alias));
+ else
+ throw new IllegalArgumentException("Only aliased repositories are supported");
+ }
+
+}
--- /dev/null
+package org.argeo.cms.jcr.internal.servlet;
+
+import org.argeo.cms.servlet.CmsServletContext;
+
+/** Internal subclass, so that config resources can be loaded from our bundle. */
+public class DataServletContext extends CmsServletContext {
+
+}
--- /dev/null
+package org.argeo.cms.jcr.internal.servlet;
+
+import java.util.Enumeration;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.argeo.api.cms.CmsLog;
+
+public class JcrHttpUtils {
+ public final static String HEADER_AUTHORIZATION = "Authorization";
+ public final static String HEADER_WWW_AUTHENTICATE = "WWW-Authenticate";
+
+ public final static String DEFAULT_PROTECTED_HANDLERS = "/org/argeo/cms/jcr/internal/servlet/protectedHandlers.xml";
+ public final static String WEBDAV_CONFIG = "/org/argeo/cms/jcr/internal/servlet/webdav-config.xml";
+
+ static boolean isBrowser(String userAgent) {
+ return userAgent.contains("webkit") || userAgent.contains("gecko") || userAgent.contains("firefox")
+ || userAgent.contains("msie") || userAgent.contains("chrome") || userAgent.contains("chromium")
+ || userAgent.contains("opera") || userAgent.contains("browser");
+ }
+
+ public static void logResponseHeaders(CmsLog log, HttpServletResponse response) {
+ if (!log.isDebugEnabled())
+ return;
+ for (String headerName : response.getHeaderNames()) {
+ Object headerValue = response.getHeader(headerName);
+ log.debug(headerName + ": " + headerValue);
+ }
+ }
+
+ public static void logRequestHeaders(CmsLog log, HttpServletRequest request) {
+ if (!log.isDebugEnabled())
+ return;
+ for (Enumeration<String> headerNames = request.getHeaderNames(); headerNames.hasMoreElements();) {
+ String headerName = headerNames.nextElement();
+ Object headerValue = request.getHeader(headerName);
+ log.debug(headerName + ": " + headerValue);
+ }
+ log.debug(request.getRequestURI() + "\n");
+ }
+
+ public static void logRequest(CmsLog log, HttpServletRequest request) {
+ log.debug("contextPath=" + request.getContextPath());
+ log.debug("servletPath=" + request.getServletPath());
+ log.debug("requestURI=" + request.getRequestURI());
+ log.debug("queryString=" + request.getQueryString());
+ StringBuilder buf = new StringBuilder();
+ // headers
+ Enumeration<String> en = request.getHeaderNames();
+ while (en.hasMoreElements()) {
+ String header = en.nextElement();
+ Enumeration<String> values = request.getHeaders(header);
+ while (values.hasMoreElements())
+ buf.append(" " + header + ": " + values.nextElement());
+ buf.append('\n');
+ }
+
+ // attributed
+ Enumeration<String> an = request.getAttributeNames();
+ while (an.hasMoreElements()) {
+ String attr = an.nextElement();
+ Object value = request.getAttribute(attr);
+ buf.append(" " + attr + ": " + value);
+ buf.append('\n');
+ }
+ log.debug("\n" + buf);
+ }
+
+ private JcrHttpUtils() {
+
+ }
+}
--- /dev/null
+package org.argeo.cms.jcr.internal.servlet;
+
+import org.argeo.cms.servlet.PrivateWwwAuthServletContext;
+
+/** Internal subclass, so that config resources can be loaded from our bundle. */
+public class JcrServletContext extends PrivateWwwAuthServletContext {
+
+}
--- /dev/null
+package org.argeo.cms.jcr.internal.servlet;
+
+import static javax.jcr.Property.JCR_DESCRIPTION;
+import static javax.jcr.Property.JCR_LAST_MODIFIED;
+import static javax.jcr.Property.JCR_TITLE;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.security.PrivilegedExceptionAction;
+import java.util.Calendar;
+import java.util.Collection;
+
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.security.auth.Subject;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.argeo.api.cms.CmsAuth;
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.cms.CmsException;
+import org.argeo.cms.jcr.CmsJcrUtils;
+import org.argeo.jcr.JcrUtils;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.ServiceReference;
+
+public class LinkServlet extends HttpServlet {
+ private final BundleContext bc = FrameworkUtil.getBundle(getClass()).getBundleContext();
+
+ private static final long serialVersionUID = 3749990143146845708L;
+
+ @Override
+ protected void service(HttpServletRequest request, HttpServletResponse response)
+ throws ServletException, IOException {
+ String path = request.getPathInfo();
+ String userAgent = request.getHeader("User-Agent").toLowerCase();
+ boolean isBot = false;
+ // boolean isCompatibleBrowser = false;
+ if (userAgent.contains("bot") || userAgent.contains("facebook") || userAgent.contains("twitter")) {
+ isBot = true;
+ }
+ // else if (userAgent.contains("webkit") ||
+ // userAgent.contains("gecko") || userAgent.contains("firefox")
+ // || userAgent.contains("msie") || userAgent.contains("chrome") ||
+ // userAgent.contains("chromium")
+ // || userAgent.contains("opera") || userAgent.contains("browser"))
+ // {
+ // isCompatibleBrowser = true;
+ // }
+
+ if (isBot) {
+ // log.warn("# BOT " + request.getHeader("User-Agent"));
+ canonicalAnswer(request, response, path);
+ return;
+ }
+
+ // if (isCompatibleBrowser && log.isTraceEnabled())
+ // log.trace("# BWS " + request.getHeader("User-Agent"));
+ redirectTo(response, "/#" + path);
+ }
+
+ private void redirectTo(HttpServletResponse response, String location) {
+ response.setHeader("Location", location);
+ response.setStatus(HttpServletResponse.SC_FOUND);
+ }
+
+ // private boolean canonicalAnswerNeededBy(HttpServletRequest request) {
+ // String userAgent = request.getHeader("User-Agent").toLowerCase();
+ // return userAgent.startsWith("facebookexternalhit/");
+ // }
+
+ /** For bots which don't understand RWT. */
+ private void canonicalAnswer(HttpServletRequest request, HttpServletResponse response, String path) {
+ Session session = null;
+ try {
+ PrintWriter writer = response.getWriter();
+ session = Subject.doAs(anonymousLogin(), new PrivilegedExceptionAction<Session>() {
+
+ @Override
+ public Session run() throws Exception {
+ Collection<ServiceReference<Repository>> srs = bc.getServiceReferences(Repository.class,
+ "(" + CmsConstants.CN + "=" + CmsConstants.EGO_REPOSITORY + ")");
+ Repository repository = bc.getService(srs.iterator().next());
+ return repository.login();
+ }
+
+ });
+ Node node = session.getNode(path);
+ String title = node.hasProperty(JCR_TITLE) ? node.getProperty(JCR_TITLE).getString() : node.getName();
+ String desc = node.hasProperty(JCR_DESCRIPTION) ? node.getProperty(JCR_DESCRIPTION).getString() : null;
+ Calendar lastUpdate = node.hasProperty(JCR_LAST_MODIFIED) ? node.getProperty(JCR_LAST_MODIFIED).getDate()
+ : null;
+ String url = getCanonicalUrl(node, request);
+ String imgUrl = null;
+ // TODO support images
+// loop: for (NodeIterator it = node.getNodes(); it.hasNext();) {
+// // Takes the first found cms:image
+// Node child = it.nextNode();
+// if (child.isNodeType(CMS_IMAGE)) {
+// imgUrl = getDataUrl(child, request);
+// break loop;
+// }
+// }
+ StringBuilder buf = new StringBuilder();
+ buf.append("<html>");
+ buf.append("<head>");
+ writeMeta(buf, "og:title", escapeHTML(title));
+ writeMeta(buf, "og:type", "website");
+ buf.append("<meta name='twitter:card' content='summary' />");
+ buf.append("<meta name='twitter:site' content='@argeo_org' />");
+ writeMeta(buf, "og:url", url);
+ if (desc != null)
+ writeMeta(buf, "og:description", escapeHTML(desc));
+ if (imgUrl != null)
+ writeMeta(buf, "og:image", imgUrl);
+ if (lastUpdate != null)
+ writeMeta(buf, "og:updated_time", Long.toString(lastUpdate.getTime().getTime()));
+ buf.append("</head>");
+ buf.append("<body>");
+ buf.append("<p><b>!! This page is meant for indexing robots, not for real people," + " visit <a href='/#")
+ .append(path).append("'>").append(escapeHTML(title)).append("</a> instead.</b></p>");
+ writeCanonical(buf, node);
+ buf.append("</body>");
+ buf.append("</html>");
+ writer.print(buf.toString());
+
+ response.setHeader("Content-Type", "text/html");
+ writer.flush();
+ } catch (Exception e) {
+ throw new CmsException("Cannot write canonical answer", e);
+ } finally {
+ JcrUtils.logoutQuietly(session);
+ }
+ }
+
+ /**
+ * From http://stackoverflow.com/questions/1265282/recommended-method-for-
+ * escaping-html-in-java (+ escaping '). TODO Use
+ * org.apache.commons.lang.StringEscapeUtils
+ */
+ private String escapeHTML(String s) {
+ StringBuilder out = new StringBuilder(Math.max(16, s.length()));
+ for (int i = 0; i < s.length(); i++) {
+ char c = s.charAt(i);
+ if (c > 127 || c == '\'' || c == '"' || c == '<' || c == '>' || c == '&') {
+ out.append("&#");
+ out.append((int) c);
+ out.append(';');
+ } else {
+ out.append(c);
+ }
+ }
+ return out.toString();
+ }
+
+ private void writeMeta(StringBuilder buf, String tag, String value) {
+ buf.append("<meta property='").append(tag).append("' content='").append(value).append("'/>");
+ }
+
+ private void writeCanonical(StringBuilder buf, Node node) throws RepositoryException {
+ buf.append("<div>");
+ if (node.hasProperty(JCR_TITLE))
+ buf.append("<p>").append(node.getProperty(JCR_TITLE).getString()).append("</p>");
+ if (node.hasProperty(JCR_DESCRIPTION))
+ buf.append("<p>").append(node.getProperty(JCR_DESCRIPTION).getString()).append("</p>");
+ NodeIterator children = node.getNodes();
+ while (children.hasNext()) {
+ writeCanonical(buf, children.nextNode());
+ }
+ buf.append("</div>");
+ }
+
+ // DATA
+ private StringBuilder getServerBaseUrl(HttpServletRequest request) {
+ try {
+ URL url = new URL(request.getRequestURL().toString());
+ StringBuilder buf = new StringBuilder();
+ buf.append(url.getProtocol()).append("://").append(url.getHost());
+ if (url.getPort() != -1)
+ buf.append(':').append(url.getPort());
+ return buf;
+ } catch (MalformedURLException e) {
+ throw new CmsException("Cannot extract server base URL from " + request.getRequestURL(), e);
+ }
+ }
+
+ private String getDataUrl(Node node, HttpServletRequest request) throws RepositoryException {
+ try {
+ StringBuilder buf = getServerBaseUrl(request);
+ buf.append(CmsJcrUtils.getDataPath(CmsConstants.EGO_REPOSITORY, node));
+ return new URL(buf.toString()).toString();
+ } catch (MalformedURLException e) {
+ throw new CmsException("Cannot build data URL for " + node, e);
+ }
+ }
+
+ // public static String getDataPath(Node node) throws
+ // RepositoryException {
+ // assert node != null;
+ // String userId = node.getSession().getUserID();
+ //// if (log.isTraceEnabled())
+ //// log.trace(userId + " : " + node.getPath());
+ // StringBuilder buf = new StringBuilder();
+ // boolean isAnonymous =
+ // userId.equalsIgnoreCase(NodeConstants.ROLE_ANONYMOUS);
+ // if (isAnonymous)
+ // buf.append(WEBDAV_PUBLIC);
+ // else
+ // buf.append(WEBDAV_PRIVATE);
+ // Session session = node.getSession();
+ // Repository repository = session.getRepository();
+ // String cn;
+ // if (repository.isSingleValueDescriptor(NodeConstants.CN)) {
+ // cn = repository.getDescriptor(NodeConstants.CN);
+ // } else {
+ //// log.warn("No cn defined in repository, using " +
+ // NodeConstants.NODE);
+ // cn = NodeConstants.NODE;
+ // }
+ // return
+ // buf.append('/').append(cn).append('/').append(session.getWorkspace().getName()).append(node.getPath())
+ // .toString();
+ // }
+
+ private String getCanonicalUrl(Node node, HttpServletRequest request) throws RepositoryException {
+ try {
+ StringBuilder buf = getServerBaseUrl(request);
+ buf.append('/').append('!').append(node.getPath());
+ return new URL(buf.toString()).toString();
+ } catch (MalformedURLException e) {
+ throw new CmsException("Cannot build data URL for " + node, e);
+ }
+ // return request.getRequestURL().append('!').append(node.getPath())
+ // .toString();
+ }
+
+ private Subject anonymousLogin() {
+ Subject subject = new Subject();
+ LoginContext lc;
+ try {
+ lc = new LoginContext(CmsAuth.LOGIN_CONTEXT_ANONYMOUS, subject);
+ lc.login();
+ return subject;
+ } catch (LoginException e) {
+ throw new CmsException("Cannot login as anonymous", e);
+ }
+ }
+
+}
--- /dev/null
+<config>
+ <protecteditemremovehandler>
+ <class name="org.apache.jackrabbit.server.remoting.davex.AclRemoveHandler" />
+ </protecteditemremovehandler>
+</config>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+<!--
+<!DOCTYPE config [
+ <!ELEMENT config (iomanager , propertymanager, (collection | noncollection)? , filter?, mimetypeproperties?) >
+
+ <!ELEMENT iomanager (class, iohandler*) >
+ <!ELEMENT iohandler (class) >
+
+ <!ELEMENT propertymanager (class, propertyhandler*) >
+ <!ELEMENT propertyhandler (class) >
+
+ <!ELEMENT collection (nodetypes) >
+ <!ELEMENT noncollection (nodetypes) >
+
+ <!ELEMENT filter (class, namespaces?, nodetypes?) >
+
+ <!ELEMENT class >
+ <!ATTLIST class
+ name CDATA #REQUIRED
+ >
+ <!ELEMENT namespaces (prefix | uri)* >
+ <!ELEMENT prefix (CDATA) >
+ <!ELEMENT uri (CDATA) >
+
+ <!ELEMENT nodetypes (nodetype)* >
+ <!ELEMENT nodetype (CDATA) >
+
+ <!ELEMENT mimetypeproperties (mimemapping*, defaultmimetype) >
+
+ <!ELEMENT mimemapping >
+ <!ATTLIST mimemapping
+ extension CDATA #REQUIRED
+ mimetype CDATA #REQUIRED
+ >
+
+ <!ELEMENT defaultmimetype (CDATA) >
+]>
+-->
+
+<config>
+ <!--
+ Defines the IOManager implementation that is responsible for passing
+ import/export request to the individual IO-handlers.
+ -->
+ <iomanager>
+ <!-- class element defines the manager to be used. The specified class
+ must implement the IOManager interface.
+ Note, that the handlers are being added and called in the order
+ they appear in the configuration.
+ -->
+ <class name="org.apache.jackrabbit.server.io.IOManagerImpl" />
+ <iohandler>
+ <class name="org.apache.jackrabbit.server.io.VersionHandler" />
+ </iohandler>
+ <iohandler>
+ <class name="org.apache.jackrabbit.server.io.VersionHistoryHandler" />
+ </iohandler>
+<!-- <iohandler> -->
+<!-- <class name="org.apache.jackrabbit.server.io.ZipHandler" /> -->
+<!-- </iohandler> -->
+<!-- <iohandler> -->
+<!-- <class name="org.apache.jackrabbit.server.io.XmlHandler" /> -->
+<!-- </iohandler> -->
+ <iohandler>
+ <class name="org.apache.jackrabbit.server.io.DirListingExportHandler" />
+ </iohandler>
+ <iohandler>
+ <class name="org.apache.jackrabbit.server.io.DefaultHandler" />
+ </iohandler>
+ </iomanager>
+ <!--
+ Example config for iomanager that populates its list of handlers with
+ default values. Therefore the 'iohandler' elements are omited.
+ -->
+ <!--
+ <iomanager>
+ <class name="org.apache.jackrabbit.server.io.DefaultIOManager" />
+ </iomanager>
+ -->
+ <!--
+ Defines the PropertyManager implementation that is responsible for export
+ and import of resource properties.
+ -->
+ <propertymanager>
+ <!-- class element defines the manager to be used. The specified class
+ must implement the PropertyManager interface.
+ Note, that the handlers are being added and called in the order
+ they appear in the configuration.
+ -->
+ <class name="org.apache.jackrabbit.server.io.PropertyManagerImpl" />
+ <propertyhandler>
+ <class name="org.apache.jackrabbit.server.io.VersionHandler" />
+ </propertyhandler>
+ <propertyhandler>
+ <class name="org.apache.jackrabbit.server.io.VersionHistoryHandler" />
+ </propertyhandler>
+<!-- <propertyhandler> -->
+<!-- <class name="org.apache.jackrabbit.server.io.ZipHandler" /> -->
+<!-- </propertyhandler> -->
+<!-- <propertyhandler> -->
+<!-- <class name="org.apache.jackrabbit.server.io.XmlHandler" /> -->
+<!-- </propertyhandler> -->
+ <propertyhandler>
+ <class name="org.apache.jackrabbit.server.io.DefaultHandler" />
+ </propertyhandler>
+ </propertymanager>
+ <!--
+ Define nodetypes, that should never by displayed as 'collection'
+ -->
+ <noncollection>
+ <nodetypes>
+ <nodetype>nt:file</nodetype>
+ <nodetype>nt:resource</nodetype>
+ </nodetypes>
+ </noncollection>
+ <!--
+ Example: Defines nodetypes, that should always be displayed as 'collection'.
+ -->
+ <!--
+ <collection>
+ <nodetypes>
+ <nodetype>nt:folder</nodetype>
+ <nodetype>rep:root</nodetype>
+ </nodetypes>
+ </collection>
+ -->
+ <!--
+ Filter that allows to prevent certain items from being displayed.
+ Please note, that this has an effect on PROPFIND calls only and does not
+ provide limited access to those items matching any of the filters.
+
+ However specifying a filter may cause problems with PUT or MKCOL if the
+ resource to be created is being filtered out, thus resulting in inconsistent
+ responses (e.g. PUT followed by PROPFIND on parent).
+ -->
+ <filter>
+ <!-- class element defines the resource filter to be used. The specified class
+ must implement the ItemFilter interface -->
+ <class name="org.apache.jackrabbit.webdav.simple.DefaultItemFilter" />
+ <!--
+ Nodetype names to be used to filter child nodes.
+ A child node can be filtered if the declaring nodetype of its definition
+ is one of the nodetype names specified in the nodetypes Element.
+ E.g. defining 'rep:root' as filtered nodetype whould result in jcr:system
+ being hidden but no other child node of the root node, since those
+ are defined by the nodetype nt:unstructered.
+ -->
+ <!--
+ <nodetypes>
+ <nodetype>rep:root</nodetype>
+ </nodetypes>
+ -->
+ <!--
+ Namespace prefixes or uris. Items having a name that matches any of the
+ entries will be filtered.
+ -->
+ <namespaces>
+ <prefix>rep</prefix>
+ <prefix>jcr</prefix>
+ <!-- Argeo namespaces -->
+ <prefix>node</prefix>
+ <prefix>argeo</prefix>
+ <prefix>cms</prefix>
+ <prefix>slc</prefix>
+ <prefix>connect</prefix>
+ <prefix>activities</prefix>
+ <prefix>people</prefix>
+ <prefix>documents</prefix>
+ <prefix>tracker</prefix>
+ <!--
+ <uri>internal</uri>
+ <uri>http://www.jcp.org/jcr/1.0</uri>
+ -->
+ </namespaces>
+ </filter>
+
+ <!--
+ Optional 'mimetypeproperties' element.
+ It defines additional or replaces existing mappings for the MimeResolver
+ instance created by the ResourceConfig.
+ The default mappings are defined in org.apache.jackrabbit.server.io.mimetypes.properties.
+ If the default mime type defined by MimeResolver is 'application/octet-stream'.
+ -->
+ <!--
+ <mimetypeproperties>
+ <mimemapping extension="rtf" mimetype="application/rtf" />
+ <mimemapping extension="ott" mimetype="application/vnd.oasis.opendocument.text-template" />
+ <defaultmimetype>text/html</defaultmimetype>
+ </mimetypeproperties>
+ -->
+</config>
--- /dev/null
+<ldap = 'http://www.argeo.org/ns/ldap'>
--- /dev/null
+<node = 'http://www.argeo.org/ns/node'>
+
+[node:userHome]
+mixin
+- ldap:uid (STRING) m
+
+[node:groupHome]
+mixin
+- ldap:cn (STRING) m
--- /dev/null
+package org.argeo.cms.jcr.tabular;
+
+import java.io.OutputStream;
+
+import org.argeo.cms.tabular.TabularWriter;
+import org.argeo.util.CsvWriter;
+
+/** Write tabular content in a stream as CSV. Wraps a {@link CsvWriter}. */
+public class CsvTabularWriter implements TabularWriter {
+ private CsvWriter csvWriter;
+
+ public CsvTabularWriter(OutputStream out) {
+ this.csvWriter = new CsvWriter(out);
+ }
+
+ public void appendRow(Object[] row) {
+ csvWriter.writeLine(row);
+ }
+
+ public void close() {
+ }
+
+}
--- /dev/null
+package org.argeo.cms.jcr.tabular;
+
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ArrayBlockingQueue;
+
+import javax.jcr.Binary;
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.Property;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+
+import org.apache.commons.io.IOUtils;
+import org.argeo.cms.ArgeoTypes;
+import org.argeo.cms.tabular.ArrayTabularRow;
+import org.argeo.cms.tabular.TabularColumn;
+import org.argeo.cms.tabular.TabularRow;
+import org.argeo.cms.tabular.TabularRowIterator;
+import org.argeo.jcr.JcrException;
+import org.argeo.util.CsvParser;
+
+/** Iterates over the rows of a {@link ArgeoTypes#ARGEO_TABLE} node. */
+public class JcrTabularRowIterator implements TabularRowIterator {
+ private Boolean hasNext = null;
+ private Boolean parsingCompleted = false;
+
+ private Long currentRowNumber = 0l;
+
+ private List<TabularColumn> header = new ArrayList<TabularColumn>();
+
+ /** referenced so that we can close it */
+ private Binary binary;
+ private InputStream in;
+
+ private CsvParser csvParser;
+ private ArrayBlockingQueue<List<String>> textLines;
+
+ public JcrTabularRowIterator(Node tableNode) {
+ try {
+ for (NodeIterator it = tableNode.getNodes(); it.hasNext();) {
+ Node node = it.nextNode();
+ if (node.isNodeType(ArgeoTypes.ARGEO_COLUMN)) {
+ Integer type = PropertyType.valueFromName(node.getProperty(
+ Property.JCR_REQUIRED_TYPE).getString());
+ TabularColumn tc = new TabularColumn(node.getProperty(
+ Property.JCR_TITLE).getString(), type);
+ header.add(tc);
+ }
+ }
+ Node contentNode = tableNode.getNode(Property.JCR_CONTENT);
+ if (contentNode.isNodeType(ArgeoTypes.ARGEO_CSV)) {
+ textLines = new ArrayBlockingQueue<List<String>>(1000);
+ csvParser = new CsvParser() {
+ protected void processLine(Integer lineNumber,
+ List<String> header, List<String> tokens) {
+ try {
+ textLines.put(tokens);
+ } catch (InterruptedException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ // textLines.add(tokens);
+ if (hasNext == null) {
+ hasNext = true;
+ synchronized (JcrTabularRowIterator.this) {
+ JcrTabularRowIterator.this.notifyAll();
+ }
+ }
+ }
+ };
+ csvParser.setNoHeader(true);
+ binary = contentNode.getProperty(Property.JCR_DATA).getBinary();
+ in = binary.getStream();
+ Thread thread = new Thread(contentNode.getPath() + " reader") {
+ public void run() {
+ try {
+ csvParser.parse(in);
+ } finally {
+ parsingCompleted = true;
+ IOUtils.closeQuietly(in);
+ }
+ }
+ };
+ thread.start();
+ }
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot read table " + tableNode, e);
+ }
+ }
+
+ public synchronized boolean hasNext() {
+ // we don't know if there is anything available
+ // while (hasNext == null)
+ // try {
+ // wait();
+ // } catch (InterruptedException e) {
+ // // silent
+ // // FIXME better deal with interruption
+ // Thread.currentThread().interrupt();
+ // break;
+ // }
+
+ // buffer not empty
+ if (!textLines.isEmpty())
+ return true;
+
+ // maybe the parsing is finished but the flag has not been set
+ while (!parsingCompleted && textLines.isEmpty())
+ try {
+ wait(100);
+ } catch (InterruptedException e) {
+ // silent
+ // FIXME better deal with interruption
+ Thread.currentThread().interrupt();
+ break;
+ }
+
+ // buffer not empty
+ if (!textLines.isEmpty())
+ return true;
+
+ // (parsingCompleted && textLines.isEmpty())
+ return false;
+
+ // if (!hasNext && textLines.isEmpty()) {
+ // if (in != null) {
+ // IOUtils.closeQuietly(in);
+ // in = null;
+ // }
+ // if (binary != null) {
+ // JcrUtils.closeQuietly(binary);
+ // binary = null;
+ // }
+ // return false;
+ // } else
+ // return true;
+ }
+
+ public synchronized TabularRow next() {
+ try {
+ List<String> tokens = textLines.take();
+ List<Object> objs = new ArrayList<Object>(tokens.size());
+ for (String token : tokens) {
+ // TODO convert to other formats using header
+ objs.add(token);
+ }
+ currentRowNumber++;
+ return new ArrayTabularRow(objs);
+ } catch (InterruptedException e) {
+ // silent
+ // FIXME better deal with interruption
+ }
+ return null;
+ }
+
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+
+ public Long getCurrentRowNumber() {
+ return currentRowNumber;
+ }
+
+ public List<TabularColumn> getHeader() {
+ return header;
+ }
+
+}
--- /dev/null
+package org.argeo.cms.jcr.tabular;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.util.List;
+
+import javax.jcr.Binary;
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+
+import org.apache.commons.io.IOUtils;
+import org.argeo.cms.ArgeoTypes;
+import org.argeo.cms.tabular.TabularColumn;
+import org.argeo.cms.tabular.TabularWriter;
+import org.argeo.jcr.JcrException;
+import org.argeo.jcr.JcrUtils;
+import org.argeo.util.CsvWriter;
+
+/** Write / reference tabular content in a JCR repository. */
+public class JcrTabularWriter implements TabularWriter {
+ private Node contentNode;
+ private ByteArrayOutputStream out;
+ private CsvWriter csvWriter;
+
+ @SuppressWarnings("unused")
+ private final List<TabularColumn> columns;
+
+ /** Creates a table node */
+ public JcrTabularWriter(Node tableNode, List<TabularColumn> columns,
+ String contentNodeType) {
+ try {
+ this.columns = columns;
+ for (TabularColumn column : columns) {
+ String normalized = JcrUtils.replaceInvalidChars(column
+ .getName());
+ Node columnNode = tableNode.addNode(normalized,
+ ArgeoTypes.ARGEO_COLUMN);
+ columnNode.setProperty(Property.JCR_TITLE, column.getName());
+ if (column.getType() != null)
+ columnNode.setProperty(Property.JCR_REQUIRED_TYPE,
+ PropertyType.nameFromValue(column.getType()));
+ else
+ columnNode.setProperty(Property.JCR_REQUIRED_TYPE,
+ PropertyType.TYPENAME_STRING);
+ }
+ contentNode = tableNode.addNode(Property.JCR_CONTENT,
+ contentNodeType);
+ if (contentNodeType.equals(ArgeoTypes.ARGEO_CSV)) {
+ contentNode.setProperty(Property.JCR_MIMETYPE, "text/csv");
+ contentNode.setProperty(Property.JCR_ENCODING, "UTF-8");
+ out = new ByteArrayOutputStream();
+ csvWriter = new CsvWriter(out);
+ }
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot create table node " + tableNode, e);
+ }
+ }
+
+ public void appendRow(Object[] row) {
+ csvWriter.writeLine(row);
+ }
+
+ public void close() {
+ Binary binary = null;
+ InputStream in = null;
+ try {
+ // TODO parallelize with pipes and writing from another thread
+ in = new ByteArrayInputStream(out.toByteArray());
+ binary = contentNode.getSession().getValueFactory()
+ .createBinary(in);
+ contentNode.setProperty(Property.JCR_DATA, binary);
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot store data in " + contentNode, e);
+ } finally {
+ IOUtils.closeQuietly(in);
+ JcrUtils.closeQuietly(binary);
+ }
+ }
+}
--- /dev/null
+/** Argeo CMS implementation of the Argeo Tabular API (CSV, JCR). */
+package org.argeo.cms.jcr.tabular;
\ No newline at end of file
--- /dev/null
+package org.argeo.jackrabbit;
+
+import java.util.Map;
+
+import javax.security.auth.Subject;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.login.LoginException;
+import javax.security.auth.spi.LoginModule;
+
+import org.apache.jackrabbit.core.security.SecurityConstants;
+import org.apache.jackrabbit.core.security.principal.AdminPrincipal;
+
+@Deprecated
+public class JackrabbitAdminLoginModule implements LoginModule {
+ private Subject subject;
+
+ @Override
+ public void initialize(Subject subject, CallbackHandler callbackHandler,
+ Map<String, ?> sharedState, Map<String, ?> options) {
+ this.subject = subject;
+ }
+
+ @Override
+ public boolean login() throws LoginException {
+ // TODO check permission?
+ return true;
+ }
+
+ @Override
+ public boolean commit() throws LoginException {
+ subject.getPrincipals().add(
+ new AdminPrincipal(SecurityConstants.ADMIN_ID));
+ return true;
+ }
+
+ @Override
+ public boolean abort() throws LoginException {
+ return true;
+ }
+
+ @Override
+ public boolean logout() throws LoginException {
+ subject.getPrincipals().removeAll(
+ subject.getPrincipals(AdminPrincipal.class));
+ return true;
+ }
+
+}
--- /dev/null
+package org.argeo.jackrabbit;
+
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.net.URL;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.jackrabbit.commons.cnd.CndImporter;
+import org.apache.jackrabbit.commons.cnd.ParseException;
+import org.apache.jackrabbit.core.config.RepositoryConfig;
+import org.apache.jackrabbit.core.fs.FileSystemException;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.jcr.JcrCallback;
+import org.argeo.jcr.JcrException;
+import org.argeo.jcr.JcrUtils;
+
+/** Migrate the data in a Jackrabbit repository. */
+@Deprecated
+public class JackrabbitDataModelMigration implements Comparable<JackrabbitDataModelMigration> {
+ private final static CmsLog log = CmsLog.getLog(JackrabbitDataModelMigration.class);
+
+ private String dataModelNodePath;
+ private String targetVersion;
+ private URL migrationCnd;
+ private JcrCallback dataModification;
+
+ /**
+ * Expects an already started repository with the old data model to migrate.
+ * Expects to be run with admin rights (Repository.login() will be used).
+ *
+ * @return true if a migration was performed and the repository needs to be
+ * restarted and its caches cleared.
+ */
+ public Boolean migrate(Session session) {
+ long begin = System.currentTimeMillis();
+ Reader reader = null;
+ try {
+ // check if already migrated
+ if (!session.itemExists(dataModelNodePath)) {
+// log.warn("Node " + dataModelNodePath + " does not exist: nothing to migrate.");
+ return false;
+ }
+// Node dataModelNode = session.getNode(dataModelNodePath);
+// if (dataModelNode.hasProperty(ArgeoNames.ARGEO_DATA_MODEL_VERSION)) {
+// String currentVersion = dataModelNode.getProperty(
+// ArgeoNames.ARGEO_DATA_MODEL_VERSION).getString();
+// if (compareVersions(currentVersion, targetVersion) >= 0) {
+// log.info("Data model at version " + currentVersion
+// + ", no need to migrate.");
+// return false;
+// }
+// }
+
+ // apply transitional CND
+ if (migrationCnd != null) {
+ reader = new InputStreamReader(migrationCnd.openStream());
+ CndImporter.registerNodeTypes(reader, session, true);
+ session.save();
+// log.info("Registered migration node types from " + migrationCnd);
+ }
+
+ // modify data
+ dataModification.execute(session);
+
+ // apply changes
+ session.save();
+
+ long duration = System.currentTimeMillis() - begin;
+// log.info("Migration of data model " + dataModelNodePath + " to " + targetVersion + " performed in "
+// + duration + "ms");
+ return true;
+ } catch (RepositoryException e) {
+ JcrUtils.discardQuietly(session);
+ throw new JcrException("Migration of data model " + dataModelNodePath + " to " + targetVersion + " failed.",
+ e);
+ } catch (ParseException | IOException e) {
+ JcrUtils.discardQuietly(session);
+ throw new RuntimeException(
+ "Migration of data model " + dataModelNodePath + " to " + targetVersion + " failed.", e);
+ } finally {
+ JcrUtils.logoutQuietly(session);
+ IOUtils.closeQuietly(reader);
+ }
+ }
+
+ protected static int compareVersions(String version1, String version2) {
+ // TODO do a proper version analysis and comparison
+ return version1.compareTo(version2);
+ }
+
+ /** To be called on a stopped repository. */
+ public static void clearRepositoryCaches(RepositoryConfig repositoryConfig) {
+ try {
+ String customeNodeTypesPath = "/nodetypes/custom_nodetypes.xml";
+ // FIXME causes weird error in Eclipse
+ repositoryConfig.getFileSystem().deleteFile(customeNodeTypesPath);
+ if (log.isDebugEnabled())
+ log.debug("Cleared " + customeNodeTypesPath);
+ } catch (RuntimeException e) {
+ throw e;
+ } catch (RepositoryException e) {
+ throw new JcrException(e);
+ } catch (FileSystemException e) {
+ throw new RuntimeException("Cannot clear node types cache.",e);
+ }
+
+ // File customNodeTypes = new File(home.getPath()
+ // + "/repository/nodetypes/custom_nodetypes.xml");
+ // if (customNodeTypes.exists()) {
+ // customNodeTypes.delete();
+ // if (log.isDebugEnabled())
+ // log.debug("Cleared " + customNodeTypes);
+ // } else {
+ // log.warn("File " + customNodeTypes + " not found.");
+ // }
+ }
+
+ /*
+ * FOR USE IN (SORTED) SETS
+ */
+
+ public int compareTo(JackrabbitDataModelMigration dataModelMigration) {
+ // TODO make ordering smarter
+ if (dataModelNodePath.equals(dataModelMigration.dataModelNodePath))
+ return compareVersions(targetVersion, dataModelMigration.targetVersion);
+ else
+ return dataModelNodePath.compareTo(dataModelMigration.dataModelNodePath);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof JackrabbitDataModelMigration))
+ return false;
+ JackrabbitDataModelMigration dataModelMigration = (JackrabbitDataModelMigration) obj;
+ return dataModelNodePath.equals(dataModelMigration.dataModelNodePath)
+ && targetVersion.equals(dataModelMigration.targetVersion);
+ }
+
+ @Override
+ public int hashCode() {
+ return targetVersion.hashCode();
+ }
+
+ public void setDataModelNodePath(String dataModelNodePath) {
+ this.dataModelNodePath = dataModelNodePath;
+ }
+
+ public void setTargetVersion(String targetVersion) {
+ this.targetVersion = targetVersion;
+ }
+
+ public void setMigrationCnd(URL migrationCnd) {
+ this.migrationCnd = migrationCnd;
+ }
+
+ public void setDataModification(JcrCallback dataModification) {
+ this.dataModification = dataModification;
+ }
+
+ public String getDataModelNodePath() {
+ return dataModelNodePath;
+ }
+
+ public String getTargetVersion() {
+ return targetVersion;
+ }
+
+}
--- /dev/null
+package org.argeo.jackrabbit.client;
+
+import java.util.Map;
+
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.RepositoryFactory;
+
+import org.apache.jackrabbit.jcr2spi.Jcr2spiRepositoryFactory;
+import org.apache.jackrabbit.jcr2spi.RepositoryImpl;
+import org.apache.jackrabbit.spi.RepositoryServiceFactory;
+
+/** A customised {@link RepositoryFactory} access a remote DAVEX service. */
+public class ClientDavexRepositoryFactory implements RepositoryFactory {
+ public final static String JACKRABBIT_DAVEX_URI = ClientDavexRepositoryServiceFactory.PARAM_REPOSITORY_URI;
+ public final static String JACKRABBIT_REMOTE_DEFAULT_WORKSPACE = ClientDavexRepositoryServiceFactory.PARAM_WORKSPACE_NAME_DEFAULT;
+
+ @SuppressWarnings("rawtypes")
+ @Override
+ public Repository getRepository(Map parameters) throws RepositoryException {
+ RepositoryServiceFactory repositoryServiceFactory = new ClientDavexRepositoryServiceFactory();
+ return RepositoryImpl
+ .create(new Jcr2spiRepositoryFactory.RepositoryConfigImpl(repositoryServiceFactory, parameters));
+ }
+
+}
--- /dev/null
+package org.argeo.jackrabbit.client;
+
+import javax.jcr.RepositoryException;
+
+import org.apache.http.client.protocol.HttpClientContext;
+import org.apache.http.protocol.HttpContext;
+import org.apache.jackrabbit.spi.SessionInfo;
+import org.apache.jackrabbit.spi2davex.BatchReadConfig;
+import org.apache.jackrabbit.spi2davex.RepositoryServiceImpl;
+
+/**
+ * Wrapper for {@link RepositoryServiceImpl} in order to access the underlying
+ * {@link HttpClientContext}.
+ */
+public class ClientDavexRepositoryService extends RepositoryServiceImpl {
+
+ public ClientDavexRepositoryService(String jcrServerURI, BatchReadConfig batchReadConfig)
+ throws RepositoryException {
+ super(jcrServerURI, batchReadConfig);
+ }
+
+ public ClientDavexRepositoryService(String jcrServerURI, String defaultWorkspaceName,
+ BatchReadConfig batchReadConfig, int itemInfoCacheSize, int maximumHttpConnections)
+ throws RepositoryException {
+ super(jcrServerURI, defaultWorkspaceName, batchReadConfig, itemInfoCacheSize, maximumHttpConnections);
+ }
+
+ public ClientDavexRepositoryService(String jcrServerURI, String defaultWorkspaceName,
+ BatchReadConfig batchReadConfig, int itemInfoCacheSize) throws RepositoryException {
+ super(jcrServerURI, defaultWorkspaceName, batchReadConfig, itemInfoCacheSize);
+ }
+
+ @Override
+ protected HttpContext getContext(SessionInfo sessionInfo) throws RepositoryException {
+ HttpClientContext result = HttpClientContext.create();
+ result.setAuthCache(new NonSerialBasicAuthCache());
+ return result;
+ }
+
+}
--- /dev/null
+package org.argeo.jackrabbit.client;
+
+import java.util.Map;
+
+import javax.jcr.RepositoryException;
+
+import org.apache.jackrabbit.spi.RepositoryService;
+import org.apache.jackrabbit.spi.commons.ItemInfoCacheImpl;
+import org.apache.jackrabbit.spi2davex.BatchReadConfig;
+import org.apache.jackrabbit.spi2davex.Spi2davexRepositoryServiceFactory;
+
+/**
+ * Wrapper for {@link Spi2davexRepositoryServiceFactory} in order to create a
+ * {@link ClientDavexRepositoryService}.
+ */
+public class ClientDavexRepositoryServiceFactory extends Spi2davexRepositoryServiceFactory {
+ @Override
+ public RepositoryService createRepositoryService(Map<?, ?> parameters) throws RepositoryException {
+ // retrieve the repository uri
+ String uri;
+ if (parameters == null) {
+ uri = System.getProperty(PARAM_REPOSITORY_URI);
+ } else {
+ Object repoUri = parameters.get(PARAM_REPOSITORY_URI);
+ uri = (repoUri == null) ? null : repoUri.toString();
+ }
+ if (uri == null) {
+ uri = DEFAULT_REPOSITORY_URI;
+ }
+
+ // load other optional configuration parameters
+ BatchReadConfig brc = null;
+ int itemInfoCacheSize = ItemInfoCacheImpl.DEFAULT_CACHE_SIZE;
+ int maximumHttpConnections = 0;
+
+ // since JCR-4120 the default workspace name is no longer set to 'default'
+ // note: if running with JCR Server < 1.5 a default workspace name must
+ // therefore be configured
+ String workspaceNameDefault = null;
+
+ if (parameters != null) {
+ // batchRead config
+ Object param = parameters.get(PARAM_BATCHREAD_CONFIG);
+ if (param != null && param instanceof BatchReadConfig) {
+ brc = (BatchReadConfig) param;
+ }
+
+ // itemCache size config
+ param = parameters.get(PARAM_ITEMINFO_CACHE_SIZE);
+ if (param != null) {
+ try {
+ itemInfoCacheSize = Integer.parseInt(param.toString());
+ } catch (NumberFormatException e) {
+ // ignore, use default
+ }
+ }
+
+ // max connections config
+ param = parameters.get(PARAM_MAX_CONNECTIONS);
+ if (param != null) {
+ try {
+ maximumHttpConnections = Integer.parseInt(param.toString());
+ } catch (NumberFormatException e) {
+ // using default
+ }
+ }
+
+ param = parameters.get(PARAM_WORKSPACE_NAME_DEFAULT);
+ if (param != null) {
+ workspaceNameDefault = param.toString();
+ }
+ }
+
+ if (maximumHttpConnections > 0) {
+ return new ClientDavexRepositoryService(uri, workspaceNameDefault, brc, itemInfoCacheSize,
+ maximumHttpConnections);
+ } else {
+ return new ClientDavexRepositoryService(uri, workspaceNameDefault, brc, itemInfoCacheSize);
+ }
+ }
+
+}
--- /dev/null
+package org.argeo.jackrabbit.client;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.HashMap;
+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.apache.http.client.protocol.HttpClientContext;
+import org.apache.http.protocol.HttpContext;
+import org.apache.jackrabbit.jcr2dav.Jcr2davRepositoryFactory;
+import org.apache.jackrabbit.jcr2spi.Jcr2spiRepositoryFactory;
+import org.apache.jackrabbit.jcr2spi.RepositoryImpl;
+import org.apache.jackrabbit.spi.RepositoryService;
+import org.apache.jackrabbit.spi.RepositoryServiceFactory;
+import org.apache.jackrabbit.spi.SessionInfo;
+import org.apache.jackrabbit.spi.commons.ItemInfoCacheImpl;
+import org.apache.jackrabbit.spi2davex.BatchReadConfig;
+import org.apache.jackrabbit.spi2davex.RepositoryServiceImpl;
+import org.apache.jackrabbit.spi2davex.Spi2davexRepositoryServiceFactory;
+import org.argeo.jcr.JcrUtils;
+
+/** Minimal client to test JCR DAVEX connectivity. */
+public class JackrabbitClient {
+ final static String JACKRABBIT_REPOSITORY_URI = "org.apache.jackrabbit.repository.uri";
+ final static String JACKRABBIT_DAVEX_URI = "org.apache.jackrabbit.spi2davex.uri";
+ final static String JACKRABBIT_REMOTE_DEFAULT_WORKSPACE = "org.apache.jackrabbit.spi2davex.WorkspaceNameDefault";
+
+ public static void main(String[] args) {
+ String repoUri = args.length == 0 ? "http://root:demo@localhost:7070/jcr/ego" : args[0];
+ String workspace = args.length < 2 ? "home" : args[1];
+
+ Repository repository = null;
+ Session session = null;
+
+ URI uri;
+ try {
+ uri = new URI(repoUri);
+ } catch (URISyntaxException e1) {
+ throw new IllegalArgumentException(e1);
+ }
+
+ if (uri.getScheme().equals("http") || uri.getScheme().equals("https")) {
+
+ RepositoryFactory repositoryFactory = new Jcr2davRepositoryFactory() {
+ @SuppressWarnings("rawtypes")
+ public Repository getRepository(Map parameters) throws RepositoryException {
+ RepositoryServiceFactory repositoryServiceFactory = new Spi2davexRepositoryServiceFactory() {
+
+ @Override
+ public RepositoryService createRepositoryService(Map<?, ?> parameters)
+ throws RepositoryException {
+ Object uri = parameters.get(JACKRABBIT_DAVEX_URI);
+ Object defaultWorkspace = parameters.get(JACKRABBIT_REMOTE_DEFAULT_WORKSPACE);
+ BatchReadConfig brc = null;
+ return new RepositoryServiceImpl(uri.toString(), defaultWorkspace.toString(), brc,
+ ItemInfoCacheImpl.DEFAULT_CACHE_SIZE) {
+
+ @Override
+ protected HttpContext getContext(SessionInfo sessionInfo) throws RepositoryException {
+ HttpClientContext result = HttpClientContext.create();
+ result.setAuthCache(new NonSerialBasicAuthCache());
+ return result;
+ }
+
+ };
+ }
+ };
+ return RepositoryImpl.create(
+ new Jcr2spiRepositoryFactory.RepositoryConfigImpl(repositoryServiceFactory, parameters));
+ }
+ };
+ Map<String, String> params = new HashMap<String, String>();
+ params.put(JACKRABBIT_DAVEX_URI, repoUri.toString());
+ // FIXME make it configurable
+ params.put(JACKRABBIT_REMOTE_DEFAULT_WORKSPACE, "sys");
+
+ try {
+ repository = repositoryFactory.getRepository(params);
+ if (repository != null)
+ session = repository.login(workspace);
+ else
+ throw new IllegalArgumentException("Repository " + repoUri + " not found");
+ } catch (RepositoryException e) {
+ e.printStackTrace();
+ }
+
+ } else {
+ Path path = Paths.get(uri.getPath());
+ }
+
+ try {
+ Node rootNode = session.getRootNode();
+ NodeIterator nit = rootNode.getNodes();
+ while (nit.hasNext()) {
+ System.out.println(nit.nextNode().getPath());
+ }
+
+ Node newNode = JcrUtils.mkdirs(rootNode, "dir/subdir");
+ System.out.println("Created folder " + newNode.getPath());
+ Node newFile = JcrUtils.copyBytesAsFile(newNode, "test.txt", "TEST".getBytes());
+ System.out.println("Created file " + newFile.getPath());
+ try (BufferedReader reader = new BufferedReader(new InputStreamReader(JcrUtils.getFileAsStream(newFile)))) {
+ System.out.println("Read " + reader.readLine());
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ newNode.getParent().remove();
+ System.out.println("Removed new nodes");
+ } catch (RepositoryException e) {
+ e.printStackTrace();
+ }
+ }
+}
--- /dev/null
+package org.argeo.jackrabbit.client;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.http.HttpHost;
+import org.apache.http.auth.AuthScheme;
+import org.apache.http.client.AuthCache;
+
+/**
+ * Implementation of {@link AuthCache} which doesn't use serialization, as it is
+ * not supported by GraalVM at this stage.
+ */
+public class NonSerialBasicAuthCache implements AuthCache {
+ private final Map<HttpHost, AuthScheme> cache;
+
+ public NonSerialBasicAuthCache() {
+ cache = new ConcurrentHashMap<HttpHost, AuthScheme>();
+ }
+
+ @Override
+ public void put(HttpHost host, AuthScheme authScheme) {
+ cache.put(host, authScheme);
+ }
+
+ @Override
+ public AuthScheme get(HttpHost host) {
+ return cache.get(host);
+ }
+
+ @Override
+ public void remove(HttpHost host) {
+ cache.remove(host);
+ }
+
+ @Override
+ public void clear() {
+ cache.clear();
+ }
+
+}
--- /dev/null
+package org.argeo.jackrabbit.fs;
+
+import org.argeo.jcr.fs.JcrFileSystemProvider;
+
+public abstract class AbstractJackrabbitFsProvider extends JcrFileSystemProvider {
+
+}
--- /dev/null
+package org.argeo.jackrabbit.fs;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.file.DirectoryStream;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystemAlreadyExistsException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.jcr.Repository;
+import javax.jcr.RepositoryFactory;
+import javax.jcr.Session;
+
+import org.argeo.jackrabbit.client.ClientDavexRepositoryFactory;
+import org.argeo.jcr.fs.JcrFileSystem;
+import org.argeo.jcr.fs.JcrFsException;
+
+/**
+ * A file system provider based on a JCR repository remotely accessed via the
+ * DAVEX protocol.
+ */
+public class DavexFsProvider extends AbstractJackrabbitFsProvider {
+ final static String DEFAULT_JACKRABBIT_REMOTE_DEFAULT_WORKSPACE = "sys";
+
+ private Map<String, JcrFileSystem> fileSystems = new HashMap<>();
+
+ @Override
+ public String getScheme() {
+ return "davex";
+ }
+
+ @Override
+ public FileSystem newFileSystem(URI uri, Map<String, ?> env) throws IOException {
+ if (uri.getHost() == null)
+ throw new IllegalArgumentException("An host should be provided");
+ try {
+ URI repoUri = new URI("http", uri.getUserInfo(), uri.getHost(), uri.getPort(), uri.getPath(), null, null);
+ String repoKey = repoUri.toString();
+ if (fileSystems.containsKey(repoKey))
+ throw new FileSystemAlreadyExistsException("CMS file system already exists for " + repoKey);
+ RepositoryFactory repositoryFactory = new ClientDavexRepositoryFactory();
+ return tryGetRepo(repositoryFactory, repoUri, "home");
+ } catch (URISyntaxException e) {
+ throw new IllegalArgumentException("Cannot open file system " + uri, e);
+ }
+ }
+
+ private JcrFileSystem tryGetRepo(RepositoryFactory repositoryFactory, URI repoUri, String workspace)
+ throws IOException {
+ Map<String, String> params = new HashMap<String, String>();
+ params.put(ClientDavexRepositoryFactory.JACKRABBIT_DAVEX_URI, repoUri.toString());
+ // TODO better integrate with OSGi or other configuration than system
+ // properties.
+ String remoteDefaultWorkspace = System.getProperty(
+ ClientDavexRepositoryFactory.JACKRABBIT_REMOTE_DEFAULT_WORKSPACE,
+ DEFAULT_JACKRABBIT_REMOTE_DEFAULT_WORKSPACE);
+ params.put(ClientDavexRepositoryFactory.JACKRABBIT_REMOTE_DEFAULT_WORKSPACE, remoteDefaultWorkspace);
+ Repository repository = null;
+ Session session = null;
+ try {
+ repository = repositoryFactory.getRepository(params);
+ if (repository != null)
+ session = repository.login(workspace);
+ } catch (Exception e) {
+ // silent
+ }
+
+ if (session == null) {
+ if (repoUri.getPath() == null || repoUri.getPath().equals("/"))
+ return null;
+ String repoUriStr = repoUri.toString();
+ if (repoUriStr.endsWith("/"))
+ repoUriStr = repoUriStr.substring(0, repoUriStr.length() - 1);
+ String nextRepoUriStr = repoUriStr.substring(0, repoUriStr.lastIndexOf('/'));
+ String nextWorkspace = repoUriStr.substring(repoUriStr.lastIndexOf('/') + 1);
+ URI nextUri;
+ try {
+ nextUri = new URI(nextRepoUriStr);
+ } catch (URISyntaxException e) {
+ throw new IllegalArgumentException("Badly formatted URI", e);
+ }
+ return tryGetRepo(repositoryFactory, nextUri, nextWorkspace);
+ } else {
+ JcrFileSystem fileSystem = new JcrFileSystem(this, repository);
+ fileSystems.put(repoUri.toString() + "/" + workspace, fileSystem);
+ return fileSystem;
+ }
+ }
+
+ @Override
+ public FileSystem getFileSystem(URI uri) {
+ return currentUserFileSystem(uri);
+ }
+
+ @Override
+ public Path getPath(URI uri) {
+ JcrFileSystem fileSystem = currentUserFileSystem(uri);
+ if (fileSystem == null)
+ try {
+ fileSystem = (JcrFileSystem) newFileSystem(uri, new HashMap<String, Object>());
+ if (fileSystem == null)
+ throw new IllegalArgumentException("No file system found for " + uri);
+ } catch (IOException e) {
+ throw new JcrFsException("Could not autocreate file system", e);
+ }
+ URI repoUri = null;
+ try {
+ repoUri = new URI("http", uri.getUserInfo(), uri.getHost(), uri.getPort(), uri.getPath(), null, null);
+ } catch (URISyntaxException e) {
+ throw new IllegalArgumentException(e);
+ }
+ String uriStr = repoUri.toString();
+ String localPath = null;
+ for (String key : fileSystems.keySet()) {
+ if (uriStr.startsWith(key)) {
+ localPath = uriStr.toString().substring(key.length());
+ }
+ }
+ if ("".equals(localPath))
+ localPath = "/";
+ return fileSystem.getPath(localPath);
+ }
+
+ private JcrFileSystem currentUserFileSystem(URI uri) {
+ for (String key : fileSystems.keySet()) {
+ if (uri.toString().startsWith(key))
+ return fileSystems.get(key);
+ }
+ return null;
+ }
+
+ public static void main(String args[]) {
+ try {
+ DavexFsProvider fsProvider = new DavexFsProvider();
+ Path path = fsProvider.getPath(new URI("davex://root:demo@localhost:7070/jcr/ego/"));
+ System.out.println(path);
+ DirectoryStream<Path> ds = Files.newDirectoryStream(path);
+ for (Path p : ds) {
+ System.out.println("- " + p);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+}
--- /dev/null
+package org.argeo.jackrabbit.fs;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.nio.file.FileSystem;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.jcr.Credentials;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.SimpleCredentials;
+
+import org.apache.jackrabbit.core.RepositoryImpl;
+import org.apache.jackrabbit.core.config.RepositoryConfig;
+import org.argeo.jcr.fs.JcrFileSystem;
+import org.argeo.jcr.fs.JcrFsException;
+
+public class JackrabbitMemoryFsProvider extends AbstractJackrabbitFsProvider {
+ private RepositoryImpl repository;
+ private JcrFileSystem fileSystem;
+
+ private Credentials credentials;
+
+ public JackrabbitMemoryFsProvider() {
+ String username = System.getProperty("user.name");
+ credentials = new SimpleCredentials(username, username.toCharArray());
+ }
+
+ @Override
+ public String getScheme() {
+ return "jcr+memory";
+ }
+
+ @Override
+ public FileSystem newFileSystem(URI uri, Map<String, ?> env) throws IOException {
+ try {
+ Path tempDir = Files.createTempDirectory("fs-memory");
+ URL confUrl = JackrabbitMemoryFsProvider.class.getResource("fs-memory.xml");
+ RepositoryConfig repositoryConfig = RepositoryConfig.create(confUrl.toURI(), tempDir.toString());
+ repository = RepositoryImpl.create(repositoryConfig);
+ postRepositoryCreation(repository);
+ fileSystem = new JcrFileSystem(this, repository, credentials);
+ return fileSystem;
+ } catch (RepositoryException | URISyntaxException e) {
+ throw new IOException("Cannot login to repository", e);
+ }
+ }
+
+ @Override
+ public FileSystem getFileSystem(URI uri) {
+ return fileSystem;
+ }
+
+ @Override
+ public Path getPath(URI uri) {
+ String path = uri.getPath();
+ if (fileSystem == null)
+ try {
+ newFileSystem(uri, new HashMap<String, Object>());
+ } catch (IOException e) {
+ throw new JcrFsException("Could not autocreate file system", e);
+ }
+ return fileSystem.getPath(path);
+ }
+
+ public Repository getRepository() {
+ return repository;
+ }
+
+ public Session login() throws RepositoryException {
+ return getRepository().login(credentials);
+ }
+
+ /**
+ * Called after the repository has been created and before the file system is
+ * created.
+ */
+ protected void postRepositoryCreation(RepositoryImpl repositoryImpl) throws RepositoryException {
+
+ }
+}
--- /dev/null
+<?xml version="1.0"?>
+<!DOCTYPE Repository PUBLIC "Jackrabbit 2.6" "http://jackrabbit.apache.org/dtd/repository-2.6.dtd">
+<Repository>
+ <!-- File system and datastore -->
+ <FileSystem
+ class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
+
+ <!-- Workspace templates -->
+ <Workspaces rootPath="${rep.home}/workspaces"
+ defaultWorkspace="main" configRootPath="/workspaces" />
+ <Workspace name="${wsp.name}">
+ <FileSystem
+ class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
+ <PersistenceManager
+ class="org.apache.jackrabbit.core.persistence.bundle.BundleFsPersistenceManager">
+ </PersistenceManager>
+ <SearchIndex
+ class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+ <param name="path" value="${wsp.home}/index" />
+ <param name="directoryManagerClass"
+ value="org.apache.jackrabbit.core.query.lucene.directory.RAMDirectoryManager" />
+ <param name="extractorPoolSize" value="0" />
+ <FileSystem
+ class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
+ </SearchIndex>
+ </Workspace>
+
+ <!-- Versioning -->
+ <Versioning rootPath="${rep.home}/version">
+ <FileSystem
+ class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
+ <PersistenceManager
+ class="org.apache.jackrabbit.core.persistence.bundle.BundleFsPersistenceManager">
+ </PersistenceManager>
+ </Versioning>
+
+ <!-- Indexing -->
+ <SearchIndex
+ class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+ <param name="path" value="${rep.home}/index" />
+ <param name="directoryManagerClass"
+ value="org.apache.jackrabbit.core.query.lucene.directory.RAMDirectoryManager" />
+ <param name="extractorPoolSize" value="0" />
+ <FileSystem
+ class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
+ </SearchIndex>
+
+ <!-- Security -->
+ <Security appName="Jackrabbit">
+ <LoginModule
+ class="org.apache.jackrabbit.core.security.SimpleLoginModule" />
+ <!-- <SecurityManager class="org.argeo.security.jackrabbit.ArgeoSecurityManager" -->
+ <!-- workspaceName="security" /> -->
+ <!-- <AccessManager class="org.argeo.security.jackrabbit.ArgeoAccessManager"
+ /> -->
+ </Security>
+</Repository>
\ No newline at end of file
--- /dev/null
+/** Java NIO file system implementation based on Jackrabbit. */
+package org.argeo.jackrabbit.fs;
\ No newline at end of file
--- /dev/null
+/** Generic Jackrabbit utilities. */
+package org.argeo.jackrabbit;
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE Repository PUBLIC "Jackrabbit 2.6" "http://jackrabbit.apache.org/dtd/repository-2.6.dtd">
+<Repository>
+ <!-- Shared datasource -->
+ <DataSources>
+ <DataSource name="dataSource">
+ <param name="driver" value="org.h2.Driver" />
+ <param name="url" value="${dburl}" />
+ <param name="user" value="${dbuser}" />
+ <param name="password" value="${dbpassword}" />
+ <param name="databaseType" value="h2" />
+ <param name="maxPoolSize" value="${maxPoolSize}" />
+ </DataSource>
+ </DataSources>
+
+ <!-- File system and datastore -->
+ <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
+ <param name="dataSourceName" value="dataSource" />
+ <param name="schema" value="default" />
+ <param name="schemaObjectPrefix" value="fs_" />
+ </FileSystem>
+ <DataStore class="org.apache.jackrabbit.core.data.FileDataStore">
+ <param name="path" value="${rep.home}/datastore" />
+ </DataStore>
+
+ <!-- Workspace templates -->
+ <Workspaces rootPath="${rep.home}/workspaces"
+ defaultWorkspace="${defaultWorkspace}" />
+ <Workspace name="${wsp.name}">
+ <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
+ <param name="dataSourceName" value="dataSource" />
+ <param name="schema" value="default" />
+ <param name="schemaObjectPrefix" value="${wsp.name}_fs_" />
+ </FileSystem>
+ <PersistenceManager
+ class="org.apache.jackrabbit.core.persistence.pool.H2PersistenceManager">
+ <param name="dataSourceName" value="dataSource" />
+ <param name="schemaObjectPrefix" value="${wsp.name}_pm_" />
+ <param name="bundleCacheSize" value="${bundleCacheMB}" />
+ </PersistenceManager>
+ <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+ <param name="path" value="${wsp.home}/index" />
+ <param name="extractorPoolSize" value="${extractorPoolSize}" />
+ <param name="cacheSize" value="${searchCacheSize}" />
+ <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
+ </SearchIndex>
+ <WorkspaceSecurity>
+ <AccessControlProvider
+ class="org.argeo.security.jackrabbit.ArgeoAccessControlProvider" />
+ </WorkspaceSecurity>
+ </Workspace>
+
+ <!-- Versioning -->
+ <Versioning rootPath="${rep.home}/version">
+ <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
+ <param name="dataSourceName" value="dataSource" />
+ <param name="schema" value="default" />
+ <param name="schemaObjectPrefix" value="fs_ver_" />
+ </FileSystem>
+ <PersistenceManager
+ class="org.apache.jackrabbit.core.persistence.pool.H2PersistenceManager">
+ <param name="dataSourceName" value="dataSource" />
+ <param name="schemaObjectPrefix" value="pm_ver_" />
+ <param name="bundleCacheSize" value="${bundleCacheMB}" />
+ </PersistenceManager>
+ </Versioning>
+
+ <!-- Indexing -->
+ <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+ <param name="path" value="${rep.home}/index" />
+ <param name="extractorPoolSize" value="${extractorPoolSize}" />
+ <param name="cacheSize" value="${searchCacheSize}" />
+ <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
+ </SearchIndex>
+
+ <!-- Security -->
+ <Security appName="Jackrabbit">
+ <SecurityManager class="org.argeo.security.jackrabbit.ArgeoSecurityManager"
+ workspaceName="security" />
+ <AccessManager class="org.argeo.security.jackrabbit.ArgeoAccessManager" />
+ </Security>
+</Repository>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE Repository PUBLIC "Jackrabbit 2.6" "http://jackrabbit.apache.org/dtd/repository-2.6.dtd">
+<Repository>
+ <!-- File system and datastore -->
+ <FileSystem class="org.apache.jackrabbit.core.fs.local.LocalFileSystem">
+ <param name="path" value="${rep.home}/repository" />
+ </FileSystem>
+ <DataStore class="org.apache.jackrabbit.core.data.FileDataStore">
+ <param name="path" value="${rep.home}/datastore" />
+ </DataStore>
+
+ <!-- Workspace templates -->
+ <Workspaces rootPath="${rep.home}/workspaces"
+ defaultWorkspace="${defaultWorkspace}" />
+ <Workspace name="${wsp.name}">
+ <FileSystem class="org.apache.jackrabbit.core.fs.local.LocalFileSystem">
+ <param name="path" value="${wsp.home}" />
+ </FileSystem>
+ <PersistenceManager
+ class="org.apache.jackrabbit.core.persistence.bundle.BundleFsPersistenceManager">
+ <param name="bundleCacheSize" value="${bundleCacheMB}" />
+ </PersistenceManager>
+ <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+ <param name="path" value="${wsp.home}/index" />
+ <param name="extractorPoolSize" value="${extractorPoolSize}" />
+ <param name="cacheSize" value="${searchCacheSize}" />
+ <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
+ </SearchIndex>
+ <WorkspaceSecurity>
+ <AccessControlProvider
+ class="org.argeo.security.jackrabbit.ArgeoAccessControlProvider" />
+ </WorkspaceSecurity>
+ </Workspace>
+
+ <!-- Versioning -->
+ <Versioning rootPath="${rep.home}/version">
+ <FileSystem class="org.apache.jackrabbit.core.fs.local.LocalFileSystem">
+ <param name="path" value="${rep.home}/version" />
+ </FileSystem>
+ <PersistenceManager
+ class="org.apache.jackrabbit.core.persistence.bundle.BundleFsPersistenceManager">
+ <param name="bundleCacheSize" value="${bundleCacheMB}" />
+ </PersistenceManager>
+ </Versioning>
+
+ <!-- Indexing -->
+ <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+ <param name="path" value="${rep.home}/index" />
+ <param name="extractorPoolSize" value="${extractorPoolSize}" />
+ <param name="cacheSize" value="${searchCacheSize}" />
+ <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
+ </SearchIndex>
+
+ <!-- Security -->
+ <Security appName="Jackrabbit">
+ <SecurityManager class="org.argeo.security.jackrabbit.ArgeoSecurityManager"
+ workspaceName="security" />
+ <AccessManager class="org.argeo.security.jackrabbit.ArgeoAccessManager" />
+ </Security>
+</Repository>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0"?>
+<!DOCTYPE Repository PUBLIC "Jackrabbit 2.6" "http://jackrabbit.apache.org/dtd/repository-2.6.dtd">
+<Repository>
+ <!-- File system and datastore -->
+ <FileSystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
+
+ <!-- Workspace templates -->
+ <Workspaces rootPath="${rep.home}/workspaces"
+ defaultWorkspace="${defaultWorkspace}" configRootPath="/workspaces" />
+ <Workspace name="${wsp.name}">
+ <FileSystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
+ <PersistenceManager
+ class="org.apache.jackrabbit.core.persistence.bundle.BundleFsPersistenceManager">
+ <param name="blobFSBlockSize" value="1" />
+ <param name="bundleCacheSize" value="${bundleCacheMB}" />
+ </PersistenceManager>
+ <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+ <param name="path" value="${wsp.home}/index" />
+ <param name="directoryManagerClass"
+ value="org.apache.jackrabbit.core.query.lucene.directory.RAMDirectoryManager" />
+ <param name="extractorPoolSize" value="${extractorPoolSize}" />
+ <param name="cacheSize" value="${searchCacheSize}" />
+ <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
+ <FileSystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
+ </SearchIndex>
+ </Workspace>
+
+ <!-- Versioning -->
+ <Versioning rootPath="${rep.home}/version">
+ <FileSystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
+ <PersistenceManager
+ class="org.apache.jackrabbit.core.persistence.bundle.BundleFsPersistenceManager">
+ <param name="blobFSBlockSize" value="1" />
+ <param name="bundleCacheSize" value="${bundleCacheMB}" />
+ </PersistenceManager>
+ </Versioning>
+
+ <!-- Indexing -->
+ <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+ <param name="path" value="${rep.home}/index" />
+ <param name="directoryManagerClass"
+ value="org.apache.jackrabbit.core.query.lucene.directory.RAMDirectoryManager" />
+ <param name="extractorPoolSize" value="${extractorPoolSize}" />
+ <param name="cacheSize" value="${searchCacheSize}" />
+ <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
+ <FileSystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
+ </SearchIndex>
+
+ <!-- Security -->
+ <Security appName="Jackrabbit">
+ <SecurityManager class="org.argeo.security.jackrabbit.ArgeoSecurityManager"
+ workspaceName="security" />
+ <AccessManager class="org.argeo.security.jackrabbit.ArgeoAccessManager" />
+ </Security>
+</Repository>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0"?>
+<!DOCTYPE Repository PUBLIC "Jackrabbit 2.6" "http://jackrabbit.apache.org/dtd/repository-2.6.dtd">
+<Repository>
+ <!-- Shared datasource -->
+ <DataSources>
+ <DataSource name="dataSource">
+ <param name="driver" value="org.postgresql.Driver" />
+ <param name="url" value="${dburl}" />
+ <param name="user" value="${dbuser}" />
+ <param name="password" value="${dbpassword}" />
+ <param name="databaseType" value="postgresql" />
+ <param name="maxPoolSize" value="${maxPoolSize}" />
+ </DataSource>
+ </DataSources>
+
+ <!-- File system and datastore -->
+ <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
+ <param name="dataSourceName" value="dataSource" />
+ <param name="schema" value="postgresql" />
+ <param name="schemaObjectPrefix" value="fs_" />
+ </FileSystem>
+ <DataStore class="org.apache.jackrabbit.core.data.FileDataStore">
+ <param name="path" value="${rep.home}/datastore" />
+ </DataStore>
+
+ <!-- Workspace templates -->
+ <Workspaces rootPath="${rep.home}/workspaces"
+ defaultWorkspace="${defaultWorkspace}" />
+ <Workspace name="${wsp.name}">
+ <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
+ <param name="dataSourceName" value="dataSource" />
+ <param name="schema" value="postgresql" />
+ <param name="schemaObjectPrefix" value="${wsp.name}_fs_" />
+ </FileSystem>
+ <PersistenceManager
+ class="org.apache.jackrabbit.core.persistence.pool.PostgreSQLPersistenceManager">
+ <param name="dataSourceName" value="dataSource" />
+ <param name="schemaObjectPrefix" value="${wsp.name}_pm_" />
+ <param name="bundleCacheSize" value="${bundleCacheMB}" />
+ </PersistenceManager>
+ <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+ <param name="path" value="${wsp.home}/index" />
+ <param name="extractorPoolSize" value="${extractorPoolSize}" />
+ <param name="cacheSize" value="${searchCacheSize}" />
+ <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
+ </SearchIndex>
+ <WorkspaceSecurity>
+ <AccessControlProvider
+ class="org.argeo.security.jackrabbit.ArgeoAccessControlProvider" />
+ </WorkspaceSecurity>
+ </Workspace>
+
+ <!-- Versioning -->
+ <Versioning rootPath="${rep.home}/version">
+ <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
+ <param name="dataSourceName" value="dataSource" />
+ <param name="schema" value="postgresql" />
+ <param name="schemaObjectPrefix" value="fs_ver_" />
+ </FileSystem>
+ <PersistenceManager
+ class="org.apache.jackrabbit.core.persistence.pool.PostgreSQLPersistenceManager">
+ <param name="dataSourceName" value="dataSource" />
+ <param name="schemaObjectPrefix" value="pm_ver_" />
+ <param name="bundleCacheSize" value="${bundleCacheMB}" />
+ </PersistenceManager>
+ </Versioning>
+
+ <!-- Indexing -->
+ <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+ <param name="path" value="${rep.home}/index" />
+ <param name="extractorPoolSize" value="${extractorPoolSize}" />
+ <param name="cacheSize" value="${searchCacheSize}" />
+ <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
+ </SearchIndex>
+
+ <!-- Security -->
+ <Security appName="Jackrabbit">
+ <SecurityManager class="org.argeo.security.jackrabbit.ArgeoSecurityManager"
+ workspaceName="security" />
+ <AccessManager class="org.argeo.security.jackrabbit.ArgeoAccessManager" />
+ </Security>
+</Repository>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0"?>
+<!DOCTYPE Repository PUBLIC "Jackrabbit 2.6" "http://jackrabbit.apache.org/dtd/repository-2.6.dtd">
+<Repository>
+ <!-- Shared datasource -->
+ <DataSources>
+ <DataSource name="dataSource">
+ <param name="driver" value="org.postgresql.Driver" />
+ <param name="url" value="${dburl}" />
+ <param name="user" value="${dbuser}" />
+ <param name="password" value="${dbpassword}" />
+ <param name="databaseType" value="postgresql" />
+ <param name="maxPoolSize" value="${maxPoolSize}" />
+ </DataSource>
+ </DataSources>
+
+ <!-- File system and datastore -->
+ <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
+ <param name="dataSourceName" value="dataSource" />
+ <param name="schema" value="postgresql" />
+ <param name="schemaObjectPrefix" value="fs_" />
+ </FileSystem>
+
+ <!-- Workspace templates -->
+ <Workspaces rootPath="${rep.home}/workspaces"
+ defaultWorkspace="${defaultWorkspace}" />
+ <Workspace name="${wsp.name}">
+ <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
+ <param name="dataSourceName" value="dataSource" />
+ <param name="schema" value="postgresql" />
+ <param name="schemaObjectPrefix" value="${wsp.name}_fs_" />
+ </FileSystem>
+ <PersistenceManager
+ class="org.apache.jackrabbit.core.persistence.pool.PostgreSQLPersistenceManager">
+ <param name="dataSourceName" value="dataSource" />
+ <param name="schemaObjectPrefix" value="${wsp.name}_pm_" />
+ <param name="bundleCacheSize" value="${bundleCacheMB}" />
+ </PersistenceManager>
+ <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+ <param name="path" value="${wsp.home}/index" />
+ <param name="extractorPoolSize" value="${extractorPoolSize}" />
+ <param name="cacheSize" value="${searchCacheSize}" />
+ <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
+ </SearchIndex>
+ <WorkspaceSecurity>
+ <AccessControlProvider
+ class="org.argeo.security.jackrabbit.ArgeoAccessControlProvider" />
+ </WorkspaceSecurity>
+ </Workspace>
+
+ <!-- Versioning -->
+ <Versioning rootPath="${rep.home}/version">
+ <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
+ <param name="dataSourceName" value="dataSource" />
+ <param name="schema" value="postgresql" />
+ <param name="schemaObjectPrefix" value="fs_ver_" />
+ </FileSystem>
+ <PersistenceManager
+ class="org.apache.jackrabbit.core.persistence.pool.PostgreSQLPersistenceManager">
+ <param name="dataSourceName" value="dataSource" />
+ <param name="schemaObjectPrefix" value="pm_ver_" />
+ <param name="bundleCacheSize" value="${bundleCacheMB}" />
+ </PersistenceManager>
+ </Versioning>
+
+ <!-- Indexing -->
+ <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+ <param name="path" value="${rep.home}/index" />
+ <param name="extractorPoolSize" value="${extractorPoolSize}" />
+ <param name="cacheSize" value="${searchCacheSize}" />
+ <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
+ </SearchIndex>
+
+ <!-- Security -->
+ <Security appName="Jackrabbit">
+ <SecurityManager class="org.argeo.security.jackrabbit.ArgeoSecurityManager"
+ workspaceName="security" />
+ <AccessManager class="org.argeo.security.jackrabbit.ArgeoAccessManager" />
+ </Security>
+</Repository>
\ No newline at end of file
--- /dev/null
+package org.argeo.jackrabbit.security;
+
+import java.security.Principal;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.security.Privilege;
+
+import org.apache.jackrabbit.api.security.JackrabbitAccessControlList;
+import org.apache.jackrabbit.api.security.JackrabbitAccessControlManager;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.jcr.JcrUtils;
+
+/** Utilities around Jackrabbit security extensions. */
+public class JackrabbitSecurityUtils {
+ private final static CmsLog log = CmsLog.getLog(JackrabbitSecurityUtils.class);
+
+ /**
+ * Convenience method for denying a single privilege to a principal (user or
+ * role), typically jcr:all
+ */
+ public synchronized static void denyPrivilege(Session session, String path, String principal, String privilege)
+ throws RepositoryException {
+ List<Privilege> privileges = new ArrayList<Privilege>();
+ privileges.add(session.getAccessControlManager().privilegeFromName(privilege));
+ denyPrivileges(session, path, () -> principal, privileges);
+ }
+
+ /**
+ * Deny privileges on a path to a {@link Principal}. The path must already
+ * exist. Session is saved. Synchronized to prevent concurrent modifications of
+ * the same node.
+ */
+ public synchronized static Boolean denyPrivileges(Session session, String path, Principal principal,
+ List<Privilege> privs) throws RepositoryException {
+ // make sure the session is in line with the persisted state
+ session.refresh(false);
+ JackrabbitAccessControlManager acm = (JackrabbitAccessControlManager) session.getAccessControlManager();
+ JackrabbitAccessControlList acl = (JackrabbitAccessControlList) JcrUtils.getAccessControlList(acm, path);
+
+// accessControlEntries: for (AccessControlEntry ace : acl.getAccessControlEntries()) {
+// Principal currentPrincipal = ace.getPrincipal();
+// if (currentPrincipal.getName().equals(principal.getName())) {
+// Privilege[] currentPrivileges = ace.getPrivileges();
+// if (currentPrivileges.length != privs.size())
+// break accessControlEntries;
+// for (int i = 0; i < currentPrivileges.length; i++) {
+// Privilege currP = currentPrivileges[i];
+// Privilege p = privs.get(i);
+// if (!currP.getName().equals(p.getName())) {
+// break accessControlEntries;
+// }
+// }
+// return false;
+// }
+// }
+
+ Privilege[] privileges = privs.toArray(new Privilege[privs.size()]);
+ acl.addEntry(principal, privileges, false);
+ acm.setPolicy(path, acl);
+ if (log.isDebugEnabled()) {
+ StringBuffer privBuf = new StringBuffer();
+ for (Privilege priv : privs)
+ privBuf.append(priv.getName());
+ log.debug("Denied privileges " + privBuf + " to " + principal.getName() + " on " + path + " in '"
+ + session.getWorkspace().getName() + "'");
+ }
+ session.refresh(true);
+ session.save();
+ return true;
+ }
+
+ /** Singleton. */
+ private JackrabbitSecurityUtils() {
+
+ }
+}
--- /dev/null
+/** Generic Jackrabbit security utilities. */
+package org.argeo.jackrabbit.security;
\ No newline at end of file
--- /dev/null
+package org.argeo.jackrabbit.unit;
+
+import java.net.URL;
+
+import javax.jcr.Repository;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.jackrabbit.core.RepositoryImpl;
+import org.apache.jackrabbit.core.config.RepositoryConfig;
+import org.argeo.jcr.unit.AbstractJcrTestCase;
+
+/** Factorizes configuration of an in memory transient repository */
+public abstract class AbstractJackrabbitTestCase extends AbstractJcrTestCase {
+ protected RepositoryImpl repositoryImpl;
+
+ // protected File getRepositoryFile() throws Exception {
+ // Resource res = new ClassPathResource(
+ // "org/argeo/jackrabbit/unit/repository-memory.xml");
+ // return res.getFile();
+ // }
+
+ public AbstractJackrabbitTestCase() {
+ URL url = AbstractJackrabbitTestCase.class.getResource("jaas.config");
+ assert url != null;
+ System.setProperty("java.security.auth.login.config", url.toString());
+ }
+
+ protected Repository createRepository() throws Exception {
+ // Repository repository = new TransientRepository(getRepositoryFile(),
+ // getHomeDir());
+ RepositoryConfig repositoryConfig = RepositoryConfig.create(
+ AbstractJackrabbitTestCase.class
+ .getResourceAsStream(getRepositoryConfigResource()),
+ getHomeDir().getAbsolutePath());
+ RepositoryImpl repositoryImpl = RepositoryImpl.create(repositoryConfig);
+ return repositoryImpl;
+ }
+
+ protected String getRepositoryConfigResource() {
+ return "repository-memory.xml";
+ }
+
+ @Override
+ protected void clearRepository(Repository repository) throws Exception {
+ RepositoryImpl repositoryImpl = (RepositoryImpl) repository;
+ if (repositoryImpl != null)
+ repositoryImpl.shutdown();
+ FileUtils.deleteDirectory(getHomeDir());
+ }
+
+}
--- /dev/null
+TEST_JACKRABBIT_ADMIN {
+ org.argeo.cms.auth.DataAdminLoginModule requisite;
+};
+
+Jackrabbit {
+ org.argeo.security.jackrabbit.SystemJackrabbitLoginModule requisite;
+};
--- /dev/null
+/** Helpers for unit tests with Jackrabbit repositories. */
+package org.argeo.jackrabbit.unit;
\ No newline at end of file
--- /dev/null
+<?xml version="1.0"?>
+<!DOCTYPE Repository PUBLIC "-//The Apache Software Foundation//DTD Jackrabbit 1.6//EN"
+ "http://jackrabbit.apache.org/dtd/repository-2.0.dtd">
+<Repository>
+ <!-- Shared datasource -->
+ <DataSources>
+ <DataSource name="dataSource">
+ <param name="driver" value="org.h2.Driver" />
+ <param name="url" value="jdbc:h2:mem:jackrabbit" />
+ <param name="user" value="sa" />
+ <param name="password" value="" />
+ <param name="databaseType" value="h2" />
+ <param name="maxPoolSize" value="10" />
+ </DataSource>
+ </DataSources>
+
+ <!-- File system and datastore -->
+ <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
+ <param name="dataSourceName" value="dataSource" />
+ <param name="schema" value="default" />
+ <param name="schemaObjectPrefix" value="fs_" />
+ </FileSystem>
+ <DataStore class="org.apache.jackrabbit.core.data.db.DbDataStore">
+ <param name="dataSourceName" value="dataSource" />
+ <param name="schemaObjectPrefix" value="ds_" />
+ </DataStore>
+
+ <!-- Workspace templates -->
+ <Workspaces rootPath="${rep.home}/workspaces"
+ defaultWorkspace="dev" />
+ <Workspace name="${wsp.name}">
+ <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
+ <param name="dataSourceName" value="dataSource" />
+ <param name="schema" value="default" />
+ <param name="schemaObjectPrefix" value="${wsp.name}_fs_" />
+ </FileSystem>
+ <PersistenceManager
+ class="org.apache.jackrabbit.core.persistence.pool.H2PersistenceManager">
+ <param name="dataSourceName" value="dataSource" />
+ <param name="schemaObjectPrefix" value="${wsp.name}_pm_" />
+ </PersistenceManager>
+ <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+ <param name="path" value="${wsp.home}/index" />
+ </SearchIndex>
+ </Workspace>
+
+ <!-- Versioning -->
+ <Versioning rootPath="${rep.home}/version">
+ <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
+ <param name="dataSourceName" value="dataSource" />
+ <param name="schema" value="default" />
+ <param name="schemaObjectPrefix" value="fs_ver_" />
+ </FileSystem>
+ <PersistenceManager
+ class="org.apache.jackrabbit.core.persistence.pool.H2PersistenceManager">
+ <param name="dataSourceName" value="dataSource" />
+ <param name="schemaObjectPrefix" value="pm_ver_" />
+ </PersistenceManager>
+ </Versioning>
+
+ <!-- Indexing -->
+ <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+ <param name="path" value="${rep.home}/repository/index" />
+ <param name="extractorPoolSize" value="2" />
+ <param name="supportHighlighting" value="true" />
+ </SearchIndex>
+
+ <!-- Security -->
+ <Security appName="Jackrabbit">
+ <SecurityManager
+ class="org.apache.jackrabbit.core.security.simple.SimpleSecurityManager"
+ workspaceName="security" />
+ <AccessManager
+ class="org.apache.jackrabbit.core.security.simple.SimpleAccessManager" />
+ <LoginModule
+ class="org.apache.jackrabbit.core.security.simple.SimpleLoginModule">
+ <param name="anonymousId" value="anonymous" />
+ <param name="adminId" value="admin" />
+ </LoginModule>
+ </Security>
+</Repository>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0"?>
+<!--
+
+ Copyright (C) 2007-2012 Argeo GmbH
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+-->
+<!DOCTYPE Repository PUBLIC "-//The Apache Software Foundation//DTD Jackrabbit 1.6//EN"
+ "http://jackrabbit.apache.org/dtd/repository-2.0.dtd">
+<Repository>
+ <!-- File system and datastore -->
+ <FileSystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
+
+ <!-- Workspace templates -->
+ <Workspaces rootPath="${rep.home}/workspaces"
+ defaultWorkspace="main" configRootPath="/workspaces" />
+ <Workspace name="${wsp.name}">
+ <FileSystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
+ <PersistenceManager
+ class="org.apache.jackrabbit.core.persistence.bundle.BundleFsPersistenceManager">
+ <param name="blobFSBlockSize" value="1" />
+ </PersistenceManager>
+ <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+ <param name="path" value="${rep.home}/repository/index" />
+ <param name="directoryManagerClass"
+ value="org.apache.jackrabbit.core.query.lucene.directory.RAMDirectoryManager" />
+ <FileSystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
+ </SearchIndex>
+ </Workspace>
+
+ <!-- Versioning -->
+ <Versioning rootPath="${rep.home}/version">
+ <FileSystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
+ <PersistenceManager
+ class="org.apache.jackrabbit.core.persistence.bundle.BundleFsPersistenceManager">
+ <param name="blobFSBlockSize" value="1" />
+ </PersistenceManager>
+ </Versioning>
+
+ <!-- Indexing -->
+ <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+ <param name="path" value="${rep.home}/repository/index" />
+ <param name="directoryManagerClass"
+ value="org.apache.jackrabbit.core.query.lucene.directory.RAMDirectoryManager" />
+ <FileSystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
+ </SearchIndex>
+
+ <!-- Security -->
+ <Security appName="Jackrabbit">
+ <SecurityManager
+ class="org.apache.jackrabbit.core.security.simple.SimpleSecurityManager"
+ workspaceName="security" />
+ <AccessManager
+ class="org.apache.jackrabbit.core.security.simple.SimpleAccessManager" />
+ <LoginModule
+ class="org.apache.jackrabbit.core.security.simple.SimpleLoginModule">
+ <param name="anonymousId" value="anonymous" />
+ <param name="adminId" value="admin" />
+ </LoginModule>
+ </Security>
+</Repository>
\ No newline at end of file
--- /dev/null
+package org.argeo.jcr;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.jcr.Binary;
+import javax.jcr.Property;
+import javax.jcr.RepositoryException;
+
+/**
+ * A {@link Binary} wrapper implementing {@link AutoCloseable} for ease of use
+ * in try/catch blocks.
+ */
+public class Bin implements Binary, AutoCloseable {
+ private final Binary wrappedBinary;
+
+ public Bin(Property property) throws RepositoryException {
+ this(property.getBinary());
+ }
+
+ public Bin(Binary wrappedBinary) {
+ if (wrappedBinary == null)
+ throw new IllegalArgumentException("Wrapped binary cannot be null");
+ this.wrappedBinary = wrappedBinary;
+ }
+
+ // private static Binary getBinary(Property property) throws IOException {
+ // try {
+ // return property.getBinary();
+ // } catch (RepositoryException e) {
+ // throw new IOException("Cannot get binary from property " + property, e);
+ // }
+ // }
+
+ @Override
+ public void close() {
+ dispose();
+ }
+
+ @Override
+ public InputStream getStream() throws RepositoryException {
+ return wrappedBinary.getStream();
+ }
+
+ @Override
+ public int read(byte[] b, long position) throws IOException, RepositoryException {
+ return wrappedBinary.read(b, position);
+ }
+
+ @Override
+ public long getSize() throws RepositoryException {
+ return wrappedBinary.getSize();
+ }
+
+ @Override
+ public void dispose() {
+ wrappedBinary.dispose();
+ }
+
+}
--- /dev/null
+package org.argeo.jcr;
+
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+
+/** Wraps a collection of nodes in order to read it as a {@link NodeIterator} */
+public class CollectionNodeIterator implements NodeIterator {
+ private final Long collectionSize;
+ private final Iterator<Node> iterator;
+ private Integer position = 0;
+
+ public CollectionNodeIterator(Collection<Node> nodes) {
+ super();
+ this.collectionSize = (long) nodes.size();
+ this.iterator = nodes.iterator();
+ }
+
+ public void skip(long skipNum) {
+ if (skipNum < 0)
+ throw new IllegalArgumentException(
+ "Skip count has to be positive: " + skipNum);
+
+ for (long i = 0; i < skipNum; i++) {
+ if (!hasNext())
+ throw new NoSuchElementException("Last element past (position="
+ + getPosition() + ")");
+ nextNode();
+ }
+ }
+
+ public long getSize() {
+ return collectionSize;
+ }
+
+ public long getPosition() {
+ return position;
+ }
+
+ public boolean hasNext() {
+ return iterator.hasNext();
+ }
+
+ public Object next() {
+ return nextNode();
+ }
+
+ public void remove() {
+ iterator.remove();
+ }
+
+ public Node nextNode() {
+ Node node = iterator.next();
+ position++;
+ return node;
+ }
+
+}
--- /dev/null
+package org.argeo.jcr;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.observation.Event;
+import javax.jcr.observation.EventIterator;
+import javax.jcr.observation.EventListener;
+import javax.jcr.observation.ObservationManager;
+
+import org.argeo.api.cms.CmsLog;
+
+/** To be overridden */
+public class DefaultJcrListener implements EventListener {
+ private final static CmsLog log = CmsLog.getLog(DefaultJcrListener.class);
+ private Session session;
+ private String path = "/";
+ private Boolean deep = true;
+
+ public void start() {
+ try {
+ addEventListener(session().getWorkspace().getObservationManager());
+ if (log.isDebugEnabled())
+ log.debug("Registered JCR event listener on " + path);
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot register event listener", e);
+ }
+ }
+
+ public void stop() {
+ try {
+ session().getWorkspace().getObservationManager()
+ .removeEventListener(this);
+ if (log.isDebugEnabled())
+ log.debug("Unregistered JCR event listener on " + path);
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot unregister event listener", e);
+ }
+ }
+
+ /** Default is listen to all events */
+ protected Integer getEvents() {
+ return Event.NODE_ADDED | Event.NODE_REMOVED | Event.PROPERTY_ADDED
+ | Event.PROPERTY_CHANGED | Event.PROPERTY_REMOVED;
+ }
+
+ /** To be overidden */
+ public void onEvent(EventIterator events) {
+ while (events.hasNext()) {
+ Event event = events.nextEvent();
+ log.debug(event);
+ }
+ }
+
+ /** To be overidden */
+ protected void addEventListener(ObservationManager observationManager)
+ throws RepositoryException {
+ observationManager.addEventListener(this, getEvents(), path, deep,
+ null, null, false);
+ }
+
+ private Session session() {
+ return session;
+ }
+
+ public void setPath(String path) {
+ this.path = path;
+ }
+
+ public void setDeep(Boolean deep) {
+ this.deep = deep;
+ }
+
+ public void setSession(Session session) {
+ this.session = session;
+ }
+
+}
--- /dev/null
+package org.argeo.jcr;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.math.BigDecimal;
+import java.text.MessageFormat;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.jcr.Binary;
+import javax.jcr.ItemNotFoundException;
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.Property;
+import javax.jcr.PropertyType;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.Value;
+import javax.jcr.Workspace;
+import javax.jcr.nodetype.NodeType;
+import javax.jcr.query.Query;
+import javax.jcr.query.QueryManager;
+import javax.jcr.security.Privilege;
+import javax.jcr.version.Version;
+import javax.jcr.version.VersionHistory;
+import javax.jcr.version.VersionIterator;
+import javax.jcr.version.VersionManager;
+
+import org.apache.commons.io.IOUtils;
+
+/**
+ * Utility class whose purpose is to make using JCR less verbose by
+ * systematically using unchecked exceptions and returning <code>null</code>
+ * when something is not found. This is especially useful when writing user
+ * interfaces (such as with SWT) where listeners and callbacks expect unchecked
+ * exceptions. Loosely inspired by Java's <code>Files</code> singleton.
+ */
+public class Jcr {
+ /**
+ * The name of a node which will be serialized as XML text, as per section 7.3.1
+ * of the JCR 2.0 specifications.
+ */
+ public final static String JCR_XMLTEXT = "jcr:xmltext";
+ /**
+ * The name of a property which will be serialized as XML text, as per section
+ * 7.3.1 of the JCR 2.0 specifications.
+ */
+ public final static String JCR_XMLCHARACTERS = "jcr:xmlcharacters";
+ /**
+ * <code>jcr:name</code>, when used in another context than
+ * {@link Property#JCR_NAME}, typically to name a node rather than a property.
+ */
+ public final static String JCR_NAME = "jcr:name";
+ /**
+ * <code>jcr:path</code>, when used in another context than
+ * {@link Property#JCR_PATH}, typically to name a node rather than a property.
+ */
+ public final static String JCR_PATH = "jcr:path";
+ /**
+ * <code>jcr:primaryType</code> with prefix instead of namespace (as in
+ * {@link Property#JCR_PRIMARY_TYPE}.
+ */
+ public final static String JCR_PRIMARY_TYPE = "jcr:primaryType";
+ /**
+ * <code>jcr:mixinTypes</code> with prefix instead of namespace (as in
+ * {@link Property#JCR_MIXIN_TYPES}.
+ */
+ public final static String JCR_MIXIN_TYPES = "jcr:mixinTypes";
+ /**
+ * <code>jcr:uuid</code> with prefix instead of namespace (as in
+ * {@link Property#JCR_UUID}.
+ */
+ public final static String JCR_UUID = "jcr:uuid";
+ /**
+ * <code>jcr:created</code> with prefix instead of namespace (as in
+ * {@link Property#JCR_CREATED}.
+ */
+ public final static String JCR_CREATED = "jcr:created";
+ /**
+ * <code>jcr:createdBy</code> with prefix instead of namespace (as in
+ * {@link Property#JCR_CREATED_BY}.
+ */
+ public final static String JCR_CREATED_BY = "jcr:createdBy";
+ /**
+ * <code>jcr:lastModified</code> with prefix instead of namespace (as in
+ * {@link Property#JCR_LAST_MODIFIED}.
+ */
+ public final static String JCR_LAST_MODIFIED = "jcr:lastModified";
+ /**
+ * <code>jcr:lastModifiedBy</code> with prefix instead of namespace (as in
+ * {@link Property#JCR_LAST_MODIFIED_BY}.
+ */
+ public final static String JCR_LAST_MODIFIED_BY = "jcr:lastModifiedBy";
+
+ /**
+ * @see Node#isNodeType(String)
+ * @throws JcrException caused by {@link RepositoryException}
+ */
+ public static boolean isNodeType(Node node, String nodeTypeName) {
+ try {
+ return node.isNodeType(nodeTypeName);
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot get whether " + node + " is of type " + nodeTypeName, e);
+ }
+ }
+
+ /**
+ * @see Node#hasNodes()
+ * @throws JcrException caused by {@link RepositoryException}
+ */
+ public static boolean hasNodes(Node node) {
+ try {
+ return node.hasNodes();
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot get whether " + node + " has children.", e);
+ }
+ }
+
+ /**
+ * @see Node#getParent()
+ * @throws JcrException caused by {@link RepositoryException}
+ */
+ public static Node getParent(Node node) {
+ try {
+ return isRoot(node) ? null : node.getParent();
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot get parent of " + node, e);
+ }
+ }
+
+ /**
+ * @see Node#getParent()
+ * @throws JcrException caused by {@link RepositoryException}
+ */
+ public static String getParentPath(Node node) {
+ return getPath(getParent(node));
+ }
+
+ /**
+ * Whether this node is the root node.
+ *
+ * @throws JcrException caused by {@link RepositoryException}
+ */
+ public static boolean isRoot(Node node) {
+ try {
+ return node.getDepth() == 0;
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot get depth of " + node, e);
+ }
+ }
+
+ /**
+ * @see Node#getPath()
+ * @throws JcrException caused by {@link RepositoryException}
+ */
+ public static String getPath(Node node) {
+ try {
+ return node.getPath();
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot get path of " + node, e);
+ }
+ }
+
+ /**
+ * @see Node#getSession()
+ * @see Session#getWorkspace()
+ * @see Workspace#getName()
+ */
+ public static String getWorkspaceName(Node node) {
+ return session(node).getWorkspace().getName();
+ }
+
+ /**
+ * @see Node#getIdentifier()
+ * @throws JcrException caused by {@link RepositoryException}
+ */
+ public static String getIdentifier(Node node) {
+ try {
+ return node.getIdentifier();
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot get identifier of " + node, e);
+ }
+ }
+
+ /**
+ * @see Node#getName()
+ * @throws JcrException caused by {@link RepositoryException}
+ */
+ public static String getName(Node node) {
+ try {
+ return node.getName();
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot get name of " + node, e);
+ }
+ }
+
+ /**
+ * Returns the node name with its current index (useful for re-ordering).
+ *
+ * @see Node#getName()
+ * @see Node#getIndex()
+ * @throws JcrException caused by {@link RepositoryException}
+ */
+ public static String getIndexedName(Node node) {
+ try {
+ return node.getName() + "[" + node.getIndex() + "]";
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot get name of " + node, e);
+ }
+ }
+
+ /**
+ * @see Node#getProperty(String)
+ * @throws JcrException caused by {@link RepositoryException}
+ */
+ public static Property getProperty(Node node, String property) {
+ try {
+ if (node.hasProperty(property))
+ return node.getProperty(property);
+ else
+ return null;
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot get property " + property + " of " + node, e);
+ }
+ }
+
+ /**
+ * @see Node#getIndex()
+ * @throws JcrException caused by {@link RepositoryException}
+ */
+ public static int getIndex(Node node) {
+ try {
+ return node.getIndex();
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot get index of " + node, e);
+ }
+ }
+
+ /**
+ * If node has mixin {@link NodeType#MIX_TITLE}, return
+ * {@link Property#JCR_TITLE}, otherwise return {@link #getName(Node)}.
+ */
+ public static String getTitle(Node node) {
+ if (Jcr.isNodeType(node, NodeType.MIX_TITLE))
+ return get(node, Property.JCR_TITLE);
+ else
+ return Jcr.getName(node);
+ }
+
+ /** Accesses a {@link NodeIterator} as an {@link Iterable}. */
+ @SuppressWarnings("unchecked")
+ public static Iterable<Node> iterate(NodeIterator nodeIterator) {
+ return new Iterable<Node>() {
+
+ @Override
+ public Iterator<Node> iterator() {
+ return nodeIterator;
+ }
+ };
+ }
+
+ /**
+ * @return the children as an {@link Iterable} for use in for-each llops.
+ * @see Node#getNodes()
+ * @throws JcrException caused by {@link RepositoryException}
+ */
+ public static Iterable<Node> nodes(Node node) {
+ try {
+ return iterate(node.getNodes());
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot get children of " + node, e);
+ }
+ }
+
+ /**
+ * @return the children as a (possibly empty) {@link List}.
+ * @see Node#getNodes()
+ * @throws JcrException caused by {@link RepositoryException}
+ */
+ public static List<Node> getNodes(Node node) {
+ List<Node> nodes = new ArrayList<>();
+ try {
+ if (node.hasNodes()) {
+ NodeIterator nit = node.getNodes();
+ while (nit.hasNext())
+ nodes.add(nit.nextNode());
+ return nodes;
+ } else
+ return nodes;
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot get children of " + node, e);
+ }
+ }
+
+ /**
+ * @return the child or <code>null</node> if not found
+ * @see Node#getNode(String)
+ * @throws JcrException caused by {@link RepositoryException}
+ */
+ public static Node getNode(Node node, String child) {
+ try {
+ if (node.hasNode(child))
+ return node.getNode(child);
+ else
+ return null;
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot get child of " + node, e);
+ }
+ }
+
+ /**
+ * @return the node at this path or <code>null</node> if not found
+ * @see Session#getNode(String)
+ * @throws JcrException caused by {@link RepositoryException}
+ */
+ public static Node getNode(Session session, String path) {
+ try {
+ if (session.nodeExists(path))
+ return session.getNode(path);
+ else
+ return null;
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot get node " + path, e);
+ }
+ }
+
+ /**
+ * Add a node to this parent, setting its primary type and its mixins.
+ *
+ * @param parent the parent node
+ * @param name the name of the node, if <code>null</code>, the primary
+ * type will be used (typically for XML structures)
+ * @param primaryType the primary type, if <code>null</code>
+ * {@link NodeType#NT_UNSTRUCTURED} will be used.
+ * @param mixins the mixins
+ * @return the created node
+ * @see Node#addNode(String, String)
+ * @see Node#addMixin(String)
+ */
+ public static Node addNode(Node parent, String name, String primaryType, String... mixins) {
+ if (name == null && primaryType == null)
+ throw new IllegalArgumentException("Both node name and primary type cannot be null");
+ try {
+ Node newNode = parent.addNode(name == null ? primaryType : name,
+ primaryType == null ? NodeType.NT_UNSTRUCTURED : primaryType);
+ for (String mixin : mixins) {
+ newNode.addMixin(mixin);
+ }
+ return newNode;
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot add node " + name + " to " + parent, e);
+ }
+ }
+
+ /**
+ * Add an {@link NodeType#NT_BASE} node to this parent.
+ *
+ * @param parent the parent node
+ * @param name the name of the node, cannot be <code>null</code>
+ * @return the created node
+ *
+ * @see Node#addNode(String)
+ */
+ public static Node addNode(Node parent, String name) {
+ if (name == null)
+ throw new IllegalArgumentException("Node name cannot be null");
+ try {
+ Node newNode = parent.addNode(name);
+ return newNode;
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot add node " + name + " to " + parent, e);
+ }
+ }
+
+ /**
+ * Add mixins to a node.
+ *
+ * @param node the node
+ * @param mixins the mixins
+ * @return the created node
+ * @see Node#addMixin(String)
+ */
+ public static void addMixin(Node node, String... mixins) {
+ try {
+ for (String mixin : mixins) {
+ node.addMixin(mixin);
+ }
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot add mixins " + Arrays.asList(mixins) + " to " + node, e);
+ }
+ }
+
+ /**
+ * Removes this node.
+ *
+ * @see Node#remove()
+ */
+ public static void remove(Node node) {
+ try {
+ node.remove();
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot remove node " + node, e);
+ }
+ }
+
+ /**
+ * @return the node with htis id or <code>null</node> if not found
+ * @see Session#getNodeByIdentifier(String)
+ * @throws JcrException caused by {@link RepositoryException}
+ */
+ public static Node getNodeById(Session session, String id) {
+ try {
+ return session.getNodeByIdentifier(id);
+ } catch (ItemNotFoundException e) {
+ return null;
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot get node with id " + id, e);
+ }
+ }
+
+ /**
+ * Set a property to the given value, or remove it if the value is
+ * <code>null</code>.
+ *
+ * @throws JcrException caused by {@link RepositoryException}
+ */
+ public static void set(Node node, String property, Object value) {
+ try {
+ if (!node.hasProperty(property)) {
+ if (value != null) {
+ if (value instanceof List) {// multiple
+ List<?> lst = (List<?>) value;
+ String[] values = new String[lst.size()];
+ for (int i = 0; i < lst.size(); i++) {
+ values[i] = lst.get(i).toString();
+ }
+ node.setProperty(property, values);
+ } else {
+ node.setProperty(property, value.toString());
+ }
+ }
+ return;
+ }
+ Property prop = node.getProperty(property);
+ if (value == null) {
+ prop.remove();
+ return;
+ }
+
+ // multiple
+ if (value instanceof List) {
+ List<?> lst = (List<?>) value;
+ String[] values = new String[lst.size()];
+ // TODO better cast?
+ for (int i = 0; i < lst.size(); i++) {
+ values[i] = lst.get(i).toString();
+ }
+ if (!prop.isMultiple())
+ prop.remove();
+ node.setProperty(property, values);
+ return;
+ }
+
+ // single
+ if (prop.isMultiple()) {
+ prop.remove();
+ node.setProperty(property, value.toString());
+ return;
+ }
+
+ if (value instanceof String)
+ prop.setValue((String) value);
+ else if (value instanceof Long)
+ prop.setValue((Long) value);
+ else if (value instanceof Integer)
+ prop.setValue(((Integer) value).longValue());
+ else if (value instanceof Double)
+ prop.setValue((Double) value);
+ else if (value instanceof Float)
+ prop.setValue(((Float) value).doubleValue());
+ else if (value instanceof Calendar)
+ prop.setValue((Calendar) value);
+ else if (value instanceof BigDecimal)
+ prop.setValue((BigDecimal) value);
+ else if (value instanceof Boolean)
+ prop.setValue((Boolean) value);
+ else if (value instanceof byte[])
+ JcrUtils.setBinaryAsBytes(prop, (byte[]) value);
+ else if (value instanceof Instant) {
+ Instant instant = (Instant) value;
+ GregorianCalendar calendar = new GregorianCalendar();
+ calendar.setTime(Date.from(instant));
+ prop.setValue(calendar);
+ } else // try with toString()
+ prop.setValue(value.toString());
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot set property " + property + " of " + node + " to " + value, e);
+ }
+ }
+
+ /**
+ * Get property as {@link String}.
+ *
+ * @return the value of
+ * {@link Node#getProperty(String)}.{@link Property#getString()} or
+ * <code>null</code> if the property does not exist.
+ * @throws JcrException caused by {@link RepositoryException}
+ */
+ public static String get(Node node, String property) {
+ return get(node, property, null);
+ }
+
+ /**
+ * Get property as a {@link String}. If the property is multiple it returns the
+ * first value.
+ *
+ * @return the value of
+ * {@link Node#getProperty(String)}.{@link Property#getString()} or
+ * <code>defaultValue</code> if the property does not exist.
+ * @throws JcrException caused by {@link RepositoryException}
+ */
+ public static String get(Node node, String property, String defaultValue) {
+ try {
+ if (node.hasProperty(property)) {
+ Property p = node.getProperty(property);
+ if (!p.isMultiple())
+ return p.getString();
+ else {
+ Value[] values = p.getValues();
+ if (values.length == 0)
+ return defaultValue;
+ else
+ return values[0].getString();
+ }
+ } else
+ return defaultValue;
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot retrieve property " + property + " from " + node, e);
+ }
+ }
+
+ /**
+ * Get property as a {@link Value}.
+ *
+ * @return {@link Node#getProperty(String)} or <code>null</code> if the property
+ * does not exist.
+ * @throws JcrException caused by {@link RepositoryException}
+ */
+ public static Value getValue(Node node, String property) {
+ try {
+ if (node.hasProperty(property))
+ return node.getProperty(property).getValue();
+ else
+ return null;
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot retrieve property " + property + " from " + node, e);
+ }
+ }
+
+ /**
+ * Get property doing a best effort to cast it as the target object.
+ *
+ * @return the value of {@link Node#getProperty(String)} or
+ * <code>defaultValue</code> if the property does not exist.
+ * @throws IllegalArgumentException if the value could not be cast
+ * @throws JcrException in case of unexpected
+ * {@link RepositoryException}
+ */
+ @SuppressWarnings("unchecked")
+ public static <T> T getAs(Node node, String property, T defaultValue) {
+ try {
+ // TODO deal with multiple
+ if (node.hasProperty(property)) {
+ Property p = node.getProperty(property);
+ try {
+ if (p.isMultiple()) {
+ throw new UnsupportedOperationException("Multiple values properties are not supported");
+ }
+ Value value = p.getValue();
+ return (T) get(value);
+ } catch (ClassCastException e) {
+ throw new IllegalArgumentException(
+ "Cannot cast property of type " + PropertyType.nameFromValue(p.getType()), e);
+ }
+ } else {
+ return defaultValue;
+ }
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot retrieve property " + property + " from " + node, e);
+ }
+ }
+
+ public static <T> T getAs(Node node, String property, Class<T> clss) {
+ if(String.class.isAssignableFrom(clss)) {
+ return (T)get(node,property);
+ } else if(Long.class.isAssignableFrom(clss)) {
+ return (T)get(node,property);
+ }else {
+ throw new IllegalArgumentException("Unsupported format "+clss);
+ }
+ }
+
+ /**
+ * Get a multiple property as a list, doing a best effort to cast it as the
+ * target list.
+ *
+ * @return the value of {@link Node#getProperty(String)}.
+ * @throws IllegalArgumentException if the value could not be cast
+ * @throws JcrException in case of unexpected
+ * {@link RepositoryException}
+ */
+ public static <T> List<T> getMultiple(Node node, String property) {
+ try {
+ if (node.hasProperty(property)) {
+ Property p = node.getProperty(property);
+ return getMultiple(p);
+ } else {
+ return null;
+ }
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot retrieve multiple values property " + property + " from " + node, e);
+ }
+ }
+
+ /**
+ * Get a multiple property as a list, doing a best effort to cast it as the
+ * target list.
+ */
+ @SuppressWarnings("unchecked")
+ public static <T> List<T> getMultiple(Property p) {
+ try {
+ List<T> res = new ArrayList<>();
+ if (!p.isMultiple()) {
+ res.add((T) get(p.getValue()));
+ return res;
+ }
+ Value[] values = p.getValues();
+ for (Value value : values) {
+ res.add((T) get(value));
+ }
+ return res;
+ } catch (ClassCastException | RepositoryException e) {
+ throw new IllegalArgumentException("Cannot get property " + p, e);
+ }
+ }
+
+ /** Cast a {@link Value} to a standard Java object. */
+ public static Object get(Value value) {
+ Binary binary = null;
+ try {
+ switch (value.getType()) {
+ case PropertyType.STRING:
+ return value.getString();
+ case PropertyType.DOUBLE:
+ return (Double) value.getDouble();
+ case PropertyType.LONG:
+ return (Long) value.getLong();
+ case PropertyType.BOOLEAN:
+ return (Boolean) value.getBoolean();
+ case PropertyType.DATE:
+ return value.getDate();
+ case PropertyType.BINARY:
+ binary = value.getBinary();
+ byte[] arr = null;
+ try (InputStream in = binary.getStream(); ByteArrayOutputStream out = new ByteArrayOutputStream();) {
+ IOUtils.copy(in, out);
+ arr = out.toByteArray();
+ } catch (IOException e) {
+ throw new RuntimeException("Cannot read binary from " + value, e);
+ }
+ return arr;
+ default:
+ return value.getString();
+ }
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot cast value from " + value, e);
+ } finally {
+ if (binary != null)
+ binary.dispose();
+ }
+ }
+
+ /**
+ * Retrieves the {@link Session} related to this node.
+ *
+ * @deprecated Use {@link #getSession(Node)} instead.
+ */
+ @Deprecated
+ public static Session session(Node node) {
+ return getSession(node);
+ }
+
+ /** Retrieves the {@link Session} related to this node. */
+ public static Session getSession(Node node) {
+ try {
+ return node.getSession();
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot retrieve session related to " + node, e);
+ }
+ }
+
+ /** Retrieves the root node related to this session. */
+ public static Node getRootNode(Session session) {
+ try {
+ return session.getRootNode();
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot get root node for " + session, e);
+ }
+ }
+
+ /** Whether this item exists. */
+ public static boolean itemExists(Session session, String path) {
+ try {
+ return session.itemExists(path);
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot check whether " + path + " exists", e);
+ }
+ }
+
+ /**
+ * Saves the {@link Session} related to this node. Note that all other unrelated
+ * modifications in this session will also be saved.
+ */
+ public static void save(Node node) {
+ try {
+ Session session = node.getSession();
+// if (node.isNodeType(NodeType.MIX_LAST_MODIFIED)) {
+// set(node, Property.JCR_LAST_MODIFIED, Instant.now());
+// set(node, Property.JCR_LAST_MODIFIED_BY, session.getUserID());
+// }
+ if (session.hasPendingChanges())
+ session.save();
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot save session related to " + node + " in workspace "
+ + session(node).getWorkspace().getName(), e);
+ }
+ }
+
+ /** Login to a JCR repository. */
+ public static Session login(Repository repository, String workspace) {
+ try {
+ return repository.login(workspace);
+ } catch (RepositoryException e) {
+ throw new IllegalArgumentException("Cannot login to repository", e);
+ }
+ }
+
+ /** Safely and silently logs out a session. */
+ public static void logout(Session session) {
+ try {
+ if (session != null)
+ if (session.isLive())
+ session.logout();
+ } catch (Exception e) {
+ // silent
+ }
+ }
+
+ /** Safely and silently logs out the underlying session. */
+ public static void logout(Node node) {
+ Jcr.logout(session(node));
+ }
+
+ /*
+ * SECURITY
+ */
+ /**
+ * Add a single privilege to a node.
+ *
+ * @see Privilege
+ */
+ public static void addPrivilege(Node node, String principal, String privilege) {
+ try {
+ Session session = node.getSession();
+ JcrUtils.addPrivilege(session, node.getPath(), principal, privilege);
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot add privilege " + privilege + " to " + node, e);
+ }
+ }
+
+ /*
+ * VERSIONING
+ */
+ /** Get checked out status. */
+ public static boolean isCheckedOut(Node node) {
+ try {
+ return node.isCheckedOut();
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot retrieve checked out status of " + node, e);
+ }
+ }
+
+ /** @see VersionManager#checkpoint(String) */
+ public static void checkpoint(Node node) {
+ try {
+ versionManager(node).checkpoint(node.getPath());
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot check in " + node, e);
+ }
+ }
+
+ /** @see VersionManager#checkin(String) */
+ public static void checkin(Node node) {
+ try {
+ versionManager(node).checkin(node.getPath());
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot check in " + node, e);
+ }
+ }
+
+ /** @see VersionManager#checkout(String) */
+ public static void checkout(Node node) {
+ try {
+ versionManager(node).checkout(node.getPath());
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot check out " + node, e);
+ }
+ }
+
+ /** Get the {@link VersionManager} related to this node. */
+ public static VersionManager versionManager(Node node) {
+ try {
+ return node.getSession().getWorkspace().getVersionManager();
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot get version manager from " + node, e);
+ }
+ }
+
+ /** Get the {@link VersionHistory} related to this node. */
+ public static VersionHistory getVersionHistory(Node node) {
+ try {
+ return versionManager(node).getVersionHistory(node.getPath());
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot get version history from " + node, e);
+ }
+ }
+
+ /**
+ * The linear versions of this version history in reverse order and without the
+ * root version.
+ */
+ public static List<Version> getLinearVersions(VersionHistory versionHistory) {
+ try {
+ List<Version> lst = new ArrayList<>();
+ VersionIterator vit = versionHistory.getAllLinearVersions();
+ while (vit.hasNext())
+ lst.add(vit.nextVersion());
+ lst.remove(0);
+ Collections.reverse(lst);
+ return lst;
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot get linear versions from " + versionHistory, e);
+ }
+ }
+
+ /** The frozen node related to this {@link Version}. */
+ public static Node getFrozenNode(Version version) {
+ try {
+ return version.getFrozenNode();
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot get frozen node from " + version, e);
+ }
+ }
+
+ /** Get the base {@link Version} related to this node. */
+ public static Version getBaseVersion(Node node) {
+ try {
+ return versionManager(node).getBaseVersion(node.getPath());
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot get base version from " + node, e);
+ }
+ }
+
+ /*
+ * FILES
+ */
+ /**
+ * Returns the size of this file.
+ *
+ * @see NodeType#NT_FILE
+ */
+ public static long getFileSize(Node fileNode) {
+ try {
+ if (!fileNode.isNodeType(NodeType.NT_FILE))
+ throw new IllegalArgumentException(fileNode + " must be a file.");
+ return getBinarySize(fileNode.getNode(Node.JCR_CONTENT).getProperty(Property.JCR_DATA).getBinary());
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot get file size of " + fileNode, e);
+ }
+ }
+
+ /** Returns the size of this {@link Binary}. */
+ public static long getBinarySize(Binary binaryArg) {
+ try {
+ try (Bin binary = new Bin(binaryArg)) {
+ return binary.getSize();
+ }
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot get file size of binary " + binaryArg, e);
+ }
+ }
+
+ // QUERY
+ /** Creates a JCR-SQL2 query using {@link MessageFormat}. */
+ public static Query createQuery(QueryManager qm, String sql, Object... args) {
+ // fix single quotes
+ sql = sql.replaceAll("'", "''");
+ String query = MessageFormat.format(sql, args);
+ try {
+ return qm.createQuery(query, Query.JCR_SQL2);
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot create JCR-SQL2 query from " + query, e);
+ }
+ }
+
+ /** Executes a JCR-SQL2 query using {@link MessageFormat}. */
+ public static NodeIterator executeQuery(QueryManager qm, String sql, Object... args) {
+ Query query = createQuery(qm, sql, args);
+ try {
+ return query.execute().getNodes();
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot execute query " + sql + " with arguments " + Arrays.asList(args), e);
+ }
+ }
+
+ /** Executes a JCR-SQL2 query using {@link MessageFormat}. */
+ public static NodeIterator executeQuery(Session session, String sql, Object... args) {
+ QueryManager queryManager;
+ try {
+ queryManager = session.getWorkspace().getQueryManager();
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot get query manager from session " + session, e);
+ }
+ return executeQuery(queryManager, sql, args);
+ }
+
+ /**
+ * Executes a JCR-SQL2 query using {@link MessageFormat}, which must return a
+ * single node at most.
+ *
+ * @return the node or <code>null</code> if not found.
+ */
+ public static Node getNode(QueryManager qm, String sql, Object... args) {
+ NodeIterator nit = executeQuery(qm, sql, args);
+ if (nit.hasNext()) {
+ Node node = nit.nextNode();
+ if (nit.hasNext())
+ throw new IllegalStateException(
+ "Query " + sql + " with arguments " + Arrays.asList(args) + " returned more than one node.");
+ return node;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Executes a JCR-SQL2 query using {@link MessageFormat}, which must return a
+ * single node at most.
+ *
+ * @return the node or <code>null</code> if not found.
+ */
+ public static Node getNode(Session session, String sql, Object... args) {
+ QueryManager queryManager;
+ try {
+ queryManager = session.getWorkspace().getQueryManager();
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot get query manager from session " + session, e);
+ }
+ return getNode(queryManager, sql, args);
+ }
+
+ /** Singleton. */
+ private Jcr() {
+
+ }
+}
--- /dev/null
+package org.argeo.jcr;
+
+import java.security.Principal;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.security.AccessControlManager;
+import javax.jcr.security.Privilege;
+import javax.naming.InvalidNameException;
+import javax.naming.ldap.LdapName;
+
+/** Apply authorizations to a JCR repository. */
+public class JcrAuthorizations implements Runnable {
+ // private final static Log log =
+ // LogFactory.getLog(JcrAuthorizations.class);
+
+ private Repository repository;
+ private String workspace = null;
+
+ private String securityWorkspace = "security";
+
+ /**
+ * key := privilege1,privilege2/path/to/node<br/>
+ * value := group1,group2,user1
+ */
+ private Map<String, String> principalPrivileges = new HashMap<String, String>();
+
+ public void run() {
+ String currentWorkspace = workspace;
+ Session session = null;
+ try {
+ if (workspace != null && workspace.equals("*")) {
+ session = repository.login();
+ String[] workspaces = session.getWorkspace().getAccessibleWorkspaceNames();
+ JcrUtils.logoutQuietly(session);
+ for (String wksp : workspaces) {
+ currentWorkspace = wksp;
+ if (currentWorkspace.equals(securityWorkspace))
+ continue;
+ session = repository.login(currentWorkspace);
+ initAuthorizations(session);
+ JcrUtils.logoutQuietly(session);
+ }
+ } else {
+ session = repository.login(workspace);
+ initAuthorizations(session);
+ }
+ } catch (RepositoryException e) {
+ JcrUtils.discardQuietly(session);
+ throw new JcrException(
+ "Cannot set authorizations " + principalPrivileges + " on workspace " + currentWorkspace, e);
+ } finally {
+ JcrUtils.logoutQuietly(session);
+ }
+ }
+
+ protected void processWorkspace(String workspace) {
+ Session session = null;
+ try {
+ session = repository.login(workspace);
+ initAuthorizations(session);
+ } catch (RepositoryException e) {
+ JcrUtils.discardQuietly(session);
+ throw new JcrException(
+ "Cannot set authorizations " + principalPrivileges + " on repository " + repository, e);
+ } finally {
+ JcrUtils.logoutQuietly(session);
+ }
+ }
+
+ /** @deprecated call {@link #run()} instead. */
+ @Deprecated
+ public void init() {
+ run();
+ }
+
+ protected void initAuthorizations(Session session) throws RepositoryException {
+ AccessControlManager acm = session.getAccessControlManager();
+
+ for (String privileges : principalPrivileges.keySet()) {
+ String path = null;
+ int slashIndex = privileges.indexOf('/');
+ if (slashIndex == 0) {
+ throw new IllegalArgumentException("Privilege " + privileges + " badly formatted it starts with /");
+ } else if (slashIndex > 0) {
+ path = privileges.substring(slashIndex);
+ privileges = privileges.substring(0, slashIndex);
+ }
+
+ if (path == null)
+ path = "/";
+
+ List<Privilege> privs = new ArrayList<Privilege>();
+ for (String priv : privileges.split(",")) {
+ privs.add(acm.privilegeFromName(priv));
+ }
+
+ String principalNames = principalPrivileges.get(privileges);
+ try {
+ new LdapName(principalNames);
+ // TODO differentiate groups and users ?
+ Principal principal = getOrCreatePrincipal(session, principalNames);
+ JcrUtils.addPrivileges(session, path, principal, privs);
+ } catch (InvalidNameException e) {
+ for (String principalName : principalNames.split(",")) {
+ Principal principal = getOrCreatePrincipal(session, principalName);
+ JcrUtils.addPrivileges(session, path, principal, privs);
+ // if (log.isDebugEnabled()) {
+ // StringBuffer privBuf = new StringBuffer();
+ // for (Privilege priv : privs)
+ // privBuf.append(priv.getName());
+ // log.debug("Added privileges " + privBuf + " to "
+ // + principal.getName() + " on " + path + " in '"
+ // + session.getWorkspace().getName() + "'");
+ // }
+ }
+ }
+ }
+
+ // if (log.isDebugEnabled())
+ // log.debug("JCR authorizations applied on '"
+ // + session.getWorkspace().getName() + "'");
+ }
+
+ /**
+ * Returns a {@link SimplePrincipal}, does not check whether it exists since
+ * such capabilities is not provided by the standard JCR API. Can be
+ * overridden to provide smarter handling
+ */
+ protected Principal getOrCreatePrincipal(Session session, String principalName) throws RepositoryException {
+ return new SimplePrincipal(principalName);
+ }
+
+ // public static void addPrivileges(Session session, Principal principal,
+ // String path, List<Privilege> privs) throws RepositoryException {
+ // AccessControlManager acm = session.getAccessControlManager();
+ // // search for an access control list
+ // AccessControlList acl = null;
+ // AccessControlPolicyIterator policyIterator = acm
+ // .getApplicablePolicies(path);
+ // if (policyIterator.hasNext()) {
+ // while (policyIterator.hasNext()) {
+ // AccessControlPolicy acp = policyIterator
+ // .nextAccessControlPolicy();
+ // if (acp instanceof AccessControlList)
+ // acl = ((AccessControlList) acp);
+ // }
+ // } else {
+ // AccessControlPolicy[] existingPolicies = acm.getPolicies(path);
+ // for (AccessControlPolicy acp : existingPolicies) {
+ // if (acp instanceof AccessControlList)
+ // acl = ((AccessControlList) acp);
+ // }
+ // }
+ //
+ // if (acl != null) {
+ // acl.addAccessControlEntry(principal,
+ // privs.toArray(new Privilege[privs.size()]));
+ // acm.setPolicy(path, acl);
+ // session.save();
+ // if (log.isDebugEnabled()) {
+ // StringBuffer buf = new StringBuffer("");
+ // for (int i = 0; i < privs.size(); i++) {
+ // if (i != 0)
+ // buf.append(',');
+ // buf.append(privs.get(i).getName());
+ // }
+ // log.debug("Added privilege(s) '" + buf + "' to '"
+ // + principal.getName() + "' on " + path
+ // + " from workspace '"
+ // + session.getWorkspace().getName() + "'");
+ // }
+ // } else {
+ // throw new ArgeoJcrException("Don't know how to apply privileges "
+ // + privs + " to " + principal + " on " + path
+ // + " from workspace '" + session.getWorkspace().getName()
+ // + "'");
+ // }
+ // }
+
+ @Deprecated
+ public void setGroupPrivileges(Map<String, String> groupPrivileges) {
+ this.principalPrivileges = groupPrivileges;
+ }
+
+ public void setPrincipalPrivileges(Map<String, String> principalPrivileges) {
+ this.principalPrivileges = principalPrivileges;
+ }
+
+ public void setRepository(Repository repository) {
+ this.repository = repository;
+ }
+
+ public void setWorkspace(String workspace) {
+ this.workspace = workspace;
+ }
+
+ public void setSecurityWorkspace(String securityWorkspace) {
+ this.securityWorkspace = securityWorkspace;
+ }
+
+}
--- /dev/null
+package org.argeo.jcr;
+
+import java.util.function.Function;
+
+import javax.jcr.Session;
+
+/** An arbitrary execution on a JCR session, optionally returning a result. */
+@FunctionalInterface
+public interface JcrCallback extends Function<Session, Object> {
+ /** @deprecated Use {@link #apply(Session)} instead. */
+ @Deprecated
+ public default Object execute(Session session) {
+ return apply(session);
+ }
+}
--- /dev/null
+package org.argeo.jcr;
+
+import javax.jcr.RepositoryException;
+
+/**
+ * Wraps a {@link RepositoryException} in a {@link RuntimeException}.
+ */
+public class JcrException extends IllegalStateException {
+ private static final long serialVersionUID = -4530350094877964989L;
+
+ public JcrException(String message, RepositoryException e) {
+ super(message, e);
+ }
+
+ public JcrException(RepositoryException e) {
+ super(e);
+ }
+
+ public RepositoryException getRepositoryCause() {
+ return (RepositoryException) getCause();
+ }
+}
--- /dev/null
+package org.argeo.jcr;
+
+
+/**
+ * Simple monitor abstraction. Inspired by Eclipse IProgressMOnitor, but without
+ * dependency to it.
+ */
+public interface JcrMonitor {
+ /**
+ * Constant indicating an unknown amount of work.
+ */
+ public final static int UNKNOWN = -1;
+
+ /**
+ * Notifies that the main task is beginning. This must only be called once
+ * on a given progress monitor instance.
+ *
+ * @param name
+ * the name (or description) of the main task
+ * @param totalWork
+ * the total number of work units into which the main task is
+ * been subdivided. If the value is <code>UNKNOWN</code> the
+ * implementation is free to indicate progress in a way which
+ * doesn't require the total number of work units in advance.
+ */
+ public void beginTask(String name, int totalWork);
+
+ /**
+ * Notifies that the work is done; that is, either the main task is
+ * completed or the user canceled it. This method may be called more than
+ * once (implementations should be prepared to handle this case).
+ */
+ public void done();
+
+ /**
+ * Returns whether cancelation of current operation has been requested.
+ * Long-running operations should poll to see if cancelation has been
+ * requested.
+ *
+ * @return <code>true</code> if cancellation has been requested, and
+ * <code>false</code> otherwise
+ * @see #setCanceled(boolean)
+ */
+ public boolean isCanceled();
+
+ /**
+ * Sets the cancel state to the given value.
+ *
+ * @param value
+ * <code>true</code> indicates that cancelation has been
+ * requested (but not necessarily acknowledged);
+ * <code>false</code> clears this flag
+ * @see #isCanceled()
+ */
+ public void setCanceled(boolean value);
+
+ /**
+ * Sets the task name to the given value. This method is used to restore the
+ * task label after a nested operation was executed. Normally there is no
+ * need for clients to call this method.
+ *
+ * @param name
+ * the name (or description) of the main task
+ * @see #beginTask(java.lang.String, int)
+ */
+ public void setTaskName(String name);
+
+ /**
+ * Notifies that a subtask of the main task is beginning. Subtasks are
+ * optional; the main task might not have subtasks.
+ *
+ * @param name
+ * the name (or description) of the subtask
+ */
+ public void subTask(String name);
+
+ /**
+ * Notifies that a given number of work unit of the main task has been
+ * completed. Note that this amount represents an installment, as opposed to
+ * a cumulative amount of work done to date.
+ *
+ * @param work
+ * a non-negative number of work units just completed
+ */
+ public void worked(int work);
+
+}
--- /dev/null
+package org.argeo.jcr;
+
+import java.io.InputStream;
+import java.math.BigDecimal;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.jcr.Binary;
+import javax.jcr.Credentials;
+import javax.jcr.LoginException;
+import javax.jcr.NoSuchWorkspaceException;
+import javax.jcr.PropertyType;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.Value;
+import javax.jcr.ValueFormatException;
+
+/**
+ * Wrapper around a JCR repository which allows to simplify configuration and
+ * intercept some actions. It exposes itself as a {@link Repository}.
+ */
+public abstract class JcrRepositoryWrapper implements Repository {
+ // private final static Log log = LogFactory
+ // .getLog(JcrRepositoryWrapper.class);
+
+ // wrapped repository
+ private Repository repository;
+
+ private Map<String, String> additionalDescriptors = new HashMap<>();
+
+ private Boolean autocreateWorkspaces = false;
+
+ public JcrRepositoryWrapper(Repository repository) {
+ setRepository(repository);
+ }
+
+ /**
+ * Empty constructor
+ */
+ public JcrRepositoryWrapper() {
+ }
+
+ // /** Initializes */
+ // public void init() {
+ // }
+ //
+ // /** Shutdown the repository */
+ // public void destroy() throws Exception {
+ // }
+
+ protected void putDescriptor(String key, String value) {
+ if (Arrays.asList(getRepository().getDescriptorKeys()).contains(key))
+ throw new IllegalArgumentException("Descriptor key " + key + " is already defined in wrapped repository");
+ if (value == null)
+ additionalDescriptors.remove(key);
+ else
+ additionalDescriptors.put(key, value);
+ }
+
+ /*
+ * DELEGATED JCR REPOSITORY METHODS
+ */
+
+ public String getDescriptor(String key) {
+ if (additionalDescriptors.containsKey(key))
+ return additionalDescriptors.get(key);
+ return getRepository().getDescriptor(key);
+ }
+
+ public String[] getDescriptorKeys() {
+ if (additionalDescriptors.size() == 0)
+ return getRepository().getDescriptorKeys();
+ List<String> keys = Arrays.asList(getRepository().getDescriptorKeys());
+ keys.addAll(additionalDescriptors.keySet());
+ return keys.toArray(new String[keys.size()]);
+ }
+
+ /** Central login method */
+ public Session login(Credentials credentials, String workspaceName)
+ throws LoginException, NoSuchWorkspaceException, RepositoryException {
+ Session session;
+ try {
+ session = getRepository(workspaceName).login(credentials, workspaceName);
+ } catch (NoSuchWorkspaceException e) {
+ if (autocreateWorkspaces && workspaceName != null)
+ session = createWorkspaceAndLogsIn(credentials, workspaceName);
+ else
+ throw e;
+ }
+ processNewSession(session, workspaceName);
+ return session;
+ }
+
+ public Session login() throws LoginException, RepositoryException {
+ return login(null, null);
+ }
+
+ public Session login(Credentials credentials) throws LoginException, RepositoryException {
+ return login(credentials, null);
+ }
+
+ public Session login(String workspaceName) throws LoginException, NoSuchWorkspaceException, RepositoryException {
+ return login(null, workspaceName);
+ }
+
+ /** Called after a session has been created, does nothing by default. */
+ protected void processNewSession(Session session, String workspaceName) {
+ }
+
+ /**
+ * Wraps access to the repository, making sure it is available.
+ *
+ * @deprecated Use {@link #getDefaultRepository()} instead.
+ */
+ @Deprecated
+ protected synchronized Repository getRepository() {
+ return getDefaultRepository();
+ }
+
+ protected synchronized Repository getDefaultRepository() {
+ return repository;
+ }
+
+ protected synchronized Repository getRepository(String workspaceName) {
+ return getDefaultRepository();
+ }
+
+ /**
+ * Logs in to the default workspace, creates the required workspace, logs out,
+ * logs in to the required workspace.
+ */
+ protected Session createWorkspaceAndLogsIn(Credentials credentials, String workspaceName)
+ throws RepositoryException {
+ if (workspaceName == null)
+ throw new IllegalArgumentException("No workspace specified.");
+ Session session = getRepository(workspaceName).login(credentials);
+ session.getWorkspace().createWorkspace(workspaceName);
+ session.logout();
+ return getRepository(workspaceName).login(credentials, workspaceName);
+ }
+
+ public boolean isStandardDescriptor(String key) {
+ return getRepository().isStandardDescriptor(key);
+ }
+
+ public boolean isSingleValueDescriptor(String key) {
+ if (additionalDescriptors.containsKey(key))
+ return true;
+ return getRepository().isSingleValueDescriptor(key);
+ }
+
+ public Value getDescriptorValue(String key) {
+ if (additionalDescriptors.containsKey(key))
+ return new StrValue(additionalDescriptors.get(key));
+ return getRepository().getDescriptorValue(key);
+ }
+
+ public Value[] getDescriptorValues(String key) {
+ return getRepository().getDescriptorValues(key);
+ }
+
+ public synchronized void setRepository(Repository repository) {
+ this.repository = repository;
+ }
+
+ public void setAutocreateWorkspaces(Boolean autocreateWorkspaces) {
+ this.autocreateWorkspaces = autocreateWorkspaces;
+ }
+
+ protected static class StrValue implements Value {
+ private final String str;
+
+ public StrValue(String str) {
+ this.str = str;
+ }
+
+ @Override
+ public String getString() throws ValueFormatException, IllegalStateException, RepositoryException {
+ return str;
+ }
+
+ @Override
+ public InputStream getStream() throws RepositoryException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Binary getBinary() throws RepositoryException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public long getLong() throws ValueFormatException, RepositoryException {
+ try {
+ return Long.parseLong(str);
+ } catch (NumberFormatException e) {
+ throw new ValueFormatException("Cannot convert", e);
+ }
+ }
+
+ @Override
+ public double getDouble() throws ValueFormatException, RepositoryException {
+ try {
+ return Double.parseDouble(str);
+ } catch (NumberFormatException e) {
+ throw new ValueFormatException("Cannot convert", e);
+ }
+ }
+
+ @Override
+ public BigDecimal getDecimal() throws ValueFormatException, RepositoryException {
+ try {
+ return new BigDecimal(str);
+ } catch (NumberFormatException e) {
+ throw new ValueFormatException("Cannot convert", e);
+ }
+ }
+
+ @Override
+ public Calendar getDate() throws ValueFormatException, RepositoryException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean getBoolean() throws ValueFormatException, RepositoryException {
+ try {
+ return Boolean.parseBoolean(str);
+ } catch (NumberFormatException e) {
+ throw new ValueFormatException("Cannot convert", e);
+ }
+ }
+
+ @Override
+ public int getType() {
+ return PropertyType.STRING;
+ }
+
+ }
+
+}
--- /dev/null
+package org.argeo.jcr;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLStreamHandler;
+
+import javax.jcr.Item;
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.nodetype.NodeType;
+
+/** URL stream handler able to deal with nt:file node and properties. NOT FINISHED */
+public class JcrUrlStreamHandler extends URLStreamHandler {
+ private final Session session;
+
+ public JcrUrlStreamHandler(Session session) {
+ this.session = session;
+ }
+
+ @Override
+ protected URLConnection openConnection(final URL u) throws IOException {
+ // TODO Auto-generated method stub
+ return new URLConnection(u) {
+
+ @Override
+ public void connect() throws IOException {
+ String itemPath = u.getPath();
+ try {
+ if (!session.itemExists(itemPath))
+ throw new IOException("No item under " + itemPath);
+
+ Item item = session.getItem(u.getPath());
+ if (item.isNode()) {
+ // this should be a nt:file node
+ Node node = (Node) item;
+ if (!node.getPrimaryNodeType().isNodeType(
+ NodeType.NT_FILE))
+ throw new IOException("Node " + node + " is not a "
+ + NodeType.NT_FILE);
+
+ } else {
+ Property property = (Property) item;
+ if(property.getType()==PropertyType.BINARY){
+ //Binary binary = property.getBinary();
+
+ }
+ }
+ } catch (RepositoryException e) {
+ IOException ioe = new IOException(
+ "Unexpected JCR exception");
+ ioe.initCause(e);
+ throw ioe;
+ }
+ }
+
+ @Override
+ public InputStream getInputStream() throws IOException {
+ // TODO Auto-generated method stub
+ return super.getInputStream();
+ }
+
+ };
+ }
+
+}
--- /dev/null
+package org.argeo.jcr;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.Principal;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+import javax.jcr.Binary;
+import javax.jcr.Credentials;
+import javax.jcr.ImportUUIDBehavior;
+import javax.jcr.NamespaceRegistry;
+import javax.jcr.NoSuchWorkspaceException;
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.Property;
+import javax.jcr.PropertyIterator;
+import javax.jcr.PropertyType;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.Value;
+import javax.jcr.Workspace;
+import javax.jcr.nodetype.NoSuchNodeTypeException;
+import javax.jcr.nodetype.NodeType;
+import javax.jcr.observation.EventListener;
+import javax.jcr.query.Query;
+import javax.jcr.query.QueryResult;
+import javax.jcr.security.AccessControlEntry;
+import javax.jcr.security.AccessControlList;
+import javax.jcr.security.AccessControlManager;
+import javax.jcr.security.AccessControlPolicy;
+import javax.jcr.security.AccessControlPolicyIterator;
+import javax.jcr.security.Privilege;
+
+import org.apache.commons.io.IOUtils;
+
+/** Utility methods to simplify common JCR operations. */
+public class JcrUtils {
+
+// final private static Log log = LogFactory.getLog(JcrUtils.class);
+
+ /**
+ * Not complete yet. See
+ * http://www.day.com/specs/jcr/2.0/3_Repository_Model.html#3.2.2%20Local
+ * %20Names
+ */
+ public final static char[] INVALID_NAME_CHARACTERS = { '/', ':', '[', ']', '|', '*', /* invalid for XML: */ '<',
+ '>', '&' };
+
+ /** Prevents instantiation */
+ private JcrUtils() {
+ }
+
+ /**
+ * Queries one single node.
+ *
+ * @return one single node or null if none was found
+ * @throws JcrException if more than one node was found
+ */
+ public static Node querySingleNode(Query query) {
+ NodeIterator nodeIterator;
+ try {
+ QueryResult queryResult = query.execute();
+ nodeIterator = queryResult.getNodes();
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot execute query " + query, e);
+ }
+ Node node;
+ if (nodeIterator.hasNext())
+ node = nodeIterator.nextNode();
+ else
+ return null;
+
+ if (nodeIterator.hasNext())
+ throw new IllegalArgumentException("Query returned more than one node.");
+ return node;
+ }
+
+ /** Retrieves the node name from the provided path */
+ public static String nodeNameFromPath(String path) {
+ if (path.equals("/"))
+ return "";
+ if (path.charAt(0) != '/')
+ throw new IllegalArgumentException("Path " + path + " must start with a '/'");
+ String pathT = path;
+ if (pathT.charAt(pathT.length() - 1) == '/')
+ pathT = pathT.substring(0, pathT.length() - 2);
+
+ int index = pathT.lastIndexOf('/');
+ return pathT.substring(index + 1);
+ }
+
+ /** Retrieves the parent path of the provided path */
+ public static String parentPath(String path) {
+ if (path.equals("/"))
+ throw new IllegalArgumentException("Root path '/' has no parent path");
+ if (path.charAt(0) != '/')
+ throw new IllegalArgumentException("Path " + path + " must start with a '/'");
+ String pathT = path;
+ if (pathT.charAt(pathT.length() - 1) == '/')
+ pathT = pathT.substring(0, pathT.length() - 2);
+
+ int index = pathT.lastIndexOf('/');
+ return pathT.substring(0, index);
+ }
+
+ /** The provided data as a path ('/' at the end, not the beginning) */
+ public static String dateAsPath(Calendar cal) {
+ return dateAsPath(cal, false);
+ }
+
+ /**
+ * Creates a deep path based on a URL:
+ * http://subdomain.example.com/to/content?args becomes
+ * com/example/subdomain/to/content
+ */
+ public static String urlAsPath(String url) {
+ try {
+ URL u = new URL(url);
+ StringBuffer path = new StringBuffer(url.length());
+ // invert host
+ path.append(hostAsPath(u.getHost()));
+ // we don't put port since it may not always be there and may change
+ path.append(u.getPath());
+ return path.toString();
+ } catch (MalformedURLException e) {
+ throw new IllegalArgumentException("Cannot generate URL path for " + url, e);
+ }
+ }
+
+ /** Set the {@link NodeType#NT_ADDRESS} properties based on this URL. */
+ public static void urlToAddressProperties(Node node, String url) {
+ try {
+ URL u = new URL(url);
+ node.setProperty(Property.JCR_PROTOCOL, u.getProtocol());
+ node.setProperty(Property.JCR_HOST, u.getHost());
+ node.setProperty(Property.JCR_PORT, Integer.toString(u.getPort()));
+ node.setProperty(Property.JCR_PATH, normalizePath(u.getPath()));
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot set URL " + url + " as nt:address properties", e);
+ } catch (MalformedURLException e) {
+ throw new IllegalArgumentException("Cannot set URL " + url + " as nt:address properties", e);
+ }
+ }
+
+ /** Build URL based on the {@link NodeType#NT_ADDRESS} properties. */
+ public static String urlFromAddressProperties(Node node) {
+ try {
+ URL u = new URL(node.getProperty(Property.JCR_PROTOCOL).getString(),
+ node.getProperty(Property.JCR_HOST).getString(),
+ (int) node.getProperty(Property.JCR_PORT).getLong(),
+ node.getProperty(Property.JCR_PATH).getString());
+ return u.toString();
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot get URL from nt:address properties of " + node, e);
+ } catch (MalformedURLException e) {
+ throw new IllegalArgumentException("Cannot get URL from nt:address properties of " + node, e);
+ }
+ }
+
+ /*
+ * PATH UTILITIES
+ */
+
+ /**
+ * Make sure that: starts with '/', do not end with '/', do not have '//'
+ */
+ public static String normalizePath(String path) {
+ List<String> tokens = tokenize(path);
+ StringBuffer buf = new StringBuffer(path.length());
+ for (String token : tokens) {
+ buf.append('/');
+ buf.append(token);
+ }
+ return buf.toString();
+ }
+
+ /**
+ * Creates a path from a FQDN, inverting the order of the component:
+ * www.argeo.org becomes org.argeo.www
+ */
+ public static String hostAsPath(String host) {
+ StringBuffer path = new StringBuffer(host.length());
+ String[] hostTokens = host.split("\\.");
+ for (int i = hostTokens.length - 1; i >= 0; i--) {
+ path.append(hostTokens[i]);
+ if (i != 0)
+ path.append('/');
+ }
+ return path.toString();
+ }
+
+ /**
+ * Creates a path from a UUID (e.g. 6ebda899-217d-4bf1-abe4-2839085c8f3c becomes
+ * 6ebda899-217d/4bf1/abe4/2839085c8f3c/). '/' at the end, not the beginning
+ */
+ public static String uuidAsPath(String uuid) {
+ StringBuffer path = new StringBuffer(uuid.length());
+ String[] tokens = uuid.split("-");
+ for (int i = 0; i < tokens.length; i++) {
+ path.append(tokens[i]);
+ if (i != 0)
+ path.append('/');
+ }
+ return path.toString();
+ }
+
+ /**
+ * The provided data as a path ('/' at the end, not the beginning)
+ *
+ * @param cal the date
+ * @param addHour whether to add hour as well
+ */
+ public static String dateAsPath(Calendar cal, Boolean addHour) {
+ StringBuffer buf = new StringBuffer(14);
+ buf.append('Y');
+ buf.append(cal.get(Calendar.YEAR));
+ buf.append('/');
+
+ int month = cal.get(Calendar.MONTH) + 1;
+ buf.append('M');
+ if (month < 10)
+ buf.append(0);
+ buf.append(month);
+ buf.append('/');
+
+ int day = cal.get(Calendar.DAY_OF_MONTH);
+ buf.append('D');
+ if (day < 10)
+ buf.append(0);
+ buf.append(day);
+ buf.append('/');
+
+ if (addHour) {
+ int hour = cal.get(Calendar.HOUR_OF_DAY);
+ buf.append('H');
+ if (hour < 10)
+ buf.append(0);
+ buf.append(hour);
+ buf.append('/');
+ }
+ return buf.toString();
+
+ }
+
+ /** Converts in one call a string into a gregorian calendar. */
+ public static Calendar parseCalendar(DateFormat dateFormat, String value) {
+ try {
+ Date date = dateFormat.parse(value);
+ Calendar calendar = new GregorianCalendar();
+ calendar.setTime(date);
+ return calendar;
+ } catch (ParseException e) {
+ throw new IllegalArgumentException("Cannot parse " + value + " with date format " + dateFormat, e);
+ }
+
+ }
+
+ /** The last element of a path. */
+ public static String lastPathElement(String path) {
+ if (path.charAt(path.length() - 1) == '/')
+ throw new IllegalArgumentException("Path " + path + " cannot end with '/'");
+ int index = path.lastIndexOf('/');
+ if (index < 0)
+ return path;
+ return path.substring(index + 1);
+ }
+
+ /**
+ * Call {@link Node#getName()} without exceptions (useful in super
+ * constructors).
+ */
+ public static String getNameQuietly(Node node) {
+ try {
+ return node.getName();
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot get name from " + node, e);
+ }
+ }
+
+ /**
+ * Call {@link Node#getProperty(String)} without exceptions (useful in super
+ * constructors).
+ */
+ public static String getStringPropertyQuietly(Node node, String propertyName) {
+ try {
+ return node.getProperty(propertyName).getString();
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot get name from " + node, e);
+ }
+ }
+
+// /**
+// * Routine that get the child with this name, adding it if it does not already
+// * exist
+// */
+// public static Node getOrAdd(Node parent, String name, String primaryNodeType) throws RepositoryException {
+// return parent.hasNode(name) ? parent.getNode(name) : parent.addNode(name, primaryNodeType);
+// }
+
+ /**
+ * Routine that get the child with this name, adding it if it does not already
+ * exist
+ */
+ public static Node getOrAdd(Node parent, String name, String primaryNodeType, String... mixinNodeTypes)
+ throws RepositoryException {
+ Node node;
+ if (parent.hasNode(name)) {
+ node = parent.getNode(name);
+ if (primaryNodeType != null && !node.isNodeType(primaryNodeType))
+ throw new IllegalArgumentException("Node " + node + " exists but is of primary node type "
+ + node.getPrimaryNodeType().getName() + ", not " + primaryNodeType);
+ for (String mixin : mixinNodeTypes) {
+ if (!node.isNodeType(mixin))
+ node.addMixin(mixin);
+ }
+ return node;
+ } else {
+ node = primaryNodeType != null ? parent.addNode(name, primaryNodeType) : parent.addNode(name);
+ for (String mixin : mixinNodeTypes) {
+ node.addMixin(mixin);
+ }
+ return node;
+ }
+ }
+
+ /**
+ * Routine that get the child with this name, adding it if it does not already
+ * exist
+ */
+ public static Node getOrAdd(Node parent, String name) throws RepositoryException {
+ return parent.hasNode(name) ? parent.getNode(name) : parent.addNode(name);
+ }
+
+ /** Convert a {@link NodeIterator} to a list of {@link Node} */
+ public static List<Node> nodeIteratorToList(NodeIterator nodeIterator) {
+ List<Node> nodes = new ArrayList<Node>();
+ while (nodeIterator.hasNext()) {
+ nodes.add(nodeIterator.nextNode());
+ }
+ return nodes;
+ }
+
+ /*
+ * PROPERTIES
+ */
+
+ /**
+ * Concisely get the string value of a property or null if this node doesn't
+ * have this property
+ */
+ public static String get(Node node, String propertyName) {
+ try {
+ if (!node.hasProperty(propertyName))
+ return null;
+ return node.getProperty(propertyName).getString();
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot get property " + propertyName + " of " + node, e);
+ }
+ }
+
+ /** Concisely get the path of the given node. */
+ public static String getPath(Node node) {
+ try {
+ return node.getPath();
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot get path of " + node, e);
+ }
+ }
+
+ /** Concisely get the boolean value of a property */
+ public static Boolean check(Node node, String propertyName) {
+ try {
+ return node.getProperty(propertyName).getBoolean();
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot get property " + propertyName + " of " + node, e);
+ }
+ }
+
+ /** Concisely get the bytes array value of a property */
+ public static byte[] getBytes(Node node, String propertyName) {
+ try {
+ return getBinaryAsBytes(node.getProperty(propertyName));
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot get property " + propertyName + " of " + node, e);
+ }
+ }
+
+ /*
+ * MKDIRS
+ */
+
+ /**
+ * Create sub nodes relative to a parent node
+ */
+ public static Node mkdirs(Node parentNode, String relativePath) {
+ return mkdirs(parentNode, relativePath, null, null);
+ }
+
+ /**
+ * Create sub nodes relative to a parent node
+ *
+ * @param nodeType the type of the leaf node
+ */
+ public static Node mkdirs(Node parentNode, String relativePath, String nodeType) {
+ return mkdirs(parentNode, relativePath, nodeType, null);
+ }
+
+ /**
+ * Create sub nodes relative to a parent node
+ *
+ * @param nodeType the type of the leaf node
+ */
+ public static Node mkdirs(Node parentNode, String relativePath, String nodeType, String intermediaryNodeType) {
+ List<String> tokens = tokenize(relativePath);
+ Node currParent = parentNode;
+ try {
+ for (int i = 0; i < tokens.size(); i++) {
+ String name = tokens.get(i);
+ if (currParent.hasNode(name)) {
+ currParent = currParent.getNode(name);
+ } else {
+ if (i != (tokens.size() - 1)) {// intermediary
+ currParent = currParent.addNode(name, intermediaryNodeType);
+ } else {// leaf
+ currParent = currParent.addNode(name, nodeType);
+ }
+ }
+ }
+ return currParent;
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot mkdirs relative path " + relativePath + " from " + parentNode, e);
+ }
+ }
+
+ /**
+ * Synchronized and save is performed, to avoid race conditions in initializers
+ * leading to duplicate nodes.
+ */
+ public synchronized static Node mkdirsSafe(Session session, String path, String type) {
+ try {
+ if (session.hasPendingChanges())
+ throw new IllegalStateException("Session has pending changes, save them first.");
+ Node node = mkdirs(session, path, type);
+ session.save();
+ return node;
+ } catch (RepositoryException e) {
+ discardQuietly(session);
+ throw new JcrException("Cannot safely make directories", e);
+ }
+ }
+
+ public synchronized static Node mkdirsSafe(Session session, String path) {
+ return mkdirsSafe(session, path, null);
+ }
+
+ /** Creates the nodes making path, if they don't exist. */
+ public static Node mkdirs(Session session, String path) {
+ return mkdirs(session, path, null, null, false);
+ }
+
+ /**
+ * @param type the type of the leaf node
+ */
+ public static Node mkdirs(Session session, String path, String type) {
+ return mkdirs(session, path, type, null, false);
+ }
+
+ /**
+ * Creates the nodes making path, if they don't exist. This is up to the caller
+ * to save the session. Use with caution since it can create duplicate nodes if
+ * used concurrently. Requires read access to the root node of the workspace.
+ */
+ public static Node mkdirs(Session session, String path, String type, String intermediaryNodeType,
+ Boolean versioning) {
+ try {
+ if (path.equals("/"))
+ return session.getRootNode();
+
+ if (session.itemExists(path)) {
+ Node node = session.getNode(path);
+ // check type
+ if (type != null && !node.isNodeType(type) && !node.getPath().equals("/"))
+ throw new IllegalArgumentException("Node " + node + " exists but is of type "
+ + node.getPrimaryNodeType().getName() + " not of type " + type);
+ // TODO: check versioning
+ return node;
+ }
+
+ // StringBuffer current = new StringBuffer("/");
+ // Node currentNode = session.getRootNode();
+
+ Node currentNode = findClosestExistingParent(session, path);
+ String closestExistingParentPath = currentNode.getPath();
+ StringBuffer current = new StringBuffer(closestExistingParentPath);
+ if (!closestExistingParentPath.endsWith("/"))
+ current.append('/');
+ Iterator<String> it = tokenize(path.substring(closestExistingParentPath.length())).iterator();
+ while (it.hasNext()) {
+ String part = it.next();
+ current.append(part).append('/');
+ if (!session.itemExists(current.toString())) {
+ if (!it.hasNext() && type != null)
+ currentNode = currentNode.addNode(part, type);
+ else if (it.hasNext() && intermediaryNodeType != null)
+ currentNode = currentNode.addNode(part, intermediaryNodeType);
+ else
+ currentNode = currentNode.addNode(part);
+ if (versioning)
+ currentNode.addMixin(NodeType.MIX_VERSIONABLE);
+// if (log.isTraceEnabled())
+// log.debug("Added folder " + part + " as " + current);
+ } else {
+ currentNode = (Node) session.getItem(current.toString());
+ }
+ }
+ return currentNode;
+ } catch (RepositoryException e) {
+ discardQuietly(session);
+ throw new JcrException("Cannot mkdirs " + path, e);
+ } finally {
+ }
+ }
+
+ private static Node findClosestExistingParent(Session session, String path) throws RepositoryException {
+ int idx = path.lastIndexOf('/');
+ if (idx == 0)
+ return session.getRootNode();
+ String parentPath = path.substring(0, idx);
+ if (session.itemExists(parentPath))
+ return session.getNode(parentPath);
+ else
+ return findClosestExistingParent(session, parentPath);
+ }
+
+ /** Convert a path to the list of its tokens */
+ public static List<String> tokenize(String path) {
+ List<String> tokens = new ArrayList<String>();
+ boolean optimized = false;
+ if (!optimized) {
+ String[] rawTokens = path.split("/");
+ for (String token : rawTokens) {
+ if (!token.equals(""))
+ tokens.add(token);
+ }
+ } else {
+ StringBuffer curr = new StringBuffer();
+ char[] arr = path.toCharArray();
+ chars: for (int i = 0; i < arr.length; i++) {
+ char c = arr[i];
+ if (c == '/') {
+ if (i == 0 || (i == arr.length - 1))
+ continue chars;
+ if (curr.length() > 0) {
+ tokens.add(curr.toString());
+ curr = new StringBuffer();
+ }
+ } else
+ curr.append(c);
+ }
+ if (curr.length() > 0) {
+ tokens.add(curr.toString());
+ curr = new StringBuffer();
+ }
+ }
+ return Collections.unmodifiableList(tokens);
+ }
+
+ // /**
+ // * use {@link #mkdirs(Session, String, String, String, Boolean)} instead.
+ // *
+ // * @deprecated
+ // */
+ // @Deprecated
+ // public static Node mkdirs(Session session, String path, String type,
+ // Boolean versioning) {
+ // return mkdirs(session, path, type, type, false);
+ // }
+
+ /**
+ * Safe and repository implementation independent registration of a namespace.
+ */
+ public static void registerNamespaceSafely(Session session, String prefix, String uri) {
+ try {
+ registerNamespaceSafely(session.getWorkspace().getNamespaceRegistry(), prefix, uri);
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot find namespace registry", e);
+ }
+ }
+
+ /**
+ * Safe and repository implementation independent registration of a namespace.
+ */
+ public static void registerNamespaceSafely(NamespaceRegistry nr, String prefix, String uri) {
+ try {
+ String[] prefixes = nr.getPrefixes();
+ for (String pref : prefixes)
+ if (pref.equals(prefix)) {
+ String registeredUri = nr.getURI(pref);
+ if (!registeredUri.equals(uri))
+ throw new IllegalArgumentException("Prefix " + pref + " already registered for URI "
+ + registeredUri + " which is different from provided URI " + uri);
+ else
+ return;// skip
+ }
+ nr.registerNamespace(prefix, uri);
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot register namespace " + uri + " under prefix " + prefix, e);
+ }
+ }
+
+// /** Recursively outputs the contents of the given node. */
+// public static void debug(Node node) {
+// debug(node, log);
+// }
+//
+// /** Recursively outputs the contents of the given node. */
+// public static void debug(Node node, Log log) {
+// try {
+// // First output the node path
+// log.debug(node.getPath());
+// // Skip the virtual (and large!) jcr:system subtree
+// if (node.getName().equals("jcr:system")) {
+// return;
+// }
+//
+// // Then the children nodes (recursive)
+// NodeIterator it = node.getNodes();
+// while (it.hasNext()) {
+// Node childNode = it.nextNode();
+// debug(childNode, log);
+// }
+//
+// // Then output the properties
+// PropertyIterator properties = node.getProperties();
+// // log.debug("Property are : ");
+//
+// properties: while (properties.hasNext()) {
+// Property property = properties.nextProperty();
+// if (property.getType() == PropertyType.BINARY)
+// continue properties;// skip
+// if (property.getDefinition().isMultiple()) {
+// // A multi-valued property, print all values
+// Value[] values = property.getValues();
+// for (int i = 0; i < values.length; i++) {
+// log.debug(property.getPath() + "=" + values[i].getString());
+// }
+// } else {
+// // A single-valued property
+// log.debug(property.getPath() + "=" + property.getString());
+// }
+// }
+// } catch (Exception e) {
+// log.error("Could not debug " + node, e);
+// }
+//
+// }
+
+// /** Logs the effective access control policies */
+// public static void logEffectiveAccessPolicies(Node node) {
+// try {
+// logEffectiveAccessPolicies(node.getSession(), node.getPath());
+// } catch (RepositoryException e) {
+// log.error("Cannot log effective access policies of " + node, e);
+// }
+// }
+//
+// /** Logs the effective access control policies */
+// public static void logEffectiveAccessPolicies(Session session, String path) {
+// if (!log.isDebugEnabled())
+// return;
+//
+// try {
+// AccessControlPolicy[] effectivePolicies = session.getAccessControlManager().getEffectivePolicies(path);
+// if (effectivePolicies.length > 0) {
+// for (AccessControlPolicy policy : effectivePolicies) {
+// if (policy instanceof AccessControlList) {
+// AccessControlList acl = (AccessControlList) policy;
+// log.debug("Access control list for " + path + "\n" + accessControlListSummary(acl));
+// }
+// }
+// } else {
+// log.debug("No effective access control policy for " + path);
+// }
+// } catch (RepositoryException e) {
+// log.error("Cannot log effective access policies of " + path, e);
+// }
+// }
+
+ /** Returns a human-readable summary of this access control list. */
+ public static String accessControlListSummary(AccessControlList acl) {
+ StringBuffer buf = new StringBuffer("");
+ try {
+ for (AccessControlEntry ace : acl.getAccessControlEntries()) {
+ buf.append('\t').append(ace.getPrincipal().getName()).append('\n');
+ for (Privilege priv : ace.getPrivileges())
+ buf.append("\t\t").append(priv.getName()).append('\n');
+ }
+ return buf.toString();
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot write summary of " + acl, e);
+ }
+ }
+
+ /** Copy the whole workspace via a system view XML. */
+ public static void copyWorkspaceXml(Session fromSession, Session toSession) {
+ Workspace fromWorkspace = fromSession.getWorkspace();
+ Workspace toWorkspace = toSession.getWorkspace();
+ String errorMsg = "Cannot copy workspace " + fromWorkspace + " to " + toWorkspace + " via XML.";
+
+ try (PipedInputStream in = new PipedInputStream(1024 * 1024);) {
+ new Thread(() -> {
+ try (PipedOutputStream out = new PipedOutputStream(in)) {
+ fromSession.exportSystemView("/", out, false, false);
+ out.flush();
+ } catch (IOException e) {
+ throw new RuntimeException(errorMsg, e);
+ } catch (RepositoryException e) {
+ throw new JcrException(errorMsg, e);
+ }
+ }, "Copy workspace" + fromWorkspace + " to " + toWorkspace).start();
+
+ toSession.importXML("/", in, ImportUUIDBehavior.IMPORT_UUID_COLLISION_REPLACE_EXISTING);
+ toSession.save();
+ } catch (IOException e) {
+ throw new RuntimeException(errorMsg, e);
+ } catch (RepositoryException e) {
+ throw new JcrException(errorMsg, e);
+ }
+ }
+
+ /**
+ * Copies recursively the content of a node to another one. Do NOT copy the
+ * property values of {@link NodeType#MIX_CREATED} and
+ * {@link NodeType#MIX_LAST_MODIFIED}, but update the
+ * {@link Property#JCR_LAST_MODIFIED} and {@link Property#JCR_LAST_MODIFIED_BY}
+ * properties if the target node has the {@link NodeType#MIX_LAST_MODIFIED}
+ * mixin.
+ */
+ public static void copy(Node fromNode, Node toNode) {
+ try {
+ if (toNode.getDefinition().isProtected())
+ return;
+
+ // add mixins
+ for (NodeType mixinType : fromNode.getMixinNodeTypes()) {
+ try {
+ toNode.addMixin(mixinType.getName());
+ } catch (NoSuchNodeTypeException e) {
+ // ignore unknown mixins
+ // TODO log it
+ }
+ }
+
+ // process properties
+ PropertyIterator pit = fromNode.getProperties();
+ properties: while (pit.hasNext()) {
+ Property fromProperty = pit.nextProperty();
+ String propertyName = fromProperty.getName();
+ if (toNode.hasProperty(propertyName) && toNode.getProperty(propertyName).getDefinition().isProtected())
+ continue properties;
+
+ if (fromProperty.getDefinition().isProtected())
+ continue properties;
+
+ if (propertyName.equals("jcr:created") || propertyName.equals("jcr:createdBy")
+ || propertyName.equals("jcr:lastModified") || propertyName.equals("jcr:lastModifiedBy"))
+ continue properties;
+
+ if (fromProperty.isMultiple()) {
+ toNode.setProperty(propertyName, fromProperty.getValues());
+ } else {
+ toNode.setProperty(propertyName, fromProperty.getValue());
+ }
+ }
+
+ // update jcr:lastModified and jcr:lastModifiedBy in toNode in case
+ // they existed, before adding the mixins
+ updateLastModified(toNode, true);
+
+ // process children nodes
+ NodeIterator nit = fromNode.getNodes();
+ while (nit.hasNext()) {
+ Node fromChild = nit.nextNode();
+ Integer index = fromChild.getIndex();
+ String nodeRelPath = fromChild.getName() + "[" + index + "]";
+ Node toChild;
+ if (toNode.hasNode(nodeRelPath))
+ toChild = toNode.getNode(nodeRelPath);
+ else {
+ try {
+ toChild = toNode.addNode(fromChild.getName(), fromChild.getPrimaryNodeType().getName());
+ } catch (NoSuchNodeTypeException e) {
+ // ignore unknown primary types
+ // TODO log it
+ return;
+ }
+ }
+ copy(fromChild, toChild);
+ }
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot copy " + fromNode + " to " + toNode, e);
+ }
+ }
+
+ /**
+ * Check whether all first-level properties (except jcr:* properties) are equal.
+ * Skip jcr:* properties
+ */
+ public static Boolean allPropertiesEquals(Node reference, Node observed, Boolean onlyCommonProperties) {
+ try {
+ PropertyIterator pit = reference.getProperties();
+ props: while (pit.hasNext()) {
+ Property propReference = pit.nextProperty();
+ String propName = propReference.getName();
+ if (propName.startsWith("jcr:"))
+ continue props;
+
+ if (!observed.hasProperty(propName))
+ if (onlyCommonProperties)
+ continue props;
+ else
+ return false;
+ // TODO: deal with multiple property values?
+ if (!observed.getProperty(propName).getValue().equals(propReference.getValue()))
+ return false;
+ }
+ return true;
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot check all properties equals of " + reference + " and " + observed, e);
+ }
+ }
+
+ public static Map<String, PropertyDiff> diffProperties(Node reference, Node observed) {
+ Map<String, PropertyDiff> diffs = new TreeMap<String, PropertyDiff>();
+ diffPropertiesLevel(diffs, null, reference, observed);
+ return diffs;
+ }
+
+ /**
+ * Compare the properties of two nodes. Recursivity to child nodes is not yet
+ * supported. Skip jcr:* properties.
+ */
+ static void diffPropertiesLevel(Map<String, PropertyDiff> diffs, String baseRelPath, Node reference,
+ Node observed) {
+ try {
+ // check removed and modified
+ PropertyIterator pit = reference.getProperties();
+ props: while (pit.hasNext()) {
+ Property p = pit.nextProperty();
+ String name = p.getName();
+ if (name.startsWith("jcr:"))
+ continue props;
+
+ if (!observed.hasProperty(name)) {
+ String relPath = propertyRelPath(baseRelPath, name);
+ PropertyDiff pDiff = new PropertyDiff(PropertyDiff.REMOVED, relPath, p.getValue(), null);
+ diffs.put(relPath, pDiff);
+ } else {
+ if (p.isMultiple()) {
+ // FIXME implement multiple
+ } else {
+ Value referenceValue = p.getValue();
+ Value newValue = observed.getProperty(name).getValue();
+ if (!referenceValue.equals(newValue)) {
+ String relPath = propertyRelPath(baseRelPath, name);
+ PropertyDiff pDiff = new PropertyDiff(PropertyDiff.MODIFIED, relPath, referenceValue,
+ newValue);
+ diffs.put(relPath, pDiff);
+ }
+ }
+ }
+ }
+ // check added
+ pit = observed.getProperties();
+ props: while (pit.hasNext()) {
+ Property p = pit.nextProperty();
+ String name = p.getName();
+ if (name.startsWith("jcr:"))
+ continue props;
+ if (!reference.hasProperty(name)) {
+ if (p.isMultiple()) {
+ // FIXME implement multiple
+ } else {
+ String relPath = propertyRelPath(baseRelPath, name);
+ PropertyDiff pDiff = new PropertyDiff(PropertyDiff.ADDED, relPath, null, p.getValue());
+ diffs.put(relPath, pDiff);
+ }
+ }
+ }
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot diff " + reference + " and " + observed, e);
+ }
+ }
+
+ /**
+ * Compare only a restricted list of properties of two nodes. No recursivity.
+ *
+ */
+ public static Map<String, PropertyDiff> diffProperties(Node reference, Node observed, List<String> properties) {
+ Map<String, PropertyDiff> diffs = new TreeMap<String, PropertyDiff>();
+ try {
+ Iterator<String> pit = properties.iterator();
+
+ props: while (pit.hasNext()) {
+ String name = pit.next();
+ if (!reference.hasProperty(name)) {
+ if (!observed.hasProperty(name))
+ continue props;
+ Value val = observed.getProperty(name).getValue();
+ try {
+ // empty String but not null
+ if ("".equals(val.getString()))
+ continue props;
+ } catch (Exception e) {
+ // not parseable as String, silent
+ }
+ PropertyDiff pDiff = new PropertyDiff(PropertyDiff.ADDED, name, null, val);
+ diffs.put(name, pDiff);
+ } else if (!observed.hasProperty(name)) {
+ PropertyDiff pDiff = new PropertyDiff(PropertyDiff.REMOVED, name,
+ reference.getProperty(name).getValue(), null);
+ diffs.put(name, pDiff);
+ } else {
+ Value referenceValue = reference.getProperty(name).getValue();
+ Value newValue = observed.getProperty(name).getValue();
+ if (!referenceValue.equals(newValue)) {
+ PropertyDiff pDiff = new PropertyDiff(PropertyDiff.MODIFIED, name, referenceValue, newValue);
+ diffs.put(name, pDiff);
+ }
+ }
+ }
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot diff " + reference + " and " + observed, e);
+ }
+ return diffs;
+ }
+
+ /** Builds a property relPath to be used in the diff. */
+ private static String propertyRelPath(String baseRelPath, String propertyName) {
+ if (baseRelPath == null)
+ return propertyName;
+ else
+ return baseRelPath + '/' + propertyName;
+ }
+
+ /**
+ * Normalizes a name so that it can be stored in contexts not supporting names
+ * with ':' (typically databases). Replaces ':' by '_'.
+ */
+ public static String normalize(String name) {
+ return name.replace(':', '_');
+ }
+
+ /**
+ * Replaces characters which are invalid in a JCR name by '_'. Currently not
+ * exhaustive.
+ *
+ * @see JcrUtils#INVALID_NAME_CHARACTERS
+ */
+ public static String replaceInvalidChars(String name) {
+ return replaceInvalidChars(name, '_');
+ }
+
+ /**
+ * Replaces characters which are invalid in a JCR name. Currently not
+ * exhaustive.
+ *
+ * @see JcrUtils#INVALID_NAME_CHARACTERS
+ */
+ public static String replaceInvalidChars(String name, char replacement) {
+ boolean modified = false;
+ char[] arr = name.toCharArray();
+ for (int i = 0; i < arr.length; i++) {
+ char c = arr[i];
+ invalid: for (char invalid : INVALID_NAME_CHARACTERS) {
+ if (c == invalid) {
+ arr[i] = replacement;
+ modified = true;
+ break invalid;
+ }
+ }
+ }
+ if (modified)
+ return new String(arr);
+ else
+ // do not create new object if unnecessary
+ return name;
+ }
+
+ // /**
+ // * Removes forbidden characters from a path, replacing them with '_'
+ // *
+ // * @deprecated use {@link #replaceInvalidChars(String)} instead
+ // */
+ // public static String removeForbiddenCharacters(String str) {
+ // return str.replace('[', '_').replace(']', '_').replace('/', '_').replace('*',
+ // '_');
+ //
+ // }
+
+ /** Cleanly disposes a {@link Binary} even if it is null. */
+ public static void closeQuietly(Binary binary) {
+ if (binary == null)
+ return;
+ binary.dispose();
+ }
+
+ /** Retrieve a {@link Binary} as a byte array */
+ public static byte[] getBinaryAsBytes(Property property) {
+ try (ByteArrayOutputStream out = new ByteArrayOutputStream();
+ Bin binary = new Bin(property);
+ InputStream in = binary.getStream()) {
+ IOUtils.copy(in, out);
+ return out.toByteArray();
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot read binary " + property + " as bytes", e);
+ } catch (IOException e) {
+ throw new RuntimeException("Cannot read binary " + property + " as bytes", e);
+ }
+ }
+
+ /** Writes a {@link Binary} from a byte array */
+ public static void setBinaryAsBytes(Node node, String property, byte[] bytes) {
+ Binary binary = null;
+ try (InputStream in = new ByteArrayInputStream(bytes)) {
+ binary = node.getSession().getValueFactory().createBinary(in);
+ node.setProperty(property, binary);
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot set binary " + property + " as bytes", e);
+ } catch (IOException e) {
+ throw new RuntimeException("Cannot set binary " + property + " as bytes", e);
+ } finally {
+ closeQuietly(binary);
+ }
+ }
+
+ /** Writes a {@link Binary} from a byte array */
+ public static void setBinaryAsBytes(Property prop, byte[] bytes) {
+ Binary binary = null;
+ try (InputStream in = new ByteArrayInputStream(bytes)) {
+ binary = prop.getSession().getValueFactory().createBinary(in);
+ prop.setValue(binary);
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot set binary " + prop + " as bytes", e);
+ } catch (IOException e) {
+ throw new RuntimeException("Cannot set binary " + prop + " as bytes", e);
+ } finally {
+ closeQuietly(binary);
+ }
+ }
+
+ /**
+ * Creates depth from a string (typically a username) by adding levels based on
+ * its first characters: "aBcD",2 becomes a/aB
+ */
+ public static String firstCharsToPath(String str, Integer nbrOfChars) {
+ if (str.length() < nbrOfChars)
+ throw new IllegalArgumentException("String " + str + " length must be greater or equal than " + nbrOfChars);
+ StringBuffer path = new StringBuffer("");
+ StringBuffer curr = new StringBuffer("");
+ for (int i = 0; i < nbrOfChars; i++) {
+ curr.append(str.charAt(i));
+ path.append(curr);
+ if (i < nbrOfChars - 1)
+ path.append('/');
+ }
+ return path.toString();
+ }
+
+ /**
+ * Discards the current changes in the session attached to this node. To be used
+ * typically in a catch block.
+ *
+ * @see #discardQuietly(Session)
+ */
+ public static void discardUnderlyingSessionQuietly(Node node) {
+ try {
+ discardQuietly(node.getSession());
+ } catch (RepositoryException e) {
+ // silent
+ }
+ }
+
+ /**
+ * Discards the current changes in a session by calling
+ * {@link Session#refresh(boolean)} with <code>false</code>, only logging
+ * potential errors when doing so. To be used typically in a catch block.
+ */
+ public static void discardQuietly(Session session) {
+ try {
+ if (session != null)
+ session.refresh(false);
+ } catch (RepositoryException e) {
+ // silent
+ }
+ }
+
+ /**
+ * Login to a workspace with implicit credentials, creates the workspace with
+ * these credentials if it does not already exist.
+ */
+ public static Session loginOrCreateWorkspace(Repository repository, String workspaceName)
+ throws RepositoryException {
+ return loginOrCreateWorkspace(repository, workspaceName, null);
+ }
+
+ /**
+ * Login to a workspace with implicit credentials, creates the workspace with
+ * these credentials if it does not already exist.
+ */
+ public static Session loginOrCreateWorkspace(Repository repository, String workspaceName, Credentials credentials)
+ throws RepositoryException {
+ Session workspaceSession = null;
+ Session defaultSession = null;
+ try {
+ try {
+ workspaceSession = repository.login(credentials, workspaceName);
+ } catch (NoSuchWorkspaceException e) {
+ // try to create workspace
+ defaultSession = repository.login(credentials);
+ defaultSession.getWorkspace().createWorkspace(workspaceName);
+ workspaceSession = repository.login(credentials, workspaceName);
+ }
+ return workspaceSession;
+ } finally {
+ logoutQuietly(defaultSession);
+ }
+ }
+
+ /**
+ * Logs out the session, not throwing any exception, even if it is null.
+ * {@link Jcr#logout(Session)} should rather be used.
+ */
+ public static void logoutQuietly(Session session) {
+ Jcr.logout(session);
+// try {
+// if (session != null)
+// if (session.isLive())
+// session.logout();
+// } catch (Exception e) {
+// // silent
+// }
+ }
+
+ /**
+ * Convenient method to add a listener. uuids passed as null, deep=true,
+ * local=true, only one node type
+ */
+ public static void addListener(Session session, EventListener listener, int eventTypes, String basePath,
+ String nodeType) {
+ try {
+ session.getWorkspace().getObservationManager().addEventListener(listener, eventTypes, basePath, true, null,
+ nodeType == null ? null : new String[] { nodeType }, true);
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot add JCR listener " + listener + " to session " + session, e);
+ }
+ }
+
+ /** Removes a listener without throwing exception */
+ public static void removeListenerQuietly(Session session, EventListener listener) {
+ if (session == null || !session.isLive())
+ return;
+ try {
+ session.getWorkspace().getObservationManager().removeEventListener(listener);
+ } catch (RepositoryException e) {
+ // silent
+ }
+ }
+
+ /**
+ * Quietly unregisters an {@link EventListener} from the udnerlying workspace of
+ * this node.
+ */
+ public static void unregisterQuietly(Node node, EventListener eventListener) {
+ try {
+ unregisterQuietly(node.getSession().getWorkspace(), eventListener);
+ } catch (RepositoryException e) {
+ // silent
+ }
+ }
+
+ /** Quietly unregisters an {@link EventListener} from this workspace */
+ public static void unregisterQuietly(Workspace workspace, EventListener eventListener) {
+ if (eventListener == null)
+ return;
+ try {
+ workspace.getObservationManager().removeEventListener(eventListener);
+ } catch (RepositoryException e) {
+ // silent
+ }
+ }
+
+ /**
+ * Checks whether {@link Property#JCR_LAST_MODIFIED} or (afterwards)
+ * {@link Property#JCR_CREATED} are set and returns it as an {@link Instant}.
+ */
+ public static Instant getModified(Node node) {
+ Calendar calendar = null;
+ try {
+ if (node.hasProperty(Property.JCR_LAST_MODIFIED))
+ calendar = node.getProperty(Property.JCR_LAST_MODIFIED).getDate();
+ else if (node.hasProperty(Property.JCR_CREATED))
+ calendar = node.getProperty(Property.JCR_CREATED).getDate();
+ else
+ throw new IllegalArgumentException("No modification time found in " + node);
+ return calendar.toInstant();
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot get modification time for " + node, e);
+ }
+
+ }
+
+ /**
+ * Get {@link Property#JCR_CREATED} as an {@link Instant}, if it is set.
+ */
+ public static Instant getCreated(Node node) {
+ Calendar calendar = null;
+ try {
+ if (node.hasProperty(Property.JCR_CREATED))
+ calendar = node.getProperty(Property.JCR_CREATED).getDate();
+ else
+ throw new IllegalArgumentException("No created time found in " + node);
+ return calendar.toInstant();
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot get created time for " + node, e);
+ }
+
+ }
+
+ /**
+ * Updates the {@link Property#JCR_LAST_MODIFIED} property with the current time
+ * and the {@link Property#JCR_LAST_MODIFIED_BY} property with the underlying
+ * session user id.
+ */
+ public static void updateLastModified(Node node) {
+ updateLastModified(node, false);
+ }
+
+ /**
+ * Updates the {@link Property#JCR_LAST_MODIFIED} property with the current time
+ * and the {@link Property#JCR_LAST_MODIFIED_BY} property with the underlying
+ * session user id. In Jackrabbit 2.x,
+ * <a href="https://issues.apache.org/jira/browse/JCR-2233">these properties are
+ * not automatically updated</a>, hence the need for manual update. The session
+ * is not saved.
+ */
+ public static void updateLastModified(Node node, boolean addMixin) {
+ try {
+ if (addMixin && !node.isNodeType(NodeType.MIX_LAST_MODIFIED))
+ node.addMixin(NodeType.MIX_LAST_MODIFIED);
+ node.setProperty(Property.JCR_LAST_MODIFIED, new GregorianCalendar());
+ node.setProperty(Property.JCR_LAST_MODIFIED_BY, node.getSession().getUserID());
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot update last modified on " + node, e);
+ }
+ }
+
+ /**
+ * Update lastModified recursively until this parent.
+ *
+ * @param node the node
+ * @param untilPath the base path, null is equivalent to "/"
+ */
+ public static void updateLastModifiedAndParents(Node node, String untilPath) {
+ updateLastModifiedAndParents(node, untilPath, true);
+ }
+
+ /**
+ * Update lastModified recursively until this parent.
+ *
+ * @param node the node
+ * @param untilPath the base path, null is equivalent to "/"
+ */
+ public static void updateLastModifiedAndParents(Node node, String untilPath, boolean addMixin) {
+ try {
+ if (untilPath != null && !node.getPath().startsWith(untilPath))
+ throw new IllegalArgumentException(node + " is not under " + untilPath);
+ updateLastModified(node, addMixin);
+ if (untilPath == null) {
+ if (!node.getPath().equals("/"))
+ updateLastModifiedAndParents(node.getParent(), untilPath, addMixin);
+ } else {
+ if (!node.getPath().equals(untilPath))
+ updateLastModifiedAndParents(node.getParent(), untilPath, addMixin);
+ }
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot update lastModified from " + node + " until " + untilPath, e);
+ }
+ }
+
+ /**
+ * Returns a String representing the short version (see
+ * <a href="http://jackrabbit.apache.org/node-type-notation.html"> Node type
+ * Notation </a> attributes grammar) of the main business attributes of this
+ * property definition
+ *
+ * @param prop
+ */
+ public static String getPropertyDefinitionAsString(Property prop) {
+ StringBuffer sbuf = new StringBuffer();
+ try {
+ if (prop.getDefinition().isAutoCreated())
+ sbuf.append("a");
+ if (prop.getDefinition().isMandatory())
+ sbuf.append("m");
+ if (prop.getDefinition().isProtected())
+ sbuf.append("p");
+ if (prop.getDefinition().isMultiple())
+ sbuf.append("*");
+ } catch (RepositoryException re) {
+ throw new JcrException("unexpected error while getting property definition as String", re);
+ }
+ return sbuf.toString();
+ }
+
+ /**
+ * Estimate the sub tree size from current node. Computation is based on the Jcr
+ * {@link Property#getLength()} method. Note : it is not the exact size used on
+ * the disk by the current part of the JCR Tree.
+ */
+
+ public static long getNodeApproxSize(Node node) {
+ long curNodeSize = 0;
+ try {
+ PropertyIterator pi = node.getProperties();
+ while (pi.hasNext()) {
+ Property prop = pi.nextProperty();
+ if (prop.isMultiple()) {
+ int nb = prop.getLengths().length;
+ for (int i = 0; i < nb; i++) {
+ curNodeSize += (prop.getLengths()[i] > 0 ? prop.getLengths()[i] : 0);
+ }
+ } else
+ curNodeSize += (prop.getLength() > 0 ? prop.getLength() : 0);
+ }
+
+ NodeIterator ni = node.getNodes();
+ while (ni.hasNext())
+ curNodeSize += getNodeApproxSize(ni.nextNode());
+ return curNodeSize;
+ } catch (RepositoryException re) {
+ throw new JcrException("Unexpected error while recursively determining node size.", re);
+ }
+ }
+
+ /*
+ * SECURITY
+ */
+
+ /**
+ * Convenience method for adding a single privilege to a principal (user or
+ * role), typically jcr:all
+ */
+ public synchronized static void addPrivilege(Session session, String path, String principal, String privilege)
+ throws RepositoryException {
+ List<Privilege> privileges = new ArrayList<Privilege>();
+ privileges.add(session.getAccessControlManager().privilegeFromName(privilege));
+ addPrivileges(session, path, new SimplePrincipal(principal), privileges);
+ }
+
+ /**
+ * Add privileges on a path to a {@link Principal}. The path must already exist.
+ * Session is saved. Synchronized to prevent concurrent modifications of the
+ * same node.
+ */
+ public synchronized static Boolean addPrivileges(Session session, String path, Principal principal,
+ List<Privilege> privs) throws RepositoryException {
+ // make sure the session is in line with the persisted state
+ session.refresh(false);
+ AccessControlManager acm = session.getAccessControlManager();
+ AccessControlList acl = getAccessControlList(acm, path);
+
+ accessControlEntries: for (AccessControlEntry ace : acl.getAccessControlEntries()) {
+ Principal currentPrincipal = ace.getPrincipal();
+ if (currentPrincipal.getName().equals(principal.getName())) {
+ Privilege[] currentPrivileges = ace.getPrivileges();
+ if (currentPrivileges.length != privs.size())
+ break accessControlEntries;
+ for (int i = 0; i < currentPrivileges.length; i++) {
+ Privilege currP = currentPrivileges[i];
+ Privilege p = privs.get(i);
+ if (!currP.getName().equals(p.getName())) {
+ break accessControlEntries;
+ }
+ }
+ return false;
+ }
+ }
+
+ Privilege[] privileges = privs.toArray(new Privilege[privs.size()]);
+ acl.addAccessControlEntry(principal, privileges);
+ acm.setPolicy(path, acl);
+// if (log.isDebugEnabled()) {
+// StringBuffer privBuf = new StringBuffer();
+// for (Privilege priv : privs)
+// privBuf.append(priv.getName());
+// log.debug("Added privileges " + privBuf + " to " + principal.getName() + " on " + path + " in '"
+// + session.getWorkspace().getName() + "'");
+// }
+ session.refresh(true);
+ session.save();
+ return true;
+ }
+
+ /**
+ * Gets the first available access control list for this path, throws exception
+ * if not found
+ */
+ public synchronized static AccessControlList getAccessControlList(AccessControlManager acm, String path)
+ throws RepositoryException {
+ // search for an access control list
+ AccessControlList acl = null;
+ AccessControlPolicyIterator policyIterator = acm.getApplicablePolicies(path);
+ applicablePolicies: if (policyIterator.hasNext()) {
+ while (policyIterator.hasNext()) {
+ AccessControlPolicy acp = policyIterator.nextAccessControlPolicy();
+ if (acp instanceof AccessControlList) {
+ acl = ((AccessControlList) acp);
+ break applicablePolicies;
+ }
+ }
+ } else {
+ AccessControlPolicy[] existingPolicies = acm.getPolicies(path);
+ existingPolicies: for (AccessControlPolicy acp : existingPolicies) {
+ if (acp instanceof AccessControlList) {
+ acl = ((AccessControlList) acp);
+ break existingPolicies;
+ }
+ }
+ }
+ if (acl != null)
+ return acl;
+ else
+ throw new IllegalArgumentException("ACL not found at " + path);
+ }
+
+ /** Clear authorizations for a user at this path */
+ public synchronized static void clearAccessControList(Session session, String path, String username)
+ throws RepositoryException {
+ AccessControlManager acm = session.getAccessControlManager();
+ AccessControlList acl = getAccessControlList(acm, path);
+ for (AccessControlEntry ace : acl.getAccessControlEntries()) {
+ if (ace.getPrincipal().getName().equals(username)) {
+ acl.removeAccessControlEntry(ace);
+ }
+ }
+ // the new access control list must be applied otherwise this call:
+ // acl.removeAccessControlEntry(ace); has no effect
+ acm.setPolicy(path, acl);
+ session.refresh(true);
+ session.save();
+ }
+
+ /*
+ * FILES UTILITIES
+ */
+ /**
+ * Creates the nodes making the path as {@link NodeType#NT_FOLDER}
+ */
+ public static Node mkfolders(Session session, String path) {
+ return mkdirs(session, path, NodeType.NT_FOLDER, NodeType.NT_FOLDER, false);
+ }
+
+ /**
+ * Copy only nt:folder and nt:file, without their additional types and
+ * properties.
+ *
+ * @param recursive if true copies folders as well, otherwise only first level
+ * files
+ * @return how many files were copied
+ */
+ public static Long copyFiles(Node fromNode, Node toNode, Boolean recursive, JcrMonitor monitor, boolean onlyAdd) {
+ long count = 0l;
+
+ // Binary binary = null;
+ // InputStream in = null;
+ try {
+ NodeIterator fromChildren = fromNode.getNodes();
+ children: while (fromChildren.hasNext()) {
+ if (monitor != null && monitor.isCanceled())
+ throw new IllegalStateException("Copy cancelled before it was completed");
+
+ Node fromChild = fromChildren.nextNode();
+ String fileName = fromChild.getName();
+ if (fromChild.isNodeType(NodeType.NT_FILE)) {
+ if (onlyAdd && toNode.hasNode(fileName)) {
+ monitor.subTask("Skip existing " + fileName);
+ continue children;
+ }
+
+ if (monitor != null)
+ monitor.subTask("Copy " + fileName);
+ try (Bin binary = new Bin(fromChild.getNode(Node.JCR_CONTENT).getProperty(Property.JCR_DATA));
+ InputStream in = binary.getStream();) {
+ copyStreamAsFile(toNode, fileName, in);
+ } catch (IOException e) {
+ throw new RuntimeException("Cannot copy " + fileName + " to " + toNode, e);
+ }
+
+ // save session
+ toNode.getSession().save();
+ count++;
+
+// if (log.isDebugEnabled())
+// log.debug("Copied file " + fromChild.getPath());
+ if (monitor != null)
+ monitor.worked(1);
+ } else if (fromChild.isNodeType(NodeType.NT_FOLDER) && recursive) {
+ Node toChildFolder;
+ if (toNode.hasNode(fileName)) {
+ toChildFolder = toNode.getNode(fileName);
+ if (!toChildFolder.isNodeType(NodeType.NT_FOLDER))
+ throw new IllegalArgumentException(toChildFolder + " is not of type nt:folder");
+ } else {
+ toChildFolder = toNode.addNode(fileName, NodeType.NT_FOLDER);
+
+ // save session
+ toNode.getSession().save();
+ }
+ count = count + copyFiles(fromChild, toChildFolder, recursive, monitor, onlyAdd);
+ }
+ }
+ return count;
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot copy files between " + fromNode + " and " + toNode, e);
+ } finally {
+ // in case there was an exception
+ // IOUtils.closeQuietly(in);
+ // closeQuietly(binary);
+ }
+ }
+
+ /**
+ * Iteratively count all file nodes in subtree, inefficient but can be useful
+ * when query are poorly supported, such as in remoting.
+ */
+ public static Long countFiles(Node node) {
+ Long localCount = 0l;
+ try {
+ for (NodeIterator nit = node.getNodes(); nit.hasNext();) {
+ Node child = nit.nextNode();
+ if (child.isNodeType(NodeType.NT_FOLDER))
+ localCount = localCount + countFiles(child);
+ else if (child.isNodeType(NodeType.NT_FILE))
+ localCount = localCount + 1;
+ }
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot count all children of " + node, e);
+ }
+ return localCount;
+ }
+
+ /**
+ * Copy a file as an nt:file, assuming an nt:folder hierarchy. The session is
+ * NOT saved.
+ *
+ * @return the created file node
+ */
+ @Deprecated
+ public static Node copyFile(Node folderNode, File file) {
+ try (InputStream in = new FileInputStream(file)) {
+ return copyStreamAsFile(folderNode, file.getName(), in);
+ } catch (IOException e) {
+ throw new RuntimeException("Cannot copy file " + file + " under " + folderNode, e);
+ }
+ }
+
+ /** Copy bytes as an nt:file */
+ public static Node copyBytesAsFile(Node folderNode, String fileName, byte[] bytes) {
+ // InputStream in = null;
+ try (InputStream in = new ByteArrayInputStream(bytes)) {
+ // in = new ByteArrayInputStream(bytes);
+ return copyStreamAsFile(folderNode, fileName, in);
+ } catch (IOException e) {
+ throw new RuntimeException("Cannot copy file " + fileName + " under " + folderNode, e);
+ // } finally {
+ // IOUtils.closeQuietly(in);
+ }
+ }
+
+ /**
+ * Copy a stream as an nt:file, assuming an nt:folder hierarchy. The session is
+ * NOT saved.
+ *
+ * @return the created file node
+ */
+ public static Node copyStreamAsFile(Node folderNode, String fileName, InputStream in) {
+ Binary binary = null;
+ try {
+ Node fileNode;
+ Node contentNode;
+ if (folderNode.hasNode(fileName)) {
+ fileNode = folderNode.getNode(fileName);
+ if (!fileNode.isNodeType(NodeType.NT_FILE))
+ throw new IllegalArgumentException(fileNode + " is not of type nt:file");
+ // we assume that the content node is already there
+ contentNode = fileNode.getNode(Node.JCR_CONTENT);
+ } else {
+ fileNode = folderNode.addNode(fileName, NodeType.NT_FILE);
+ contentNode = fileNode.addNode(Node.JCR_CONTENT, NodeType.NT_UNSTRUCTURED);
+ }
+ binary = contentNode.getSession().getValueFactory().createBinary(in);
+ contentNode.setProperty(Property.JCR_DATA, binary);
+ updateLastModified(contentNode);
+ return fileNode;
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot create file node " + fileName + " under " + folderNode, e);
+ } finally {
+ closeQuietly(binary);
+ }
+ }
+
+ /** Read an an nt:file as an {@link InputStream}. */
+ public static InputStream getFileAsStream(Node fileNode) throws RepositoryException {
+ return fileNode.getNode(Node.JCR_CONTENT).getProperty(Property.JCR_DATA).getBinary().getStream();
+ }
+
+ /**
+ * Set the properties of {@link NodeType#MIX_MIMETYPE} on the content of this
+ * file node.
+ */
+ public static void setFileMimeType(Node fileNode, String mimeType, String encoding) throws RepositoryException {
+ Node contentNode = fileNode.getNode(Node.JCR_CONTENT);
+ if (mimeType != null)
+ contentNode.setProperty(Property.JCR_MIMETYPE, mimeType);
+ if (encoding != null)
+ contentNode.setProperty(Property.JCR_ENCODING, encoding);
+ // TODO remove properties if args are null?
+ }
+
+ public static void copyFilesToFs(Node baseNode, Path targetDir, boolean recursive) {
+ try {
+ Files.createDirectories(targetDir);
+ for (NodeIterator nit = baseNode.getNodes(); nit.hasNext();) {
+ Node node = nit.nextNode();
+ if (node.isNodeType(NodeType.NT_FILE)) {
+ Path filePath = targetDir.resolve(node.getName());
+ try (OutputStream out = Files.newOutputStream(filePath); InputStream in = getFileAsStream(node)) {
+ IOUtils.copy(in, out);
+ }
+ } else if (recursive && node.isNodeType(NodeType.NT_FOLDER)) {
+ Path dirPath = targetDir.resolve(node.getName());
+ copyFilesToFs(node, dirPath, true);
+ }
+ }
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot copy " + baseNode + " to " + targetDir, e);
+ } catch (IOException e) {
+ throw new RuntimeException("Cannot copy " + baseNode + " to " + targetDir, e);
+ }
+ }
+
+ /**
+ * Computes the checksum of an nt:file.
+ *
+ * @deprecated use separate digest utilities
+ */
+ @Deprecated
+ public static String checksumFile(Node fileNode, String algorithm) {
+ try (InputStream in = fileNode.getNode(Node.JCR_CONTENT).getProperty(Property.JCR_DATA).getBinary()
+ .getStream()) {
+ return digest(algorithm, in);
+ } catch (IOException e) {
+ throw new RuntimeException("Cannot checksum file " + fileNode + " with algorithm " + algorithm, e);
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot checksum file " + fileNode + " with algorithm " + algorithm, e);
+ }
+ }
+
+ @Deprecated
+ private static String digest(String algorithm, InputStream in) {
+ final Integer byteBufferCapacity = 100 * 1024;// 100 KB
+ try {
+ MessageDigest digest = MessageDigest.getInstance(algorithm);
+ byte[] buffer = new byte[byteBufferCapacity];
+ int read = 0;
+ while ((read = in.read(buffer)) > 0) {
+ digest.update(buffer, 0, read);
+ }
+
+ byte[] checksum = digest.digest();
+ String res = encodeHexString(checksum);
+ return res;
+ } catch (IOException e) {
+ throw new RuntimeException("Cannot digest with algorithm " + algorithm, e);
+ } catch (NoSuchAlgorithmException e) {
+ throw new IllegalArgumentException("Cannot digest with algorithm " + algorithm, e);
+ }
+ }
+
+ /**
+ * From
+ * http://stackoverflow.com/questions/9655181/how-to-convert-a-byte-array-to
+ * -a-hex-string-in-java
+ */
+ @Deprecated
+ private static String encodeHexString(byte[] bytes) {
+ final char[] hexArray = "0123456789abcdef".toCharArray();
+ char[] hexChars = new char[bytes.length * 2];
+ for (int j = 0; j < bytes.length; j++) {
+ int v = bytes[j] & 0xFF;
+ hexChars[j * 2] = hexArray[v >>> 4];
+ hexChars[j * 2 + 1] = hexArray[v & 0x0F];
+ }
+ return new String(hexChars);
+ }
+
+ /** Export a subtree as a compact XML without namespaces. */
+ public static void toSimpleXml(Node node, StringBuilder sb) throws RepositoryException {
+ sb.append('<');
+ String nodeName = node.getName();
+ int colIndex = nodeName.indexOf(':');
+ if (colIndex > 0) {
+ nodeName = nodeName.substring(colIndex + 1);
+ }
+ sb.append(nodeName);
+ PropertyIterator pit = node.getProperties();
+ properties: while (pit.hasNext()) {
+ Property p = pit.nextProperty();
+ // skip multiple properties
+ if (p.isMultiple())
+ continue properties;
+ String propertyName = p.getName();
+ int pcolIndex = propertyName.indexOf(':');
+ // skip properties with namespaces
+ if (pcolIndex > 0)
+ continue properties;
+ // skip binaries
+ if (p.getType() == PropertyType.BINARY) {
+ continue properties;
+ // TODO retrieve identifier?
+ }
+ sb.append(' ');
+ sb.append(propertyName);
+ sb.append('=');
+ sb.append('\"').append(p.getString()).append('\"');
+ }
+
+ if (node.hasNodes()) {
+ sb.append('>');
+ NodeIterator children = node.getNodes();
+ while (children.hasNext()) {
+ toSimpleXml(children.nextNode(), sb);
+ }
+ sb.append("</");
+ sb.append(nodeName);
+ sb.append('>');
+ } else {
+ sb.append("/>");
+ }
+ }
+
+}
--- /dev/null
+package org.argeo.jcr;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.RepositoryException;
+import javax.jcr.Value;
+
+/** Uilities around the JCR extensions. */
+public class JcrxApi {
+ public final static String MD5 = "MD5";
+ public final static String SHA1 = "SHA1";
+ public final static String SHA256 = "SHA-256";
+ public final static String SHA512 = "SHA-512";
+
+ public final static String EMPTY_MD5 = "d41d8cd98f00b204e9800998ecf8427e";
+ public final static String EMPTY_SHA1 = "da39a3ee5e6b4b0d3255bfef95601890afd80709";
+ public final static String EMPTY_SHA256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
+ public final static String EMPTY_SHA512 = "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e";
+
+ public final static int LENGTH_MD5 = EMPTY_MD5.length();
+ public final static int LENGTH_SHA1 = EMPTY_SHA1.length();
+ public final static int LENGTH_SHA256 = EMPTY_SHA256.length();
+ public final static int LENGTH_SHA512 = EMPTY_SHA512.length();
+
+ /*
+ * XML
+ */
+ /**
+ * Get the XML text of this child node.
+ */
+ public static String getXmlValue(Node node, String name) {
+ try {
+ if (!node.hasNode(name))
+ return null;
+ Node child = node.getNode(name);
+ return getXmlValue(child);
+ } catch (RepositoryException e) {
+ throw new IllegalStateException("Cannot get " + name + " as XML text", e);
+ }
+ }
+
+ /**
+ * Get the XML text of this node.
+ */
+ public static String getXmlValue(Node node) {
+ try {
+ if (!node.hasNode(Jcr.JCR_XMLTEXT))
+ return null;
+ Node xmlText = node.getNode(Jcr.JCR_XMLTEXT);
+ if (!xmlText.hasProperty(Jcr.JCR_XMLCHARACTERS))
+ throw new IllegalArgumentException(
+ "Node " + xmlText + " has no " + Jcr.JCR_XMLCHARACTERS + " property");
+ return xmlText.getProperty(Jcr.JCR_XMLCHARACTERS).getString();
+ } catch (RepositoryException e) {
+ throw new IllegalStateException("Cannot get " + node + " as XML text", e);
+ }
+ }
+
+ /**
+ * Set as a subnode which will be exported as an XML element.
+ */
+ public static void setXmlValue(Node node, String name, String value) {
+ try {
+ if (node.hasNode(name)) {
+ Node child = node.getNode(name);
+ setXmlValue(node, child, value);
+ } else
+ node.addNode(name, JcrxType.JCRX_XMLVALUE).addNode(Jcr.JCR_XMLTEXT, JcrxType.JCRX_XMLTEXT)
+ .setProperty(Jcr.JCR_XMLCHARACTERS, value);
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot set " + name + " as XML text", e);
+ }
+ }
+
+ public static void setXmlValue(Node node, Node child, String value) {
+ try {
+ if (!child.hasNode(Jcr.JCR_XMLTEXT))
+ child.addNode(Jcr.JCR_XMLTEXT, JcrxType.JCRX_XMLTEXT);
+ child.getNode(Jcr.JCR_XMLTEXT).setProperty(Jcr.JCR_XMLCHARACTERS, value);
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot set " + child + " as XML text", e);
+ }
+ }
+
+ /**
+ * Add a checksum replacing the one which was previously set with the same
+ * length.
+ */
+ public static void addChecksum(Node node, String checksum) {
+ try {
+ if (!node.hasProperty(JcrxName.JCRX_SUM)) {
+ node.setProperty(JcrxName.JCRX_SUM, new String[] { checksum });
+ return;
+ } else {
+ int stringLength = checksum.length();
+ Property property = node.getProperty(JcrxName.JCRX_SUM);
+ List<Value> values = Arrays.asList(property.getValues());
+ Integer indexToRemove = null;
+ values: for (int i = 0; i < values.size(); i++) {
+ Value value = values.get(i);
+ if (value.getString().length() == stringLength) {
+ indexToRemove = i;
+ break values;
+ }
+ }
+ if (indexToRemove != null)
+ values.set(indexToRemove, node.getSession().getValueFactory().createValue(checksum));
+ else
+ values.add(0, node.getSession().getValueFactory().createValue(checksum));
+ property.setValue(values.toArray(new Value[values.size()]));
+ }
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot set checksum on " + node, e);
+ }
+ }
+
+ /** Replace all checksums. */
+ public static void setChecksums(Node node, List<String> checksums) {
+ try {
+ node.setProperty(JcrxName.JCRX_SUM, checksums.toArray(new String[checksums.size()]));
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot set checksums on " + node, e);
+ }
+ }
+
+ /** Replace all checksums. */
+ public static List<String> getChecksums(Node node) {
+ try {
+ List<String> res = new ArrayList<>();
+ if (!node.hasProperty(JcrxName.JCRX_SUM))
+ return res;
+ Property property = node.getProperty(JcrxName.JCRX_SUM);
+ for (Value value : property.getValues()) {
+ res.add(value.getString());
+ }
+ return res;
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot get checksums from " + node, e);
+ }
+ }
+
+// /** Replace all checksums with this single one. */
+// public static void setChecksum(Node node, String checksum) {
+// setChecksums(node, Collections.singletonList(checksum));
+// }
+
+ /** Retrieves the checksum with this algorithm, or null if not found. */
+ public static String getChecksum(Node node, String algorithm) {
+ int stringLength;
+ switch (algorithm) {
+ case MD5:
+ stringLength = LENGTH_MD5;
+ break;
+ case SHA1:
+ stringLength = LENGTH_SHA1;
+ break;
+ case SHA256:
+ stringLength = LENGTH_SHA256;
+ break;
+ case SHA512:
+ stringLength = LENGTH_SHA512;
+ break;
+ default:
+ throw new IllegalArgumentException("Unkown algorithm " + algorithm);
+ }
+ return getChecksum(node, stringLength);
+ }
+
+ /** Retrieves the checksum with this string length, or null if not found. */
+ public static String getChecksum(Node node, int stringLength) {
+ try {
+ if (!node.hasProperty(JcrxName.JCRX_SUM))
+ return null;
+ Property property = node.getProperty(JcrxName.JCRX_SUM);
+ for (Value value : property.getValues()) {
+ String str = value.getString();
+ if (str.length() == stringLength)
+ return str;
+ }
+ return null;
+ } catch (RepositoryException e) {
+ throw new IllegalStateException("Cannot get checksum for " + node, e);
+ }
+ }
+
+}
--- /dev/null
+package org.argeo.jcr;
+
+/** Names declared by the JCR extensions. */
+public interface JcrxName {
+ /** The multiple property holding various coherent checksums. */
+ public final static String JCRX_SUM = "{http://www.argeo.org/ns/jcrx}sum";
+}
--- /dev/null
+package org.argeo.jcr;
+
+/** Node types declared by the JCR extensions. */
+public interface JcrxType {
+ /**
+ * Node type for an XML value, which will be serialized in XML as an element
+ * containing text.
+ */
+ public final static String JCRX_XMLVALUE = "{http://www.argeo.org/ns/jcrx}xmlvalue";
+
+ /** Node type for the node containing the text. */
+ public final static String JCRX_XMLTEXT = "{http://www.argeo.org/ns/jcrx}xmltext";
+
+ /** Mixin node type for a set of checksums. */
+ public final static String JCRX_CSUM = "{http://www.argeo.org/ns/jcrx}csum";
+
+}
--- /dev/null
+package org.argeo.jcr;
+
+import javax.jcr.Value;
+
+/** The result of the comparison of two JCR properties. */
+public class PropertyDiff {
+ public final static Integer MODIFIED = 0;
+ public final static Integer ADDED = 1;
+ public final static Integer REMOVED = 2;
+
+ private final Integer type;
+ private final String relPath;
+ private final Value referenceValue;
+ private final Value newValue;
+
+ public PropertyDiff(Integer type, String relPath, Value referenceValue, Value newValue) {
+ super();
+
+ if (type == MODIFIED) {
+ if (referenceValue == null || newValue == null)
+ throw new IllegalArgumentException("Reference and new values must be specified.");
+ } else if (type == ADDED) {
+ if (referenceValue != null || newValue == null)
+ throw new IllegalArgumentException("New value and only it must be specified.");
+ } else if (type == REMOVED) {
+ if (referenceValue == null || newValue != null)
+ throw new IllegalArgumentException("Reference value and only it must be specified.");
+ } else {
+ throw new IllegalArgumentException("Unkown diff type " + type);
+ }
+
+ if (relPath == null)
+ throw new IllegalArgumentException("Relative path must be specified");
+
+ this.type = type;
+ this.relPath = relPath;
+ this.referenceValue = referenceValue;
+ this.newValue = newValue;
+ }
+
+ public Integer getType() {
+ return type;
+ }
+
+ public String getRelPath() {
+ return relPath;
+ }
+
+ public Value getReferenceValue() {
+ return referenceValue;
+ }
+
+ public Value getNewValue() {
+ return newValue;
+ }
+
+}
--- /dev/null
+package org.argeo.jcr;
+
+import java.security.Principal;
+
+/** Canonical implementation of a {@link Principal} */
+class SimplePrincipal implements Principal {
+ private final String name;
+
+ public SimplePrincipal(String name) {
+ if (name == null)
+ throw new IllegalArgumentException("Principal name cannot be null");
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public int hashCode() {
+ return name.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null)
+ return false;
+ if (obj instanceof Principal)
+ return name.equals((((Principal) obj).getName()));
+ return name.equals(obj.toString());
+ }
+
+ @Override
+ protected Object clone() throws CloneNotSupportedException {
+ return new SimplePrincipal(name);
+ }
+
+ @Override
+ public String toString() {
+ return name;
+ }
+
+}
--- /dev/null
+package org.argeo.jcr;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import javax.jcr.LoginException;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.SimpleCredentials;
+
+import org.argeo.api.cms.CmsLog;
+
+/** Proxy JCR sessions and attach them to calling threads. */
+@Deprecated
+public abstract class ThreadBoundJcrSessionFactory {
+ private final static CmsLog log = CmsLog.getLog(ThreadBoundJcrSessionFactory.class);
+
+ private Repository repository;
+ /** can be injected as list, only used if repository is null */
+ private List<Repository> repositories;
+
+ private ThreadLocal<Session> session = new ThreadLocal<Session>();
+ private final Session proxiedSession;
+ /** If workspace is null, default will be used. */
+ private String workspace = null;
+
+ private String defaultUsername = "demo";
+ private String defaultPassword = "demo";
+ private Boolean forceDefaultCredentials = false;
+
+ private boolean active = true;
+
+ // monitoring
+ private final List<Thread> threads = Collections.synchronizedList(new ArrayList<Thread>());
+ private final Map<Long, Session> activeSessions = Collections.synchronizedMap(new HashMap<Long, Session>());
+ private MonitoringThread monitoringThread;
+
+ public ThreadBoundJcrSessionFactory() {
+ Class<?>[] interfaces = { Session.class };
+ proxiedSession = (Session) Proxy.newProxyInstance(ThreadBoundJcrSessionFactory.class.getClassLoader(),
+ interfaces, new JcrSessionInvocationHandler());
+ }
+
+ /** Logs in to the repository using various strategies. */
+ protected synchronized Session login() {
+ if (!isActive())
+ throw new IllegalStateException("Thread bound session factory inactive");
+
+ // discard session previously attached to this thread
+ Thread thread = Thread.currentThread();
+ if (activeSessions.containsKey(thread.getId())) {
+ Session oldSession = activeSessions.remove(thread.getId());
+ oldSession.logout();
+ session.remove();
+ }
+
+ Session newSession = null;
+ // first try to login without credentials, assuming the underlying login
+ // module will have dealt with authentication (typically using Spring
+ // Security)
+ if (!forceDefaultCredentials)
+ try {
+ newSession = repository().login(workspace);
+ } catch (LoginException e1) {
+ log.warn("Cannot login without credentials: " + e1.getMessage());
+ // invalid credentials, go to the next step
+ } catch (RepositoryException e1) {
+ // other kind of exception, fail
+ throw new JcrException("Cannot log in to repository", e1);
+ }
+
+ // log using default username / password (useful for testing purposes)
+ if (newSession == null)
+ try {
+ SimpleCredentials sc = new SimpleCredentials(defaultUsername, defaultPassword.toCharArray());
+ newSession = repository().login(sc, workspace);
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot log in to repository", e);
+ }
+
+ session.set(newSession);
+ // Log and monitor new session
+ if (log.isTraceEnabled())
+ log.trace("Logged in to JCR session " + newSession + "; userId=" + newSession.getUserID());
+
+ // monitoring
+ activeSessions.put(thread.getId(), newSession);
+ threads.add(thread);
+ return newSession;
+ }
+
+ public Object getObject() {
+ return proxiedSession;
+ }
+
+ public void init() throws Exception {
+ // log.error("SHOULD NOT BE USED ANYMORE");
+ monitoringThread = new MonitoringThread();
+ monitoringThread.start();
+ }
+
+ public void dispose() throws Exception {
+ // if (activeSessions.size() == 0)
+ // return;
+
+ if (log.isTraceEnabled())
+ log.trace("Cleaning up " + activeSessions.size() + " active JCR sessions...");
+
+ deactivate();
+ for (Session sess : activeSessions.values()) {
+ JcrUtils.logoutQuietly(sess);
+ }
+ activeSessions.clear();
+ }
+
+ protected Boolean isActive() {
+ return active;
+ }
+
+ protected synchronized void deactivate() {
+ active = false;
+ notifyAll();
+ }
+
+ protected synchronized void removeSession(Thread thread) {
+ if (!isActive())
+ return;
+ activeSessions.remove(thread.getId());
+ threads.remove(thread);
+ }
+
+ protected synchronized void cleanDeadThreads() {
+ if (!isActive())
+ return;
+ Iterator<Thread> it = threads.iterator();
+ while (it.hasNext()) {
+ Thread thread = it.next();
+ if (!thread.isAlive() && isActive()) {
+ if (activeSessions.containsKey(thread.getId())) {
+ Session session = activeSessions.get(thread.getId());
+ activeSessions.remove(thread.getId());
+ session.logout();
+ if (log.isTraceEnabled())
+ log.trace("Cleaned up JCR session (userID=" + session.getUserID() + ") from dead thread "
+ + thread.getId());
+ }
+ it.remove();
+ }
+ }
+ try {
+ wait(1000);
+ } catch (InterruptedException e) {
+ // silent
+ }
+ }
+
+ public Class<? extends Session> getObjectType() {
+ return Session.class;
+ }
+
+ public boolean isSingleton() {
+ return true;
+ }
+
+ /**
+ * Called before a method is actually called, allowing to check the session or
+ * re-login it (e.g. if authentication has changed). The default implementation
+ * returns the session.
+ */
+ protected Session preCall(Session session) {
+ return session;
+ }
+
+ protected Repository repository() {
+ if (repository != null)
+ return repository;
+ if (repositories != null) {
+ // hardened for OSGi dynamic services
+ Iterator<Repository> it = repositories.iterator();
+ if (it.hasNext())
+ return it.next();
+ }
+ throw new IllegalStateException("No repository injected");
+ }
+
+ // /** Useful for declarative registration of OSGi services (blueprint) */
+ // public void register(Repository repository, Map<?, ?> params) {
+ // this.repository = repository;
+ // }
+ //
+ // /** Useful for declarative registration of OSGi services (blueprint) */
+ // public void unregister(Repository repository, Map<?, ?> params) {
+ // this.repository = null;
+ // }
+
+ public void setRepository(Repository repository) {
+ this.repository = repository;
+ }
+
+ public void setRepositories(List<Repository> repositories) {
+ this.repositories = repositories;
+ }
+
+ public void setDefaultUsername(String defaultUsername) {
+ this.defaultUsername = defaultUsername;
+ }
+
+ public void setDefaultPassword(String defaultPassword) {
+ this.defaultPassword = defaultPassword;
+ }
+
+ public void setForceDefaultCredentials(Boolean forceDefaultCredentials) {
+ this.forceDefaultCredentials = forceDefaultCredentials;
+ }
+
+ public void setWorkspace(String workspace) {
+ this.workspace = workspace;
+ }
+
+ protected class JcrSessionInvocationHandler implements InvocationHandler {
+
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable, RepositoryException {
+ Session threadSession = session.get();
+ if (threadSession == null) {
+ if ("logout".equals(method.getName()))// no need to login
+ return Void.TYPE;
+ else if ("toString".equals(method.getName()))// maybe logging
+ return "Uninitialized Argeo thread bound JCR session";
+ threadSession = login();
+ }
+
+ preCall(threadSession);
+ Object ret;
+ try {
+ ret = method.invoke(threadSession, args);
+ } catch (InvocationTargetException e) {
+ Throwable cause = e.getCause();
+ if (cause instanceof RepositoryException)
+ throw (RepositoryException) cause;
+ else
+ throw cause;
+ }
+ if ("logout".equals(method.getName())) {
+ session.remove();
+ Thread thread = Thread.currentThread();
+ removeSession(thread);
+ if (log.isTraceEnabled())
+ log.trace("Logged out JCR session (userId=" + threadSession.getUserID() + ") on thread "
+ + thread.getId());
+ }
+ return ret;
+ }
+ }
+
+ /** Monitors registered thread in order to clean up dead ones. */
+ private class MonitoringThread extends Thread {
+
+ public MonitoringThread() {
+ super("ThreadBound JCR Session Monitor");
+ }
+
+ @Override
+ public void run() {
+ while (isActive()) {
+ cleanDeadThreads();
+ }
+ }
+
+ }
+}
--- /dev/null
+package org.argeo.jcr;
+
+import java.util.Calendar;
+import java.util.Map;
+
+/**
+ * Generic Object that enables the creation of history reports based on a JCR
+ * versionable node. userId and creation date are added to the map of
+ * PropertyDiff.
+ *
+ * These two fields might be null
+ *
+ */
+public class VersionDiff {
+
+ private String userId;
+ private Map<String, PropertyDiff> diffs;
+ private Calendar updateTime;
+
+ public VersionDiff(String userId, Calendar updateTime,
+ Map<String, PropertyDiff> diffs) {
+ this.userId = userId;
+ this.updateTime = updateTime;
+ this.diffs = diffs;
+ }
+
+ public String getUserId() {
+ return userId;
+ }
+
+ public Map<String, PropertyDiff> getDiffs() {
+ return diffs;
+ }
+
+ public Calendar getUpdateTime() {
+ return updateTime;
+ }
+}
--- /dev/null
+package org.argeo.jcr.fs;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.channels.Channels;
+import java.nio.channels.FileChannel;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.channels.SeekableByteChannel;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+
+import javax.jcr.Binary;
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.nodetype.NodeType;
+
+import org.argeo.jcr.JcrUtils;
+
+/** A read/write {@link SeekableByteChannel} based on a {@link Binary}. */
+public class BinaryChannel implements SeekableByteChannel {
+ private final Node file;
+ private Binary binary;
+ private boolean open = true;
+
+ private long position = 0;
+
+ private FileChannel fc = null;
+
+ public BinaryChannel(Node file, Path path) throws RepositoryException, IOException {
+ this.file = file;
+ Session session = file.getSession();
+ synchronized (session) {
+ if (file.isNodeType(NodeType.NT_FILE)) {
+ if (file.hasNode(Node.JCR_CONTENT)) {
+ Node data = file.getNode(Property.JCR_CONTENT);
+ this.binary = data.getProperty(Property.JCR_DATA).getBinary();
+ } else {
+ Node data = file.addNode(Node.JCR_CONTENT, NodeType.NT_UNSTRUCTURED);
+ data.addMixin(NodeType.MIX_LAST_MODIFIED);
+ try (InputStream in = new ByteArrayInputStream(new byte[0])) {
+ this.binary = data.getSession().getValueFactory().createBinary(in);
+ }
+ data.setProperty(Property.JCR_DATA, this.binary);
+
+ // MIME type
+ String mime = Files.probeContentType(path);
+ // String mime = fileTypeMap.getContentType(file.getName());
+ data.setProperty(Property.JCR_MIMETYPE, mime);
+
+ session.refresh(true);
+ session.save();
+ session.notifyAll();
+ }
+ } else {
+ throw new IllegalArgumentException(
+ "Unsupported file node " + file + " (" + file.getPrimaryNodeType() + ")");
+ }
+ }
+ }
+
+ @Override
+ public synchronized boolean isOpen() {
+ return open;
+ }
+
+ @Override
+ public synchronized void close() throws IOException {
+ if (isModified()) {
+ Binary newBinary = null;
+ try {
+ Session session = file.getSession();
+ synchronized (session) {
+ fc.position(0);
+ InputStream in = Channels.newInputStream(fc);
+ newBinary = session.getValueFactory().createBinary(in);
+ file.getNode(Property.JCR_CONTENT).setProperty(Property.JCR_DATA, newBinary);
+ session.refresh(true);
+ session.save();
+ open = false;
+ session.notifyAll();
+ }
+ } catch (RepositoryException e) {
+ throw new IOException("Cannot close " + file, e);
+ } finally {
+ JcrUtils.closeQuietly(newBinary);
+ // IOUtils.closeQuietly(fc);
+ if (fc != null) {
+ fc.close();
+ }
+ }
+ } else {
+ clearReadState();
+ open = false;
+ }
+ }
+
+ @Override
+ public int read(ByteBuffer dst) throws IOException {
+ if (isModified()) {
+ return fc.read(dst);
+ } else {
+
+ try {
+ int read;
+ byte[] arr = dst.array();
+ read = binary.read(arr, position);
+
+ if (read != -1)
+ position = position + read;
+ return read;
+ } catch (RepositoryException e) {
+ throw new IOException("Cannot read into buffer", e);
+ }
+ }
+ }
+
+ @Override
+ public int write(ByteBuffer src) throws IOException {
+ int written = getFileChannel().write(src);
+ return written;
+ }
+
+ @Override
+ public long position() throws IOException {
+ if (isModified())
+ return getFileChannel().position();
+ else
+ return position;
+ }
+
+ @Override
+ public SeekableByteChannel position(long newPosition) throws IOException {
+ if (isModified()) {
+ getFileChannel().position(position);
+ } else {
+ this.position = newPosition;
+ }
+ return this;
+ }
+
+ @Override
+ public long size() throws IOException {
+ if (isModified()) {
+ return getFileChannel().size();
+ } else {
+ try {
+ return binary.getSize();
+ } catch (RepositoryException e) {
+ throw new IOException("Cannot get size", e);
+ }
+ }
+ }
+
+ @Override
+ public SeekableByteChannel truncate(long size) throws IOException {
+ getFileChannel().truncate(size);
+ return this;
+ }
+
+ private FileChannel getFileChannel() throws IOException {
+ try {
+ if (fc == null) {
+ Path tempPath = Files.createTempFile(getClass().getSimpleName(), null);
+ fc = FileChannel.open(tempPath, StandardOpenOption.WRITE, StandardOpenOption.READ,
+ StandardOpenOption.DELETE_ON_CLOSE, StandardOpenOption.SPARSE);
+ ReadableByteChannel readChannel = Channels.newChannel(binary.getStream());
+ fc.transferFrom(readChannel, 0, binary.getSize());
+ clearReadState();
+ }
+ return fc;
+ } catch (RepositoryException e) {
+ throw new IOException("Cannot get temp file channel", e);
+ }
+ }
+
+ private boolean isModified() {
+ return fc != null;
+ }
+
+ private void clearReadState() {
+ position = -1;
+ JcrUtils.closeQuietly(binary);
+ binary = null;
+ }
+}
--- /dev/null
+package org.argeo.jcr.fs;
+
+import static javax.jcr.Property.JCR_CREATED;
+import static javax.jcr.Property.JCR_LAST_MODIFIED;
+
+import java.nio.file.attribute.FileTime;
+import java.time.Instant;
+
+import javax.jcr.Binary;
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.RepositoryException;
+import javax.jcr.nodetype.NodeType;
+
+import org.argeo.jcr.JcrUtils;
+
+public class JcrBasicfileAttributes implements NodeFileAttributes {
+ private final Node node;
+
+ private final static FileTime EPOCH = FileTime.fromMillis(0);
+
+ public JcrBasicfileAttributes(Node node) {
+ if (node == null)
+ throw new JcrFsException("Node underlying the attributes cannot be null");
+ this.node = node;
+ }
+
+ @Override
+ public FileTime lastModifiedTime() {
+ try {
+ if (node.hasProperty(JCR_LAST_MODIFIED)) {
+ Instant instant = node.getProperty(JCR_LAST_MODIFIED).getDate().toInstant();
+ return FileTime.from(instant);
+ } else if (node.hasProperty(JCR_CREATED)) {
+ Instant instant = node.getProperty(JCR_CREATED).getDate().toInstant();
+ return FileTime.from(instant);
+ }
+// if (node.isNodeType(NodeType.MIX_LAST_MODIFIED)) {
+// Instant instant = node.getProperty(Property.JCR_LAST_MODIFIED).getDate().toInstant();
+// return FileTime.from(instant);
+// }
+ return EPOCH;
+ } catch (RepositoryException e) {
+ throw new JcrFsException("Cannot get last modified time", e);
+ }
+ }
+
+ @Override
+ public FileTime lastAccessTime() {
+ return lastModifiedTime();
+ }
+
+ @Override
+ public FileTime creationTime() {
+ try {
+ if (node.hasProperty(JCR_CREATED)) {
+ Instant instant = node.getProperty(JCR_CREATED).getDate().toInstant();
+ return FileTime.from(instant);
+ } else if (node.hasProperty(JCR_LAST_MODIFIED)) {
+ Instant instant = node.getProperty(JCR_LAST_MODIFIED).getDate().toInstant();
+ return FileTime.from(instant);
+ }
+// if (node.isNodeType(NodeType.MIX_CREATED)) {
+// Instant instant = node.getProperty(JCR_CREATED).getDate().toInstant();
+// return FileTime.from(instant);
+// }
+ return EPOCH;
+ } catch (RepositoryException e) {
+ throw new JcrFsException("Cannot get creation time", e);
+ }
+ }
+
+ @Override
+ public boolean isRegularFile() {
+ try {
+ return node.isNodeType(NodeType.NT_FILE);
+ } catch (RepositoryException e) {
+ throw new JcrFsException("Cannot check if regular file", e);
+ }
+ }
+
+ @Override
+ public boolean isDirectory() {
+ try {
+ if (node.isNodeType(NodeType.NT_FOLDER))
+ return true;
+ // all other non file nodes
+ return !(node.isNodeType(NodeType.NT_FILE) || node.isNodeType(NodeType.NT_LINKED_FILE));
+ } catch (RepositoryException e) {
+ throw new JcrFsException("Cannot check if directory", e);
+ }
+ }
+
+ @Override
+ public boolean isSymbolicLink() {
+ try {
+ return node.isNodeType(NodeType.NT_LINKED_FILE);
+ } catch (RepositoryException e) {
+ throw new JcrFsException("Cannot check if linked file", e);
+ }
+ }
+
+ @Override
+ public boolean isOther() {
+ return !(isDirectory() || isRegularFile() || isSymbolicLink());
+ }
+
+ @Override
+ public long size() {
+ if (isRegularFile()) {
+ Binary binary = null;
+ try {
+ binary = node.getNode(Property.JCR_CONTENT).getProperty(Property.JCR_DATA).getBinary();
+ return binary.getSize();
+ } catch (RepositoryException e) {
+ throw new JcrFsException("Cannot check size", e);
+ } finally {
+ JcrUtils.closeQuietly(binary);
+ }
+ }
+ return -1;
+ }
+
+ @Override
+ public Object fileKey() {
+ try {
+ return node.getIdentifier();
+ } catch (RepositoryException e) {
+ throw new JcrFsException("Cannot get identifier", e);
+ }
+ }
+
+ @Override
+ public Node getNode() {
+ return node;
+ }
+
+}
--- /dev/null
+package org.argeo.jcr.fs;
+
+import java.io.IOException;
+import java.nio.file.FileStore;
+import java.nio.file.FileSystem;
+import java.nio.file.Path;
+import java.nio.file.PathMatcher;
+import java.nio.file.WatchService;
+import java.nio.file.attribute.UserPrincipalLookupService;
+import java.nio.file.spi.FileSystemProvider;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+
+import javax.jcr.Credentials;
+import javax.jcr.Node;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.nodetype.NodeType;
+
+import org.argeo.api.acr.fs.AbstractFsStore;
+import org.argeo.api.acr.fs.AbstractFsSystem;
+import org.argeo.jcr.Jcr;
+import org.argeo.jcr.JcrUtils;
+
+public class JcrFileSystem extends AbstractFsSystem<WorkspaceFileStore> {
+ private final JcrFileSystemProvider provider;
+
+ private final Repository repository;
+ private Session session;
+ private WorkspaceFileStore baseFileStore;
+
+ private Map<String, WorkspaceFileStore> mounts = new TreeMap<>();
+
+ private String userHomePath = null;
+
+ @Deprecated
+ public JcrFileSystem(JcrFileSystemProvider provider, Session session) throws IOException {
+ super();
+ this.provider = provider;
+ baseFileStore = new WorkspaceFileStore(null, session.getWorkspace());
+ this.session = session;
+// Node userHome = provider.getUserHome(session);
+// if (userHome != null)
+// try {
+// userHomePath = userHome.getPath();
+// } catch (RepositoryException e) {
+// throw new IOException("Cannot retrieve user home path", e);
+// }
+ this.repository = null;
+ }
+
+ public JcrFileSystem(JcrFileSystemProvider provider, Repository repository) throws IOException {
+ this(provider, repository, null);
+ }
+
+ public JcrFileSystem(JcrFileSystemProvider provider, Repository repository, Credentials credentials)
+ throws IOException {
+ super();
+ this.provider = provider;
+ this.repository = repository;
+ try {
+ this.session = credentials == null ? repository.login() : repository.login(credentials);
+ baseFileStore = new WorkspaceFileStore(null, session.getWorkspace());
+ workspaces: for (String workspaceName : baseFileStore.getWorkspace().getAccessibleWorkspaceNames()) {
+ if (workspaceName.equals(baseFileStore.getWorkspace().getName()))
+ continue workspaces;// do not mount base
+ if (workspaceName.equals("security")) {
+ continue workspaces;// do not mount security workspace
+ // TODO make it configurable
+ }
+ Session mountSession = credentials == null ? repository.login(workspaceName)
+ : repository.login(credentials, workspaceName);
+ String mountPath = JcrPath.separator + workspaceName;
+ mounts.put(mountPath, new WorkspaceFileStore(mountPath, mountSession.getWorkspace()));
+ }
+ } catch (RepositoryException e) {
+ throw new IOException("Cannot initialise file system", e);
+ }
+
+ Node userHome = provider.getUserHome(repository);
+ if (userHome != null)
+ try {
+ userHomePath = toFsPath(userHome);
+ } catch (RepositoryException e) {
+ throw new IOException("Cannot retrieve user home path", e);
+ } finally {
+ JcrUtils.logoutQuietly(Jcr.session(userHome));
+ }
+ }
+
+ public String toFsPath(Node node) throws RepositoryException {
+ return getFileStore(node).toFsPath(node);
+ }
+
+ /** Whether this node should be skipped in directory listings */
+ public boolean skipNode(Node node) throws RepositoryException {
+ if (node.isNodeType(NodeType.NT_HIERARCHY_NODE))
+ return false;
+ return true;
+ }
+
+ public String getUserHomePath() {
+ return userHomePath;
+ }
+
+ public WorkspaceFileStore getFileStore(String path) {
+ WorkspaceFileStore res = baseFileStore;
+ for (String mountPath : mounts.keySet()) {
+ if (path.equals(mountPath))
+ return mounts.get(mountPath);
+ if (path.startsWith(mountPath + JcrPath.separator)) {
+ res = mounts.get(mountPath);
+ // we keep the last one
+ }
+ }
+ assert res != null;
+ return res;
+ }
+
+ public WorkspaceFileStore getFileStore(Node node) throws RepositoryException {
+ String workspaceName = node.getSession().getWorkspace().getName();
+ if (workspaceName.equals(baseFileStore.getWorkspace().getName()))
+ return baseFileStore;
+ for (String mountPath : mounts.keySet()) {
+ WorkspaceFileStore fileStore = mounts.get(mountPath);
+ if (workspaceName.equals(fileStore.getWorkspace().getName()))
+ return fileStore;
+ }
+ throw new IllegalStateException("No workspace mount found for " + node + " in workspace " + workspaceName);
+ }
+
+ public Iterator<JcrPath> listDirectMounts(Path base) {
+ String baseStr = base.toString();
+ Set<JcrPath> res = new HashSet<>();
+ mounts: for (String mountPath : mounts.keySet()) {
+ if (mountPath.equals(baseStr))
+ continue mounts;
+ if (mountPath.startsWith(baseStr)) {
+ JcrPath path = new JcrPath(this, mountPath);
+ Path relPath = base.relativize(path);
+ if (relPath.getNameCount() == 1)
+ res.add(path);
+ }
+ }
+ return res.iterator();
+ }
+
+ public WorkspaceFileStore getBaseFileStore() {
+ return baseFileStore;
+ }
+
+ @Override
+ public FileSystemProvider provider() {
+ return provider;
+ }
+
+ @Override
+ public void close() throws IOException {
+ JcrUtils.logoutQuietly(session);
+ for (String mountPath : mounts.keySet()) {
+ WorkspaceFileStore fileStore = mounts.get(mountPath);
+ try {
+ fileStore.close();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ @Override
+ public boolean isOpen() {
+ return session.isLive();
+ }
+
+ @Override
+ public boolean isReadOnly() {
+ return false;
+ }
+
+ @Override
+ public String getSeparator() {
+ return JcrPath.separator;
+ }
+
+ @Override
+ public Iterable<Path> getRootDirectories() {
+ Set<Path> single = new HashSet<>();
+ single.add(new JcrPath(this, JcrPath.separator));
+ return single;
+ }
+
+ @Override
+ public Iterable<FileStore> getFileStores() {
+ List<FileStore> stores = new ArrayList<>();
+ stores.add(baseFileStore);
+ stores.addAll(mounts.values());
+ return stores;
+ }
+
+ @Override
+ public Set<String> supportedFileAttributeViews() {
+ try {
+ String[] prefixes = session.getNamespacePrefixes();
+ Set<String> res = new HashSet<>();
+ for (String prefix : prefixes)
+ res.add(prefix);
+ res.add("basic");
+ return res;
+ } catch (RepositoryException e) {
+ throw new JcrFsException("Cannot get supported file attributes views", e);
+ }
+ }
+
+ @Override
+ public Path getPath(String first, String... more) {
+ StringBuilder sb = new StringBuilder(first);
+ // TODO Make it more robust
+ for (String part : more)
+ sb.append('/').append(part);
+ return new JcrPath(this, sb.toString());
+ }
+
+ @Override
+ public PathMatcher getPathMatcher(String syntaxAndPattern) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public UserPrincipalLookupService getUserPrincipalLookupService() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public WatchService newWatchService() throws IOException {
+ throw new UnsupportedOperationException();
+ }
+
+// public Session getSession() {
+// return session;
+// }
+
+ public Repository getRepository() {
+ return repository;
+ }
+
+}
--- /dev/null
+package org.argeo.jcr.fs;
+
+import java.io.IOException;
+import java.nio.channels.SeekableByteChannel;
+import java.nio.file.AccessMode;
+import java.nio.file.CopyOption;
+import java.nio.file.DirectoryNotEmptyException;
+import java.nio.file.DirectoryStream;
+import java.nio.file.DirectoryStream.Filter;
+import java.nio.file.FileStore;
+import java.nio.file.LinkOption;
+import java.nio.file.NoSuchFileException;
+import java.nio.file.OpenOption;
+import java.nio.file.Path;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.nio.file.attribute.FileAttribute;
+import java.nio.file.attribute.FileAttributeView;
+import java.nio.file.spi.FileSystemProvider;
+import java.util.Calendar;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.PropertyIterator;
+import javax.jcr.PropertyType;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.nodetype.NodeType;
+import javax.jcr.nodetype.PropertyDefinition;
+
+import org.argeo.jcr.JcrUtils;
+
+/** Operations on a {@link JcrFileSystem}. */
+public abstract class JcrFileSystemProvider extends FileSystemProvider {
+
+ @Override
+ public SeekableByteChannel newByteChannel(Path path, Set<? extends OpenOption> options, FileAttribute<?>... attrs)
+ throws IOException {
+ Node node = toNode(path);
+ try {
+ if (node == null) {
+ Node parent = toNode(path.getParent());
+ if (parent == null)
+ throw new IOException("No parent directory for " + path);
+ if (parent.getPrimaryNodeType().isNodeType(NodeType.NT_FILE)
+ || parent.getPrimaryNodeType().isNodeType(NodeType.NT_LINKED_FILE))
+ throw new IOException(path + " parent is a file");
+
+ String fileName = path.getFileName().toString();
+ fileName = Text.escapeIllegalJcrChars(fileName);
+ node = parent.addNode(fileName, NodeType.NT_FILE);
+ node.addMixin(NodeType.MIX_CREATED);
+// node.addMixin(NodeType.MIX_LAST_MODIFIED);
+ }
+ if (!node.isNodeType(NodeType.NT_FILE))
+ throw new UnsupportedOperationException(node + " must be a file");
+ return new BinaryChannel(node, path);
+ } catch (RepositoryException e) {
+ discardChanges(node);
+ throw new IOException("Cannot read file", e);
+ }
+ }
+
+ @Override
+ public DirectoryStream<Path> newDirectoryStream(Path dir, Filter<? super Path> filter) throws IOException {
+ try {
+ Node base = toNode(dir);
+ if (base == null)
+ throw new IOException(dir + " is not a JCR node");
+ JcrFileSystem fileSystem = (JcrFileSystem) dir.getFileSystem();
+ return new NodeDirectoryStream(fileSystem, base.getNodes(), fileSystem.listDirectMounts(dir), filter);
+ } catch (RepositoryException e) {
+ throw new IOException("Cannot list directory", e);
+ }
+ }
+
+ @Override
+ public void createDirectory(Path dir, FileAttribute<?>... attrs) throws IOException {
+ Node node = toNode(dir);
+ try {
+ if (node == null) {
+ Node parent = toNode(dir.getParent());
+ if (parent == null)
+ throw new IOException("Parent of " + dir + " does not exist");
+ Session session = parent.getSession();
+ synchronized (session) {
+ if (parent.getPrimaryNodeType().isNodeType(NodeType.NT_FILE)
+ || parent.getPrimaryNodeType().isNodeType(NodeType.NT_LINKED_FILE))
+ throw new IOException(dir + " parent is a file");
+ String fileName = dir.getFileName().toString();
+ fileName = Text.escapeIllegalJcrChars(fileName);
+ node = parent.addNode(fileName, NodeType.NT_FOLDER);
+ node.addMixin(NodeType.MIX_CREATED);
+ node.addMixin(NodeType.MIX_LAST_MODIFIED);
+ save(session);
+ }
+ } else {
+ // if (!node.getPrimaryNodeType().isNodeType(NodeType.NT_FOLDER))
+ // throw new FileExistsException(dir + " exists and is not a directory");
+ }
+ } catch (RepositoryException e) {
+ discardChanges(node);
+ throw new IOException("Cannot create directory " + dir, e);
+ }
+ }
+
+ @Override
+ public void delete(Path path) throws IOException {
+ Node node = toNode(path);
+ try {
+ if (node == null)
+ throw new NoSuchFileException(path + " does not exist");
+ Session session = node.getSession();
+ synchronized (session) {
+ session.refresh(false);
+ if (node.getPrimaryNodeType().isNodeType(NodeType.NT_FILE))
+ node.remove();
+ else if (node.getPrimaryNodeType().isNodeType(NodeType.NT_FOLDER)) {
+ if (node.hasNodes())// TODO check only files
+ throw new DirectoryNotEmptyException(path.toString());
+ node.remove();
+ }
+ save(session);
+ }
+ } catch (RepositoryException e) {
+ discardChanges(node);
+ throw new IOException("Cannot delete " + path, e);
+ }
+
+ }
+
+ @Override
+ public void copy(Path source, Path target, CopyOption... options) throws IOException {
+ Node sourceNode = toNode(source);
+ Node targetNode = toNode(target);
+ try {
+ Session targetSession = targetNode.getSession();
+ synchronized (targetSession) {
+ JcrUtils.copy(sourceNode, targetNode);
+ save(targetSession);
+ }
+ } catch (RepositoryException e) {
+ discardChanges(sourceNode);
+ discardChanges(targetNode);
+ throw new IOException("Cannot copy from " + source + " to " + target, e);
+ }
+ }
+
+ @Override
+ public void move(Path source, Path target, CopyOption... options) throws IOException {
+ JcrFileSystem sourceFileSystem = (JcrFileSystem) source.getFileSystem();
+ WorkspaceFileStore sourceStore = sourceFileSystem.getFileStore(source.toString());
+ WorkspaceFileStore targetStore = sourceFileSystem.getFileStore(target.toString());
+ try {
+ if (sourceStore.equals(targetStore)) {
+ sourceStore.getWorkspace().move(sourceStore.toJcrPath(source.toString()),
+ targetStore.toJcrPath(target.toString()));
+ } else {
+ // TODO implement it
+ throw new UnsupportedOperationException("Can only move paths within the same workspace.");
+ }
+ } catch (RepositoryException e) {
+ throw new IOException("Cannot move from " + source + " to " + target, e);
+ }
+
+// Node sourceNode = toNode(source);
+// try {
+// Session session = sourceNode.getSession();
+// synchronized (session) {
+// session.move(sourceNode.getPath(), target.toString());
+// save(session);
+// }
+// } catch (RepositoryException e) {
+// discardChanges(sourceNode);
+// throw new IOException("Cannot move from " + source + " to " + target, e);
+// }
+ }
+
+ @Override
+ public boolean isSameFile(Path path, Path path2) throws IOException {
+ if (path.getFileSystem() != path2.getFileSystem())
+ return false;
+ boolean equ = path.equals(path2);
+ if (equ)
+ return true;
+ else {
+ try {
+ Node node = toNode(path);
+ Node node2 = toNode(path2);
+ return node.isSame(node2);
+ } catch (RepositoryException e) {
+ throw new IOException("Cannot check whether " + path + " and " + path2 + " are same", e);
+ }
+ }
+
+ }
+
+ @Override
+ public boolean isHidden(Path path) throws IOException {
+ return path.getFileName().toString().charAt(0) == '.';
+ }
+
+ @Override
+ public FileStore getFileStore(Path path) throws IOException {
+ JcrFileSystem fileSystem = (JcrFileSystem) path.getFileSystem();
+ return fileSystem.getFileStore(path.toString());
+ }
+
+ @Override
+ public void checkAccess(Path path, AccessMode... modes) throws IOException {
+ Node node = toNode(path);
+ if (node == null)
+ throw new NoSuchFileException(path + " does not exist");
+ // TODO check access via JCR api
+ }
+
+ @Override
+ public <V extends FileAttributeView> V getFileAttributeView(Path path, Class<V> type, LinkOption... options) {
+ throw new UnsupportedOperationException();
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public <A extends BasicFileAttributes> A readAttributes(Path path, Class<A> type, LinkOption... options)
+ throws IOException {
+ // TODO check if assignable
+ Node node = toNode(path);
+ if (node == null) {
+ throw new IOException("JCR node not found for " + path);
+ }
+ return (A) new JcrBasicfileAttributes(node);
+ }
+
+ @Override
+ public Map<String, Object> readAttributes(Path path, String attributes, LinkOption... options) throws IOException {
+ try {
+ Node node = toNode(path);
+ String pattern = attributes.replace(',', '|');
+ Map<String, Object> res = new HashMap<String, Object>();
+ PropertyIterator it = node.getProperties(pattern);
+ props: while (it.hasNext()) {
+ Property prop = it.nextProperty();
+ PropertyDefinition pd = prop.getDefinition();
+ if (pd.isMultiple())
+ continue props;
+ int requiredType = pd.getRequiredType();
+ switch (requiredType) {
+ case PropertyType.LONG:
+ res.put(prop.getName(), prop.getLong());
+ break;
+ case PropertyType.DOUBLE:
+ res.put(prop.getName(), prop.getDouble());
+ break;
+ case PropertyType.BOOLEAN:
+ res.put(prop.getName(), prop.getBoolean());
+ break;
+ case PropertyType.DATE:
+ res.put(prop.getName(), prop.getDate());
+ break;
+ case PropertyType.BINARY:
+ byte[] arr = JcrUtils.getBinaryAsBytes(prop);
+ res.put(prop.getName(), arr);
+ break;
+ default:
+ res.put(prop.getName(), prop.getString());
+ }
+ }
+ return res;
+ } catch (RepositoryException e) {
+ throw new IOException("Cannot read attributes of " + path, e);
+ }
+ }
+
+ @Override
+ public void setAttribute(Path path, String attribute, Object value, LinkOption... options) throws IOException {
+ Node node = toNode(path);
+ try {
+ Session session = node.getSession();
+ synchronized (session) {
+ if (value instanceof byte[]) {
+ JcrUtils.setBinaryAsBytes(node, attribute, (byte[]) value);
+ } else if (value instanceof Calendar) {
+ node.setProperty(attribute, (Calendar) value);
+ } else {
+ node.setProperty(attribute, value.toString());
+ }
+ save(session);
+ }
+ } catch (RepositoryException e) {
+ discardChanges(node);
+ throw new IOException("Cannot set attribute " + attribute + " on " + path, e);
+ }
+ }
+
+ protected Node toNode(Path path) {
+ try {
+ return ((JcrPath) path).getNode();
+ } catch (RepositoryException e) {
+ throw new JcrFsException("Cannot convert path " + path + " to JCR Node", e);
+ }
+ }
+
+ /** Discard changes in the underlying session */
+ protected void discardChanges(Node node) {
+ if (node == null)
+ return;
+ try {
+ // discard changes
+ node.getSession().refresh(false);
+ } catch (RepositoryException e) {
+ e.printStackTrace();
+ // TODO log out session?
+ // TODO use Commons logging?
+ }
+ }
+
+ /** Make sure save is robust. */
+ protected void save(Session session) throws RepositoryException {
+ session.refresh(true);
+ session.save();
+ session.notifyAll();
+ }
+
+ /**
+ * To be overriden in order to support the ~ path, with an implementation
+ * specific concept of user home.
+ *
+ * @return null by default
+ */
+ public Node getUserHome(Repository session) {
+ return null;
+ }
+
+}
--- /dev/null
+package org.argeo.jcr.fs;
+
+
+/** Exception related to the JCR FS */
+public class JcrFsException extends RuntimeException {
+ private static final long serialVersionUID = -7973896038244922980L;
+
+ public JcrFsException(String message, Throwable e) {
+ super(message, e);
+ }
+
+ public JcrFsException(String message) {
+ super(message);
+ }
+}
--- /dev/null
+package org.argeo.jcr.fs;
+
+import java.nio.file.Path;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+
+import org.argeo.api.acr.fs.AbstractFsPath;
+
+/** A {@link Path} which contains a reference to a JCR {@link Node}. */
+public class JcrPath extends AbstractFsPath<JcrFileSystem, WorkspaceFileStore> {
+ final static String separator = "/";
+ final static char separatorChar = '/';
+
+// private final JcrFileSystem fs;
+// /** null for non absolute paths */
+// private final WorkspaceFileStore fileStore;
+// private final String[] path;// null means root
+// private final boolean absolute;
+//
+// // optim
+// private final int hashCode;
+
+ public JcrPath(JcrFileSystem filesSystem, String path) {
+ super(filesSystem, path);
+// this.fs = filesSystem;
+// if (path == null)
+// throw new JcrFsException("Path cannot be null");
+// if (path.equals(separator)) {// root
+// this.path = null;
+// this.absolute = true;
+// this.hashCode = 0;
+// this.fileStore = fs.getBaseFileStore();
+// return;
+// } else if (path.equals("")) {// empty path
+// this.path = new String[] { "" };
+// this.absolute = false;
+// this.fileStore = null;
+// this.hashCode = "".hashCode();
+// return;
+// }
+//
+// if (path.equals("~")) {// home
+// path = filesSystem.getUserHomePath();
+// if (path == null)
+// throw new JcrFsException("No home directory available");
+// }
+//
+// this.absolute = path.charAt(0) == separatorChar ? true : false;
+//
+// this.fileStore = absolute ? fs.getFileStore(path) : null;
+//
+// String trimmedPath = path.substring(absolute ? 1 : 0,
+// path.charAt(path.length() - 1) == separatorChar ? path.length() - 1 : path.length());
+// this.path = trimmedPath.split(separator);
+// for (int i = 0; i < this.path.length; i++) {
+// this.path[i] = Text.unescapeIllegalJcrChars(this.path[i]);
+// }
+// this.hashCode = this.path[this.path.length - 1].hashCode();
+// assert !(absolute && fileStore == null);
+ }
+
+ public JcrPath(JcrFileSystem filesSystem, Node node) throws RepositoryException {
+ this(filesSystem, filesSystem.getFileStore(node).toFsPath(node));
+ }
+
+ /** Internal optimisation */
+ private JcrPath(JcrFileSystem filesSystem, WorkspaceFileStore fileStore, String[] path, boolean absolute) {
+ super(filesSystem, fileStore, path, absolute);
+// this.fs = filesSystem;
+// this.path = path;
+// this.absolute = path == null ? true : absolute;
+// if (this.absolute && fileStore == null)
+// throw new IllegalArgumentException("Absolute path requires a file store");
+// if (!this.absolute && fileStore != null)
+// throw new IllegalArgumentException("A file store should not be provided for a relative path");
+// this.fileStore = fileStore;
+// this.hashCode = path == null ? 0 : path[path.length - 1].hashCode();
+// assert !(absolute && fileStore == null);
+ }
+
+ protected String cleanUpSegment(String segment) {
+ return Text.unescapeIllegalJcrChars(segment);
+ }
+
+ @Override
+ protected JcrPath newInstance(String path) {
+ return new JcrPath(getFileSystem(), path);
+ }
+
+ @Override
+ protected JcrPath newInstance(String[] segments, boolean absolute) {
+ return new JcrPath(getFileSystem(), getFileStore(), segments, absolute);
+
+ }
+
+// @Override
+// public FileSystem getFileSystem() {
+// return fs;
+// }
+//
+// @Override
+// public boolean isAbsolute() {
+// return absolute;
+// }
+//
+// @Override
+// public Path getRoot() {
+// if (path == null)
+// return this;
+// return new JcrPath(fs, separator);
+// }
+//
+// @Override
+// public String toString() {
+// return toFsPath(path);
+// }
+//
+// private String toFsPath(String[] path) {
+// if (path == null)
+// return "/";
+// StringBuilder sb = new StringBuilder();
+// if (isAbsolute())
+// sb.append('/');
+// for (int i = 0; i < path.length; i++) {
+// if (i != 0)
+// sb.append('/');
+// sb.append(path[i]);
+// }
+// return sb.toString();
+// }
+
+// @Deprecated
+// private String toJcrPath() {
+// return toJcrPath(path);
+// }
+//
+// @Deprecated
+// private String toJcrPath(String[] path) {
+// if (path == null)
+// return "/";
+// StringBuilder sb = new StringBuilder();
+// if (isAbsolute())
+// sb.append('/');
+// for (int i = 0; i < path.length; i++) {
+// if (i != 0)
+// sb.append('/');
+// sb.append(Text.escapeIllegalJcrChars(path[i]));
+// }
+// return sb.toString();
+// }
+
+// @Override
+// public Path getFileName() {
+// if (path == null)
+// return null;
+// return new JcrPath(fs, path[path.length - 1]);
+// }
+//
+// @Override
+// public Path getParent() {
+// if (path == null)
+// return null;
+// if (path.length == 1)// root
+// return new JcrPath(fs, separator);
+// String[] parentPath = Arrays.copyOfRange(path, 0, path.length - 1);
+// if (!absolute)
+// return new JcrPath(fs, null, parentPath, absolute);
+// else
+// return new JcrPath(fs, toFsPath(parentPath));
+// }
+//
+// @Override
+// public int getNameCount() {
+// if (path == null)
+// return 0;
+// return path.length;
+// }
+//
+// @Override
+// public Path getName(int index) {
+// if (path == null)
+// return null;
+// return new JcrPath(fs, path[index]);
+// }
+//
+// @Override
+// public Path subpath(int beginIndex, int endIndex) {
+// if (path == null)
+// return null;
+// String[] parentPath = Arrays.copyOfRange(path, beginIndex, endIndex);
+// return new JcrPath(fs, null, parentPath, false);
+// }
+//
+// @Override
+// public boolean startsWith(Path other) {
+// return toString().startsWith(other.toString());
+// }
+//
+// @Override
+// public boolean startsWith(String other) {
+// return toString().startsWith(other);
+// }
+//
+// @Override
+// public boolean endsWith(Path other) {
+// return toString().endsWith(other.toString());
+// }
+//
+// @Override
+// public boolean endsWith(String other) {
+// return toString().endsWith(other);
+// }
+
+// @Override
+// public Path normalize() {
+// // always normalized
+// return this;
+// }
+
+// @Override
+// public Path resolve(Path other) {
+// JcrPath otherPath = (JcrPath) other;
+// if (otherPath.isAbsolute())
+// return other;
+// String[] newPath;
+// if (path == null) {
+// newPath = new String[otherPath.path.length];
+// System.arraycopy(otherPath.path, 0, newPath, 0, otherPath.path.length);
+// } else {
+// newPath = new String[path.length + otherPath.path.length];
+// System.arraycopy(path, 0, newPath, 0, path.length);
+// System.arraycopy(otherPath.path, 0, newPath, path.length, otherPath.path.length);
+// }
+// if (!absolute)
+// return new JcrPath(fs, null, newPath, absolute);
+// else {
+// return new JcrPath(fs, toFsPath(newPath));
+// }
+// }
+//
+// @Override
+// public final Path resolve(String other) {
+// return resolve(getFileSystem().getPath(other));
+// }
+//
+// @Override
+// public final Path resolveSibling(Path other) {
+// if (other == null)
+// throw new NullPointerException();
+// Path parent = getParent();
+// return (parent == null) ? other : parent.resolve(other);
+// }
+//
+// @Override
+// public final Path resolveSibling(String other) {
+// return resolveSibling(getFileSystem().getPath(other));
+// }
+//
+// @Override
+// public final Iterator<Path> iterator() {
+// return new Iterator<Path>() {
+// private int i = 0;
+//
+// @Override
+// public boolean hasNext() {
+// return (i < getNameCount());
+// }
+//
+// @Override
+// public Path next() {
+// if (i < getNameCount()) {
+// Path result = getName(i);
+// i++;
+// return result;
+// } else {
+// throw new NoSuchElementException();
+// }
+// }
+//
+// @Override
+// public void remove() {
+// throw new UnsupportedOperationException();
+// }
+// };
+// }
+//
+// @Override
+// public Path relativize(Path other) {
+// if (equals(other))
+// return new JcrPath(fs, "");
+// if (other.startsWith(this)) {
+// String p1 = toString();
+// String p2 = other.toString();
+// String relative = p2.substring(p1.length(), p2.length());
+// if (relative.charAt(0) == '/')
+// relative = relative.substring(1);
+// return new JcrPath(fs, relative);
+// }
+// throw new IllegalArgumentException(other + " cannot be relativized against " + this);
+// }
+
+// @Override
+// public URI toUri() {
+// try {
+// return new URI(fs.provider().getScheme(), toString(), null);
+// } catch (URISyntaxException e) {
+// throw new JcrFsException("Cannot create URI for " + toString(), e);
+// }
+// }
+//
+// @Override
+// public Path toAbsolutePath() {
+// if (isAbsolute())
+// return this;
+// return new JcrPath(fs, fileStore, path, true);
+// }
+//
+// @Override
+// public Path toRealPath(LinkOption... options) throws IOException {
+// return this;
+// }
+//
+// @Override
+// public File toFile() {
+// throw new UnsupportedOperationException();
+// }
+
+ public Node getNode() throws RepositoryException {
+ if (!isAbsolute())// TODO default dir
+ throw new JcrFsException("Cannot get a JCR node from a relative path");
+ assert getFileStore() != null;
+ return getFileStore().toNode(getSegments());
+// String pathStr = toJcrPath();
+// Session session = fs.getSession();
+// // TODO synchronize on the session ?
+// if (!session.itemExists(pathStr))
+// return null;
+// return session.getNode(pathStr);
+ }
+//
+// @Override
+// public boolean equals(Object obj) {
+// if (!(obj instanceof JcrPath))
+// return false;
+// JcrPath other = (JcrPath) obj;
+//
+// if (path == null) {// root
+// if (other.path == null)// root
+// return true;
+// else
+// return false;
+// } else {
+// if (other.path == null)// root
+// return false;
+// }
+// // non root
+// if (path.length != other.path.length)
+// return false;
+// for (int i = 0; i < path.length; i++) {
+// if (!path[i].equals(other.path[i]))
+// return false;
+// }
+// return true;
+// }
+
+// @Override
+// public int hashCode() {
+// return hashCode;
+// }
+
+// @Override
+// protected Object clone() throws CloneNotSupportedException {
+// return new JcrPath(fs, toString());
+// }
+
+// @Override
+// protected void finalize() throws Throwable {
+// Arrays.fill(path, null);
+// }
+
+
+
+}
--- /dev/null
+package org.argeo.jcr.fs;
+
+import java.io.IOException;
+import java.nio.file.DirectoryStream;
+import java.nio.file.Path;
+import java.util.Iterator;
+
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+
+public class NodeDirectoryStream implements DirectoryStream<Path> {
+ private final JcrFileSystem fs;
+ private final NodeIterator nodeIterator;
+ private final Iterator<JcrPath> additionalPaths;
+ private final Filter<? super Path> filter;
+
+ public NodeDirectoryStream(JcrFileSystem fs, NodeIterator nodeIterator, Iterator<JcrPath> additionalPaths,
+ Filter<? super Path> filter) {
+ this.fs = fs;
+ this.nodeIterator = nodeIterator;
+ this.additionalPaths = additionalPaths;
+ this.filter = filter;
+ }
+
+ @Override
+ public void close() throws IOException {
+ }
+
+ @Override
+ public Iterator<Path> iterator() {
+ return new Iterator<Path>() {
+ private JcrPath next = null;
+
+ @Override
+ public synchronized boolean hasNext() {
+ if (next != null)
+ return true;
+ nodes: while (nodeIterator.hasNext()) {
+ try {
+ Node node = nodeIterator.nextNode();
+ String nodeName = node.getName();
+ if (nodeName.startsWith("rep:") || nodeName.startsWith("jcr:"))
+ continue nodes;
+ if (fs.skipNode(node))
+ continue nodes;
+ next = new JcrPath(fs, node);
+ if (filter != null) {
+ if (filter.accept(next))
+ break nodes;
+ } else
+ break nodes;
+ } catch (Exception e) {
+ throw new JcrFsException("Could not get next path", e);
+ }
+ }
+
+ if (next == null) {
+ if (additionalPaths.hasNext())
+ next = additionalPaths.next();
+ }
+
+ return next != null;
+ }
+
+ @Override
+ public synchronized Path next() {
+ if (!hasNext())// make sure has next has been called
+ return null;
+ JcrPath res = next;
+ next = null;
+ return res;
+ }
+
+ };
+ }
+
+}
--- /dev/null
+package org.argeo.jcr.fs;
+
+import java.nio.file.attribute.BasicFileAttributes;
+
+import javax.jcr.Node;
+
+public interface NodeFileAttributes extends BasicFileAttributes {
+ public Node getNode();
+}
--- /dev/null
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.argeo.jcr.fs;
+
+import java.io.ByteArrayOutputStream;
+import java.io.UnsupportedEncodingException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.BitSet;
+import java.util.Properties;
+
+/**
+ * <b>Hacked from org.apache.jackrabbit.util.Text in Jackrabbit JCR Commons</b>
+ * This Class provides some text related utilities
+ */
+class Text {
+
+ /**
+ * Hidden constructor.
+ */
+ private Text() {
+ }
+
+ /**
+ * used for the md5
+ */
+ public static final char[] hexTable = "0123456789abcdef".toCharArray();
+
+ /**
+ * Calculate an MD5 hash of the string given.
+ *
+ * @param data
+ * the data to encode
+ * @param enc
+ * the character encoding to use
+ * @return a hex encoded string of the md5 digested input
+ */
+ public static String md5(String data, String enc) throws UnsupportedEncodingException {
+ try {
+ return digest("MD5", data.getBytes(enc));
+ } catch (NoSuchAlgorithmException e) {
+ throw new InternalError("MD5 digest not available???");
+ }
+ }
+
+ /**
+ * Calculate an MD5 hash of the string given using 'utf-8' encoding.
+ *
+ * @param data
+ * the data to encode
+ * @return a hex encoded string of the md5 digested input
+ */
+ public static String md5(String data) {
+ try {
+ return md5(data, "utf-8");
+ } catch (UnsupportedEncodingException e) {
+ throw new InternalError("UTF8 digest not available???");
+ }
+ }
+
+ /**
+ * Digest the plain string using the given algorithm.
+ *
+ * @param algorithm
+ * The alogrithm for the digest. This algorithm must be supported
+ * by the MessageDigest class.
+ * @param data
+ * The plain text String to be digested.
+ * @param enc
+ * The character encoding to use
+ * @return The digested plain text String represented as Hex digits.
+ * @throws java.security.NoSuchAlgorithmException
+ * if the desired algorithm is not supported by the
+ * MessageDigest class.
+ * @throws java.io.UnsupportedEncodingException
+ * if the encoding is not supported
+ */
+ public static String digest(String algorithm, String data, String enc)
+ throws NoSuchAlgorithmException, UnsupportedEncodingException {
+
+ return digest(algorithm, data.getBytes(enc));
+ }
+
+ /**
+ * Digest the plain string using the given algorithm.
+ *
+ * @param algorithm
+ * The algorithm for the digest. This algorithm must be supported
+ * by the MessageDigest class.
+ * @param data
+ * the data to digest with the given algorithm
+ * @return The digested plain text String represented as Hex digits.
+ * @throws java.security.NoSuchAlgorithmException
+ * if the desired algorithm is not supported by the
+ * MessageDigest class.
+ */
+ public static String digest(String algorithm, byte[] data) throws NoSuchAlgorithmException {
+
+ MessageDigest md = MessageDigest.getInstance(algorithm);
+ byte[] digest = md.digest(data);
+ StringBuilder res = new StringBuilder(digest.length * 2);
+ for (byte b : digest) {
+ res.append(hexTable[(b >> 4) & 15]);
+ res.append(hexTable[b & 15]);
+ }
+ return res.toString();
+ }
+
+ /**
+ * returns an array of strings decomposed of the original string, split at
+ * every occurrence of 'ch'. if 2 'ch' follow each other with no
+ * intermediate characters, empty "" entries are avoided.
+ *
+ * @param str
+ * the string to decompose
+ * @param ch
+ * the character to use a split pattern
+ * @return an array of strings
+ */
+ public static String[] explode(String str, int ch) {
+ return explode(str, ch, false);
+ }
+
+ /**
+ * returns an array of strings decomposed of the original string, split at
+ * every occurrence of 'ch'.
+ *
+ * @param str
+ * the string to decompose
+ * @param ch
+ * the character to use a split pattern
+ * @param respectEmpty
+ * if <code>true</code>, empty elements are generated
+ * @return an array of strings
+ */
+ public static String[] explode(String str, int ch, boolean respectEmpty) {
+ if (str == null || str.length() == 0) {
+ return new String[0];
+ }
+
+ ArrayList<String> strings = new ArrayList<String>();
+ int pos;
+ int lastpos = 0;
+
+ // add snipples
+ while ((pos = str.indexOf(ch, lastpos)) >= 0) {
+ if (pos - lastpos > 0 || respectEmpty) {
+ strings.add(str.substring(lastpos, pos));
+ }
+ lastpos = pos + 1;
+ }
+ // add rest
+ if (lastpos < str.length()) {
+ strings.add(str.substring(lastpos));
+ } else if (respectEmpty && lastpos == str.length()) {
+ strings.add("");
+ }
+
+ // return string array
+ return strings.toArray(new String[strings.size()]);
+ }
+
+ /**
+ * Concatenates all strings in the string array using the specified
+ * delimiter.
+ *
+ * @param arr
+ * @param delim
+ * @return the concatenated string
+ */
+ public static String implode(String[] arr, String delim) {
+ StringBuilder buf = new StringBuilder();
+ for (int i = 0; i < arr.length; i++) {
+ if (i > 0) {
+ buf.append(delim);
+ }
+ buf.append(arr[i]);
+ }
+ return buf.toString();
+ }
+
+ /**
+ * Replaces all occurrences of <code>oldString</code> in <code>text</code>
+ * with <code>newString</code>.
+ *
+ * @param text
+ * @param oldString
+ * old substring to be replaced with <code>newString</code>
+ * @param newString
+ * new substring to replace occurrences of <code>oldString</code>
+ * @return a string
+ */
+ public static String replace(String text, String oldString, String newString) {
+ if (text == null || oldString == null || newString == null) {
+ throw new IllegalArgumentException("null argument");
+ }
+ int pos = text.indexOf(oldString);
+ if (pos == -1) {
+ return text;
+ }
+ int lastPos = 0;
+ StringBuilder sb = new StringBuilder(text.length());
+ while (pos != -1) {
+ sb.append(text.substring(lastPos, pos));
+ sb.append(newString);
+ lastPos = pos + oldString.length();
+ pos = text.indexOf(oldString, lastPos);
+ }
+ if (lastPos < text.length()) {
+ sb.append(text.substring(lastPos));
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Replaces XML characters in the given string that might need escaping as
+ * XML text or attribute
+ *
+ * @param text
+ * text to be escaped
+ * @return a string
+ */
+ public static String encodeIllegalXMLCharacters(String text) {
+ return encodeMarkupCharacters(text, false);
+ }
+
+ /**
+ * Replaces HTML characters in the given string that might need escaping as
+ * HTML text or attribute
+ *
+ * @param text
+ * text to be escaped
+ * @return a string
+ */
+ public static String encodeIllegalHTMLCharacters(String text) {
+ return encodeMarkupCharacters(text, true);
+ }
+
+ private static String encodeMarkupCharacters(String text, boolean isHtml) {
+ if (text == null) {
+ throw new IllegalArgumentException("null argument");
+ }
+ StringBuilder buf = null;
+ int length = text.length();
+ int pos = 0;
+ for (int i = 0; i < length; i++) {
+ int ch = text.charAt(i);
+ switch (ch) {
+ case '<':
+ case '>':
+ case '&':
+ case '"':
+ case '\'':
+ if (buf == null) {
+ buf = new StringBuilder();
+ }
+ if (i > 0) {
+ buf.append(text.substring(pos, i));
+ }
+ pos = i + 1;
+ break;
+ default:
+ continue;
+ }
+ if (ch == '<') {
+ buf.append("<");
+ } else if (ch == '>') {
+ buf.append(">");
+ } else if (ch == '&') {
+ buf.append("&");
+ } else if (ch == '"') {
+ buf.append(""");
+ } else if (ch == '\'') {
+ buf.append(isHtml ? "'" : "'");
+ }
+ }
+ if (buf == null) {
+ return text;
+ } else {
+ if (pos < length) {
+ buf.append(text.substring(pos));
+ }
+ return buf.toString();
+ }
+ }
+
+ /**
+ * The list of characters that are not encoded by the <code>escape()</code>
+ * and <code>unescape()</code> METHODS. They contains the characters as
+ * defined 'unreserved' in section 2.3 of the RFC 2396 'URI generic syntax':
+ * <p>
+ *
+ * <pre>
+ * unreserved = alphanum | mark
+ * mark = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")"
+ * </pre>
+ */
+ public static BitSet URISave;
+
+ /**
+ * Same as {@link #URISave} but also contains the '/'
+ */
+ public static BitSet URISaveEx;
+
+ static {
+ URISave = new BitSet(256);
+ int i;
+ for (i = 'a'; i <= 'z'; i++) {
+ URISave.set(i);
+ }
+ for (i = 'A'; i <= 'Z'; i++) {
+ URISave.set(i);
+ }
+ for (i = '0'; i <= '9'; i++) {
+ URISave.set(i);
+ }
+ URISave.set('-');
+ URISave.set('_');
+ URISave.set('.');
+ URISave.set('!');
+ URISave.set('~');
+ URISave.set('*');
+ URISave.set('\'');
+ URISave.set('(');
+ URISave.set(')');
+
+ URISaveEx = (BitSet) URISave.clone();
+ URISaveEx.set('/');
+ }
+
+ /**
+ * Does an URL encoding of the <code>string</code> using the
+ * <code>escape</code> character. The characters that don't need encoding
+ * are those defined 'unreserved' in section 2.3 of the 'URI generic syntax'
+ * RFC 2396, but without the escape character.
+ *
+ * @param string
+ * the string to encode.
+ * @param escape
+ * the escape character.
+ * @return the escaped string
+ * @throws NullPointerException
+ * if <code>string</code> is <code>null</code>.
+ */
+ public static String escape(String string, char escape) {
+ return escape(string, escape, false);
+ }
+
+ /**
+ * Does an URL encoding of the <code>string</code> using the
+ * <code>escape</code> character. The characters that don't need encoding
+ * are those defined 'unreserved' in section 2.3 of the 'URI generic syntax'
+ * RFC 2396, but without the escape character. If <code>isPath</code> is
+ * <code>true</code>, additionally the slash '/' is ignored, too.
+ *
+ * @param string
+ * the string to encode.
+ * @param escape
+ * the escape character.
+ * @param isPath
+ * if <code>true</code>, the string is treated as path
+ * @return the escaped string
+ * @throws NullPointerException
+ * if <code>string</code> is <code>null</code>.
+ */
+ public static String escape(String string, char escape, boolean isPath) {
+ try {
+ BitSet validChars = isPath ? URISaveEx : URISave;
+ byte[] bytes = string.getBytes("utf-8");
+ StringBuilder out = new StringBuilder(bytes.length);
+ for (byte aByte : bytes) {
+ int c = aByte & 0xff;
+ if (validChars.get(c) && c != escape) {
+ out.append((char) c);
+ } else {
+ out.append(escape);
+ out.append(hexTable[(c >> 4) & 0x0f]);
+ out.append(hexTable[(c) & 0x0f]);
+ }
+ }
+ return out.toString();
+ } catch (UnsupportedEncodingException e) {
+ throw new InternalError(e.toString());
+ }
+ }
+
+ /**
+ * Does a URL encoding of the <code>string</code>. The characters that don't
+ * need encoding are those defined 'unreserved' in section 2.3 of the 'URI
+ * generic syntax' RFC 2396.
+ *
+ * @param string
+ * the string to encode
+ * @return the escaped string
+ * @throws NullPointerException
+ * if <code>string</code> is <code>null</code>.
+ */
+ public static String escape(String string) {
+ return escape(string, '%');
+ }
+
+ /**
+ * Does a URL encoding of the <code>path</code>. The characters that don't
+ * need encoding are those defined 'unreserved' in section 2.3 of the 'URI
+ * generic syntax' RFC 2396. In contrast to the {@link #escape(String)}
+ * method, not the entire path string is escaped, but every individual part
+ * (i.e. the slashes are not escaped).
+ *
+ * @param path
+ * the path to encode
+ * @return the escaped path
+ * @throws NullPointerException
+ * if <code>path</code> is <code>null</code>.
+ */
+ public static String escapePath(String path) {
+ return escape(path, '%', true);
+ }
+
+ /**
+ * Does a URL decoding of the <code>string</code> using the
+ * <code>escape</code> character. Please note that in opposite to the
+ * {@link java.net.URLDecoder} it does not transform the + into spaces.
+ *
+ * @param string
+ * the string to decode
+ * @param escape
+ * the escape character
+ * @return the decoded string
+ * @throws NullPointerException
+ * if <code>string</code> is <code>null</code>.
+ * @throws IllegalArgumentException
+ * if the 2 characters following the escape character do not
+ * represent a hex-number or if not enough characters follow an
+ * escape character
+ */
+ public static String unescape(String string, char escape) {
+ try {
+ byte[] utf8 = string.getBytes("utf-8");
+
+ // Check whether escape occurs at invalid position
+ if ((utf8.length >= 1 && utf8[utf8.length - 1] == escape)
+ || (utf8.length >= 2 && utf8[utf8.length - 2] == escape)) {
+ throw new IllegalArgumentException("Premature end of escape sequence at end of input");
+ }
+
+ ByteArrayOutputStream out = new ByteArrayOutputStream(utf8.length);
+ for (int k = 0; k < utf8.length; k++) {
+ byte b = utf8[k];
+ if (b == escape) {
+ out.write((decodeDigit(utf8[++k]) << 4) + decodeDigit(utf8[++k]));
+ } else {
+ out.write(b);
+ }
+ }
+
+ return new String(out.toByteArray(), "utf-8");
+ } catch (UnsupportedEncodingException e) {
+ throw new InternalError(e.toString());
+ }
+ }
+
+ /**
+ * Does a URL decoding of the <code>string</code>. Please note that in
+ * opposite to the {@link java.net.URLDecoder} it does not transform the +
+ * into spaces.
+ *
+ * @param string
+ * the string to decode
+ * @return the decoded string
+ * @throws NullPointerException
+ * if <code>string</code> is <code>null</code>.
+ * @throws ArrayIndexOutOfBoundsException
+ * if not enough character follow an escape character
+ * @throws IllegalArgumentException
+ * if the 2 characters following the escape character do not
+ * represent a hex-number.
+ */
+ public static String unescape(String string) {
+ return unescape(string, '%');
+ }
+
+ /**
+ * Escapes all illegal JCR name characters of a string. The encoding is
+ * loosely modeled after URI encoding, but only encodes the characters it
+ * absolutely needs to in order to make the resulting string a valid JCR
+ * name. Use {@link #unescapeIllegalJcrChars(String)} for decoding.
+ * <p>
+ * QName EBNF:<br>
+ * <xmp> simplename ::= onecharsimplename | twocharsimplename |
+ * threeormorecharname onecharsimplename ::= (* Any Unicode character
+ * except: '.', '/', ':', '[', ']', '*', '|' or any whitespace character *)
+ * twocharsimplename ::= '.' onecharsimplename | onecharsimplename '.' |
+ * onecharsimplename onecharsimplename threeormorecharname ::= nonspace
+ * string nonspace string ::= char | string char char ::= nonspace | ' '
+ * nonspace ::= (* Any Unicode character except: '/', ':', '[', ']', '*',
+ * '|' or any whitespace character *) </xmp>
+ *
+ * @param name
+ * the name to escape
+ * @return the escaped name
+ */
+ public static String escapeIllegalJcrChars(String name) {
+ return escapeIllegalChars(name, "%/:[]*|\t\r\n");
+ }
+
+ /**
+ * Escapes all illegal JCR 1.0 name characters of a string. Use
+ * {@link #unescapeIllegalJcrChars(String)} for decoding.
+ * <p>
+ * QName EBNF:<br>
+ * <xmp> simplename ::= onecharsimplename | twocharsimplename |
+ * threeormorecharname onecharsimplename ::= (* Any Unicode character
+ * except: '.', '/', ':', '[', ']', '*', ''', '"', '|' or any whitespace
+ * character *) twocharsimplename ::= '.' onecharsimplename |
+ * onecharsimplename '.' | onecharsimplename onecharsimplename
+ * threeormorecharname ::= nonspace string nonspace string ::= char | string
+ * char char ::= nonspace | ' ' nonspace ::= (* Any Unicode character
+ * except: '/', ':', '[', ']', '*', ''', '"', '|' or any whitespace
+ * character *) </xmp>
+ *
+ * @since Apache Jackrabbit 2.3.2 and 2.2.10
+ * @see <a href=
+ * "https://issues.apache.org/jira/browse/JCR-3128">JCR-3128</a>
+ * @param name
+ * the name to escape
+ * @return the escaped name
+ */
+ public static String escapeIllegalJcr10Chars(String name) {
+ return escapeIllegalChars(name, "%/:[]*'\"|\t\r\n");
+ }
+
+ private static String escapeIllegalChars(String name, String illegal) {
+ StringBuilder buffer = new StringBuilder(name.length() * 2);
+ for (int i = 0; i < name.length(); i++) {
+ char ch = name.charAt(i);
+ if (illegal.indexOf(ch) != -1 || (ch == '.' && name.length() < 3)
+ || (ch == ' ' && (i == 0 || i == name.length() - 1))) {
+ buffer.append('%');
+ buffer.append(Character.toUpperCase(Character.forDigit(ch / 16, 16)));
+ buffer.append(Character.toUpperCase(Character.forDigit(ch % 16, 16)));
+ } else {
+ buffer.append(ch);
+ }
+ }
+ return buffer.toString();
+ }
+
+ /**
+ * Escapes illegal XPath search characters at the end of a string.
+ * <p>
+ * Example:<br>
+ * A search string like 'test?' will run into a ParseException documented in
+ * http://issues.apache.org/jira/browse/JCR-1248
+ *
+ * @param s
+ * the string to encode
+ * @return the escaped string
+ */
+ public static String escapeIllegalXpathSearchChars(String s) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(s.substring(0, (s.length() - 1)));
+ char c = s.charAt(s.length() - 1);
+ // NOTE: keep this in sync with _ESCAPED_CHAR below!
+ if (c == '!' || c == '(' || c == ':' || c == '^' || c == '[' || c == ']' || c == '{' || c == '}' || c == '?') {
+ sb.append('\\');
+ }
+ sb.append(c);
+ return sb.toString();
+ }
+
+ /**
+ * Unescapes previously escaped jcr chars.
+ * <p>
+ * Please note, that this does not exactly the same as the url related
+ * {@link #unescape(String)}, since it handles the byte-encoding
+ * differently.
+ *
+ * @param name
+ * the name to unescape
+ * @return the unescaped name
+ */
+ public static String unescapeIllegalJcrChars(String name) {
+ StringBuilder buffer = new StringBuilder(name.length());
+ int i = name.indexOf('%');
+ while (i > -1 && i + 2 < name.length()) {
+ buffer.append(name.toCharArray(), 0, i);
+ int a = Character.digit(name.charAt(i + 1), 16);
+ int b = Character.digit(name.charAt(i + 2), 16);
+ if (a > -1 && b > -1) {
+ buffer.append((char) (a * 16 + b));
+ name = name.substring(i + 3);
+ } else {
+ buffer.append('%');
+ name = name.substring(i + 1);
+ }
+ i = name.indexOf('%');
+ }
+ buffer.append(name);
+ return buffer.toString();
+ }
+
+ /**
+ * Returns the name part of the path. If the given path is already a name
+ * (i.e. contains no slashes) it is returned.
+ *
+ * @param path
+ * the path
+ * @return the name part or <code>null</code> if <code>path</code> is
+ * <code>null</code>.
+ */
+ public static String getName(String path) {
+ return getName(path, '/');
+ }
+
+ /**
+ * Returns the name part of the path, delimited by the given
+ * <code>delim</code>. If the given path is already a name (i.e. contains no
+ * <code>delim</code> characters) it is returned.
+ *
+ * @param path
+ * the path
+ * @param delim
+ * the delimiter
+ * @return the name part or <code>null</code> if <code>path</code> is
+ * <code>null</code>.
+ */
+ public static String getName(String path, char delim) {
+ return path == null ? null : path.substring(path.lastIndexOf(delim) + 1);
+ }
+
+ /**
+ * Same as {@link #getName(String)} but adding the possibility to pass paths
+ * that end with a trailing '/'
+ *
+ * @see #getName(String)
+ */
+ public static String getName(String path, boolean ignoreTrailingSlash) {
+ if (ignoreTrailingSlash && path != null && path.endsWith("/") && path.length() > 1) {
+ path = path.substring(0, path.length() - 1);
+ }
+ return getName(path);
+ }
+
+ /**
+ * Returns the namespace prefix of the given <code>qname</code>. If the
+ * prefix is missing, an empty string is returned. Please note, that this
+ * method does not validate the name or prefix.
+ * </p>
+ * the qname has the format: qname := [prefix ':'] local;
+ *
+ * @param qname
+ * a qualified name
+ * @return the prefix of the name or "".
+ *
+ * @see #getLocalName(String)
+ *
+ * @throws NullPointerException
+ * if <code>qname</code> is <code>null</code>
+ */
+ public static String getNamespacePrefix(String qname) {
+ int pos = qname.indexOf(':');
+ return pos >= 0 ? qname.substring(0, pos) : "";
+ }
+
+ /**
+ * Returns the local name of the given <code>qname</code>. Please note, that
+ * this method does not validate the name.
+ * </p>
+ * the qname has the format: qname := [prefix ':'] local;
+ *
+ * @param qname
+ * a qualified name
+ * @return the localname
+ *
+ * @see #getNamespacePrefix(String)
+ *
+ * @throws NullPointerException
+ * if <code>qname</code> is <code>null</code>
+ */
+ public static String getLocalName(String qname) {
+ int pos = qname.indexOf(':');
+ return pos >= 0 ? qname.substring(pos + 1) : qname;
+ }
+
+ /**
+ * Determines, if two paths denote hierarchical siblins.
+ *
+ * @param p1
+ * first path
+ * @param p2
+ * second path
+ * @return true if on same level, false otherwise
+ */
+ public static boolean isSibling(String p1, String p2) {
+ int pos1 = p1.lastIndexOf('/');
+ int pos2 = p2.lastIndexOf('/');
+ return (pos1 == pos2 && pos1 >= 0 && p1.regionMatches(0, p2, 0, pos1));
+ }
+
+ /**
+ * Determines if the <code>descendant</code> path is hierarchical a
+ * descendant of <code>path</code>.
+ *
+ * @param path
+ * the current path
+ * @param descendant
+ * the potential descendant
+ * @return <code>true</code> if the <code>descendant</code> is a descendant;
+ * <code>false</code> otherwise.
+ */
+ public static boolean isDescendant(String path, String descendant) {
+ String pattern = path.endsWith("/") ? path : path + "/";
+ return !pattern.equals(descendant) && descendant.startsWith(pattern);
+ }
+
+ /**
+ * Determines if the <code>descendant</code> path is hierarchical a
+ * descendant of <code>path</code> or equal to it.
+ *
+ * @param path
+ * the path to check
+ * @param descendant
+ * the potential descendant
+ * @return <code>true</code> if the <code>descendant</code> is a descendant
+ * or equal; <code>false</code> otherwise.
+ */
+ public static boolean isDescendantOrEqual(String path, String descendant) {
+ if (path.equals(descendant)) {
+ return true;
+ } else {
+ String pattern = path.endsWith("/") ? path : path + "/";
+ return descendant.startsWith(pattern);
+ }
+ }
+
+ /**
+ * Returns the n<sup>th</sup> relative parent of the path, where n=level.
+ * <p>
+ * Example:<br>
+ * <code>
+ * Text.getRelativeParent("/foo/bar/test", 1) == "/foo/bar"
+ * </code>
+ *
+ * @param path
+ * the path of the page
+ * @param level
+ * the level of the parent
+ */
+ public static String getRelativeParent(String path, int level) {
+ int idx = path.length();
+ while (level > 0) {
+ idx = path.lastIndexOf('/', idx - 1);
+ if (idx < 0) {
+ return "";
+ }
+ level--;
+ }
+ return (idx == 0) ? "/" : path.substring(0, idx);
+ }
+
+ /**
+ * Same as {@link #getRelativeParent(String, int)} but adding the
+ * possibility to pass paths that end with a trailing '/'
+ *
+ * @see #getRelativeParent(String, int)
+ */
+ public static String getRelativeParent(String path, int level, boolean ignoreTrailingSlash) {
+ if (ignoreTrailingSlash && path.endsWith("/") && path.length() > 1) {
+ path = path.substring(0, path.length() - 1);
+ }
+ return getRelativeParent(path, level);
+ }
+
+ /**
+ * Returns the n<sup>th</sup> absolute parent of the path, where n=level.
+ * <p>
+ * Example:<br>
+ * <code>
+ * Text.getAbsoluteParent("/foo/bar/test", 1) == "/foo/bar"
+ * </code>
+ *
+ * @param path
+ * the path of the page
+ * @param level
+ * the level of the parent
+ */
+ public static String getAbsoluteParent(String path, int level) {
+ int idx = 0;
+ int len = path.length();
+ while (level >= 0 && idx < len) {
+ idx = path.indexOf('/', idx + 1);
+ if (idx < 0) {
+ idx = len;
+ }
+ level--;
+ }
+ return level >= 0 ? "" : path.substring(0, idx);
+ }
+
+ /**
+ * Performs variable replacement on the given string value. Each
+ * <code>${...}</code> sequence within the given value is replaced with the
+ * value of the named parser variable. If a variable is not found in the
+ * properties an IllegalArgumentException is thrown unless
+ * <code>ignoreMissing</code> is <code>true</code>. In the later case, the
+ * missing variable is replaced by the empty string.
+ *
+ * @param value
+ * the original value
+ * @param ignoreMissing
+ * if <code>true</code>, missing variables are replaced by the
+ * empty string.
+ * @return value after variable replacements
+ * @throws IllegalArgumentException
+ * if the replacement of a referenced variable is not found
+ */
+ public static String replaceVariables(Properties variables, String value, boolean ignoreMissing)
+ throws IllegalArgumentException {
+ StringBuilder result = new StringBuilder();
+
+ // Value:
+ // +--+-+--------+-+-----------------+
+ // | |p|--> |q|--> |
+ // +--+-+--------+-+-----------------+
+ int p = 0, q = value.indexOf("${"); // Find first ${
+ while (q != -1) {
+ result.append(value.substring(p, q)); // Text before ${
+ p = q;
+ q = value.indexOf("}", q + 2); // Find }
+ if (q != -1) {
+ String variable = value.substring(p + 2, q);
+ String replacement = variables.getProperty(variable);
+ if (replacement == null) {
+ if (ignoreMissing) {
+ replacement = "";
+ } else {
+ throw new IllegalArgumentException("Replacement not found for ${" + variable + "}.");
+ }
+ }
+ result.append(replacement);
+ p = q + 1;
+ q = value.indexOf("${", p); // Find next ${
+ }
+ }
+ result.append(value.substring(p, value.length())); // Trailing text
+
+ return result.toString();
+ }
+
+ private static byte decodeDigit(byte b) {
+ if (b >= 0x30 && b <= 0x39) {
+ return (byte) (b - 0x30);
+ } else if (b >= 0x41 && b <= 0x46) {
+ return (byte) (b - 0x37);
+ } else if (b >= 0x61 && b <= 0x66) {
+ return (byte) (b - 0x57);
+ } else {
+ throw new IllegalArgumentException("Escape sequence is not hexadecimal: " + (char) b);
+ }
+ }
+
+}
--- /dev/null
+package org.argeo.jcr.fs;
+
+import java.io.IOException;
+import java.nio.file.FileStore;
+import java.nio.file.attribute.FileAttributeView;
+import java.nio.file.attribute.FileStoreAttributeView;
+import java.util.Arrays;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.Workspace;
+
+import org.argeo.api.acr.fs.AbstractFsStore;
+import org.argeo.jcr.JcrUtils;
+
+/** A {@link FileStore} implementation based on JCR {@link Workspace}. */
+public class WorkspaceFileStore extends AbstractFsStore {
+ private final String mountPath;
+ private final Workspace workspace;
+ private final String workspaceName;
+ private final int mountDepth;
+
+ public WorkspaceFileStore(String mountPath, Workspace workspace) {
+ if ("/".equals(mountPath) || "".equals(mountPath))
+ throw new IllegalArgumentException(
+ "Mount path '" + mountPath + "' is unsupported, use null for the base file store");
+ if (mountPath != null && !mountPath.startsWith(JcrPath.separator))
+ throw new IllegalArgumentException("Mount path '" + mountPath + "' cannot end with /");
+ if (mountPath != null && mountPath.endsWith(JcrPath.separator))
+ throw new IllegalArgumentException("Mount path '" + mountPath + "' cannot end with /");
+ this.mountPath = mountPath;
+ if (mountPath == null)
+ mountDepth = 0;
+ else {
+ mountDepth = mountPath.split(JcrPath.separator).length - 1;
+ }
+ this.workspace = workspace;
+ this.workspaceName = workspace.getName();
+ }
+
+ public void close() {
+ JcrUtils.logoutQuietly(workspace.getSession());
+ }
+
+ @Override
+ public String name() {
+ return workspace.getName();
+ }
+
+ @Override
+ public String type() {
+ return "workspace";
+ }
+
+ @Override
+ public boolean isReadOnly() {
+ return false;
+ }
+
+ @Override
+ public long getTotalSpace() throws IOException {
+ return 0;
+ }
+
+ @Override
+ public long getUsableSpace() throws IOException {
+ return 0;
+ }
+
+ @Override
+ public long getUnallocatedSpace() throws IOException {
+ return 0;
+ }
+
+ @Override
+ public boolean supportsFileAttributeView(Class<? extends FileAttributeView> type) {
+ return false;
+ }
+
+ @Override
+ public boolean supportsFileAttributeView(String name) {
+ return false;
+ }
+
+ @Override
+ public <V extends FileStoreAttributeView> V getFileStoreAttributeView(Class<V> type) {
+ return null;
+ }
+
+ @Override
+ public Object getAttribute(String attribute) throws IOException {
+ return workspace.getSession().getRepository().getDescriptor(attribute);
+ }
+
+ public Workspace getWorkspace() {
+ return workspace;
+ }
+
+ public String toFsPath(Node node) throws RepositoryException {
+ String nodeWorkspaceName = node.getSession().getWorkspace().getName();
+ if (!nodeWorkspaceName.equals(workspace.getName()))
+ throw new IllegalArgumentException("Icompatible " + node + " from workspace '" + nodeWorkspaceName
+ + "' in file store '" + workspace.getName() + "'");
+ return mountPath == null ? node.getPath() : mountPath + node.getPath();
+ }
+
+ public boolean isBase() {
+ return mountPath == null;
+ }
+
+ Node toNode(String[] fullPath) throws RepositoryException {
+ String jcrPath = toJcrPath(fullPath);
+ Session session = workspace.getSession();
+ if (!session.itemExists(jcrPath))
+ return null;
+ Node node = session.getNode(jcrPath);
+ return node;
+ }
+
+ String toJcrPath(String fsPath) {
+ if (fsPath.length() == 1)
+ return toJcrPath((String[]) null);// root
+ String[] arr = fsPath.substring(1).split("/");
+// if (arr.length == 0 || (arr.length == 1 && arr[0].equals("")))
+// return toJcrPath((String[]) null);// root
+// else
+ return toJcrPath(arr);
+ }
+
+ private String toJcrPath(String[] path) {
+ if (path == null)
+ return "/";
+ if (path.length < mountDepth)
+ throw new IllegalArgumentException(
+ "Path " + Arrays.asList(path) + " is no compatible with mount " + mountPath);
+
+ if (!isBase()) {
+ // check mount compatibility
+ StringBuilder mount = new StringBuilder();
+ mount.append('/');
+ for (int i = 0; i < mountDepth; i++) {
+ if (i != 0)
+ mount.append('/');
+ mount.append(Text.escapeIllegalJcrChars(path[i]));
+ }
+ if (!mountPath.equals(mount.toString()))
+ throw new IllegalArgumentException(
+ "Path " + Arrays.asList(path) + " is no compatible with mount " + mountPath);
+ }
+
+ StringBuilder sb = new StringBuilder();
+ sb.append('/');
+ for (int i = mountDepth; i < path.length; i++) {
+ if (i != mountDepth)
+ sb.append('/');
+ sb.append(Text.escapeIllegalJcrChars(path[i]));
+ }
+ return sb.toString();
+ }
+
+ public String getMountPath() {
+ return mountPath;
+ }
+
+ public String getWorkspaceName() {
+ return workspaceName;
+ }
+
+ public int getMountDepth() {
+ return mountDepth;
+ }
+
+ @Override
+ public int hashCode() {
+ return workspaceName.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof WorkspaceFileStore))
+ return false;
+ WorkspaceFileStore other = (WorkspaceFileStore) obj;
+ return workspaceName.equals(other.workspaceName);
+ }
+
+ @Override
+ public String toString() {
+ return "WorkspaceFileStore " + workspaceName;
+ }
+
+}
--- /dev/null
+/** Java NIO file system implementation based on plain JCR. */
+package org.argeo.jcr.fs;
\ No newline at end of file
--- /dev/null
+//
+// JCR EXTENSIONS
+//
+<jcrx = "http://www.argeo.org/ns/jcrx">
+
+[jcrx:xmlvalue]
+- *
++ jcr:xmltext (jcrx:xmltext) = jcrx:xmltext
+
+[jcrx:xmltext]
+ - jcr:xmlcharacters (STRING) mandatory
+
+[jcrx:csum]
+mixin
+ - jcrx:sum (STRING) *
+
\ No newline at end of file
--- /dev/null
+/** Generic JCR utilities. */
+package org.argeo.jcr;
\ No newline at end of file
--- /dev/null
+package org.argeo.jcr.proxy;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+
+import javax.jcr.Binary;
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.nodetype.NodeType;
+
+import org.argeo.api.cms.CmsLog;
+import org.argeo.jcr.JcrException;
+import org.argeo.jcr.JcrUtils;
+
+/** Base class for URL based proxys. */
+public abstract class AbstractUrlProxy implements ResourceProxy {
+ private final static CmsLog log = CmsLog.getLog(AbstractUrlProxy.class);
+
+ private Repository jcrRepository;
+ private Session jcrAdminSession;
+ private String proxyWorkspace = "proxy";
+
+ protected abstract Node retrieve(Session session, String path);
+
+ void init() {
+ try {
+ jcrAdminSession = JcrUtils.loginOrCreateWorkspace(jcrRepository, proxyWorkspace);
+ beforeInitSessionSave(jcrAdminSession);
+ if (jcrAdminSession.hasPendingChanges())
+ jcrAdminSession.save();
+ } catch (RepositoryException e) {
+ JcrUtils.discardQuietly(jcrAdminSession);
+ throw new JcrException("Cannot initialize URL proxy", e);
+ }
+ }
+
+ /**
+ * Called before the (admin) session is saved at the end of the initialization.
+ * Does nothing by default, to be overridden.
+ */
+ protected void beforeInitSessionSave(Session session) throws RepositoryException {
+ }
+
+ void destroy() {
+ JcrUtils.logoutQuietly(jcrAdminSession);
+ }
+
+ /**
+ * Called before the (admin) session is logged out when resources are released.
+ * Does nothing by default, to be overridden.
+ */
+ protected void beforeDestroySessionLogout() throws RepositoryException {
+ }
+
+ public Node proxy(String path) {
+ // we open a JCR session with client credentials in order not to use the
+ // admin session in multiple thread or make it a bottleneck.
+ Node nodeAdmin = null;
+ Node nodeClient = null;
+ Session clientSession = null;
+ try {
+ clientSession = jcrRepository.login(proxyWorkspace);
+ if (!clientSession.itemExists(path) || shouldUpdate(clientSession, path)) {
+ nodeAdmin = retrieveAndSave(path);
+ if (nodeAdmin != null)
+ nodeClient = clientSession.getNode(path);
+ } else
+ nodeClient = clientSession.getNode(path);
+ return nodeClient;
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot proxy " + path, e);
+ } finally {
+ if (nodeClient == null)
+ JcrUtils.logoutQuietly(clientSession);
+ }
+ }
+
+ protected synchronized Node retrieveAndSave(String path) {
+ try {
+ Node node = retrieve(jcrAdminSession, path);
+ if (node == null)
+ return null;
+ jcrAdminSession.save();
+ return node;
+ } catch (RepositoryException e) {
+ JcrUtils.discardQuietly(jcrAdminSession);
+ throw new JcrException("Cannot retrieve and save " + path, e);
+ } finally {
+ notifyAll();
+ }
+ }
+
+ /** Session is not saved */
+ protected synchronized Node proxyUrl(Session session, String remoteUrl, String path) throws RepositoryException {
+ Node node = null;
+ if (session.itemExists(path)) {
+ // throw new ArgeoJcrException("Node " + path + " already exists");
+ }
+ try (InputStream in = new URL(remoteUrl).openStream()) {
+ // URL u = new URL(remoteUrl);
+ // in = u.openStream();
+ node = importFile(session, path, in);
+ } catch (IOException e) {
+ if (log.isDebugEnabled()) {
+ log.debug("Cannot read " + remoteUrl + ", skipping... " + e.getMessage());
+ // log.trace("Cannot read because of ", e);
+ }
+ JcrUtils.discardQuietly(session);
+ // } finally {
+ // IOUtils.closeQuietly(in);
+ }
+ return node;
+ }
+
+ protected synchronized Node importFile(Session session, String path, InputStream in) throws RepositoryException {
+ Binary binary = null;
+ try {
+ Node content = null;
+ Node node = null;
+ if (!session.itemExists(path)) {
+ node = JcrUtils.mkdirs(session, path, NodeType.NT_FILE, NodeType.NT_FOLDER, false);
+ content = node.addNode(Node.JCR_CONTENT, NodeType.NT_UNSTRUCTURED);
+ } else {
+ node = session.getNode(path);
+ content = node.getNode(Node.JCR_CONTENT);
+ }
+ binary = session.getValueFactory().createBinary(in);
+ content.setProperty(Property.JCR_DATA, binary);
+ JcrUtils.updateLastModifiedAndParents(node, null, true);
+ return node;
+ } finally {
+ JcrUtils.closeQuietly(binary);
+ }
+ }
+
+ /** Whether the file should be updated. */
+ protected Boolean shouldUpdate(Session clientSession, String nodePath) {
+ return false;
+ }
+
+ public void setJcrRepository(Repository jcrRepository) {
+ this.jcrRepository = jcrRepository;
+ }
+
+ public void setProxyWorkspace(String localWorkspace) {
+ this.proxyWorkspace = localWorkspace;
+ }
+
+}
--- /dev/null
+package org.argeo.jcr.proxy;
+
+import javax.jcr.Node;
+
+/** A proxy which nows how to resolve and synchronize relative URLs */
+public interface ResourceProxy {
+ /**
+ * Proxy the file referenced by this relative path in the underlying
+ * repository. A new session is created by each call, so the underlying
+ * session of the returned node must be closed by the caller.
+ *
+ * @return the proxied Node, <code>null</code> if the resource was not found
+ * (e.g. HTTP 404)
+ */
+ public Node proxy(String relativePath);
+}
--- /dev/null
+package org.argeo.jcr.proxy;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.RepositoryException;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.io.FilenameUtils;
+import org.apache.commons.io.IOUtils;
+import org.argeo.jcr.JcrException;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.jcr.Bin;
+import org.argeo.jcr.JcrUtils;
+
+/** Wraps a proxy via HTTP */
+public class ResourceProxyServlet extends HttpServlet {
+ private static final long serialVersionUID = -8886549549223155801L;
+
+ private final static CmsLog log = CmsLog
+ .getLog(ResourceProxyServlet.class);
+
+ private ResourceProxy proxy;
+
+ private String contentTypeCharset = "UTF-8";
+
+ @Override
+ protected void doGet(HttpServletRequest request,
+ HttpServletResponse response) throws ServletException, IOException {
+ String path = request.getPathInfo();
+
+ if (log.isTraceEnabled()) {
+ log.trace("path=" + path);
+ log.trace("UserPrincipal = " + request.getUserPrincipal().getName());
+ log.trace("SessionID = " + request.getSession(false).getId());
+ log.trace("ContextPath = " + request.getContextPath());
+ log.trace("ServletPath = " + request.getServletPath());
+ log.trace("PathInfo = " + request.getPathInfo());
+ log.trace("Method = " + request.getMethod());
+ log.trace("User-Agent = " + request.getHeader("User-Agent"));
+ }
+
+ Node node = null;
+ try {
+ node = proxy.proxy(path);
+ if (node == null)
+ response.sendError(404);
+ else
+ processResponse(node, response);
+ } finally {
+ if (node != null)
+ try {
+ JcrUtils.logoutQuietly(node.getSession());
+ } catch (RepositoryException e) {
+ // silent
+ }
+ }
+
+ }
+
+ /** Retrieve the content of the node. */
+ protected void processResponse(Node node, HttpServletResponse response) {
+// Binary binary = null;
+// InputStream in = null;
+ try(Bin binary = new Bin( node.getNode(Property.JCR_CONTENT)
+ .getProperty(Property.JCR_DATA));InputStream in = binary.getStream()) {
+ String fileName = node.getName();
+ String ext = FilenameUtils.getExtension(fileName);
+
+ // TODO use a more generic / standard approach
+ // see http://svn.apache.org/viewvc/tomcat/trunk/conf/web.xml
+ String contentType;
+ if ("xml".equals(ext))
+ contentType = "text/xml;charset=" + contentTypeCharset;
+ else if ("jar".equals(ext))
+ contentType = "application/java-archive";
+ else if ("zip".equals(ext))
+ contentType = "application/zip";
+ else if ("gz".equals(ext))
+ contentType = "application/x-gzip";
+ else if ("bz2".equals(ext))
+ contentType = "application/x-bzip2";
+ else if ("tar".equals(ext))
+ contentType = "application/x-tar";
+ else if ("rpm".equals(ext))
+ contentType = "application/x-redhat-package-manager";
+ else
+ contentType = "application/octet-stream";
+ contentType = contentType + ";name=\"" + fileName + "\"";
+ response.setHeader("Content-Disposition", "attachment; filename=\""
+ + fileName + "\"");
+ response.setHeader("Expires", "0");
+ response.setHeader("Cache-Control", "no-cache, must-revalidate");
+ response.setHeader("Pragma", "no-cache");
+
+ response.setContentType(contentType);
+
+ IOUtils.copy(in, response.getOutputStream());
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot download " + node, e);
+ } catch (IOException e) {
+ throw new RuntimeException("Cannot download " + node, e);
+ }
+ }
+
+ public void setProxy(ResourceProxy resourceProxy) {
+ this.proxy = resourceProxy;
+ }
+
+}
--- /dev/null
+/** Components to build proxys based on JCR. */
+package org.argeo.jcr.proxy;
\ No newline at end of file
--- /dev/null
+package org.argeo.jcr.unit;
+
+import java.io.File;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.SimpleCredentials;
+import javax.security.auth.Subject;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+
+import org.apache.commons.io.FileUtils;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.jcr.JcrException;
+
+import junit.framework.TestCase;
+
+/** Base for unit tests with a JCR repository. */
+public abstract class AbstractJcrTestCase extends TestCase {
+ private final static CmsLog log = CmsLog.getLog(AbstractJcrTestCase.class);
+
+ private Repository repository;
+ private Session session = null;
+
+ public final static String LOGIN_CONTEXT_TEST_SYSTEM = "TEST_JACKRABBIT_ADMIN";
+
+ // protected abstract File getRepositoryFile() throws Exception;
+
+ protected abstract Repository createRepository() throws Exception;
+
+ protected abstract void clearRepository(Repository repository) throws Exception;
+
+ @Override
+ protected void setUp() throws Exception {
+ File homeDir = getHomeDir();
+ FileUtils.deleteDirectory(homeDir);
+ repository = createRepository();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ if (session != null) {
+ session.logout();
+ if (log.isTraceEnabled())
+ log.trace("Logout session");
+ }
+ clearRepository(repository);
+ }
+
+ protected Session session() {
+ if (session != null && session.isLive())
+ return session;
+ Session session;
+ if (getLoginContext() != null) {
+ LoginContext lc;
+ try {
+ lc = new LoginContext(getLoginContext());
+ lc.login();
+ } catch (LoginException e) {
+ throw new IllegalStateException("JAAS login failed", e);
+ }
+ session = Subject.doAs(lc.getSubject(), new PrivilegedAction<Session>() {
+
+ @Override
+ public Session run() {
+ return login();
+ }
+
+ });
+ } else
+ session = login();
+ this.session = session;
+ return this.session;
+ }
+
+ protected String getLoginContext() {
+ return null;
+ }
+
+ protected Session login() {
+ try {
+ if (log.isTraceEnabled())
+ log.trace("Login session");
+ Subject subject = Subject.getSubject(AccessController.getContext());
+ if (subject != null)
+ return getRepository().login();
+ else
+ return getRepository().login(new SimpleCredentials("demo", "demo".toCharArray()));
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot login to repository", e);
+ }
+ }
+
+ protected Repository getRepository() {
+ return repository;
+ }
+
+ /**
+ * enables children class to set an existing repository in case it is not
+ * deleted on startup, to test migration by instance
+ */
+ public void setRepository(Repository repository) {
+ this.repository = repository;
+ }
+
+ protected File getHomeDir() {
+ File homeDir = new File(System.getProperty("java.io.tmpdir"),
+ AbstractJcrTestCase.class.getSimpleName() + "-" + System.getProperty("user.name"));
+ return homeDir;
+ }
+
+}
--- /dev/null
+/** Helpers for unit tests with JCR repositories. */
+package org.argeo.jcr.unit;
\ No newline at end of file
--- /dev/null
+package org.argeo.jcr.xml;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.Map;
+import java.util.TreeMap;
+
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.Property;
+import javax.jcr.PropertyIterator;
+import javax.jcr.RepositoryException;
+import javax.jcr.Value;
+import javax.jcr.nodetype.NodeType;
+
+import org.argeo.jcr.Jcr;
+
+/** Utilities around JCR and XML. */
+public class JcrXmlUtils {
+ /**
+ * Convenience method calling {@link #toXmlElements(Writer, Node, boolean)} with
+ * <code>false</code>.
+ */
+ public static void toXmlElements(Writer writer, Node node) throws RepositoryException, IOException {
+ toXmlElements(writer, node, null, false, false, false);
+ }
+
+ /**
+ * Write JCR properties as XML elements in a tree structure whose elements are
+ * named by node primary type.
+ *
+ * @param writer the writer to use
+ * @param node the subtree
+ * @param depth maximal depth, or if <code>null</code> the whole
+ * subtree. It must be positive, with depth 0
+ * describing just the node without its children.
+ * @param withMetadata whether to write the primary type and mixins as
+ * elements
+ * @param withPrefix whether to keep the namespace prefixes
+ * @param propertiesAsElements whether single properties should be written as
+ * elements rather than attributes. If
+ * <code>false</code>, multiple properties will be
+ * skipped.
+ */
+ public static void toXmlElements(Writer writer, Node node, Integer depth, boolean withMetadata, boolean withPrefix,
+ boolean propertiesAsElements) throws RepositoryException, IOException {
+ if (depth != null && depth < 0)
+ throw new IllegalArgumentException("Depth " + depth + " is negative.");
+
+ if (node.getName().equals(Jcr.JCR_XMLTEXT)) {
+ writer.write(node.getProperty(Jcr.JCR_XMLCHARACTERS).getString());
+ return;
+ }
+
+ if (!propertiesAsElements) {
+ Map<String, String> attrs = new TreeMap<>();
+ PropertyIterator pit = node.getProperties();
+ properties: while (pit.hasNext()) {
+ Property p = pit.nextProperty();
+ if (!p.isMultiple()) {
+ String pName = p.getName();
+ if (!withMetadata && (pName.equals(Jcr.JCR_PRIMARY_TYPE) || pName.equals(Jcr.JCR_UUID)
+ || pName.equals(Jcr.JCR_CREATED) || pName.equals(Jcr.JCR_CREATED_BY)
+ || pName.equals(Jcr.JCR_LAST_MODIFIED) || pName.equals(Jcr.JCR_LAST_MODIFIED_BY)))
+ continue properties;
+ attrs.put(withPrefix(p.getName(), withPrefix), p.getString());
+ }
+ }
+ if (withMetadata && node.hasProperty(Property.JCR_UUID))
+ attrs.put("id", "urn:uuid:" + node.getProperty(Property.JCR_UUID).getString());
+ attrs.put(withPrefix ? Jcr.JCR_NAME : "name", node.getName());
+ writeStart(writer, withPrefix(node.getPrimaryNodeType().getName(), withPrefix), attrs, node.hasNodes());
+ } else {
+ if (withMetadata && node.hasProperty(Property.JCR_UUID)) {
+ writeStart(writer, withPrefix(node.getPrimaryNodeType().getName(), withPrefix), "id",
+ "urn:uuid:" + node.getProperty(Property.JCR_UUID).getString());
+ } else {
+ writeStart(writer, withPrefix(node.getPrimaryNodeType().getName(), withPrefix));
+ }
+ // name
+ writeStart(writer, withPrefix ? Jcr.JCR_NAME : "name");
+ writer.append(node.getName());
+ writeEnd(writer, withPrefix ? Jcr.JCR_NAME : "name");
+ }
+
+ // mixins
+ if (withMetadata) {
+ for (NodeType mixin : node.getMixinNodeTypes()) {
+ writeStart(writer, withPrefix ? Jcr.JCR_MIXIN_TYPES : "mixinTypes");
+ writer.append(mixin.getName());
+ writeEnd(writer, withPrefix ? Jcr.JCR_MIXIN_TYPES : "mixinTypes");
+ }
+ }
+
+ // properties as elements
+ if (propertiesAsElements) {
+ PropertyIterator pit = node.getProperties();
+ properties: while (pit.hasNext()) {
+ Property p = pit.nextProperty();
+ if (p.isMultiple()) {
+ for (Value value : p.getValues()) {
+ writeStart(writer, withPrefix(p.getName(), withPrefix));
+ writer.write(value.getString());
+ writeEnd(writer, withPrefix(p.getName(), withPrefix));
+ }
+ } else {
+ Value value = p.getValue();
+ String pName = p.getName();
+ if (!withMetadata && (pName.equals(Jcr.JCR_PRIMARY_TYPE) || pName.equals(Jcr.JCR_UUID)
+ || pName.equals(Jcr.JCR_CREATED) || pName.equals(Jcr.JCR_CREATED_BY)
+ || pName.equals(Jcr.JCR_LAST_MODIFIED) || pName.equals(Jcr.JCR_LAST_MODIFIED_BY)))
+ continue properties;
+ writeStart(writer, withPrefix(p.getName(), withPrefix));
+ writer.write(value.getString());
+ writeEnd(writer, withPrefix(p.getName(), withPrefix));
+ }
+ }
+ }
+
+ // children
+ if (node.hasNodes()) {
+ if (depth == null || depth > 0) {
+ NodeIterator nit = node.getNodes();
+ while (nit.hasNext()) {
+ toXmlElements(writer, nit.nextNode(), depth == null ? null : depth - 1, withMetadata, withPrefix,
+ propertiesAsElements);
+ }
+ }
+ writeEnd(writer, withPrefix(node.getPrimaryNodeType().getName(), withPrefix));
+ }
+ }
+
+ private static String withPrefix(String str, boolean withPrefix) {
+ if (withPrefix)
+ return str;
+ int index = str.indexOf(':');
+ if (index < 0)
+ return str;
+ return str.substring(index + 1);
+ }
+
+ private static void writeStart(Writer writer, String tagName) throws IOException {
+ writer.append('<');
+ writer.append(tagName);
+ writer.append('>');
+ }
+
+ private static void writeStart(Writer writer, String tagName, String attr, String value) throws IOException {
+ writer.append('<');
+ writer.append(tagName);
+ writer.append(' ');
+ writer.append(attr);
+ writer.append("=\"");
+ writer.append(value);
+ writer.append("\">");
+ }
+
+ private static void writeStart(Writer writer, String tagName, Map<String, String> attrs, boolean hasChildren)
+ throws IOException {
+ writer.append('<');
+ writer.append(tagName);
+ for (String attr : attrs.keySet()) {
+ writer.append(' ');
+ writer.append(attr);
+ writer.append("=\"");
+ writer.append(attrs.get(attr));
+ writer.append('\"');
+ }
+ if (hasChildren)
+ writer.append('>');
+ else
+ writer.append("/>");
+ }
+
+ private static void writeEnd(Writer writer, String tagName) throws IOException {
+ writer.append("</");
+ writer.append(tagName);
+ writer.append('>');
+ }
+
+ /** Singleton. */
+ private JcrXmlUtils() {
+
+ }
+
+}
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" ?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
+ <xsl:output method="xml" indent="yes"/>
+ <xsl:template match="/|comment()|processing-instruction()">
+ <xsl:copy>
+ <xsl:apply-templates/>
+ </xsl:copy>
+ </xsl:template>
+ <xsl:template match="*">
+ <xsl:element name="{local-name()}">
+ <xsl:apply-templates select="@*|node()"/>
+ </xsl:element>
+ </xsl:template>
+ <xsl:template match="@*">
+ <xsl:attribute name="{local-name()}">
+ <xsl:value-of select="."/>
+ </xsl:attribute>
+ </xsl:template>
+</xsl:stylesheet>
\ No newline at end of file
--- /dev/null
+package org.argeo.maintenance;
+
+import java.io.IOException;
+import java.util.EnumSet;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.jcr.NoSuchWorkspaceException;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.argeo.api.cms.CmsLog;
+import org.argeo.cms.jcr.CmsJcrUtils;
+import org.argeo.jcr.Jcr;
+import org.argeo.jcr.JcrUtils;
+import org.argeo.osgi.transaction.WorkTransaction;
+import org.argeo.util.naming.Distinguished;
+import org.osgi.service.useradmin.Group;
+import org.osgi.service.useradmin.Role;
+import org.osgi.service.useradmin.UserAdmin;
+
+/** Make sure roles and access rights are properly configured. */
+public abstract class AbstractMaintenanceService {
+ private final static CmsLog log = CmsLog.getLog(AbstractMaintenanceService.class);
+
+ private Repository repository;
+// private UserAdminService userAdminService;
+ private UserAdmin userAdmin;
+ private WorkTransaction userTransaction;
+
+ public void init() {
+ makeSureRolesExists(getRequiredRoles());
+ configureStandardRoles();
+
+ Set<String> workspaceNames = getWorkspaceNames();
+ if (workspaceNames == null || workspaceNames.isEmpty()) {
+ configureJcr(repository, null);
+ } else {
+ for (String workspaceName : workspaceNames)
+ configureJcr(repository, workspaceName);
+ }
+ }
+
+ /** Configures a workspace. */
+ protected void configureJcr(Repository repository, String workspaceName) {
+ Session adminSession;
+ try {
+ adminSession = CmsJcrUtils.openDataAdminSession(repository, workspaceName);
+ } catch (RuntimeException e1) {
+ if (e1.getCause() != null && e1.getCause() instanceof NoSuchWorkspaceException) {
+ Session defaultAdminSession = CmsJcrUtils.openDataAdminSession(repository, null);
+ try {
+ defaultAdminSession.getWorkspace().createWorkspace(workspaceName);
+ log.info("Created JCR workspace " + workspaceName);
+ } catch (RepositoryException e) {
+ throw new IllegalStateException("Cannot create workspace " + workspaceName, e);
+ } finally {
+ Jcr.logout(defaultAdminSession);
+ }
+ adminSession = CmsJcrUtils.openDataAdminSession(repository, workspaceName);
+ } else
+ throw e1;
+ }
+ try {
+ if (prepareJcrTree(adminSession)) {
+ configurePrivileges(adminSession);
+ }
+ } catch (RepositoryException | IOException e) {
+ throw new IllegalStateException("Cannot initialise JCR data layer.", e);
+ } finally {
+ JcrUtils.logoutQuietly(adminSession);
+ }
+ }
+
+ /** To be overridden. */
+ protected Set<String> getWorkspaceNames() {
+ return null;
+ }
+
+ /**
+ * To be overridden in order to programmatically set relationships between
+ * roles. Does nothing by default.
+ */
+ protected void configureStandardRoles() {
+ }
+
+ /**
+ * Creates the base JCR tree structure expected for this app if necessary.
+ *
+ * Expects a clean session ({@link Session#hasPendingChanges()} should return
+ * false) and saves it once the changes have been done. Thus the session can be
+ * rolled back if an exception occurs.
+ *
+ * @return true if something as been updated
+ */
+ public boolean prepareJcrTree(Session adminSession) throws RepositoryException, IOException {
+ return false;
+ }
+
+ /**
+ * Adds app specific default privileges.
+ *
+ * Expects a clean session ({@link Session#hasPendingChanges()} should return
+ * false} and saves it once the changes have been done. Thus the session can be
+ * rolled back if an exception occurs.
+ *
+ * Warning: no check is done and corresponding privileges are always added, so
+ * only call this when necessary
+ */
+ public void configurePrivileges(Session session) throws RepositoryException {
+ }
+
+ /** The system roles that must be available in the system. */
+ protected Set<String> getRequiredRoles() {
+ return new HashSet<>();
+ }
+
+ public void destroy() {
+
+ }
+
+ /*
+ * UTILITIES
+ */
+
+ /** Create these roles as group if they don't exist. */
+ protected void makeSureRolesExists(EnumSet<? extends Distinguished> enumSet) {
+ makeSureRolesExists(Distinguished.enumToDns(enumSet));
+ }
+
+ /** Create these roles as group if they don't exist. */
+ protected void makeSureRolesExists(Set<String> requiredRoles) {
+ if (requiredRoles == null)
+ return;
+ if (getUserAdmin() == null) {
+ log.warn("No user admin service available, cannot make sure that role exists");
+ return;
+ }
+ for (String role : requiredRoles) {
+ Role systemRole = getUserAdmin().getRole(role);
+ if (systemRole == null) {
+ try {
+ getUserTransaction().begin();
+ getUserAdmin().createRole(role, Role.GROUP);
+ getUserTransaction().commit();
+ log.info("Created role " + role);
+ } catch (Exception e) {
+ try {
+ getUserTransaction().rollback();
+ } catch (Exception e1) {
+ // silent
+ }
+ throw new IllegalStateException("Cannot create role " + role, e);
+ }
+ }
+ }
+ }
+
+ /** Add a user or group to a group. */
+ protected void addToGroup(String groupToAddDn, String groupDn) {
+ if (groupToAddDn.contentEquals(groupDn)) {
+ if (log.isTraceEnabled())
+ log.trace("Ignore adding group " + groupDn + " to itself");
+ return;
+ }
+
+ if (getUserAdmin() == null) {
+ log.warn("No user admin service available, cannot add group " + groupToAddDn + " to " + groupDn);
+ return;
+ }
+ Group groupToAdd = (Group) getUserAdmin().getRole(groupToAddDn);
+ if (groupToAdd == null)
+ throw new IllegalArgumentException("Group " + groupToAddDn + " not found");
+ Group group = (Group) getUserAdmin().getRole(groupDn);
+ if (group == null)
+ throw new IllegalArgumentException("Group " + groupDn + " not found");
+ try {
+ getUserTransaction().begin();
+ if (group.addMember(groupToAdd))
+ log.info("Added " + groupToAddDn + " to " + group);
+ getUserTransaction().commit();
+ } catch (Exception e) {
+ try {
+ getUserTransaction().rollback();
+ } catch (Exception e1) {
+ // silent
+ }
+ throw new IllegalStateException("Cannot add " + groupToAddDn + " to " + groupDn);
+ }
+ }
+
+ /*
+ * DEPENDENCY INJECTION
+ */
+ public void setRepository(Repository repository) {
+ this.repository = repository;
+ }
+
+// public void setUserAdminService(UserAdminService userAdminService) {
+// this.userAdminService = userAdminService;
+// }
+
+ protected WorkTransaction getUserTransaction() {
+ return userTransaction;
+ }
+
+ protected UserAdmin getUserAdmin() {
+ return userAdmin;
+ }
+
+ public void setUserAdmin(UserAdmin userAdmin) {
+ this.userAdmin = userAdmin;
+ }
+
+ public void setUserTransaction(WorkTransaction userTransaction) {
+ this.userTransaction = userTransaction;
+ }
+
+}
--- /dev/null
+package org.argeo.maintenance;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import javax.naming.InvalidNameException;
+import javax.naming.ldap.LdapName;
+
+import org.argeo.api.cms.CmsLog;
+import org.argeo.osgi.transaction.WorkTransaction;
+import org.osgi.service.useradmin.Role;
+import org.osgi.service.useradmin.UserAdmin;
+
+/**
+ * Register one or many roles via a user admin service. Does nothing if the role
+ * is already registered.
+ */
+public class SimpleRoleRegistration implements Runnable {
+ private final static CmsLog log = CmsLog.getLog(SimpleRoleRegistration.class);
+
+ private String role;
+ private List<String> roles = new ArrayList<String>();
+ private UserAdmin userAdmin;
+ private WorkTransaction userTransaction;
+
+ @Override
+ public void run() {
+ try {
+ userTransaction.begin();
+ if (role != null && !roleExists(role))
+ newRole(toDn(role));
+
+ for (String r : roles)
+ if (!roleExists(r))
+ newRole(toDn(r));
+ userTransaction.commit();
+ } catch (Exception e) {
+ try {
+ userTransaction.rollback();
+ } catch (Exception e1) {
+ log.error("Cannot rollback", e1);
+ }
+ throw new IllegalArgumentException("Cannot add roles", e);
+ }
+ }
+
+ private boolean roleExists(String role) {
+ return userAdmin.getRole(toDn(role).toString()) != null;
+ }
+
+ protected void newRole(LdapName r) {
+ userAdmin.createRole(r.toString(), Role.GROUP);
+ log.info("Added role " + r + " required by application.");
+ }
+
+ public void register(UserAdmin userAdminService, Map<?, ?> properties) {
+ this.userAdmin = userAdminService;
+ run();
+ }
+
+ protected LdapName toDn(String name) {
+ try {
+ return new LdapName("cn=" + name + ",ou=roles,ou=node");
+ } catch (InvalidNameException e) {
+ throw new IllegalArgumentException("Badly formatted role name " + name, e);
+ }
+ }
+
+ public void setRole(String role) {
+ this.role = role;
+ }
+
+ public void setRoles(List<String> roles) {
+ this.roles = roles;
+ }
+
+ public void setUserAdmin(UserAdmin userAdminService) {
+ this.userAdmin = userAdminService;
+ }
+
+ public void setUserTransaction(WorkTransaction userTransaction) {
+ this.userTransaction = userTransaction;
+ }
+
+}
--- /dev/null
+package org.argeo.maintenance.backup;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Writer;
+import java.util.Arrays;
+import java.util.Base64;
+import java.util.Set;
+import java.util.TreeSet;
+
+import javax.jcr.Binary;
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.apache.commons.io.IOUtils;
+import org.argeo.jcr.Jcr;
+import org.argeo.jcr.JcrException;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+/** XML handler serialising a JCR system view. */
+public class BackupContentHandler extends DefaultHandler {
+ final static int MAX_DEPTH = 1024;
+ final static String SV_NAMESPACE_URI = "http://www.jcp.org/jcr/sv/1.0";
+ final static String SV_PREFIX = "sv";
+ // elements
+ final static String NODE = "node";
+ final static String PROPERTY = "property";
+ final static String VALUE = "value";
+ // attributes
+ final static String NAME = "name";
+ final static String MULTIPLE = "multiple";
+ final static String TYPE = "type";
+
+ // values
+ final static String BINARY = "Binary";
+ final static String JCR_CONTENT = "jcr:content";
+
+ private Writer out;
+ private Session session;
+ private Set<String> contentPaths = new TreeSet<>();
+
+ boolean prettyPrint = true;
+
+ private final String parentPath;
+
+// private boolean inSystem = false;
+
+ public BackupContentHandler(Writer out, Node node) {
+ super();
+ this.out = out;
+ this.session = Jcr.getSession(node);
+ parentPath = Jcr.getParentPath(node);
+ }
+
+ private int currentDepth = -1;
+ private String[] currentPath = new String[MAX_DEPTH];
+
+ private boolean currentPropertyIsMultiple = false;
+ private String currentEncoded = null;
+ private Base64.Encoder base64encore = Base64.getEncoder();
+
+ @Override
+ public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
+ boolean isNode;
+ boolean isProperty;
+ switch (localName) {
+ case NODE:
+ isNode = true;
+ isProperty = false;
+ break;
+ case PROPERTY:
+ isNode = false;
+ isProperty = true;
+ break;
+ default:
+ isNode = false;
+ isProperty = false;
+ }
+
+ if (isNode) {
+ String nodeName = attributes.getValue(SV_NAMESPACE_URI, NAME);
+ currentDepth = currentDepth + 1;
+// if (currentDepth >= 0)
+ currentPath[currentDepth] = nodeName;
+// System.out.println(getCurrentPath() + " , depth=" + currentDepth);
+// if ("jcr:system".equals(nodeName)) {
+// inSystem = true;
+// }
+ }
+// if (inSystem)
+// return;
+
+ if (SV_NAMESPACE_URI.equals(uri))
+ try {
+ if (prettyPrint) {
+ if (isNode) {
+ out.write(spaces());
+ out.write("<!-- ");
+ out.write(getCurrentJcrPath());
+ out.write(" -->\n");
+ out.write(spaces());
+ } else if (isProperty)
+ out.write(spaces());
+ else if (currentPropertyIsMultiple)
+ out.write(spaces());
+ }
+
+ out.write("<");
+ out.write(SV_PREFIX + ":" + localName);
+ if (isProperty)
+ currentPropertyIsMultiple = false; // always reset
+ for (int i = 0; i < attributes.getLength(); i++) {
+ String ns = attributes.getURI(i);
+ if (SV_NAMESPACE_URI.equals(ns)) {
+ String attrName = attributes.getLocalName(i);
+ String attrValue = attributes.getValue(i);
+ out.write(" ");
+ out.write(SV_PREFIX + ":" + attrName);
+ out.write("=");
+ out.write("\"");
+ out.write(attrValue);
+ out.write("\"");
+ if (isProperty) {
+ if (MULTIPLE.equals(attrName))
+ currentPropertyIsMultiple = Boolean.parseBoolean(attrValue);
+ else if (TYPE.equals(attrName)) {
+ if (BINARY.equals(attrValue)) {
+ if (JCR_CONTENT.equals(getCurrentName())) {
+ contentPaths.add(getCurrentJcrPath());
+ } else {
+ Binary binary = session.getNode(getCurrentJcrPath()).getProperty(attrName)
+ .getBinary();
+ try (InputStream in = binary.getStream()) {
+ currentEncoded = base64encore.encodeToString(IOUtils.toByteArray(in));
+ } finally {
+
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ if (isNode && currentDepth == 0) {
+ // out.write(" xmlns=\"" + SV_NAMESPACE_URI + "\"");
+ out.write(" xmlns:" + SV_PREFIX + "=\"" + SV_NAMESPACE_URI + "\"");
+ }
+ out.write(">");
+
+ if (prettyPrint)
+ if (isNode)
+ out.write("\n");
+ else if (isProperty && currentPropertyIsMultiple)
+ out.write("\n");
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ } catch (RepositoryException e) {
+ throw new JcrException(e);
+ }
+ }
+
+ @Override
+ public void endElement(String uri, String localName, String qName) throws SAXException {
+ boolean isNode = localName.equals(NODE);
+ boolean isValue = localName.equals(VALUE);
+ if (prettyPrint)
+ if (!isValue)
+ try {
+ if (isNode || currentPropertyIsMultiple)
+ out.write(spaces());
+ } catch (IOException e1) {
+ throw new RuntimeException(e1);
+ }
+ if (isNode) {
+// System.out.println("endElement " + getCurrentPath() + " , depth=" + currentDepth);
+// if (currentDepth > 0)
+ currentPath[currentDepth] = null;
+ currentDepth = currentDepth - 1;
+// if (inSystem) {
+// // System.out.println("Skip " + getCurrentPath()+" ,
+// // currentDepth="+currentDepth);
+// if (currentDepth == 0) {
+// inSystem = false;
+// return;
+// }
+// }
+ }
+// if (inSystem)
+// return;
+ if (SV_NAMESPACE_URI.equals(uri))
+ try {
+ if (isValue && currentEncoded != null) {
+ out.write(currentEncoded);
+ }
+ currentEncoded = null;
+ out.write("</");
+ out.write(SV_PREFIX + ":" + localName);
+ out.write(">");
+ if (prettyPrint)
+ if (!isValue)
+ out.write("\n");
+ else {
+ if (currentPropertyIsMultiple)
+ out.write("\n");
+ }
+ if (currentDepth == 0)
+ out.flush();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+
+ }
+
+ private char[] spaces() {
+ char[] arr = new char[currentDepth];
+ Arrays.fill(arr, ' ');
+ return arr;
+ }
+
+ @Override
+ public void characters(char[] ch, int start, int length) throws SAXException {
+// if (inSystem)
+// return;
+ try {
+ out.write(ch, start, length);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ protected String getCurrentName() {
+ assert currentDepth >= 0;
+// if (currentDepth == 0)
+// return "jcr:root";
+ return currentPath[currentDepth];
+ }
+
+ protected String getCurrentJcrPath() {
+// if (currentDepth == 0)
+// return "/";
+ StringBuilder sb = new StringBuilder(parentPath.equals("/") ? "" : parentPath);
+ for (int i = 0; i <= currentDepth; i++) {
+// if (i != 0)
+ sb.append('/');
+ sb.append(currentPath[i]);
+ }
+ return sb.toString();
+ }
+
+ public Set<String> getContentPaths() {
+ return contentPaths;
+ }
+
+}
--- /dev/null
+package org.argeo.maintenance.backup;
+
+import java.io.BufferedWriter;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.net.URI;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.jar.JarOutputStream;
+import java.util.jar.Manifest;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipException;
+import java.util.zip.ZipOutputStream;
+
+import javax.jcr.Binary;
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.Property;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.RepositoryFactory;
+import javax.jcr.Session;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.jackrabbit.api.JackrabbitSession;
+import org.apache.jackrabbit.api.JackrabbitValue;
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.cms.jcr.CmsJcrUtils;
+import org.argeo.jackrabbit.client.ClientDavexRepositoryFactory;
+import org.argeo.jcr.Jcr;
+import org.argeo.jcr.JcrException;
+import org.argeo.jcr.JcrUtils;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+
+/**
+ * Performs a backup of the data based only on programmatic interfaces. Useful
+ * for migration or live backup. Physical backups of the underlying file
+ * systems, databases, LDAP servers, etc. should be performed for disaster
+ * recovery.
+ */
+public class LogicalBackup implements Runnable {
+ private final static CmsLog log = CmsLog.getLog(LogicalBackup.class);
+
+ public final static String WORKSPACES_BASE = "workspaces/";
+ public final static String FILES_BASE = "files/";
+ public final static String OSGI_BASE = "share/osgi/";
+
+ public final static String JCR_SYSTEM = "jcr:system";
+ public final static String JCR_VERSION_STORAGE_PATH = "/jcr:system/jcr:versionStorage";
+
+ private final Repository repository;
+ private String defaultWorkspace;
+ private final BundleContext bundleContext;
+
+ private final ZipOutputStream zout;
+ private final Path basePath;
+
+ private ExecutorService executorService;
+
+ private boolean performSoftwareBackup = false;
+
+ private Map<String, String> checksums = new TreeMap<>();
+
+ private int threadCount = 5;
+
+ private boolean backupFailed = false;
+
+ public LogicalBackup(BundleContext bundleContext, Repository repository, Path basePath) {
+ this.repository = repository;
+ this.zout = null;
+ this.basePath = basePath;
+ this.bundleContext = bundleContext;
+ }
+
+ @Override
+ public void run() {
+ try {
+ log.info("Start logical backup to " + basePath);
+ perform();
+ } catch (Exception e) {
+ log.error("Unexpected exception when performing logical backup", e);
+ throw new IllegalStateException("Logical backup failed", e);
+ }
+
+ }
+
+ public void perform() throws RepositoryException, IOException {
+ if (executorService != null && !executorService.isTerminated())
+ throw new IllegalStateException("Another backup is running");
+ executorService = Executors.newFixedThreadPool(threadCount);
+ long begin = System.currentTimeMillis();
+ // software backup
+ if (bundleContext != null && performSoftwareBackup)
+ executorService.submit(() -> performSoftwareBackup(bundleContext));
+
+ // data backup
+ Session defaultSession = login(null);
+ defaultWorkspace = defaultSession.getWorkspace().getName();
+ try {
+ String[] workspaceNames = defaultSession.getWorkspace().getAccessibleWorkspaceNames();
+ workspaces: for (String workspaceName : workspaceNames) {
+ if ("security".equals(workspaceName))
+ continue workspaces;
+ performDataBackup(workspaceName);
+ }
+ } finally {
+ JcrUtils.logoutQuietly(defaultSession);
+ executorService.shutdown();
+ try {
+ executorService.awaitTermination(24, TimeUnit.HOURS);
+ } catch (InterruptedException e) {
+ // silent
+ throw new IllegalStateException("Backup was interrupted before completion", e);
+ }
+ }
+ // versions
+ executorService = Executors.newFixedThreadPool(threadCount);
+ try {
+ performVersionsBackup();
+ } finally {
+ executorService.shutdown();
+ try {
+ executorService.awaitTermination(24, TimeUnit.HOURS);
+ } catch (InterruptedException e) {
+ // silent
+ throw new IllegalStateException("Backup was interrupted before completion", e);
+ }
+ }
+ long duration = System.currentTimeMillis() - begin;
+ if (isBackupFailed())
+ log.info("System logical backup failed after " + (duration / 60000) + "min " + (duration / 1000) + "s");
+ else
+ log.info("System logical backup completed in " + (duration / 60000) + "min " + (duration / 1000) + "s");
+ }
+
+ protected void performDataBackup(String workspaceName) throws RepositoryException, IOException {
+ Session session = login(workspaceName);
+ try {
+ nodes: for (NodeIterator nit = session.getRootNode().getNodes(); nit.hasNext();) {
+ if (isBackupFailed())
+ return;
+ Node nodeToExport = nit.nextNode();
+ if (JCR_SYSTEM.equals(nodeToExport.getName()))
+ continue nodes;
+ String nodePath = nodeToExport.getPath();
+ Future<Set<String>> contentPathsFuture = executorService
+ .submit(() -> performNodeBackup(workspaceName, nodePath));
+ executorService.submit(() -> performFilesBackup(workspaceName, contentPathsFuture));
+ }
+ } finally {
+ Jcr.logout(session);
+ }
+ }
+
+ protected void performVersionsBackup() throws RepositoryException, IOException {
+ Session session = login(defaultWorkspace);
+ Node versionStorageNode = session.getNode(JCR_VERSION_STORAGE_PATH);
+ try {
+ for (NodeIterator nit = versionStorageNode.getNodes(); nit.hasNext();) {
+ Node nodeToExport = nit.nextNode();
+ String nodePath = nodeToExport.getPath();
+ if (isBackupFailed())
+ return;
+ Future<Set<String>> contentPathsFuture = executorService
+ .submit(() -> performNodeBackup(defaultWorkspace, nodePath));
+ executorService.submit(() -> performFilesBackup(defaultWorkspace, contentPathsFuture));
+ }
+ } finally {
+ Jcr.logout(session);
+ }
+
+ }
+
+ protected Set<String> performNodeBackup(String workspaceName, String nodePath) {
+ Session session = login(workspaceName);
+ try {
+ Node nodeToExport = session.getNode(nodePath);
+// String nodeName = nodeToExport.getName();
+// if (nodeName.startsWith("jcr:") || nodeName.startsWith("rep:"))
+// continue nodes;
+// // TODO make it more robust / configurable
+// if (nodeName.equals("user"))
+// continue nodes;
+ String relativePath = WORKSPACES_BASE + workspaceName + nodePath + ".xml";
+ OutputStream xmlOut = openOutputStream(relativePath);
+ BackupContentHandler contentHandler;
+ try (Writer writer = new BufferedWriter(new OutputStreamWriter(xmlOut, StandardCharsets.UTF_8))) {
+ contentHandler = new BackupContentHandler(writer, nodeToExport);
+ session.exportSystemView(nodeToExport.getPath(), contentHandler, true, false);
+ if (log.isDebugEnabled())
+ log.debug(workspaceName + ":" + nodePath + " metadata exported to " + relativePath);
+ }
+
+ // Files
+ Set<String> contentPaths = contentHandler.getContentPaths();
+ return contentPaths;
+ } catch (Exception e) {
+ markBackupFailed("Cannot backup node " + workspaceName + ":" + nodePath, e);
+ throw new ThreadDeath();
+ } finally {
+ Jcr.logout(session);
+ }
+ }
+
+ protected void performFilesBackup(String workspaceName, Future<Set<String>> contentPathsFuture) {
+ Set<String> contentPaths;
+ try {
+ contentPaths = contentPathsFuture.get(24, TimeUnit.HOURS);
+ } catch (InterruptedException | ExecutionException | TimeoutException e1) {
+ markBackupFailed("Cannot retrieve content paths for workspace " + workspaceName, e1);
+ return;
+ }
+ if (contentPaths == null || contentPaths.size() == 0)
+ return;
+ Session session = login(workspaceName);
+ try {
+ String workspacesFilesBasePath = FILES_BASE + workspaceName;
+ for (String path : contentPaths) {
+ if (isBackupFailed())
+ return;
+ Node contentNode = session.getNode(path);
+ Binary binary = null;
+ try {
+ binary = contentNode.getProperty(Property.JCR_DATA).getBinary();
+ String fileRelativePath = workspacesFilesBasePath + contentNode.getParent().getPath();
+
+ // checksum
+ boolean skip = false;
+ String checksum = null;
+ if (session instanceof JackrabbitSession) {
+ JackrabbitValue value = (JackrabbitValue) contentNode.getProperty(Property.JCR_DATA).getValue();
+// ReferenceBinary referenceBinary = (ReferenceBinary) binary;
+ checksum = value.getContentIdentity();
+ }
+ if (checksum != null) {
+ if (!checksums.containsKey(checksum)) {
+ checksums.put(checksum, fileRelativePath);
+ } else {
+ skip = true;
+ String sourcePath = checksums.get(checksum);
+ if (log.isTraceEnabled())
+ log.trace(fileRelativePath + " : already " + sourcePath + " with checksum " + checksum);
+ createLink(sourcePath, fileRelativePath);
+ try (Writer writerSum = new OutputStreamWriter(
+ openOutputStream(fileRelativePath + ".sha256"), StandardCharsets.UTF_8)) {
+ writerSum.write(checksum);
+ }
+ }
+ }
+
+ // copy file
+ if (!skip)
+ try (InputStream in = binary.getStream();
+ OutputStream out = openOutputStream(fileRelativePath)) {
+ IOUtils.copy(in, out);
+ if (log.isTraceEnabled())
+ log.trace("Workspace " + workspaceName + ": file content exported to "
+ + fileRelativePath);
+ }
+ } finally {
+ JcrUtils.closeQuietly(binary);
+ }
+ }
+ if (log.isDebugEnabled())
+ log.debug(workspaceName + ":" + contentPaths.size() + " files exported to " + workspacesFilesBasePath);
+ } catch (Exception e) {
+ markBackupFailed("Cannot backup files from " + workspaceName + ":", e);
+ } finally {
+ Jcr.logout(session);
+ }
+ }
+
+ protected OutputStream openOutputStream(String relativePath) throws IOException {
+ if (zout != null) {
+ ZipEntry entry = new ZipEntry(relativePath);
+ zout.putNextEntry(entry);
+ return zout;
+ } else if (basePath != null) {
+ Path targetPath = basePath.resolve(Paths.get(relativePath));
+ Files.createDirectories(targetPath.getParent());
+ return Files.newOutputStream(targetPath);
+ } else {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ protected void createLink(String source, String target) throws IOException {
+ if (zout != null) {
+ // TODO implement for zip
+ throw new UnsupportedOperationException();
+ } else if (basePath != null) {
+ Path sourcePath = basePath.resolve(Paths.get(source));
+ Path targetPath = basePath.resolve(Paths.get(target));
+ Path relativeSource = targetPath.getParent().relativize(sourcePath);
+ Files.createDirectories(targetPath.getParent());
+ Files.createSymbolicLink(targetPath, relativeSource);
+ } else {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ protected void closeOutputStream(String relativePath, OutputStream out) throws IOException {
+ if (zout != null) {
+ zout.closeEntry();
+ } else if (basePath != null) {
+ out.close();
+ } else {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ protected Session login(String workspaceName) {
+ if (bundleContext != null) {// local
+ return CmsJcrUtils.openDataAdminSession(repository, workspaceName);
+ } else {// remote
+ try {
+ return repository.login(workspaceName);
+ } catch (RepositoryException e) {
+ throw new JcrException(e);
+ }
+ }
+ }
+
+ public final static void main(String[] args) throws Exception {
+ if (args.length == 0) {
+ printUsage("No argument");
+ System.exit(1);
+ }
+ URI uri = new URI(args[0]);
+ Repository repository = createRemoteRepository(uri);
+ Path basePath = args.length > 1 ? Paths.get(args[1]) : Paths.get(System.getProperty("user.dir"));
+ if (!Files.exists(basePath))
+ Files.createDirectories(basePath);
+ LogicalBackup backup = new LogicalBackup(null, repository, basePath);
+ backup.run();
+ }
+
+ private static void printUsage(String errorMessage) {
+ if (errorMessage != null)
+ System.err.println(errorMessage);
+ System.out.println("Usage: LogicalBackup <remote URL> [<target directory>]");
+
+ }
+
+ protected static Repository createRemoteRepository(URI uri) throws RepositoryException {
+ RepositoryFactory repositoryFactory = new ClientDavexRepositoryFactory();
+ Map<String, String> params = new HashMap<String, String>();
+ params.put(ClientDavexRepositoryFactory.JACKRABBIT_DAVEX_URI, uri.toString());
+ // TODO make it configurable
+ params.put(ClientDavexRepositoryFactory.JACKRABBIT_REMOTE_DEFAULT_WORKSPACE, CmsConstants.SYS_WORKSPACE);
+ return repositoryFactory.getRepository(params);
+ }
+
+ public void performSoftwareBackup(BundleContext bundleContext) {
+ String bootBasePath = OSGI_BASE + "boot";
+ Bundle[] bundles = bundleContext.getBundles();
+ for (Bundle bundle : bundles) {
+ String relativePath = bootBasePath + "/" + bundle.getSymbolicName() + ".jar";
+ Dictionary<String, String> headers = bundle.getHeaders();
+ Manifest manifest = new Manifest();
+ Enumeration<String> headerKeys = headers.keys();
+ while (headerKeys.hasMoreElements()) {
+ String headerKey = headerKeys.nextElement();
+ String headerValue = headers.get(headerKey);
+ manifest.getMainAttributes().putValue(headerKey, headerValue);
+ }
+ try (JarOutputStream jarOut = new JarOutputStream(openOutputStream(relativePath), manifest)) {
+ Enumeration<URL> resourcePaths = bundle.findEntries("/", "*", true);
+ resources: while (resourcePaths.hasMoreElements()) {
+ URL entryUrl = resourcePaths.nextElement();
+ String entryPath = entryUrl.getPath();
+ if (entryPath.equals(""))
+ continue resources;
+ if (entryPath.endsWith("/"))
+ continue resources;
+ String entryName = entryPath.substring(1);// remove first '/'
+ if (entryUrl.getPath().equals("/META-INF/"))
+ continue resources;
+ if (entryUrl.getPath().equals("/META-INF/MANIFEST.MF"))
+ continue resources;
+ // dev
+ if (entryUrl.getPath().startsWith("/target"))
+ continue resources;
+ if (entryUrl.getPath().startsWith("/src"))
+ continue resources;
+ if (entryUrl.getPath().startsWith("/ext"))
+ continue resources;
+
+ if (entryName.startsWith("bin/")) {// dev
+ entryName = entryName.substring("bin/".length());
+ }
+
+ ZipEntry entry = new ZipEntry(entryName);
+ try (InputStream in = entryUrl.openStream()) {
+ try {
+ jarOut.putNextEntry(entry);
+ } catch (ZipException e) {// duplicate
+ continue resources;
+ }
+ IOUtils.copy(in, jarOut);
+ jarOut.closeEntry();
+// log.info(entryUrl);
+ } catch (FileNotFoundException e) {
+ log.warn(entryUrl + ": " + e.getMessage());
+ }
+ }
+ } catch (IOException e1) {
+ throw new RuntimeException("Cannot export bundle " + bundle, e1);
+ }
+ }
+ if (log.isDebugEnabled())
+ log.debug(bundles.length + " OSGi bundles exported to " + bootBasePath);
+
+ }
+
+ protected synchronized void markBackupFailed(Object message, Exception e) {
+ log.error(message, e);
+ backupFailed = true;
+ notifyAll();
+ if (executorService != null)
+ executorService.shutdownNow();
+ }
+
+ protected boolean isBackupFailed() {
+ return backupFailed;
+ }
+}
--- /dev/null
+package org.argeo.maintenance.backup;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.DirectoryStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import javax.jcr.ImportUUIDBehavior;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.cms.jcr.CmsJcrUtils;
+import org.argeo.jcr.Jcr;
+import org.argeo.jcr.JcrException;
+import org.argeo.jcr.JcrUtils;
+import org.osgi.framework.BundleContext;
+
+/** Restores a backup in the format defined by {@link LogicalBackup}. */
+public class LogicalRestore implements Runnable {
+ private final static CmsLog log = CmsLog.getLog(LogicalRestore.class);
+
+ private final Repository repository;
+ private final BundleContext bundleContext;
+ private final Path basePath;
+
+ public LogicalRestore(BundleContext bundleContext, Repository repository, Path basePath) {
+ this.repository = repository;
+ this.basePath = basePath;
+ this.bundleContext = bundleContext;
+ }
+
+ @Override
+ public void run() {
+ Path workspaces = basePath.resolve(LogicalBackup.WORKSPACES_BASE);
+ try {
+ // import jcr:system first
+// Session defaultSession = NodeUtils.openDataAdminSession(repository, null);
+// try (DirectoryStream<Path> xmls = Files.newDirectoryStream(
+// workspaces.resolve(NodeConstants.SYS_WORKSPACE + LogicalBackup.JCR_VERSION_STORAGE_PATH),
+// "*.xml")) {
+// for (Path xml : xmls) {
+// try (InputStream in = Files.newInputStream(xml)) {
+// defaultSession.getWorkspace().importXML(LogicalBackup.JCR_VERSION_STORAGE_PATH, in,
+// ImportUUIDBehavior.IMPORT_UUID_COLLISION_REPLACE_EXISTING);
+// if (log.isDebugEnabled())
+// log.debug("Restored " + xml + " to " + defaultSession.getWorkspace().getName() + ":");
+// }
+// }
+// } finally {
+// Jcr.logout(defaultSession);
+// }
+
+ // non-system content
+ try (DirectoryStream<Path> workspaceDirs = Files.newDirectoryStream(workspaces)) {
+ for (Path workspacePath : workspaceDirs) {
+ String workspaceName = workspacePath.getFileName().toString();
+ Session session = JcrUtils.loginOrCreateWorkspace(repository, workspaceName);
+ try (DirectoryStream<Path> xmls = Files.newDirectoryStream(workspacePath, "*.xml")) {
+ xmls: for (Path xml : xmls) {
+ if (xml.getFileName().toString().startsWith("rep:"))
+ continue xmls;
+ try (InputStream in = Files.newInputStream(xml)) {
+ session.getWorkspace().importXML("/", in,
+ ImportUUIDBehavior.IMPORT_UUID_COLLISION_REPLACE_EXISTING);
+ if (log.isDebugEnabled())
+ log.debug("Restored " + xml + " to workspace " + workspaceName);
+ }
+ }
+ } finally {
+ Jcr.logout(session);
+ }
+ }
+ }
+ } catch (IOException e) {
+ throw new RuntimeException("Cannot restore backup from " + basePath, e);
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot restore backup from " + basePath, e);
+ }
+ }
+
+}
--- /dev/null
+/** Argeo Node backup utilities. */
+package org.argeo.maintenance.backup;
\ No newline at end of file
--- /dev/null
+package org.argeo.maintenance.internal;
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import javax.jcr.Repository;
+
+import org.argeo.maintenance.backup.LogicalBackup;
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+
+public class Activator implements BundleActivator {
+
+ @Override
+ public void start(BundleContext context) throws Exception {
+ // Start backup
+ Repository repository = context.getService(context.getServiceReference(Repository.class));
+ Path basePath = Paths.get(System.getProperty("user.dir"), "backup");
+ LogicalBackup backup = new LogicalBackup(context, repository, basePath);
+ backup.run();
+ }
+
+ @Override
+ public void stop(BundleContext context) throws Exception {
+ }
+
+}
--- /dev/null
+/** Utilities for the maintenance of an Argeo Node. */
+package org.argeo.maintenance;
\ No newline at end of file
--- /dev/null
+package org.argeo.security.jackrabbit;
+
+import java.security.Principal;
+import java.util.Map;
+import java.util.Set;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.apache.jackrabbit.core.security.authorization.acl.ACLProvider;
+
+/** Argeo specific access control provider */
+public class ArgeoAccessControlProvider extends ACLProvider {
+
+ @SuppressWarnings({ "rawtypes", "unchecked" })
+ @Override
+ public void init(Session systemSession, Map configuration) throws RepositoryException {
+ if (!configuration.containsKey(PARAM_ALLOW_UNKNOWN_PRINCIPALS))
+ configuration.put(PARAM_ALLOW_UNKNOWN_PRINCIPALS, "true");
+ if (!configuration.containsKey(PARAM_OMIT_DEFAULT_PERMISSIONS))
+ configuration.put(PARAM_OMIT_DEFAULT_PERMISSIONS, "true");
+ super.init(systemSession, configuration);
+ }
+
+ @Override
+ public boolean canAccessRoot(Set<Principal> principals) throws RepositoryException {
+ return super.canAccessRoot(principals);
+ }
+
+}
--- /dev/null
+package org.argeo.security.jackrabbit;
+
+import javax.jcr.PathNotFoundException;
+import javax.jcr.RepositoryException;
+import javax.jcr.security.Privilege;
+
+import org.apache.jackrabbit.core.id.ItemId;
+import org.apache.jackrabbit.core.security.DefaultAccessManager;
+import org.apache.jackrabbit.spi.Path;
+
+/**
+ * Intermediary class in order to have a consistent naming in config files. Does
+ * nothing for the time being, but may in the future.
+ */
+public class ArgeoAccessManager extends DefaultAccessManager {
+
+ @Override
+ public boolean canRead(Path itemPath, ItemId itemId)
+ throws RepositoryException {
+ return super.canRead(itemPath, itemId);
+ }
+
+ @Override
+ public Privilege[] getPrivileges(String absPath)
+ throws PathNotFoundException, RepositoryException {
+ return super.getPrivileges(absPath);
+ }
+
+ @Override
+ public boolean hasPrivileges(String absPath, Privilege[] privileges)
+ throws PathNotFoundException, RepositoryException {
+ return super.hasPrivileges(absPath, privileges);
+ }
+
+}
--- /dev/null
+package org.argeo.security.jackrabbit;
+
+import javax.security.auth.Subject;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+
+import org.apache.jackrabbit.core.security.authentication.AuthContext;
+
+/** Wraps a regular {@link LoginContext}, using the proper class loader. */
+class ArgeoAuthContext implements AuthContext {
+ private LoginContext lc;
+
+ public ArgeoAuthContext(String appName, Subject subject, CallbackHandler callbackHandler) {
+ try {
+ lc = new LoginContext(appName, subject, callbackHandler);
+ } catch (LoginException e) {
+ throw new IllegalStateException("Cannot configure Jackrabbit login context", e);
+ }
+ }
+
+ @Override
+ public void login() throws LoginException {
+ lc.login();
+ }
+
+ @Override
+ public Subject getSubject() {
+ return lc.getSubject();
+ }
+
+ @Override
+ public void logout() throws LoginException {
+ lc.logout();
+ }
+
+}
--- /dev/null
+package org.argeo.security.jackrabbit;
+
+import java.security.Principal;
+import java.util.HashSet;
+import java.util.Properties;
+import java.util.Set;
+
+import javax.jcr.Credentials;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.security.auth.Subject;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.x500.X500Principal;
+
+import org.apache.jackrabbit.api.security.user.UserManager;
+import org.apache.jackrabbit.core.DefaultSecurityManager;
+import org.apache.jackrabbit.core.security.AMContext;
+import org.apache.jackrabbit.core.security.AccessManager;
+import org.apache.jackrabbit.core.security.SecurityConstants;
+import org.apache.jackrabbit.core.security.SystemPrincipal;
+import org.apache.jackrabbit.core.security.authentication.AuthContext;
+import org.apache.jackrabbit.core.security.authentication.CallbackHandlerImpl;
+import org.apache.jackrabbit.core.security.authorization.WorkspaceAccessManager;
+import org.apache.jackrabbit.core.security.principal.AdminPrincipal;
+import org.apache.jackrabbit.core.security.principal.PrincipalProvider;
+import org.argeo.api.cms.CmsSession;
+import org.argeo.api.cms.DataAdminPrincipal;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.api.cms.AnonymousPrincipal;
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.cms.osgi.CmsOsgiUtils;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
+
+/** Customises Jackrabbit security. */
+public class ArgeoSecurityManager extends DefaultSecurityManager {
+ private final static CmsLog log = CmsLog.getLog(ArgeoSecurityManager.class);
+
+ private BundleContext cmsBundleContext = null;
+
+ public ArgeoSecurityManager() {
+ if (FrameworkUtil.getBundle(CmsSession.class) != null) {
+ cmsBundleContext = FrameworkUtil.getBundle(CmsSession.class).getBundleContext();
+ }
+ }
+
+ public AuthContext getAuthContext(Credentials creds, Subject subject, String workspaceName)
+ throws RepositoryException {
+ checkInitialized();
+
+ CallbackHandler cbHandler = new CallbackHandlerImpl(creds, getSystemSession(), getPrincipalProviderRegistry(),
+ adminId, anonymousId);
+ String appName = "Jackrabbit";
+ return new ArgeoAuthContext(appName, subject, cbHandler);
+ }
+
+ @Override
+ public AccessManager getAccessManager(Session session, AMContext amContext) throws RepositoryException {
+ synchronized (getSystemSession()) {
+ return super.getAccessManager(session, amContext);
+ }
+ }
+
+ @Override
+ public UserManager getUserManager(Session session) throws RepositoryException {
+ synchronized (getSystemSession()) {
+ return super.getUserManager(session);
+ }
+ }
+
+ @Override
+ protected PrincipalProvider createDefaultPrincipalProvider(Properties[] moduleConfig) throws RepositoryException {
+ return super.createDefaultPrincipalProvider(moduleConfig);
+ }
+
+ /** Called once when the session is created */
+ @Override
+ public String getUserID(Subject subject, String workspaceName) throws RepositoryException {
+ boolean isAnonymous = !subject.getPrincipals(AnonymousPrincipal.class).isEmpty();
+ boolean isDataAdmin = !subject.getPrincipals(DataAdminPrincipal.class).isEmpty();
+ boolean isJackrabbitSystem = !subject.getPrincipals(SystemPrincipal.class).isEmpty();
+ Set<X500Principal> userPrincipal = subject.getPrincipals(X500Principal.class);
+ boolean isRegularUser = !userPrincipal.isEmpty();
+ CmsSession cmsSession = null;
+ if (cmsBundleContext != null) {
+ cmsSession = CmsOsgiUtils.getCmsSession(cmsBundleContext, subject);
+ if (log.isTraceEnabled())
+ log.trace("Opening JCR session for CMS session " + cmsSession);
+ }
+
+ if (isAnonymous) {
+ if (isDataAdmin || isJackrabbitSystem || isRegularUser)
+ throw new IllegalStateException("Inconsistent " + subject);
+ else
+ return CmsConstants.ROLE_ANONYMOUS;
+ } else if (isRegularUser) {// must be before DataAdmin
+ if (isAnonymous || isJackrabbitSystem)
+ throw new IllegalStateException("Inconsistent " + subject);
+ else {
+ if (userPrincipal.size() > 1) {
+ StringBuilder buf = new StringBuilder();
+ for (X500Principal principal : userPrincipal)
+ buf.append(' ').append('\"').append(principal).append('\"');
+ throw new RuntimeException("Multiple user principals:" + buf);
+ }
+ return userPrincipal.iterator().next().getName();
+ }
+ } else if (isDataAdmin) {
+ if (isAnonymous || isJackrabbitSystem || isRegularUser)
+ throw new IllegalStateException("Inconsistent " + subject);
+ else {
+ assert !subject.getPrincipals(AdminPrincipal.class).isEmpty();
+ return CmsConstants.ROLE_DATA_ADMIN;
+ }
+ } else if (isJackrabbitSystem) {
+ if (isAnonymous || isDataAdmin || isRegularUser)
+ throw new IllegalStateException("Inconsistent " + subject);
+ else
+ return super.getUserID(subject, workspaceName);
+ } else {
+ throw new IllegalStateException("Unrecognized subject type: " + subject);
+ }
+ }
+
+ @Override
+ protected WorkspaceAccessManager createDefaultWorkspaceAccessManager() {
+ WorkspaceAccessManager wam = super.createDefaultWorkspaceAccessManager();
+ ArgeoWorkspaceAccessManagerImpl workspaceAccessManager = new ArgeoWorkspaceAccessManagerImpl(wam);
+ if (log.isTraceEnabled())
+ log.trace("Created workspace access manager");
+ return workspaceAccessManager;
+ }
+
+ private class ArgeoWorkspaceAccessManagerImpl implements SecurityConstants, WorkspaceAccessManager {
+ private final WorkspaceAccessManager wam;
+
+ public ArgeoWorkspaceAccessManagerImpl(WorkspaceAccessManager wam) {
+ super();
+ this.wam = wam;
+ }
+
+ public void init(Session systemSession) throws RepositoryException {
+ wam.init(systemSession);
+ Repository repository = systemSession.getRepository();
+ if (log.isTraceEnabled())
+ log.trace("Initialised workspace access manager on repository " + repository
+ + ", systemSession workspace: " + systemSession.getWorkspace().getName());
+ }
+
+ public void close() throws RepositoryException {
+ }
+
+ public boolean grants(Set<Principal> principals, String workspaceName) throws RepositoryException {
+ // TODO: implements finer access to workspaces
+ if (log.isTraceEnabled())
+ log.trace("Grants " + new HashSet<>(principals) + " access to workspace '" + workspaceName + "'");
+ return true;
+ // return wam.grants(principals, workspaceName);
+ }
+ }
+
+}
--- /dev/null
+package org.argeo.security.jackrabbit;
+
+import java.util.Map;
+import java.util.Set;
+
+import javax.security.auth.Subject;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.login.LoginException;
+import javax.security.auth.spi.LoginModule;
+import javax.security.auth.x500.X500Principal;
+
+import org.apache.jackrabbit.core.security.AnonymousPrincipal;
+import org.apache.jackrabbit.core.security.SecurityConstants;
+import org.apache.jackrabbit.core.security.principal.AdminPrincipal;
+import org.argeo.api.cms.DataAdminPrincipal;
+
+/** JAAS login module used when initiating a new Jackrabbit session. */
+public class SystemJackrabbitLoginModule implements LoginModule {
+ private Subject subject;
+
+ @Override
+ public void initialize(Subject subject, CallbackHandler callbackHandler, Map<String, ?> sharedState,
+ Map<String, ?> options) {
+ this.subject = subject;
+ }
+
+ @Override
+ public boolean login() throws LoginException {
+ return true;
+ }
+
+ @Override
+ public boolean commit() throws LoginException {
+ Set<org.argeo.api.cms.AnonymousPrincipal> anonPrincipal = subject
+ .getPrincipals(org.argeo.api.cms.AnonymousPrincipal.class);
+ if (!anonPrincipal.isEmpty()) {
+ subject.getPrincipals().add(new AnonymousPrincipal());
+ return true;
+ }
+
+ Set<DataAdminPrincipal> initPrincipal = subject.getPrincipals(DataAdminPrincipal.class);
+ if (!initPrincipal.isEmpty()) {
+ subject.getPrincipals().add(new AdminPrincipal(SecurityConstants.ADMIN_ID));
+ return true;
+ }
+
+ Set<X500Principal> userPrincipal = subject.getPrincipals(X500Principal.class);
+ if (userPrincipal.isEmpty())
+ throw new LoginException("Subject must be pre-authenticated");
+ if (userPrincipal.size() > 1)
+ throw new LoginException("Multiple user principals " + userPrincipal);
+
+ return true;
+ }
+
+ @Override
+ public boolean abort() throws LoginException {
+ return true;
+ }
+
+ @Override
+ public boolean logout() throws LoginException {
+ subject.getPrincipals().removeAll(subject.getPrincipals(AnonymousPrincipal.class));
+ subject.getPrincipals().removeAll(subject.getPrincipals(AdminPrincipal.class));
+ return true;
+ }
+}
--- /dev/null
+/** Integration of Jackrabbit with Argeo security model. */
+package org.argeo.security.jackrabbit;
\ 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-11"/>
+ <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.cms.ui</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
+/MANIFEST.MF
--- /dev/null
+Bundle-Activator: org.argeo.cms.ui.internal.Activator
+Bundle-ActivationPolicy: lazy
+
+Import-Package: org.eclipse.swt,\
+org.eclipse.jface.window,\
+org.eclipse.core.commands,\
+javax.jcr.security,\
+org.eclipse.rap.fileupload;version="[2.1,4)",\
+org.eclipse.rap.rwt;version="[2.1,4)",\
+org.eclipse.rap.rwt.application;version="[2.1,4)",\
+org.eclipse.rap.rwt.client;version="[2.1,4)",\
+org.eclipse.rap.rwt.client.service;version="[2.1,4)",\
+org.eclipse.rap.rwt.service;version="[2.1,4)",\
+org.eclipse.rap.rwt.widgets;version="[2.1,4)",\
+org.osgi.*;version=0.0.0,\
+*
+
+## TODO: in order to enable single sourcing, we have introduced dummy RAP packages
+# in the RCP specific bundle org.argeo.eclipse.ui.rap.
+# this was working with RAP 2.x but since we upgrade Rap to 3.1.x,
+# there is a version range issue cause org.argeo.eclipse.ui.rap bundle is still in 2.x
+# We enable large RAP version range as a workaround that must be fixed
\ No newline at end of file
--- /dev/null
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+ .,\
+ icons/
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.argeo.commons</groupId>
+ <artifactId>jcr</artifactId>
+ <version>2.3-SNAPSHOT</version>
+ <relativePath>..</relativePath>
+ </parent>
+ <artifactId>org.argeo.cms.ui</artifactId>
+ <name>CMS UI</name>
+ <packaging>jar</packaging>
+ <dependencies>
+ <dependency>
+ <groupId>org.argeo.commons</groupId>
+ <artifactId>org.argeo.cms</artifactId>
+ <version>2.3-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>org.argeo.commons</groupId>
+ <artifactId>org.argeo.cms.swt</artifactId>
+ <version>2.3-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>org.argeo.commons</groupId>
+ <artifactId>org.argeo.cms.jcr</artifactId>
+ <version>2.3-SNAPSHOT</version>
+ </dependency>
+
+ <!-- Specific -->
+ <dependency>
+ <groupId>org.argeo.commons.rap</groupId>
+ <artifactId>org.argeo.swt.specific.rap</artifactId>
+ <version>2.3-SNAPSHOT</version>
+ <scope>provided</scope>
+ </dependency>
+
+ <!-- UI -->
+ <dependency>
+ <groupId>org.argeo.tp.rap.e4</groupId>
+ <artifactId>org.eclipse.rap.rwt</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.argeo.tp.rap.e4</groupId>
+ <artifactId>org.eclipse.core.commands</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.argeo.tp.rap.e4</groupId>
+ <artifactId>org.eclipse.rap.jface</artifactId>
+ <scope>provided</scope>
+ </dependency>
+
+ <!-- TODO move it to specific -->
+ <dependency>
+ <groupId>org.argeo.tp.rap.e4</groupId>
+ <artifactId>org.eclipse.rap.filedialog</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.argeo.tp.rap.e4</groupId>
+ <artifactId>org.eclipse.rap.fileupload</artifactId>
+ <scope>provided</scope>
+ </dependency>
+
+ </dependencies>
+</project>
\ No newline at end of file
--- /dev/null
+package org.argeo.cms.ui;
+
+import java.util.EventObject;
+
+/** Notify of the edition lifecycle */
+public class CmsEditionEvent extends EventObject {
+ private static final long serialVersionUID = 950914736016693110L;
+
+ public final static Integer START_EDITING = 0;
+ public final static Integer STOP_EDITING = 1;
+
+ private final Integer type;
+
+ public CmsEditionEvent(Object source, Integer type) {
+ super(source);
+ this.type = type;
+ }
+
+ public Integer getType() {
+ return type;
+ }
+
+}
--- /dev/null
+package org.argeo.cms.ui;
+
+import org.argeo.api.cms.Cms2DSize;
+
+/** Commons constants */
+@Deprecated
+public interface CmsUiConstants {
+ // DATAKEYS
+// public final static String STYLE = EclipseUiConstants.CSS_CLASS;
+// public final static String MARKUP = EclipseUiConstants.MARKUP_SUPPORT;
+ @Deprecated
+ /* RWT.CUSTOM_ITEM_HEIGHT */
+ public final static String ITEM_HEIGHT = "org.eclipse.rap.rwt.customItemHeight";
+
+ // EVENT DETAILS
+ @Deprecated
+ /* RWT.HYPERLINK */
+ public final static int HYPERLINK = 1 << 26;
+
+ // STANDARD RESOURCES
+ public final static String LOADING_IMAGE = "icons/loading.gif";
+
+ public final static String NO_IMAGE = "icons/noPic-square-640px.png";
+ public final static Cms2DSize NO_IMAGE_SIZE = new Cms2DSize(320, 320);
+ public final static Float NO_IMAGE_RATIO = 1f;
+ // MISCEALLENEOUS
+ String DATE_TIME_FORMAT = "dd/MM/yyyy, HH:mm";
+}
--- /dev/null
+package org.argeo.cms.ui;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+
+import org.argeo.api.cms.MvcProvider;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+
+/** Stateless factory building an SWT user interface given a JCR context. */
+@FunctionalInterface
+public interface CmsUiProvider extends MvcProvider<Composite, Node, Control> {
+ /**
+ * Initialises a user interface.
+ *
+ * @param parent the parent composite
+ * @param context a context node (holding the JCR underlying session), or null
+ */
+ Control createUi(Composite parent, Node context) throws RepositoryException;
+
+ @Override
+ default Control createUiPart(Composite parent, Node context) {
+ try {
+ return createUi(parent, context);
+ } catch (RepositoryException e) {
+ throw new IllegalStateException("Cannot create UI for context " + context, e);
+ }
+ }
+
+}
--- /dev/null
+package org.argeo.cms.ui;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+/** CmsUiProvider notified of initialisation with a system session. */
+public interface LifeCycleUiProvider extends CmsUiProvider {
+ public void init(Session adminSession) throws RepositoryException;
+
+ public void destroy();
+}
--- /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.core.runtime.ListenerList;
+import org.eclipse.jface.dialogs.IPageChangeProvider;
+import org.eclipse.jface.dialogs.IPageChangedListener;
+import org.eclipse.jface.dialogs.PageChangedEvent;
+import org.eclipse.jface.util.SafeRunnable;
+
+/**
+ * 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.core.runtime.IProgressMonitor;
+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.forms;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+
+import org.argeo.cms.ui.viewers.EditablePart;
+import org.argeo.eclipse.ui.EclipseUiUtils;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Text;
+
+/** Editable String that displays a browsable link when read-only */
+public class EditableLink extends EditablePropertyString implements
+ EditablePart {
+ private static final long serialVersionUID = 5055000749992803591L;
+
+ private String type;
+ private String message;
+ private boolean readOnly;
+
+ public EditableLink(Composite parent, int style, Node node,
+ String propertyName, String type, String message)
+ throws RepositoryException {
+ super(parent, style, node, propertyName, message);
+ this.message = message;
+ this.type = type;
+
+ readOnly = SWT.READ_ONLY == (style & SWT.READ_ONLY);
+ if (node.hasProperty(propertyName)) {
+ this.setStyle(FormStyle.propertyText.style());
+ this.setText(node.getProperty(propertyName).getString());
+ } else {
+ this.setStyle(FormStyle.propertyMessage.style());
+ this.setText("");
+ }
+ }
+
+ public void setText(String text) {
+ Control child = getControl();
+ if (child instanceof Label) {
+ Label lbl = (Label) child;
+ if (EclipseUiUtils.isEmpty(text))
+ lbl.setText(message);
+ else if (readOnly)
+ setLinkValue(lbl, text);
+ else
+ // if canEdit() we put only the value with no link
+ // to avoid glitches of the edition life cycle
+ lbl.setText(text);
+ } else if (child instanceof Text) {
+ Text txt = (Text) child;
+ if (EclipseUiUtils.isEmpty(text)) {
+ txt.setText("");
+ txt.setMessage(message);
+ } else
+ txt.setText(text);
+ }
+ }
+
+ private void setLinkValue(Label lbl, String text) {
+ if (FormStyle.email.style().equals(type))
+ lbl.setText(FormUtils.getMailLink(text));
+ else if (FormStyle.phone.style().equals(type))
+ lbl.setText(FormUtils.getPhoneLink(text));
+ else if (FormStyle.website.style().equals(type))
+ lbl.setText(FormUtils.getUrlLink(text));
+ else if (FormStyle.facebook.style().equals(type)
+ || FormStyle.instagram.style().equals(type)
+ || FormStyle.linkedIn.style().equals(type)
+ || FormStyle.twitter.style().equals(type))
+ lbl.setText(FormUtils.getUrlLink(text));
+ }
+}
\ No newline at end of file
--- /dev/null
+package org.argeo.cms.ui.forms;
+
+import java.util.List;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.ui.viewers.EditablePart;
+import org.argeo.cms.ui.widgets.StyledControl;
+import org.argeo.eclipse.ui.EclipseUiUtils;
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.events.TraverseEvent;
+import org.eclipse.swt.events.TraverseListener;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.layout.RowLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Text;
+
+/** Display, add or remove values from a list in a CMS context */
+public class EditableMultiStringProperty extends StyledControl implements EditablePart {
+ private static final long serialVersionUID = -7044614381252178595L;
+
+ private String propertyName;
+ private String message;
+ // TODO implement the ability to provide a list of possible values
+// private String[] possibleValues;
+ private boolean canEdit;
+ private SelectionListener removeValueSL;
+ private List<String> values;
+
+ // TODO manage within the CSS
+ private int rowSpacing = 5;
+ private int rowMarging = 0;
+ private int oneValueMargingRight = 5;
+ private int btnWidth = 16;
+ private int btnHeight = 16;
+ private int btnHorizontalIndent = 3;
+
+ public EditableMultiStringProperty(Composite parent, int style, Node node, String propertyName, List<String> values,
+ String[] possibleValues, String addValueMsg, SelectionListener removeValueSelectionListener)
+ throws RepositoryException {
+ super(parent, style, node, true);
+
+ this.propertyName = propertyName;
+ this.values = values;
+// this.possibleValues = new String[] { "Un", "Deux", "Trois" };
+ this.message = addValueMsg;
+ this.canEdit = removeValueSelectionListener != null;
+ this.removeValueSL = removeValueSelectionListener;
+ }
+
+ public List<String> getValues() {
+ return values;
+ }
+
+ public void setValues(List<String> values) {
+ this.values = values;
+ }
+
+ // Row layout items do not need explicit layout data
+ protected void setControlLayoutData(Control control) {
+ }
+
+ /** To be overridden */
+ protected void setContainerLayoutData(Composite composite) {
+ composite.setLayoutData(CmsSwtUtils.fillWidth());
+ }
+
+ @Override
+ public Control getControl() {
+ return super.getControl();
+ }
+
+ @Override
+ protected Control createControl(Composite box, String style) {
+ Composite row = new Composite(box, SWT.NO_FOCUS);
+ row.setLayoutData(EclipseUiUtils.fillAll());
+
+ RowLayout rl = new RowLayout(SWT.HORIZONTAL);
+ rl.wrap = true;
+ rl.spacing = rowSpacing;
+ rl.marginRight = rl.marginLeft = rl.marginBottom = rl.marginTop = rowMarging;
+ row.setLayout(rl);
+
+ if (values != null) {
+ for (final String value : values) {
+ if (canEdit)
+ createRemovableValue(row, SWT.SINGLE, value);
+ else
+ createValueLabel(row, SWT.SINGLE, value);
+ }
+ }
+
+ if (!canEdit)
+ return row;
+ else if (isEditing())
+ return createText(row, style);
+ else
+ return createLabel(row, style);
+ }
+
+ /**
+ * Override to provide specific layout for the existing values, typically adding
+ * a pound (#) char for tags or anchor info for browsable links. We assume the
+ * parent composite already has a layout and it is the caller responsibility to
+ * apply corresponding layout data
+ */
+ protected Label createValueLabel(Composite parent, int style, String value) {
+ Label label = new Label(parent, style);
+ label.setText("#" + value);
+ CmsSwtUtils.markup(label);
+ CmsSwtUtils.style(label, FormStyle.propertyText.style());
+ return label;
+ }
+
+ private Composite createRemovableValue(Composite parent, int style, String value) {
+ Composite valCmp = new Composite(parent, SWT.NO_FOCUS);
+ GridLayout gl = EclipseUiUtils.noSpaceGridLayout(new GridLayout(2, false));
+ gl.marginRight = oneValueMargingRight;
+ valCmp.setLayout(gl);
+
+ createValueLabel(valCmp, SWT.WRAP, value);
+
+ Button deleteBtn = new Button(valCmp, SWT.FLAT);
+ deleteBtn.setData(FormConstants.LINKED_VALUE, value);
+ deleteBtn.addSelectionListener(removeValueSL);
+ CmsSwtUtils.style(deleteBtn, FormStyle.delete.style() + FormStyle.BUTTON_SUFFIX);
+ GridData gd = new GridData();
+ gd.heightHint = btnHeight;
+ gd.widthHint = btnWidth;
+ gd.horizontalIndent = btnHorizontalIndent;
+ deleteBtn.setLayoutData(gd);
+
+ return valCmp;
+ }
+
+ protected Text createText(Composite box, String style) {
+ final Text text = new Text(box, getStyle());
+ // The "add new value" text is not meant to change, so we can set it on
+ // creation
+ text.setMessage(message);
+ CmsSwtUtils.style(text, style);
+ text.setFocus();
+
+ text.addTraverseListener(new TraverseListener() {
+ private static final long serialVersionUID = 1L;
+
+ public void keyTraversed(TraverseEvent e) {
+ if (e.keyCode == SWT.CR) {
+ addValue(text);
+ e.doit = false;
+ }
+ }
+ });
+
+ // The OK button does not work with the focusOut listener
+ // because focus out is called before the OK button is pressed
+
+ // // we must call layout() now so that the row data can compute the
+ // height
+ // // of the other controls.
+ // text.getParent().layout();
+ // int height = text.getSize().y;
+ //
+ // Button okBtn = new Button(box, SWT.BORDER | SWT.PUSH | SWT.BOTTOM);
+ // okBtn.setText("OK");
+ // RowData rd = new RowData(SWT.DEFAULT, height - 2);
+ // okBtn.setLayoutData(rd);
+ //
+ // okBtn.addSelectionListener(new SelectionAdapter() {
+ // private static final long serialVersionUID = 2780819012423622369L;
+ //
+ // @Override
+ // public void widgetSelected(SelectionEvent e) {
+ // addValue(text);
+ // }
+ // });
+
+ return text;
+ }
+
+ /** Performs the real addition, overwrite to make further sanity checks */
+ protected void addValue(Text text) {
+ String value = text.getText();
+ String errMsg = null;
+
+ if (EclipseUiUtils.isEmpty(value))
+ return;
+
+ if (values.contains(value))
+ errMsg = "Dupplicated value: " + value + ", please correct and try again";
+ if (errMsg != null)
+ MessageDialog.openError(this.getShell(), "Addition not allowed", errMsg);
+ else {
+ values.add(value);
+ Composite newCmp = createRemovableValue(text.getParent(), SWT.SINGLE, value);
+ newCmp.moveAbove(text);
+ text.setText("");
+ newCmp.getParent().layout();
+ }
+ }
+
+ protected Label createLabel(Composite box, String style) {
+ if (canEdit) {
+ Label lbl = new Label(box, getStyle());
+ lbl.setText(message);
+ CmsSwtUtils.style(lbl, style);
+ CmsSwtUtils.markup(lbl);
+ if (mouseListener != null)
+ lbl.addMouseListener(mouseListener);
+ return lbl;
+ }
+ return null;
+ }
+
+ protected void clear(boolean deep) {
+ Control child = getControl();
+ if (deep)
+ super.clear(deep);
+ else {
+ child.getParent().dispose();
+ }
+ }
+
+ public void setText(String text) {
+ Control child = getControl();
+ if (child instanceof Label) {
+ Label lbl = (Label) child;
+ if (canEdit)
+ lbl.setText(text);
+ else
+ lbl.setText("");
+ } else if (child instanceof Text) {
+ Text txt = (Text) child;
+ txt.setText(text);
+ }
+ }
+
+ public synchronized void startEditing() {
+ CmsSwtUtils.style(getControl(), FormStyle.propertyText.style());
+// getControl().setData(STYLE, FormStyle.propertyText.style());
+ super.startEditing();
+ }
+
+ public synchronized void stopEditing() {
+ CmsSwtUtils.style(getControl(), FormStyle.propertyMessage.style());
+// getControl().setData(STYLE, FormStyle.propertyMessage.style());
+ super.stopEditing();
+ }
+
+ public String getPropertyName() {
+ return propertyName;
+ }
+}
\ No newline at end of file
--- /dev/null
+package org.argeo.cms.ui.forms;
+
+import java.text.DateFormat;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.ui.viewers.EditablePart;
+import org.argeo.cms.ui.widgets.StyledControl;
+import org.argeo.eclipse.ui.EclipseUiUtils;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.MouseEvent;
+import org.eclipse.swt.events.MouseListener;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.ShellAdapter;
+import org.eclipse.swt.events.ShellEvent;
+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.DateTime;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Text;
+
+/** CMS form part to display and edit a date */
+public class EditablePropertyDate extends StyledControl implements EditablePart {
+ private static final long serialVersionUID = 2500215515778162468L;
+
+ // Context
+ private String propertyName;
+ private String message;
+ private DateFormat dateFormat;
+
+ // UI Objects
+ private Text dateTxt;
+ private Button openCalBtn;
+
+ // TODO manage within the CSS
+ private int fieldBtnSpacing = 5;
+
+ /**
+ *
+ * @param parent
+ * @param style
+ * @param node
+ * @param propertyName
+ * @param message
+ * @param dateFormat provide a {@link DateFormat} as contract to be able to
+ * read/write dates as strings
+ * @throws RepositoryException
+ */
+ public EditablePropertyDate(Composite parent, int style, Node node, String propertyName, String message,
+ DateFormat dateFormat) throws RepositoryException {
+ super(parent, style, node, false);
+
+ this.propertyName = propertyName;
+ this.message = message;
+ this.dateFormat = dateFormat;
+
+ if (node.hasProperty(propertyName)) {
+ this.setStyle(FormStyle.propertyText.style());
+ this.setText(dateFormat.format(node.getProperty(propertyName).getDate().getTime()));
+ } else {
+ this.setStyle(FormStyle.propertyMessage.style());
+ this.setText(message);
+ }
+ }
+
+ public void setText(String text) {
+ Control child = getControl();
+ if (child instanceof Label) {
+ Label lbl = (Label) child;
+ if (EclipseUiUtils.isEmpty(text))
+ lbl.setText(message);
+ else
+ lbl.setText(text);
+ } else if (child instanceof Text) {
+ Text txt = (Text) child;
+ if (EclipseUiUtils.isEmpty(text)) {
+ txt.setText("");
+ } else
+ txt.setText(text);
+ }
+ }
+
+ public synchronized void startEditing() {
+ // if (dateTxt != null && !dateTxt.isDisposed())
+ CmsSwtUtils.style(getControl(), FormStyle.propertyText);
+// getControl().setData(STYLE, FormStyle.propertyText.style());
+ super.startEditing();
+ }
+
+ public synchronized void stopEditing() {
+ if (EclipseUiUtils.isEmpty(dateTxt.getText()))
+ CmsSwtUtils.style(getControl(), FormStyle.propertyMessage);
+// getControl().setData(STYLE, FormStyle.propertyMessage.style());
+ else
+ CmsSwtUtils.style(getControl(), FormStyle.propertyText);
+// getControl().setData(STYLE, FormStyle.propertyText.style());
+ super.stopEditing();
+ }
+
+ public String getPropertyName() {
+ return propertyName;
+ }
+
+ @Override
+ protected Control createControl(Composite box, String style) {
+ if (isEditing()) {
+ return createCustomEditableControl(box, style);
+ } else
+ return createLabel(box, style);
+ }
+
+ protected Label createLabel(Composite box, String style) {
+ Label lbl = new Label(box, getStyle() | SWT.WRAP);
+ lbl.setLayoutData(CmsSwtUtils.fillWidth());
+ CmsSwtUtils.style(lbl, style);
+ CmsSwtUtils.markup(lbl);
+ if (mouseListener != null)
+ lbl.addMouseListener(mouseListener);
+ return lbl;
+ }
+
+ private Control createCustomEditableControl(Composite box, String style) {
+ box.setLayoutData(CmsSwtUtils.fillWidth());
+ Composite dateComposite = new Composite(box, SWT.NONE);
+ GridLayout gl = EclipseUiUtils.noSpaceGridLayout(new GridLayout(2, false));
+ gl.horizontalSpacing = fieldBtnSpacing;
+ dateComposite.setLayout(gl);
+ dateTxt = new Text(dateComposite, SWT.BORDER);
+ CmsSwtUtils.style(dateTxt, style);
+ dateTxt.setLayoutData(new GridData(120, SWT.DEFAULT));
+ dateTxt.setToolTipText(
+ "Enter a date with form \"" + FormUtils.DEFAULT_SHORT_DATE_FORMAT + "\" or use the calendar");
+ openCalBtn = new Button(dateComposite, SWT.FLAT);
+ CmsSwtUtils.style(openCalBtn, FormStyle.calendar.style() + FormStyle.BUTTON_SUFFIX);
+ GridData gd = new GridData(SWT.CENTER, SWT.CENTER, false, false);
+ gd.heightHint = 17;
+ openCalBtn.setLayoutData(gd);
+ // openCalBtn.setImage(PeopleRapImages.CALENDAR_BTN);
+
+ openCalBtn.addSelectionListener(new SelectionAdapter() {
+ private static final long serialVersionUID = 1L;
+
+ public void widgetSelected(SelectionEvent event) {
+ CalendarPopup popup = new CalendarPopup(dateTxt);
+ popup.open();
+ }
+ });
+
+ // dateTxt.addFocusListener(new FocusListener() {
+ // private static final long serialVersionUID = 1L;
+ //
+ // @Override
+ // public void focusLost(FocusEvent event) {
+ // String newVal = dateTxt.getText();
+ // // Enable reset of the field
+ // if (FormUtils.notNull(newVal))
+ // calendar = null;
+ // else {
+ // try {
+ // Calendar newCal = parseDate(newVal);
+ // // DateText.this.setText(newCal);
+ // calendar = newCal;
+ // } catch (ParseException pe) {
+ // // Silent. Manage error popup?
+ // if (calendar != null)
+ // EditablePropertyDate.this.setText(calendar);
+ // }
+ // }
+ // }
+ //
+ // @Override
+ // public void focusGained(FocusEvent event) {
+ // }
+ // });
+ return dateTxt;
+ }
+
+ protected void clear(boolean deep) {
+ Control child = getControl();
+ if (deep || child instanceof Label)
+ super.clear(deep);
+ else {
+ child.getParent().dispose();
+ }
+ }
+
+ /** Enable setting a custom tooltip on the underlying text */
+ @Deprecated
+ public void setToolTipText(String toolTipText) {
+ dateTxt.setToolTipText(toolTipText);
+ }
+
+ @Deprecated
+ /** Enable setting a custom message on the underlying text */
+ public void setMessage(String message) {
+ dateTxt.setMessage(message);
+ }
+
+ @Deprecated
+ public void setText(Calendar cal) {
+ String newValueStr = "";
+ if (cal != null)
+ newValueStr = dateFormat.format(cal.getTime());
+ if (!newValueStr.equals(dateTxt.getText()))
+ dateTxt.setText(newValueStr);
+ }
+
+ // UTILITIES TO MANAGE THE CALENDAR POPUP
+ // TODO manage the popup shell in a cleaner way
+ private class CalendarPopup extends Shell {
+ private static final long serialVersionUID = 1L;
+ private DateTime dateTimeCtl;
+
+ public CalendarPopup(Control source) {
+ super(source.getDisplay(), SWT.NO_TRIM | SWT.BORDER | SWT.ON_TOP);
+ populate();
+ // Add border and shadow style
+ CmsSwtUtils.markup(CalendarPopup.this);
+ CmsSwtUtils.style(CalendarPopup.this, FormStyle.popupCalendar.style());
+ pack();
+ layout();
+ setLocation(source.toDisplay((source.getLocation().x - 2), (source.getSize().y) + 3));
+
+ addShellListener(new ShellAdapter() {
+ private static final long serialVersionUID = 5178980294808435833L;
+
+ @Override
+ public void shellDeactivated(ShellEvent e) {
+ close();
+ dispose();
+ }
+ });
+ open();
+ }
+
+ private void setProperty() {
+ // Direct set does not seems to work. investigate
+ // cal.set(dateTimeCtl.getYear(), dateTimeCtl.getMonth(),
+ // dateTimeCtl.getDay(), 12, 0);
+ Calendar cal = new GregorianCalendar();
+ cal.set(Calendar.YEAR, dateTimeCtl.getYear());
+ cal.set(Calendar.MONTH, dateTimeCtl.getMonth());
+ cal.set(Calendar.DAY_OF_MONTH, dateTimeCtl.getDay());
+ String dateStr = dateFormat.format(cal.getTime());
+ dateTxt.setText(dateStr);
+ }
+
+ protected void populate() {
+ setLayout(EclipseUiUtils.noSpaceGridLayout());
+
+ dateTimeCtl = new DateTime(this, SWT.CALENDAR);
+ dateTimeCtl.setLayoutData(EclipseUiUtils.fillAll());
+
+ Calendar calendar = FormUtils.parseDate(dateFormat, dateTxt.getText());
+
+ if (calendar != null)
+ dateTimeCtl.setDate(calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH),
+ calendar.get(Calendar.DAY_OF_MONTH));
+
+ dateTimeCtl.addSelectionListener(new SelectionAdapter() {
+ private static final long serialVersionUID = -8414377364434281112L;
+
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ setProperty();
+ }
+ });
+
+ dateTimeCtl.addMouseListener(new MouseListener() {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public void mouseUp(MouseEvent e) {
+ }
+
+ @Override
+ public void mouseDown(MouseEvent e) {
+ }
+
+ @Override
+ public void mouseDoubleClick(MouseEvent e) {
+ setProperty();
+ close();
+ dispose();
+ }
+ });
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+package org.argeo.cms.ui.forms;
+
+import static org.argeo.cms.ui.forms.FormStyle.propertyMessage;
+import static org.argeo.cms.ui.forms.FormStyle.propertyText;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.ui.viewers.EditablePart;
+import org.argeo.cms.ui.widgets.EditableText;
+import org.argeo.eclipse.ui.EclipseUiUtils;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Text;
+
+/** Editable String in a CMS context */
+public class EditablePropertyString extends EditableText implements EditablePart {
+ private static final long serialVersionUID = 5055000749992803591L;
+
+ private String propertyName;
+ private String message;
+
+ // encode the '&' character in rap
+ private final static String AMPERSAND = "&";
+ private final static String AMPERSAND_REGEX = "&(?![#a-zA-Z0-9]+;)";
+
+ public EditablePropertyString(Composite parent, int style, Node node, String propertyName, String message)
+ throws RepositoryException {
+ super(parent, style, node, true);
+ //setUseTextAsLabel(true);
+ this.propertyName = propertyName;
+ this.message = message;
+
+ if (node.hasProperty(propertyName)) {
+ this.setStyle(propertyText.style());
+ this.setText(node.getProperty(propertyName).getString());
+ } else {
+ this.setStyle(propertyMessage.style());
+ this.setText(message + " ");
+ }
+ }
+
+ public void setText(String text) {
+ Control child = getControl();
+ if (child instanceof Label) {
+ Label lbl = (Label) child;
+ if (EclipseUiUtils.isEmpty(text))
+ lbl.setText(message + " ");
+ else
+ // TODO enhance this
+ lbl.setText(text.replaceAll(AMPERSAND_REGEX, AMPERSAND));
+ } else if (child instanceof Text) {
+ Text txt = (Text) child;
+ if (EclipseUiUtils.isEmpty(text)) {
+ txt.setText("");
+ txt.setMessage(message + " ");
+ } else
+ txt.setText(text.replaceAll("<br/>", "\n"));
+ }
+ }
+
+ public synchronized void startEditing() {
+ CmsSwtUtils.style(getControl(), FormStyle.propertyText);
+ super.startEditing();
+ }
+
+ public synchronized void stopEditing() {
+ if (EclipseUiUtils.isEmpty(((Text) getControl()).getText()))
+ CmsSwtUtils.style(getControl(), FormStyle.propertyMessage);
+ else
+ CmsSwtUtils.style(getControl(), FormStyle.propertyText);
+ super.stopEditing();
+ }
+
+ public String getPropertyName() {
+ return propertyName;
+ }
+}
\ No newline at end of file
--- /dev/null
+package org.argeo.cms.ui.forms;
+
+/** Constants used in the various CMS Forms */
+public interface FormConstants {
+ // DATAKEYS
+ public final static String LINKED_VALUE = "LinkedValue";
+}
--- /dev/null
+package org.argeo.cms.ui.forms;
+
+import java.util.Observable;
+import java.util.Observer;
+
+import javax.jcr.Node;
+
+import org.argeo.api.cms.CmsEditable;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+
+/** Add life cycle management abilities to an editable form page */
+public class FormEditorHeader implements SelectionListener, Observer {
+ private static final long serialVersionUID = 7392898696542484282L;
+
+ // private final Node context;
+ private final CmsEditable cmsEditable;
+ private Button publishBtn;
+
+ // Should we provide here the ability to switch from read only to edition
+ // mode?
+ // private Button editBtn;
+ // private boolean readOnly;
+
+ // TODO add information about the current node status, typically if it is
+ // dirty or not
+
+ private Composite parent;
+ private Composite display;
+ private Object layoutData;
+
+ public FormEditorHeader(Composite parent, int style, Node context,
+ CmsEditable cmsEditable) {
+ this.cmsEditable = cmsEditable;
+ this.parent = parent;
+ // readOnly = SWT.READ_ONLY == (style & SWT.READ_ONLY);
+ // this.context = context;
+ if (this.cmsEditable instanceof Observable)
+ ((Observable) this.cmsEditable).addObserver(this);
+ refresh();
+ }
+
+ public void setLayoutData(Object layoutData) {
+ this.layoutData = layoutData;
+ if (display != null && !display.isDisposed())
+ display.setLayoutData(layoutData);
+ }
+
+ protected void refresh() {
+ if (display != null && !display.isDisposed())
+ display.dispose();
+
+ display = new Composite(parent, SWT.NONE);
+ display.setLayoutData(layoutData);
+
+ CmsSwtUtils.style(display, FormStyle.header.style());
+ display.setBackgroundMode(SWT.INHERIT_FORCE);
+
+ display.setLayout(CmsSwtUtils.noSpaceGridLayout());
+
+ publishBtn = createSimpleBtn(display, getPublishButtonLabel());
+ display.moveAbove(null);
+ parent.layout();
+ }
+
+ private Button createSimpleBtn(Composite parent, String label) {
+ Button button = new Button(parent, SWT.FLAT | SWT.PUSH);
+ button.setText(label);
+ CmsSwtUtils.style(button, FormStyle.header.style());
+ button.addSelectionListener(this);
+ return button;
+ }
+
+ private String getPublishButtonLabel() {
+ // Rather check if the current node differs from what has been
+ // previously committed
+ // For the time being, we always reach here, the underlying CmsEditable
+ // is always editing.
+ if (cmsEditable.isEditing())
+ return " Publish ";
+ else
+ return " Edit ";
+ }
+
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ if (e.getSource() == publishBtn) {
+ // For the time being, the underlying CmsEditable
+ // is always editing when we reach this point
+ if (cmsEditable.isEditing()) {
+ // we always leave the node in a check outed state
+ cmsEditable.stopEditing();
+ cmsEditable.startEditing();
+ } else {
+ cmsEditable.startEditing();
+ }
+ }
+ }
+
+ @Override
+ public void widgetDefaultSelected(SelectionEvent e) {
+ }
+
+ @Override
+ public void update(Observable o, Object arg) {
+ if (o == cmsEditable) {
+ refresh();
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+package org.argeo.cms.ui.forms;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.List;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.Value;
+import javax.jcr.ValueFormatException;
+
+import org.argeo.api.cms.Cms2DSize;
+import org.argeo.api.cms.CmsEditable;
+import org.argeo.api.cms.CmsImageManager;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.ui.viewers.AbstractPageViewer;
+import org.argeo.cms.ui.viewers.EditablePart;
+import org.argeo.cms.ui.viewers.Section;
+import org.argeo.cms.ui.viewers.SectionPart;
+import org.argeo.cms.ui.widgets.EditableImage;
+import org.argeo.cms.ui.widgets.Img;
+import org.argeo.cms.ui.widgets.StyledControl;
+import org.argeo.eclipse.ui.EclipseUiUtils;
+import org.argeo.jcr.JcrException;
+import org.argeo.jcr.JcrUtils;
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.rap.fileupload.FileDetails;
+import org.eclipse.rap.fileupload.FileUploadEvent;
+import org.eclipse.rap.fileupload.FileUploadHandler;
+import org.eclipse.rap.fileupload.FileUploadListener;
+import org.eclipse.rap.fileupload.FileUploadReceiver;
+import org.eclipse.rap.rwt.service.ServerPushSession;
+import org.eclipse.rap.rwt.widgets.FileUpload;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.FocusEvent;
+import org.eclipse.swt.events.FocusListener;
+import org.eclipse.swt.events.MouseAdapter;
+import org.eclipse.swt.events.MouseEvent;
+import org.eclipse.swt.events.MouseListener;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.layout.FormAttachment;
+import org.eclipse.swt.layout.FormData;
+import org.eclipse.swt.layout.FormLayout;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.layout.RowLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Text;
+
+/** Manage life cycle of a form page that is linked to a given node */
+public class FormPageViewer extends AbstractPageViewer {
+ private final static CmsLog log = CmsLog.getLog(FormPageViewer.class);
+ private static final long serialVersionUID = 5277789504209413500L;
+
+ private final Section mainSection;
+
+ // TODO manage within the CSS
+ private Integer labelColWidth = null;
+ private int rowLayoutHSpacing = 8;
+
+ // Context cached in the viewer
+ // The reference to translate from text to calendar and reverse
+ private DateFormat dateFormat = new SimpleDateFormat(FormUtils.DEFAULT_SHORT_DATE_FORMAT);
+ private CmsImageManager<Control, Node> imageManager;
+ private FileUploadListener fileUploadListener;
+
+ public FormPageViewer(Section mainSection, int style, CmsEditable cmsEditable) throws RepositoryException {
+ super(mainSection, style, cmsEditable);
+ this.mainSection = mainSection;
+
+ if (getCmsEditable().canEdit()) {
+ fileUploadListener = new FUL();
+ }
+ }
+
+ @Override
+ protected void prepare(EditablePart part, Object caretPosition) {
+ if (part instanceof Img) {
+ ((Img) part).setFileUploadListener(fileUploadListener);
+ }
+ }
+
+ /** To be overridden.Save the edited part. */
+ protected void save(EditablePart part) throws RepositoryException {
+ Node node = null;
+ if (part instanceof EditableMultiStringProperty) {
+ EditableMultiStringProperty ept = (EditableMultiStringProperty) part;
+ // SWT : View
+ List<String> values = ept.getValues();
+ // JCR : Model
+ node = ept.getNode();
+ String propName = ept.getPropertyName();
+ if (values.isEmpty()) {
+ if (node.hasProperty(propName))
+ node.getProperty(propName).remove();
+ } else {
+ node.setProperty(propName, values.toArray(new String[0]));
+ }
+ // => Viewer : Controller
+ } else if (part instanceof EditablePropertyString) {
+ EditablePropertyString ept = (EditablePropertyString) part;
+ // SWT : View
+ String txt = ((Text) ept.getControl()).getText();
+ // JCR : Model
+ node = ept.getNode();
+ String propName = ept.getPropertyName();
+ if (EclipseUiUtils.isEmpty(txt)) {
+ if (node.hasProperty(propName))
+ node.getProperty(propName).remove();
+ } else {
+ setPropertySilently(node, propName, txt);
+ // node.setProperty(propName, txt);
+ }
+ // node.getSession().save();
+ // => Viewer : Controller
+ } else if (part instanceof EditablePropertyDate) {
+ EditablePropertyDate ept = (EditablePropertyDate) part;
+ Calendar cal = FormUtils.parseDate(dateFormat, ((Text) ept.getControl()).getText());
+ node = ept.getNode();
+ String propName = ept.getPropertyName();
+ if (cal == null) {
+ if (node.hasProperty(propName))
+ node.getProperty(propName).remove();
+ } else {
+ node.setProperty(propName, cal);
+ }
+ // node.getSession().save();
+ // => Viewer : Controller
+ }
+ // TODO: make this configurable, sometimes we do not want to save the
+ // current session at this stage
+ if (node != null && node.getSession().hasPendingChanges()) {
+ JcrUtils.updateLastModified(node, true);
+ node.getSession().save();
+ }
+ }
+
+ @Override
+ protected void updateContent(EditablePart part) throws RepositoryException {
+ if (part instanceof EditableMultiStringProperty) {
+ EditableMultiStringProperty ept = (EditableMultiStringProperty) part;
+ // SWT : View
+ Node node = ept.getNode();
+ String propName = ept.getPropertyName();
+ List<String> valStrings = new ArrayList<String>();
+ if (node.hasProperty(propName)) {
+ Value[] values = node.getProperty(propName).getValues();
+ for (Value val : values)
+ valStrings.add(val.getString());
+ }
+ ept.setValues(valStrings);
+ } else if (part instanceof EditablePropertyString) {
+ // || part instanceof EditableLink
+ EditablePropertyString ept = (EditablePropertyString) part;
+ // JCR : Model
+ Node node = ept.getNode();
+ String propName = ept.getPropertyName();
+ if (node.hasProperty(propName)) {
+ String value = node.getProperty(propName).getString();
+ ept.setText(value);
+ } else
+ ept.setText("");
+ // => Viewer : Controller
+ } else if (part instanceof EditablePropertyDate) {
+ EditablePropertyDate ept = (EditablePropertyDate) part;
+ // JCR : Model
+ Node node = ept.getNode();
+ String propName = ept.getPropertyName();
+ if (node.hasProperty(propName))
+ ept.setText(dateFormat.format(node.getProperty(propName).getDate().getTime()));
+ else
+ ept.setText("");
+ } else if (part instanceof SectionPart) {
+ SectionPart sectionPart = (SectionPart) part;
+ Node partNode = sectionPart.getNode();
+ // use control AFTER setting style, since it may have been reset
+ if (part instanceof EditableImage) {
+ EditableImage editableImage = (EditableImage) part;
+ imageManager().load(partNode, part.getControl(), editableImage.getPreferredImageSize());
+ }
+ }
+ }
+
+ // FILE UPLOAD LISTENER
+ protected class FUL implements FileUploadListener {
+
+ public FUL() {
+ }
+
+ public void uploadProgress(FileUploadEvent event) {
+ // TODO Monitor upload progress
+ }
+
+ public void uploadFailed(FileUploadEvent event) {
+ throw new IllegalStateException("Upload failed " + event, event.getException());
+ }
+
+ public void uploadFinished(FileUploadEvent event) {
+ for (FileDetails file : event.getFileDetails()) {
+ if (log.isDebugEnabled())
+ log.debug("Received: " + file.getFileName());
+ }
+ mainSection.getDisplay().syncExec(new Runnable() {
+ @Override
+ public void run() {
+ saveEdit();
+ }
+ });
+ FileUploadHandler uploadHandler = (FileUploadHandler) event.getSource();
+ uploadHandler.dispose();
+ }
+ }
+
+ // FOCUS OUT LISTENER
+ protected FocusListener createFocusListener() {
+ return new FocusOutListener();
+ }
+
+ private class FocusOutListener implements FocusListener {
+ private static final long serialVersionUID = -6069205786732354186L;
+
+ @Override
+ public void focusLost(FocusEvent event) {
+ saveEdit();
+ }
+
+ @Override
+ public void focusGained(FocusEvent event) {
+ // does nothing;
+ }
+ }
+
+ // MOUSE LISTENER
+ @Override
+ protected MouseListener createMouseListener() {
+ return new ML();
+ }
+
+ private class ML extends MouseAdapter {
+ private static final long serialVersionUID = 8526890859876770905L;
+
+ @Override
+ public void mouseDoubleClick(MouseEvent e) {
+ if (e.button == 1) {
+ Control source = (Control) e.getSource();
+ if (getCmsEditable().canEdit()) {
+ if (getCmsEditable().isEditing() && !(getEdited() instanceof Img)) {
+ if (source == mainSection)
+ return;
+ EditablePart part = findDataParent(source);
+ upload(part);
+ } else {
+ getCmsEditable().startEditing();
+ }
+ }
+ }
+ }
+
+ @Override
+ public void mouseDown(MouseEvent e) {
+ if (getCmsEditable().isEditing()) {
+ if (e.button == 1) {
+ Control source = (Control) e.getSource();
+ EditablePart composite = findDataParent(source);
+ Point point = new Point(e.x, e.y);
+ if (!(composite instanceof Img))
+ edit(composite, source.toDisplay(point));
+ } else if (e.button == 3) {
+ // EditablePart composite = findDataParent((Control) e
+ // .getSource());
+ // if (styledTools != null)
+ // styledTools.show(composite, new Point(e.x, e.y));
+ }
+ }
+ }
+
+ protected synchronized void upload(EditablePart part) {
+ if (part instanceof SectionPart) {
+ if (part instanceof Img) {
+ if (getEdited() == part)
+ return;
+ edit(part, null);
+ layout(part.getControl());
+ }
+ }
+ }
+ }
+
+ @Override
+ public Control getControl() {
+ return mainSection;
+ }
+
+ protected CmsImageManager<Control, Node> imageManager() {
+ if (imageManager == null)
+ imageManager = (CmsImageManager<Control, Node>) CmsSwtUtils.getCmsView(mainSection).getImageManager();
+ return imageManager;
+ }
+
+ // LOCAL UI HELPERS
+ protected Section createSectionIfNeeded(Composite body, Node node) throws RepositoryException {
+ Section section = null;
+ if (node != null) {
+ section = new Section(body, SWT.NO_FOCUS, node);
+ section.setLayoutData(CmsSwtUtils.fillWidth());
+ section.setLayout(CmsSwtUtils.noSpaceGridLayout());
+ }
+ return section;
+ }
+
+ protected void createSimpleLT(Composite bodyRow, Node node, String propName, String label, String msg)
+ throws RepositoryException {
+ if (getCmsEditable().canEdit() || node.hasProperty(propName)) {
+ createPropertyLbl(bodyRow, label);
+ EditablePropertyString eps = new EditablePropertyString(bodyRow, SWT.WRAP | SWT.LEFT, node, propName, msg);
+ eps.setMouseListener(getMouseListener());
+ eps.setFocusListener(getFocusListener());
+ eps.setLayoutData(CmsSwtUtils.fillWidth());
+ }
+ }
+
+ protected void createMultiStringLT(Composite bodyRow, Node node, String propName, String label, String msg)
+ throws RepositoryException {
+ boolean canEdit = getCmsEditable().canEdit();
+ if (canEdit || node.hasProperty(propName)) {
+ createPropertyLbl(bodyRow, label);
+
+ List<String> valueStrings = new ArrayList<String>();
+
+ if (node.hasProperty(propName)) {
+ Value[] values = node.getProperty(propName).getValues();
+ for (Value value : values)
+ valueStrings.add(value.getString());
+ }
+
+ // TODO use a drop down to display possible values to the end user
+ EditableMultiStringProperty emsp = new EditableMultiStringProperty(bodyRow, SWT.SINGLE | SWT.LEAD, node,
+ propName, valueStrings, new String[] { "Implement this" }, msg,
+ canEdit ? getRemoveValueSelListener() : null);
+ addListeners(emsp);
+ // emsp.setMouseListener(getMouseListener());
+ emsp.setStyle(FormStyle.propertyMessage.style());
+ emsp.setLayoutData(CmsSwtUtils.fillWidth());
+ }
+ }
+
+ protected Label createPropertyLbl(Composite parent, String value) {
+ return createPropertyLbl(parent, value, SWT.NONE);
+ }
+
+ protected Label createPropertyLbl(Composite parent, String value, int vAlign) {
+ // boolean isSmall = CmsView.getCmsView(parent).getUxContext().isSmall();
+ Label label = new Label(parent, SWT.LEAD | SWT.WRAP);
+ label.setText(value + " ");
+ CmsSwtUtils.style(label, FormStyle.propertyLabel.style());
+ GridData gd = new GridData(SWT.LEAD, vAlign, false, false);
+ if (labelColWidth != null)
+ gd.widthHint = labelColWidth;
+ label.setLayoutData(gd);
+ return label;
+ }
+
+ protected Label newStyledLabel(Composite parent, String style, String value) {
+ Label label = new Label(parent, SWT.NONE);
+ label.setText(value);
+ CmsSwtUtils.style(label, style);
+ return label;
+ }
+
+ protected Composite createRowLayoutComposite(Composite parent) throws RepositoryException {
+ Composite bodyRow = new Composite(parent, SWT.NO_FOCUS);
+ bodyRow.setLayoutData(CmsSwtUtils.fillWidth());
+ RowLayout rl = new RowLayout(SWT.WRAP);
+ rl.type = SWT.HORIZONTAL;
+ rl.spacing = rowLayoutHSpacing;
+ rl.marginHeight = rl.marginWidth = 0;
+ rl.marginTop = rl.marginBottom = rl.marginLeft = rl.marginRight = 0;
+ bodyRow.setLayout(rl);
+ return bodyRow;
+ }
+
+ protected Composite createAddImgComposite(final Section section, Composite parent, final Node parentNode)
+ throws RepositoryException {
+
+ Composite body = new Composite(parent, SWT.NO_FOCUS);
+ body.setLayout(new GridLayout());
+
+ FormFileUploadReceiver receiver = new FormFileUploadReceiver(section, parentNode, null);
+ final FileUploadHandler currentUploadHandler = new FileUploadHandler(receiver);
+ if (fileUploadListener != null)
+ currentUploadHandler.addUploadListener(fileUploadListener);
+
+ // Button creation
+ final FileUpload fileUpload = new FileUpload(body, SWT.BORDER);
+ fileUpload.setText("Import an image");
+ fileUpload.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, true, true));
+ fileUpload.addSelectionListener(new SelectionAdapter() {
+ private static final long serialVersionUID = 4869523412991968759L;
+
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ ServerPushSession pushSession = new ServerPushSession();
+ pushSession.start();
+ String uploadURL = currentUploadHandler.getUploadUrl();
+ fileUpload.submit(uploadURL);
+ }
+ });
+
+ return body;
+ }
+
+ protected class FormFileUploadReceiver extends FileUploadReceiver {
+
+ private Node context;
+ private Section section;
+ private String name;
+
+ public FormFileUploadReceiver(Section section, Node context, String name) {
+ this.context = context;
+ this.section = section;
+ this.name = name;
+ }
+
+ @Override
+ public void receive(InputStream stream, FileDetails details) throws IOException {
+
+ if (name == null)
+ name = details.getFileName();
+
+ // TODO clean image name more carefully
+ String cleanedName = name.replaceAll("[^a-zA-Z0-9-.]", "");
+ // We add a unique prefix to workaround the cache issue: when
+ // deleting and re-adding a new image with same name, the end user
+ // browser will use the cache and the image will remain unchanged
+ // for a while
+ cleanedName = System.currentTimeMillis() % 100000 + "_" + cleanedName;
+
+ imageManager().uploadImage(context, context, cleanedName, stream, details.getContentType());
+ // TODO clean refresh strategy
+ section.getDisplay().asyncExec(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ FormPageViewer.this.refresh(section);
+ section.layout();
+ section.getParent().layout();
+ } catch (RepositoryException re) {
+ throw new JcrException("Unable to refresh " + "image section for " + context, re);
+ }
+ }
+ });
+ }
+ }
+
+ protected void addListeners(StyledControl control) {
+ control.setMouseListener(getMouseListener());
+ control.setFocusListener(getFocusListener());
+ }
+
+ protected Img createImgComposite(Composite parent, Node node, Point preferredSize) throws RepositoryException {
+ Img img = new Img(parent, SWT.NONE, node, new Cms2DSize(preferredSize.x, preferredSize.y)) {
+ private static final long serialVersionUID = 1297900641952417540L;
+
+ @Override
+ protected void setContainerLayoutData(Composite composite) {
+ composite.setLayoutData(CmsSwtUtils.grabWidth(SWT.CENTER, SWT.DEFAULT));
+ }
+
+ @Override
+ protected void setControlLayoutData(Control control) {
+ control.setLayoutData(CmsSwtUtils.grabWidth(SWT.CENTER, SWT.DEFAULT));
+ }
+ };
+ img.setLayoutData(CmsSwtUtils.grabWidth(SWT.CENTER, SWT.DEFAULT));
+ updateContent(img);
+ addListeners(img);
+ return img;
+ }
+
+ protected Composite addDeleteAbility(final Section section, final Node sessionNode, int topWeight,
+ int rightWeight) {
+ Composite comp = new Composite(section, SWT.NONE);
+ comp.setLayoutData(CmsSwtUtils.fillAll());
+ comp.setLayout(new FormLayout());
+
+ // The body to be populated
+ Composite body = new Composite(comp, SWT.NO_FOCUS);
+ body.setLayoutData(EclipseUiUtils.fillFormData());
+
+ if (getCmsEditable().canEdit()) {
+ // the delete button
+ Button deleteBtn = new Button(comp, SWT.FLAT);
+ CmsSwtUtils.style(deleteBtn, FormStyle.deleteOverlay.style());
+ FormData formData = new FormData();
+ formData.right = new FormAttachment(rightWeight, 0);
+ formData.top = new FormAttachment(topWeight, 0);
+ deleteBtn.setLayoutData(formData);
+ deleteBtn.moveAbove(body);
+
+ deleteBtn.addSelectionListener(new SelectionAdapter() {
+ private static final long serialVersionUID = 4304223543657238462L;
+
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ super.widgetSelected(e);
+ if (MessageDialog.openConfirm(section.getShell(), "Confirm deletion",
+ "Are you really you want to remove this?")) {
+ Session session;
+ try {
+ session = sessionNode.getSession();
+ Section parSection = section.getParentSection();
+ sessionNode.remove();
+ session.save();
+ refresh(parSection);
+ layout(parSection);
+ } catch (RepositoryException re) {
+ throw new JcrException("Unable to delete " + sessionNode, re);
+ }
+
+ }
+
+ }
+ });
+ }
+ return body;
+ }
+
+// // LOCAL HELPERS FOR NODE MANAGEMENT
+// private Node getOrCreateNode(Node parent, String nodeName, String nodeType) throws RepositoryException {
+// Node node = null;
+// if (getCmsEditable().canEdit() && !parent.hasNode(nodeName)) {
+// node = JcrUtils.mkdirs(parent, nodeName, nodeType);
+// parent.getSession().save();
+// }
+//
+// if (getCmsEditable().canEdit() || parent.hasNode(nodeName))
+// node = parent.getNode(nodeName);
+//
+// return node;
+// }
+
+ private SelectionListener getRemoveValueSelListener() {
+ return new SelectionAdapter() {
+ private static final long serialVersionUID = 9022259089907445195L;
+
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ Object source = e.getSource();
+ if (source instanceof Button) {
+ Button btn = (Button) source;
+ Object obj = btn.getData(FormConstants.LINKED_VALUE);
+ EditablePart ep = findDataParent(btn);
+ if (ep != null && ep instanceof EditableMultiStringProperty) {
+ EditableMultiStringProperty emsp = (EditableMultiStringProperty) ep;
+ List<String> values = emsp.getValues();
+ if (values.contains(obj)) {
+ values.remove(values.indexOf(obj));
+ emsp.setValues(values);
+ try {
+ save(emsp);
+ // TODO workaround to force refresh
+ edit(emsp, 0);
+ cancelEdit();
+ } catch (RepositoryException e1) {
+ throw new JcrException("Unable to remove value " + obj, e1);
+ }
+ layout(emsp);
+ }
+ }
+ }
+ }
+ };
+ }
+
+ protected void setPropertySilently(Node node, String propName, String value) throws RepositoryException {
+ try {
+ // TODO Clean this:
+ // Format strings to replace \n
+ value = value.replaceAll("\n", "<br/>");
+ // Do not make the update if validation fails
+ try {
+ MarkupValidatorCopy.getInstance().validate(value);
+ } catch (Exception e) {
+ log.warn("Cannot set [" + value + "] on prop " + propName + "of " + node
+ + ", String cannot be validated - " + e.getMessage());
+ return;
+ }
+ // TODO check if the newly created property is of the correct type,
+ // otherwise the property will be silently created with a STRING
+ // property type.
+ node.setProperty(propName, value);
+ } catch (ValueFormatException vfe) {
+ log.warn("Cannot set [" + value + "] on prop " + propName + "of " + node + " - " + vfe.getMessage());
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+package org.argeo.cms.ui.forms;
+
+import org.argeo.api.cms.CmsStyle;
+
+/** Syles used */
+public enum FormStyle implements CmsStyle {
+ // Main
+ form, title,
+ // main part
+ header, headerBtn, headerCombo, section, sectionHeader,
+ // Property fields
+ propertyLabel, propertyText, propertyMessage, errorMessage,
+ // Date
+ popupCalendar,
+ // Buttons
+ starred, unstarred, starOverlay, editOverlay, deleteOverlay, updateOverlay, deleteOverlaySmall, calendar, delete,
+ // Contacts
+ email, address, phone, website,
+ // Social Media
+ facebook, twitter, linkedIn, instagram;
+
+ @Override
+ public String getClassPrefix() {
+ return "argeo-form";
+ }
+
+ // TODO clean button style management
+ public final static String BUTTON_SUFFIX = "_btn";
+}
--- /dev/null
+package org.argeo.cms.ui.forms;
+
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+
+import org.argeo.api.cms.CmsView;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.cms.CmsException;
+import org.argeo.cms.ui.util.CmsUiUtils;
+import org.argeo.eclipse.ui.EclipseUiUtils;
+import org.eclipse.jface.fieldassist.ControlDecoration;
+import org.eclipse.jface.fieldassist.FieldDecorationRegistry;
+import org.eclipse.jface.viewers.DoubleClickEvent;
+import org.eclipse.jface.viewers.IDoubleClickListener;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.viewers.TableViewer;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Text;
+
+/** Utilitary methods to ease implementation of CMS forms */
+public class FormUtils {
+ private final static CmsLog log = CmsLog.getLog(FormUtils.class);
+
+ public final static String DEFAULT_SHORT_DATE_FORMAT = "dd/MM/yyyy";
+
+ /** Best effort to convert a String to a calendar. Fails silently */
+ public static Calendar parseDate(DateFormat dateFormat, String calStr) {
+ Calendar cal = null;
+ if (EclipseUiUtils.notEmpty(calStr)) {
+ try {
+ Date date = dateFormat.parse(calStr);
+ cal = new GregorianCalendar();
+ cal.setTime(date);
+ } catch (ParseException pe) {
+ // Silent
+ log.warn("Unable to parse date: " + calStr + " - msg: "
+ + pe.getMessage());
+ }
+ }
+ return cal;
+ }
+
+ /** Add a double click listener on tables that display a JCR node list */
+ public static void addCanonicalDoubleClickListener(final TableViewer v) {
+ v.addDoubleClickListener(new IDoubleClickListener() {
+
+ @Override
+ public void doubleClick(DoubleClickEvent event) {
+ CmsView cmsView = CmsUiUtils.getCmsView();
+ Node node = (Node) ((IStructuredSelection) event.getSelection())
+ .getFirstElement();
+ try {
+ cmsView.navigateTo(node.getPath());
+ } catch (RepositoryException e) {
+ throw new CmsException("Unable to get path for node "
+ + node + " before calling navigateTo(path)", e);
+ }
+ }
+ });
+ }
+
+ // MANAGE ERROR DECORATION
+
+ public static ControlDecoration addDecoration(final Text text) {
+ final ControlDecoration dynDecoration = new ControlDecoration(text,
+ SWT.LEFT);
+ Image icon = getDecorationImage(FieldDecorationRegistry.DEC_ERROR);
+ dynDecoration.setImage(icon);
+ dynDecoration.setMarginWidth(3);
+ dynDecoration.hide();
+ return dynDecoration;
+ }
+
+ public static void refreshDecoration(Text text, ControlDecoration deco,
+ boolean isValid, boolean clean) {
+ if (isValid || clean) {
+ text.setBackground(null);
+ deco.hide();
+ } else {
+ text.setBackground(new Color(text.getDisplay(), 250, 200, 150));
+ deco.show();
+ }
+ }
+
+ public static Image getDecorationImage(String image) {
+ FieldDecorationRegistry registry = FieldDecorationRegistry.getDefault();
+ return registry.getFieldDecoration(image).getImage();
+ }
+
+ public static void addCompulsoryDecoration(Label label) {
+ final ControlDecoration dynDecoration = new ControlDecoration(label,
+ SWT.RIGHT | SWT.TOP);
+ Image icon = getDecorationImage(FieldDecorationRegistry.DEC_REQUIRED);
+ dynDecoration.setImage(icon);
+ dynDecoration.setMarginWidth(3);
+ }
+
+ // TODO the read only generation of read only links for various contact type
+ // should be factorised in the cms Utils.
+ /**
+ * Creates the read-only HTML snippet to display in a label with styling
+ * enabled in order to provide a click-able phone number
+ */
+ public static String getPhoneLink(String value) {
+ return getPhoneLink(value, value);
+ }
+
+ /**
+ * Creates the read-only HTML snippet to display in a label with styling
+ * enabled in order to provide a click-able phone number
+ *
+ * @param value
+ * @param label
+ * a potentially distinct label
+ * @return
+ */
+ public static String getPhoneLink(String value, String label) {
+ StringBuilder builder = new StringBuilder();
+ builder.append("<a href=\"tel:");
+ builder.append(value).append("\" target=\"_blank\" >").append(label)
+ .append("</a>");
+ return builder.toString();
+ }
+
+ /**
+ * Creates the read-only HTML snippet to display in a label with styling
+ * enabled in order to provide a click-able mail
+ */
+ public static String getMailLink(String value) {
+ return getMailLink(value, value);
+ }
+
+ /**
+ * Creates the read-only HTML snippet to display in a label with styling
+ * enabled in order to provide a click-able mail
+ *
+ * @param value
+ * @param label
+ * a potentially distinct label
+ * @return
+ */
+ public static String getMailLink(String value, String label) {
+ StringBuilder builder = new StringBuilder();
+ value = replaceAmpersand(value);
+ builder.append("<a href=\"mailto:");
+ builder.append(value).append("\" >").append(label).append("</a>");
+ return builder.toString();
+ }
+
+ /**
+ * Creates the read-only HTML snippet to display in a label with styling
+ * enabled in order to provide a click-able link
+ */
+ public static String getUrlLink(String value) {
+ return getUrlLink(value, value);
+ }
+
+ /**
+ * Creates the read-only HTML snippet to display in a label with styling
+ * enabled in order to provide a click-able link
+ */
+ public static String getUrlLink(String value, String label) {
+ StringBuilder builder = new StringBuilder();
+ value = replaceAmpersand(value);
+ label = replaceAmpersand(label);
+ if (!(value.startsWith("http://") || value.startsWith("https://")))
+ value = "http://" + value;
+ builder.append("<a href=\"");
+ builder.append(value + "\" target=\"_blank\" >" + label + "</a>");
+ return builder.toString();
+ }
+
+ private static String AMPERSAND = "&";
+
+ /**
+ * Cleans a String by replacing any '&' by its HTML encoding '&#38;' to
+ * avoid <code>SAXParseException</code> while rendering HTML with RWT
+ */
+ public static String replaceAmpersand(String value) {
+ value = value.replaceAll("&(?![#a-zA-Z0-9]+;)", AMPERSAND);
+ return value;
+ }
+
+ // Prevents instantiation
+ private FormUtils() {
+ }
+}
--- /dev/null
+package org.argeo.cms.ui.forms;
+
+import java.io.StringReader;
+import java.text.MessageFormat;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+
+import org.eclipse.rap.rwt.SingletonUtil;
+import org.eclipse.swt.widgets.Widget;
+import org.xml.sax.Attributes;
+import org.xml.sax.InputSource;
+import org.xml.sax.helpers.DefaultHandler;
+
+/**
+ * Copy of RAP v2.3 since it is in an internal package.
+ */
+class MarkupValidatorCopy {
+
+ // Used by Eclipse Scout project
+ public static final String MARKUP_VALIDATION_DISABLED = "org.eclipse.rap.rwt.markupValidationDisabled";
+
+ private static final String DTD = createDTD();
+ private static final Map<String, String[]> SUPPORTED_ELEMENTS = createSupportedElementsMap();
+ private final SAXParser saxParser;
+
+ public static MarkupValidatorCopy getInstance() {
+ return SingletonUtil.getSessionInstance(MarkupValidatorCopy.class);
+ }
+
+ public MarkupValidatorCopy() {
+ saxParser = createSAXParser();
+ }
+
+ public void validate(String text) {
+ StringBuilder markup = new StringBuilder();
+ markup.append(DTD);
+ markup.append("<html>");
+ markup.append(text);
+ markup.append("</html>");
+ InputSource inputSource = new InputSource(new StringReader(markup.toString()));
+ try {
+ saxParser.parse(inputSource, new MarkupHandler());
+ } catch (RuntimeException exception) {
+ throw exception;
+ } catch (Exception exception) {
+ throw new IllegalArgumentException("Failed to parse markup text", exception);
+ }
+ }
+
+ public static boolean isValidationDisabledFor(Widget widget) {
+ return Boolean.TRUE.equals(widget.getData(MARKUP_VALIDATION_DISABLED));
+ }
+
+ private static SAXParser createSAXParser() {
+ SAXParser result = null;
+ SAXParserFactory parserFactory = SAXParserFactory.newInstance();
+ try {
+ result = parserFactory.newSAXParser();
+ } catch (Exception exception) {
+ throw new RuntimeException("Failed to create SAX parser", exception);
+ }
+ return result;
+ }
+
+ private static String createDTD() {
+ StringBuilder result = new StringBuilder();
+ result.append("<!DOCTYPE html [");
+ result.append("<!ENTITY quot \""\">");
+ result.append("<!ENTITY amp \"&\">");
+ result.append("<!ENTITY apos \"'\">");
+ result.append("<!ENTITY lt \"<\">");
+ result.append("<!ENTITY gt \">\">");
+ result.append("<!ENTITY nbsp \" \">");
+ result.append("<!ENTITY ensp \" \">");
+ result.append("<!ENTITY emsp \" \">");
+ result.append("<!ENTITY ndash \"–\">");
+ result.append("<!ENTITY mdash \"—\">");
+ result.append("]>");
+ return result.toString();
+ }
+
+ private static Map<String, String[]> createSupportedElementsMap() {
+ Map<String, String[]> result = new HashMap<String, String[]>();
+ result.put("html", new String[0]);
+ result.put("br", new String[0]);
+ result.put("b", new String[] { "style" });
+ result.put("strong", new String[] { "style" });
+ result.put("i", new String[] { "style" });
+ result.put("em", new String[] { "style" });
+ result.put("sub", new String[] { "style" });
+ result.put("sup", new String[] { "style" });
+ result.put("big", new String[] { "style" });
+ result.put("small", new String[] { "style" });
+ result.put("del", new String[] { "style" });
+ result.put("ins", new String[] { "style" });
+ result.put("code", new String[] { "style" });
+ result.put("samp", new String[] { "style" });
+ result.put("kbd", new String[] { "style" });
+ result.put("var", new String[] { "style" });
+ result.put("cite", new String[] { "style" });
+ result.put("dfn", new String[] { "style" });
+ result.put("q", new String[] { "style" });
+ result.put("abbr", new String[] { "style", "title" });
+ result.put("span", new String[] { "style" });
+ result.put("img", new String[] { "style", "src", "width", "height", "title", "alt" });
+ result.put("a", new String[] { "style", "href", "target", "title" });
+ return result;
+ }
+
+ private static class MarkupHandler extends DefaultHandler {
+
+ @Override
+ public void startElement(String uri, String localName, String name, Attributes attributes) {
+ checkSupportedElements(name, attributes);
+ checkSupportedAttributes(name, attributes);
+ checkMandatoryAttributes(name, attributes);
+ }
+
+ private static void checkSupportedElements(String elementName, Attributes attributes) {
+ if (!SUPPORTED_ELEMENTS.containsKey(elementName)) {
+ throw new IllegalArgumentException("Unsupported element in markup text: " + elementName);
+ }
+ }
+
+ private static void checkSupportedAttributes(String elementName, Attributes attributes) {
+ if (attributes.getLength() > 0) {
+ List<String> supportedAttributes = Arrays.asList(SUPPORTED_ELEMENTS.get(elementName));
+ int index = 0;
+ String attributeName = attributes.getQName(index);
+ while (attributeName != null) {
+ if (!supportedAttributes.contains(attributeName)) {
+ String message = "Unsupported attribute \"{0}\" for element \"{1}\" in markup text";
+ message = MessageFormat.format(message, new Object[] { attributeName, elementName });
+ throw new IllegalArgumentException(message);
+ }
+ index++;
+ attributeName = attributes.getQName(index);
+ }
+ }
+ }
+
+ private static void checkMandatoryAttributes(String elementName, Attributes attributes) {
+ checkIntAttribute(elementName, attributes, "img", "width");
+ checkIntAttribute(elementName, attributes, "img", "height");
+ }
+
+ private static void checkIntAttribute(String elementName, Attributes attributes, String checkedElementName,
+ String checkedAttributeName) {
+ if (checkedElementName.equals(elementName)) {
+ String attribute = attributes.getValue(checkedAttributeName);
+ try {
+ Integer.parseInt(attribute);
+ } catch (NumberFormatException exception) {
+ String message = "Mandatory attribute \"{0}\" for element \"{1}\" is missing or not a valid integer";
+ Object[] arguments = new Object[] { checkedAttributeName, checkedElementName };
+ message = MessageFormat.format(message, arguments);
+ throw new IllegalArgumentException(message);
+ }
+ }
+ }
+
+ }
+
+}
--- /dev/null
+/** Argeo CMS forms, based on SWT/JFace. */
+package org.argeo.cms.ui.forms;
\ No newline at end of file
--- /dev/null
+package org.argeo.cms.ui.fs;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.file.DirectoryStream;
+import java.nio.file.FileSystem;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.spi.FileSystemProvider;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.jcr.Node;
+import javax.jcr.Repository;
+import javax.jcr.Session;
+
+import org.argeo.cms.CmsException;
+import org.argeo.cms.auth.CurrentUser;
+import org.argeo.cms.jcr.CmsJcrUtils;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.eclipse.ui.ColumnDefinition;
+import org.argeo.eclipse.ui.EclipseUiUtils;
+import org.argeo.eclipse.ui.fs.FileIconNameLabelProvider;
+import org.argeo.eclipse.ui.fs.FsTableViewer;
+import org.argeo.eclipse.ui.fs.FsUiConstants;
+import org.argeo.eclipse.ui.fs.FsUiUtils;
+import org.argeo.eclipse.ui.fs.NioFileLabelProvider;
+import org.argeo.jcr.JcrUtils;
+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.Viewer;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.SashForm;
+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.events.MouseAdapter;
+import org.eclipse.swt.events.MouseEvent;
+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.layout.RowData;
+import org.eclipse.swt.layout.RowLayout;
+import org.eclipse.swt.widgets.Button;
+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.Text;
+
+/**
+ * Default CMS browser composite: a sashForm layout with bookmarks at the left
+ * hand side, a simple table in the middle and an overview at right hand side.
+ */
+public class CmsFsBrowser extends Composite {
+ // private final static Log log = LogFactory.getLog(CmsFsBrowser.class);
+ private static final long serialVersionUID = -40347919096946585L;
+
+ private final FileSystemProvider nodeFileSystemProvider;
+ private final Node currentBaseContext;
+
+ // UI Parts for the browser
+ private Composite leftPannelCmp;
+ private Composite filterCmp;
+ private Text filterTxt;
+ private FsTableViewer directoryDisplayViewer;
+ private Composite rightPannelCmp;
+
+ private FsContextMenu contextMenu;
+
+ // Local context (this composite is state full)
+ private Path initialPath;
+ private Path currDisplayedFolder;
+ private Path currSelected;
+
+ // local variables (to be cleaned)
+ private int bookmarkColWith = 500;
+
+ /*
+ * WARNING: unfinalised implementation of the mechanism to retrieve base
+ * paths
+ */
+
+ private final static String NODE_PREFIX = "node://";
+
+ private String getCurrentHomePath() {
+ Session session = null;
+ try {
+ Repository repo = currentBaseContext.getSession().getRepository();
+ session = CurrentUser.tryAs(() -> repo.login());
+ String homepath = CmsJcrUtils.getUserHome(session).getPath();
+ return homepath;
+ } catch (Exception e) {
+ throw new CmsException("Cannot retrieve Current User Home Path", e);
+ } finally {
+ JcrUtils.logoutQuietly(session);
+ }
+ }
+
+ protected Path[] getMyFilesPath() {
+ // return Paths.get(System.getProperty("user.dir"));
+ String currHomeUriStr = NODE_PREFIX + getCurrentHomePath();
+ try {
+ URI uri = new URI(currHomeUriStr);
+ FileSystem fileSystem = nodeFileSystemProvider.getFileSystem(uri);
+ if (fileSystem == null) {
+ PrivilegedExceptionAction<FileSystem> pea = new PrivilegedExceptionAction<FileSystem>() {
+ @Override
+ public FileSystem run() throws Exception {
+ return nodeFileSystemProvider.newFileSystem(uri, null);
+ }
+
+ };
+ fileSystem = CurrentUser.tryAs(pea);
+ }
+ Path[] paths = { fileSystem.getPath(getCurrentHomePath()), fileSystem.getPath("/") };
+ return paths;
+ } catch (URISyntaxException | PrivilegedActionException e) {
+ throw new RuntimeException("unable to initialise home file system for " + currHomeUriStr, e);
+ }
+ }
+
+ private Path[] getMyGroupsFilesPath() {
+ // TODO
+ Path[] paths = { Paths.get(System.getProperty("user.dir")), Paths.get("/tmp") };
+ return paths;
+ }
+
+ private Path[] getMyBookmarks() {
+ // TODO
+ Path[] paths = { Paths.get(System.getProperty("user.dir")), Paths.get("/tmp"), Paths.get("/opt") };
+ return paths;
+ }
+
+ /* End of warning */
+
+ public CmsFsBrowser(Composite parent, int style, Node context, FileSystemProvider fileSystemProvider) {
+ super(parent, style);
+ this.nodeFileSystemProvider = fileSystemProvider;
+ this.currentBaseContext = context;
+
+ this.setLayout(EclipseUiUtils.noSpaceGridLayout());
+
+ SashForm form = new SashForm(this, SWT.HORIZONTAL);
+
+ leftPannelCmp = new Composite(form, SWT.NO_FOCUS);
+ // Bookmarks are still static
+ populateBookmarks(leftPannelCmp);
+
+ Composite centerCmp = new Composite(form, SWT.BORDER | SWT.NO_FOCUS);
+ createDisplay(centerCmp);
+
+ rightPannelCmp = new Composite(form, SWT.NO_FOCUS);
+
+ form.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
+ form.setWeights(new int[] { 15, 40, 20 });
+ }
+
+ void refresh() {
+ modifyFilter(false);
+ // also refresh bookmarks and groups
+ }
+
+ private void createDisplay(final Composite parent) {
+ parent.setLayout(EclipseUiUtils.noSpaceGridLayout());
+
+ // top filter
+ filterCmp = new Composite(parent, SWT.NO_FOCUS);
+ filterCmp.setLayoutData(EclipseUiUtils.fillWidth());
+ addFilterPanel(filterCmp);
+
+ // Main display
+ directoryDisplayViewer = new FsTableViewer(parent, SWT.MULTI);
+ List<ColumnDefinition> colDefs = new ArrayList<>();
+ colDefs.add(new ColumnDefinition(new FileIconNameLabelProvider(), "Name", 250));
+ colDefs.add(new ColumnDefinition(new NioFileLabelProvider(FsUiConstants.PROPERTY_SIZE), "Size", 100));
+ colDefs.add(new ColumnDefinition(new NioFileLabelProvider(FsUiConstants.PROPERTY_TYPE), "Type", 150));
+ colDefs.add(new ColumnDefinition(new NioFileLabelProvider(FsUiConstants.PROPERTY_LAST_MODIFIED),
+ "Last modified", 400));
+ final Table table = directoryDisplayViewer.configureDefaultTable(colDefs);
+ table.setLayoutData(EclipseUiUtils.fillAll());
+
+ // table.addKeyListener(new KeyListener() {
+ // private static final long serialVersionUID = -8083424284436715709L;
+ //
+ // @Override
+ // public void keyReleased(KeyEvent e) {
+ // }
+ //
+ // @Override
+ // public void keyPressed(KeyEvent e) {
+ // if (log.isDebugEnabled())
+ // log.debug("Key event received: " + e.keyCode);
+ // IStructuredSelection selection = (IStructuredSelection)
+ // directoryDisplayViewer.getSelection();
+ // Path selected = null;
+ // if (!selection.isEmpty())
+ // selected = ((Path) selection.getFirstElement());
+ // if (e.keyCode == SWT.CR) {
+ // if (!Files.isDirectory(selected))
+ // return;
+ // if (selected != null) {
+ // currDisplayedFolder = selected;
+ // directoryDisplayViewer.setInput(currDisplayedFolder, "*");
+ // }
+ // } else if (e.keyCode == SWT.BS) {
+ // currDisplayedFolder = currDisplayedFolder.getParent();
+ // directoryDisplayViewer.setInput(currDisplayedFolder, "*");
+ // directoryDisplayViewer.getTable().setFocus();
+ // }
+ // }
+ // });
+
+ directoryDisplayViewer.addSelectionChangedListener(new ISelectionChangedListener() {
+
+ @Override
+ public void selectionChanged(SelectionChangedEvent event) {
+ IStructuredSelection selection = (IStructuredSelection) directoryDisplayViewer.getSelection();
+ Path selected = null;
+ if (selection.isEmpty())
+ setSelected(null);
+ else
+ selected = ((Path) selection.getFirstElement());
+ if (selected != null) {
+ // TODO manage multiple selection
+ setSelected(selected);
+ }
+ }
+ });
+
+ directoryDisplayViewer.addDoubleClickListener(new IDoubleClickListener() {
+ @Override
+ public void doubleClick(DoubleClickEvent event) {
+ IStructuredSelection selection = (IStructuredSelection) directoryDisplayViewer.getSelection();
+ Path selected = null;
+ if (!selection.isEmpty())
+ selected = ((Path) selection.getFirstElement());
+ if (selected != null) {
+ if (!Files.isDirectory(selected))
+ return;
+ setInput(selected);
+ }
+ }
+ });
+
+ // The context menu
+ contextMenu = new FsContextMenu(this);
+
+ table.addMouseListener(new MouseAdapter() {
+ private static final long serialVersionUID = 6737579410648595940L;
+
+ @Override
+ public void mouseDown(MouseEvent e) {
+ if (e.button == 3) {
+ // contextMenu.setCurrFolderPath(currDisplayedFolder);
+ contextMenu.show(table, new Point(e.x, e.y), currDisplayedFolder);
+ }
+ }
+ });
+ }
+
+ private void addPathElementBtn(Path path) {
+ Button elemBtn = new Button(filterCmp, SWT.PUSH);
+ String nameStr;
+ if (path.toString().equals("/"))
+ nameStr = "[jcr:root]";
+ else
+ nameStr = path.getFileName().toString();
+ elemBtn.setText(nameStr + " >> ");
+ CmsSwtUtils.style(elemBtn, FsStyles.BREAD_CRUMB_BTN);
+ elemBtn.addSelectionListener(new SelectionAdapter() {
+ private static final long serialVersionUID = -4103695476023480651L;
+
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ setInput(path);
+ }
+ });
+ }
+
+ public void setInput(Path path) {
+ if (path.equals(currDisplayedFolder))
+ return;
+ currDisplayedFolder = path;
+
+ Path diff = initialPath.relativize(currDisplayedFolder);
+
+ for (Control child : filterCmp.getChildren())
+ if (!child.equals(filterTxt))
+ child.dispose();
+
+ addPathElementBtn(initialPath);
+ Path currTarget = initialPath;
+ if (!diff.toString().equals(""))
+ for (Path pathElem : diff) {
+ currTarget = currTarget.resolve(pathElem);
+ addPathElementBtn(currTarget);
+ }
+
+ filterTxt.setText("");
+ filterTxt.moveBelow(null);
+ setSelected(null);
+ filterCmp.getParent().layout(true, true);
+ }
+
+ private void setSelected(Path path) {
+ currSelected = path;
+ setOverviewInput(path);
+ }
+
+ public Viewer getViewer() {
+ return directoryDisplayViewer;
+ }
+
+ private void populateBookmarks(Composite parent) {
+ CmsSwtUtils.clear(parent);
+ parent.setLayout(new GridLayout());
+ ISelectionChangedListener selList = new BookmarksSelChangeListener();
+
+ FsTableViewer homeViewer = new FsTableViewer(parent, SWT.SINGLE | SWT.NO_SCROLL);
+ Table table = homeViewer.configureDefaultSingleColumnTable(bookmarkColWith);
+ GridData gd = EclipseUiUtils.fillWidth();
+ gd.horizontalIndent = 10;
+ table.setLayoutData(gd);
+ homeViewer.addSelectionChangedListener(selList);
+ homeViewer.setPathsInput(getMyFilesPath());
+
+ appendTitle(parent, "Shared files");
+ FsTableViewer groupsViewer = new FsTableViewer(parent, SWT.SINGLE | SWT.NO_SCROLL);
+ table = groupsViewer.configureDefaultSingleColumnTable(bookmarkColWith);
+ gd = EclipseUiUtils.fillWidth();
+ gd.horizontalIndent = 10;
+ table.setLayoutData(gd);
+ groupsViewer.addSelectionChangedListener(selList);
+ groupsViewer.setPathsInput(getMyGroupsFilesPath());
+
+ appendTitle(parent, "My bookmarks");
+ FsTableViewer bookmarksViewer = new FsTableViewer(parent, SWT.SINGLE | SWT.NO_SCROLL);
+ table = bookmarksViewer.configureDefaultSingleColumnTable(bookmarkColWith);
+ gd = EclipseUiUtils.fillWidth();
+ gd.horizontalIndent = 10;
+ table.setLayoutData(gd);
+ bookmarksViewer.addSelectionChangedListener(selList);
+ bookmarksViewer.setPathsInput(getMyBookmarks());
+ }
+
+ /**
+ * Recreates the content of the box that displays information about the
+ * current selected Path.
+ */
+ private void setOverviewInput(Path path) {
+ try {
+ EclipseUiUtils.clear(rightPannelCmp);
+ rightPannelCmp.setLayout(new GridLayout());
+ if (path != null) {
+ // if (isImg(context)) {
+ // EditableImage image = new Img(parent, RIGHT, context,
+ // imageWidth);
+ // image.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER,
+ // true, false,
+ // 2, 1));
+ // }
+
+ Label contextL = new Label(rightPannelCmp, SWT.NONE);
+ contextL.setText(path.getFileName().toString());
+ contextL.setFont(EclipseUiUtils.getBoldFont(rightPannelCmp));
+ addProperty(rightPannelCmp, "Last modified", Files.getLastModifiedTime(path).toString());
+ // addProperty(rightPannelCmp, "Owner",
+ // Files.getOwner(path).getName());
+ if (Files.isDirectory(path)) {
+ addProperty(rightPannelCmp, "Type", "Folder");
+ } else {
+ String mimeType = Files.probeContentType(path);
+ if (EclipseUiUtils.isEmpty(mimeType))
+ mimeType = "<i>Unknown</i>";
+ addProperty(rightPannelCmp, "Type", mimeType);
+ addProperty(rightPannelCmp, "Size", FsUiUtils.humanReadableByteCount(Files.size(path), false));
+ }
+ }
+ rightPannelCmp.layout(true, true);
+ } catch (IOException e) {
+ throw new CmsException("Cannot display details for " + path.toString(), e);
+ }
+ }
+
+ private void addFilterPanel(Composite parent) {
+ RowLayout rl = new RowLayout(SWT.HORIZONTAL);
+ rl.wrap = true;
+ parent.setLayout(rl);
+ // parent.setLayout(EclipseUiUtils.noSpaceGridLayout(new GridLayout(2,
+ // false)));
+
+ filterTxt = new Text(parent, SWT.SEARCH | SWT.ICON_CANCEL);
+ filterTxt.setMessage("Search current folder");
+ filterTxt.setLayoutData(new RowData(250, SWT.DEFAULT));
+ filterTxt.addModifyListener(new ModifyListener() {
+ private static final long serialVersionUID = 1L;
+
+ public void modifyText(ModifyEvent event) {
+ modifyFilter(false);
+ }
+ });
+ filterTxt.addKeyListener(new KeyListener() {
+ private static final long serialVersionUID = 2533535233583035527L;
+
+ @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(currEdited);
+ // if (table != null && !table.isDisposed())
+ // currTable = table;
+ // }
+ //
+ // if (e.keyCode == SWT.ARROW_DOWN)
+ // currTable.setFocus();
+ // else if (e.keyCode == SWT.BS) {
+ // if (filterTxt.getText().equals("")
+ // && !(currEdited.getNameCount() == 1 ||
+ // currEdited.equals(initialPath))) {
+ // Path oldEdited = currEdited;
+ // Path parentPath = currEdited.getParent();
+ // setEdited(parentPath);
+ // if (browserCols.containsKey(parentPath))
+ // browserCols.get(parentPath).setSelected(oldEdited);
+ // filterTxt.setFocus();
+ // e.doit = false;
+ // }
+ // } else if (e.keyCode == SWT.TAB && !shiftPressed) {
+ // Path uniqueChild = getOnlyChild(currEdited,
+ // filterTxt.getText());
+ // if (uniqueChild != null) {
+ // // Highlight the unique chosen child
+ // currTable.setSelected(uniqueChild);
+ // setEdited(uniqueChild);
+ // }
+ // filterTxt.setFocus();
+ // e.doit = false;
+ // }
+ }
+ });
+ }
+
+ private Path getOnlyChild(Path parent, String filter) {
+ try (DirectoryStream<Path> stream = Files.newDirectoryStream(currDisplayedFolder, filter + "*")) {
+ Path uniqueChild = null;
+ boolean moreThanOne = false;
+ loop: for (Path entry : stream) {
+ if (uniqueChild == null) {
+ uniqueChild = entry;
+ } else {
+ moreThanOne = true;
+ break loop;
+ }
+ }
+ if (!moreThanOne)
+ return uniqueChild;
+ return null;
+ } catch (IOException ioe) {
+ throw new CmsException(
+ "Unable to determine unique child existence and get it under " + parent + " with filter " + filter,
+ ioe);
+ }
+ }
+
+ private void modifyFilter(boolean fromOutside) {
+ if (!fromOutside)
+ if (currDisplayedFolder != null) {
+ String filter = filterTxt.getText() + "*";
+ directoryDisplayViewer.setInput(currDisplayedFolder, filter);
+ }
+ }
+
+ private class BookmarksSelChangeListener implements ISelectionChangedListener {
+
+ @Override
+ public void selectionChanged(SelectionChangedEvent event) {
+ IStructuredSelection selection = (IStructuredSelection) event.getSelection();
+ if (selection.isEmpty())
+ return;
+ else {
+ Path newSelected = (Path) selection.getFirstElement();
+ if (newSelected.equals(currDisplayedFolder) && newSelected.equals(initialPath))
+ return;
+ initialPath = newSelected;
+ setInput(newSelected);
+ }
+ }
+ }
+
+ // Simplify UI implementation
+ private void addProperty(Composite parent, String propName, String value) {
+ Label contextL = new Label(parent, SWT.NONE);
+ contextL.setText(propName + ": " + value);
+ }
+
+ private Label appendTitle(Composite parent, String value) {
+ Label titleLbl = new Label(parent, SWT.NONE);
+ titleLbl.setText(value);
+ titleLbl.setFont(EclipseUiUtils.getBoldFont(parent));
+ GridData gd = EclipseUiUtils.fillWidth();
+ gd.horizontalIndent = 5;
+ gd.verticalIndent = 5;
+ titleLbl.setLayoutData(gd);
+ return titleLbl;
+ }
+}
--- /dev/null
+package org.argeo.cms.ui.fs;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.argeo.api.cms.CmsLog;
+import org.argeo.eclipse.ui.specific.FileDropAdapter;
+import org.eclipse.swt.dnd.DND;
+import org.eclipse.swt.dnd.DropTarget;
+import org.eclipse.swt.dnd.DropTargetEvent;
+import org.eclipse.swt.widgets.Control;
+
+/** Allows a control to receive file drops. */
+public class FileDrop {
+ private final static CmsLog log = CmsLog.getLog(FileDrop.class);
+
+ public void createDropTarget(Control control) {
+ FileDropAdapter fileDropAdapter = new FileDropAdapter() {
+ @Override
+ protected void processUpload(InputStream in, String fileName, String contentType) throws IOException {
+ if (log.isDebugEnabled())
+ log.debug("Process upload of " + fileName + " (" + contentType + ")");
+ processFileUpload(in, fileName, contentType);
+ }
+ };
+ DropTarget dropTarget = new DropTarget(control, DND.DROP_MOVE | DND.DROP_COPY);
+ fileDropAdapter.prepareDropTarget(control, dropTarget);
+ }
+
+ public void handleFileDrop(Control control, DropTargetEvent event) {
+ }
+
+ /** Executed in UI thread */
+ protected void processFileUpload(InputStream in, String fileName, String contentType) throws IOException {
+
+ }
+}
--- /dev/null
+package org.argeo.cms.ui.fs;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.Method;
+import java.net.URI;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.io.IOUtils;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.cms.CmsException;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.eclipse.ui.EclipseUiUtils;
+import org.argeo.eclipse.ui.dialogs.SingleValue;
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.ShellEvent;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.FileDialog;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Shell;
+
+/** Generic popup context menu to manage NIO Path in a Viewer. */
+public class FsContextMenu extends Shell {
+ private static final long serialVersionUID = -9120261153509855795L;
+
+ private final static CmsLog log = CmsLog.getLog(FsContextMenu.class);
+
+ // Default known actions
+ public final static String ACTION_ID_CREATE_FOLDER = "createFolder";
+ public final static String ACTION_ID_BOOKMARK_FOLDER = "bookmarkFolder";
+ public final static String ACTION_ID_SHARE_FOLDER = "shareFolder";
+ public final static String ACTION_ID_DOWNLOAD_FOLDER = "downloadFolder";
+ public final static String ACTION_ID_DELETE = "delete";
+ public final static String ACTION_ID_UPLOAD_FILE = "uploadFiles";
+ public final static String ACTION_ID_OPEN = "open";
+
+ // Local context
+ private final CmsFsBrowser browser;
+ // private final Viewer viewer;
+ private final static String KEY_ACTION_ID = "actionId";
+ private final static String[] DEFAULT_ACTIONS = { ACTION_ID_CREATE_FOLDER, ACTION_ID_BOOKMARK_FOLDER,
+ ACTION_ID_SHARE_FOLDER, ACTION_ID_DOWNLOAD_FOLDER, ACTION_ID_DELETE, ACTION_ID_UPLOAD_FILE,
+ ACTION_ID_OPEN };
+ private Map<String, Button> actionButtons = new HashMap<String, Button>();
+
+ private Path currFolderPath;
+
+ public FsContextMenu(CmsFsBrowser browser) { // Viewer viewer, Display
+ // display) {
+ super(browser.getDisplay(), SWT.NO_TRIM | SWT.BORDER | SWT.ON_TOP);
+ this.browser = browser;
+ setLayout(EclipseUiUtils.noSpaceGridLayout());
+
+ Composite boxCmp = new Composite(this, SWT.NO_FOCUS | SWT.BORDER);
+ boxCmp.setLayout(EclipseUiUtils.noSpaceGridLayout());
+ CmsSwtUtils.style(boxCmp, FsStyles.CONTEXT_MENU_BOX);
+ createContextMenu(boxCmp);
+
+ addShellListener(new ActionsShellListener());
+ }
+
+ protected void createContextMenu(Composite boxCmp) {
+ ActionsSelListener asl = new ActionsSelListener();
+ for (String actionId : DEFAULT_ACTIONS) {
+ Button btn = new Button(boxCmp, SWT.FLAT | SWT.PUSH | SWT.LEAD);
+ btn.setText(getLabel(actionId));
+ btn.setLayoutData(EclipseUiUtils.fillWidth());
+ CmsSwtUtils.markup(btn);
+ CmsSwtUtils.style(btn, actionId + FsStyles.BUTTON_SUFFIX);
+ btn.setData(KEY_ACTION_ID, actionId);
+ btn.addSelectionListener(asl);
+ actionButtons.put(actionId, btn);
+ }
+ }
+
+ protected String getLabel(String actionId) {
+ switch (actionId) {
+ case ACTION_ID_CREATE_FOLDER:
+ return "Create Folder";
+ case ACTION_ID_BOOKMARK_FOLDER:
+ return "Bookmark Folder";
+ case ACTION_ID_SHARE_FOLDER:
+ return "Share Folder";
+ case ACTION_ID_DOWNLOAD_FOLDER:
+ return "Download as zip archive";
+ case ACTION_ID_DELETE:
+ return "Delete";
+ case ACTION_ID_UPLOAD_FILE:
+ return "Upload Files";
+ case ACTION_ID_OPEN:
+ return "Open";
+ default:
+ throw new IllegalArgumentException("Unknown action ID " + actionId);
+ }
+ }
+
+ protected void aboutToShow(Control source, Point location) {
+ IStructuredSelection selection = ((IStructuredSelection) browser.getViewer().getSelection());
+ boolean emptySel = true;
+ boolean multiSel = false;
+ boolean isFolder = true;
+ if (selection != null && !selection.isEmpty()) {
+ emptySel = false;
+ multiSel = selection.size() > 1;
+ if (!multiSel && selection.getFirstElement() instanceof Path) {
+ isFolder = Files.isDirectory((Path) selection.getFirstElement());
+ }
+ }
+ if (emptySel) {
+ setVisible(true, ACTION_ID_CREATE_FOLDER, ACTION_ID_UPLOAD_FILE);
+ setVisible(false, ACTION_ID_SHARE_FOLDER, ACTION_ID_DOWNLOAD_FOLDER, ACTION_ID_DELETE, ACTION_ID_OPEN,
+ // to be implemented
+ ACTION_ID_BOOKMARK_FOLDER);
+ } else if (multiSel) {
+ setVisible(true, ACTION_ID_CREATE_FOLDER, ACTION_ID_UPLOAD_FILE, ACTION_ID_DELETE);
+ setVisible(false, ACTION_ID_SHARE_FOLDER, ACTION_ID_DOWNLOAD_FOLDER, ACTION_ID_OPEN,
+ // to be implemented
+ ACTION_ID_BOOKMARK_FOLDER);
+ } else if (isFolder) {
+ setVisible(true, ACTION_ID_CREATE_FOLDER, ACTION_ID_UPLOAD_FILE, ACTION_ID_DELETE);
+ setVisible(false, ACTION_ID_OPEN,
+ // to be implemented
+ ACTION_ID_SHARE_FOLDER, ACTION_ID_DOWNLOAD_FOLDER, ACTION_ID_BOOKMARK_FOLDER);
+ } else {
+ setVisible(true, ACTION_ID_CREATE_FOLDER, ACTION_ID_UPLOAD_FILE, ACTION_ID_OPEN, ACTION_ID_DELETE);
+ setVisible(false, ACTION_ID_SHARE_FOLDER, ACTION_ID_DOWNLOAD_FOLDER,
+ // to be implemented
+ ACTION_ID_BOOKMARK_FOLDER);
+ }
+ }
+
+ private void setVisible(boolean visible, String... buttonIds) {
+ for (String id : buttonIds) {
+ Button button = actionButtons.get(id);
+ button.setVisible(visible);
+ GridData gd = (GridData) button.getLayoutData();
+ gd.heightHint = visible ? SWT.DEFAULT : 0;
+ }
+ }
+
+ public void show(Control source, Point location, Path currFolderPath) {
+ if (isVisible())
+ setVisible(false);
+ // TODO find a better way to retrieve the parent path (cannot be deduced
+ // from table content because it will fail on an empty folder)
+ this.currFolderPath = currFolderPath;
+ aboutToShow(source, location);
+ pack();
+ layout();
+ if (source instanceof Control)
+ setLocation(((Control) source).toDisplay(location.x, location.y));
+ open();
+ }
+
+ class StyleButton extends Label {
+ private static final long serialVersionUID = 7731102609123946115L;
+
+ public StyleButton(Composite parent, int swtStyle) {
+ super(parent, swtStyle);
+ }
+
+ }
+
+ // class ActionsMouseListener extends MouseAdapter {
+ // private static final long serialVersionUID = -1041871937815812149L;
+ //
+ // @Override
+ // public void mouseDown(MouseEvent e) {
+ // Object eventSource = e.getSource();
+ // if (e.button == 1) {
+ // if (eventSource instanceof Button) {
+ // Button pressedBtn = (Button) eventSource;
+ // String actionId = (String) pressedBtn.getData(KEY_ACTION_ID);
+ // switch (actionId) {
+ // case ACTION_ID_CREATE_FOLDER:
+ // createFolder();
+ // break;
+ // case ACTION_ID_DELETE:
+ // deleteItems();
+ // break;
+ // default:
+ // throw new IllegalArgumentException("Unimplemented action " + actionId);
+ // // case ACTION_ID_SHARE_FOLDER:
+ // // return "Share Folder";
+ // // case ACTION_ID_DOWNLOAD_FOLDER:
+ // // return "Download as zip archive";
+ // // case ACTION_ID_UPLOAD_FILE:
+ // // return "Upload Files";
+ // // case ACTION_ID_OPEN:
+ // // return "Open";
+ // }
+ // }
+ // }
+ // viewer.getControl().setFocus();
+ // // setVisible(false);
+ // }
+ // }
+
+ class ActionsSelListener extends SelectionAdapter {
+ private static final long serialVersionUID = -1041871937815812149L;
+
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ Object eventSource = e.getSource();
+ if (eventSource instanceof Button) {
+ Button pressedBtn = (Button) eventSource;
+ String actionId = (String) pressedBtn.getData(KEY_ACTION_ID);
+ switch (actionId) {
+ case ACTION_ID_CREATE_FOLDER:
+ createFolder();
+ break;
+ case ACTION_ID_DELETE:
+ deleteItems();
+ break;
+ case ACTION_ID_OPEN:
+ openFile();
+ break;
+ case ACTION_ID_UPLOAD_FILE:
+ uploadFiles();
+ break;
+ default:
+ throw new IllegalArgumentException("Unimplemented action " + actionId);
+ // case ACTION_ID_SHARE_FOLDER:
+ // return "Share Folder";
+ // case ACTION_ID_DOWNLOAD_FOLDER:
+ // return "Download as zip archive";
+ // case ACTION_ID_OPEN:
+ // return "Open";
+ }
+ }
+ browser.setFocus();
+ // viewer.getControl().setFocus();
+ // setVisible(false);
+
+ }
+ }
+
+ class ActionsShellListener extends org.eclipse.swt.events.ShellAdapter {
+ private static final long serialVersionUID = -5092341449523150827L;
+
+ @Override
+ public void shellDeactivated(ShellEvent e) {
+ setVisible(false);
+ }
+ }
+
+ private void openFile() {
+ log.warn("Implement single sourced, workbench independant \"Open File\" action");
+ }
+
+ private void deleteItems() {
+ IStructuredSelection selection = ((IStructuredSelection) browser.getViewer().getSelection());
+ if (selection.isEmpty())
+ return;
+
+ StringBuilder builder = new StringBuilder();
+ @SuppressWarnings("unchecked")
+ Iterator<Object> iterator = selection.iterator();
+ List<Path> paths = new ArrayList<>();
+
+ while (iterator.hasNext()) {
+ Path path = (Path) iterator.next();
+ builder.append(path.getFileName() + ", ");
+ paths.add(path);
+ }
+ String msg = "You are about to delete following elements: " + builder.substring(0, builder.length() - 2)
+ + ". Are you sure?";
+ if (MessageDialog.openConfirm(this, "Confirm deletion", msg)) {
+ for (Path path : paths) {
+ try {
+ // Might have already been deleted if we are in a tree
+ Files.deleteIfExists(path);
+ } catch (IOException e) {
+ throw new CmsException("Cannot delete path " + path, e);
+ }
+ }
+ browser.refresh();
+ }
+ }
+
+ private void createFolder() {
+ String msg = "Please provide a name.";
+ String name = SingleValue.ask("Create folder", msg);
+ // TODO enhance check of name validity
+ if (EclipseUiUtils.notEmpty(name)) {
+ try {
+ Path child = currFolderPath.resolve(name);
+ if (Files.exists(child))
+ throw new CmsException("An item with name " + name + " already exists at "
+ + currFolderPath.toString() + ", cannot create");
+ else
+ Files.createDirectories(child);
+ browser.refresh();
+ } catch (IOException e) {
+ throw new CmsException("Cannot create folder " + name + " at " + currFolderPath.toString(), e);
+ }
+ }
+ }
+
+ private void uploadFiles() {
+ try {
+ FileDialog dialog = new FileDialog(browser.getShell(), SWT.MULTI);
+ dialog.setText("Choose one or more files to upload");
+
+ if (EclipseUiUtils.notEmpty(dialog.open())) {
+ String[] names = dialog.getFileNames();
+ // Workaround small differences between RAP and RCP
+ // 1. returned names are absolute path on RAP and
+ // relative in RCP
+ // 2. in RCP we must use getFilterPath that does not
+ // exists on RAP
+ Method filterMethod = null;
+ Path parPath = null;
+ try {
+ filterMethod = dialog.getClass().getDeclaredMethod("getFilterPath");
+ String filterPath = (String) filterMethod.invoke(dialog);
+ parPath = Paths.get(filterPath);
+ } catch (NoSuchMethodException nsme) { // RAP
+ }
+ if (names.length == 0)
+ return;
+ else {
+ loop: for (String name : names) {
+ Path tmpPath = Paths.get(name);
+ if (parPath != null)
+ tmpPath = parPath.resolve(tmpPath);
+ if (Files.exists(tmpPath)) {
+ URI uri = tmpPath.toUri();
+ String uriStr = uri.toString();
+
+ if (Files.isDirectory(tmpPath)) {
+ MessageDialog.openError(browser.getShell(), "Unimplemented directory import",
+ "Upload of directories in the system is not yet implemented");
+ continue loop;
+ }
+ Path targetPath = currFolderPath.resolve(tmpPath.getFileName().toString());
+ InputStream in = null;
+ try {
+ in = new ByteArrayInputStream(Files.readAllBytes(tmpPath));
+ Files.copy(in, targetPath);
+ Files.delete(tmpPath);
+ } finally {
+ IOUtils.closeQuietly(in);
+ }
+ if (log.isDebugEnabled())
+ log.debug("copied uploaded file " + uriStr + " to " + targetPath.toString());
+ } else {
+ String msg = "Cannot copy tmp file from " + tmpPath.toString();
+ if (parPath != null)
+ msg += "\nPlease remember that file upload fails when choosing files from the \"Recently Used\" bookmarks on some OS";
+ MessageDialog.openError(browser.getShell(), "Missing file", msg);
+ continue loop;
+ }
+ }
+ browser.refresh();
+ }
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ MessageDialog.openError(getShell(), "Upload has failed", "Cannot import files to " + currFolderPath);
+ }
+ }
+
+ public void setCurrFolderPath(Path currFolderPath) {
+ this.currFolderPath = currFolderPath;
+ }
+}
--- /dev/null
+package org.argeo.cms.ui.fs;
+
+/** FS Ui specific CSS styles */
+public interface FsStyles {
+ String BREAD_CRUMB_BTN = "breadCrumb_btn";
+ String CONTEXT_MENU_BOX = "contextMenu_box";
+ String BUTTON_SUFFIX = "_btn";
+}
--- /dev/null
+/** SWT/JFace file system components. */
+package org.argeo.cms.ui.fs;
\ No newline at end of file
--- /dev/null
+package org.argeo.cms.ui.internal;
+
+import org.argeo.api.cms.CmsState;
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.util.tracker.ServiceTracker;
+
+public class Activator implements BundleActivator {
+
+ // avoid dependency to RWT OSGi
+ private final static String CONTEXT_NAME_PROP = "contextName";
+
+ private static ServiceTracker<CmsState, CmsState> nodeState;
+
+ // @Override
+ public void start(BundleContext bc) throws Exception {
+ // UI
+// bc.registerService(ApplicationConfiguration.class, new MaintenanceUi(),
+// LangUtils.dico(CONTEXT_NAME_PROP, "system"));
+// bc.registerService(ApplicationConfiguration.class, new UserUi(), LangUtils.dico(CONTEXT_NAME_PROP, "user"));
+
+ nodeState = new ServiceTracker<>(bc, CmsState.class, null);
+ nodeState.open();
+ }
+
+ @Override
+ public void stop(BundleContext context) throws Exception {
+ if (nodeState != null) {
+ nodeState.close();
+ nodeState = null;
+ }
+ }
+
+ public static CmsState getNodeState() {
+ return nodeState.getService();
+ }
+}
--- /dev/null
+package org.argeo.cms.ui.internal;
+
+import java.util.ArrayList;
+
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.RepositoryException;
+
+import org.argeo.cms.CmsException;
+import org.eclipse.jface.viewers.ITreeContentProvider;
+import org.eclipse.jface.viewers.Viewer;
+
+@Deprecated
+class JcrContentProvider implements ITreeContentProvider {
+ private static final long serialVersionUID = -1333678161322488674L;
+
+ @Override
+ public void dispose() {
+ }
+
+ @Override
+ public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
+ if (newInput == null)
+ return;
+ if (!(newInput instanceof Node))
+ throw new CmsException("Input " + newInput + " must be a node");
+ }
+
+ @Override
+ public Object[] getElements(Object inputElement) {
+ try {
+ Node node = (Node) inputElement;
+ ArrayList<Node> arr = new ArrayList<Node>();
+ NodeIterator nit = node.getNodes();
+ while (nit.hasNext()) {
+ arr.add(nit.nextNode());
+ }
+ return arr.toArray();
+ } catch (RepositoryException e) {
+ throw new CmsException("Cannot get elements", e);
+ }
+ }
+
+ @Override
+ public Object[] getChildren(Object parentElement) {
+ try {
+ Node node = (Node) parentElement;
+ ArrayList<Node> arr = new ArrayList<Node>();
+ NodeIterator nit = node.getNodes();
+ while (nit.hasNext()) {
+ arr.add(nit.nextNode());
+ }
+ return arr.toArray();
+ } catch (RepositoryException e) {
+ throw new CmsException("Cannot get elements", e);
+ }
+ }
+
+ @Override
+ public Object getParent(Object element) {
+ try {
+ Node node = (Node) element;
+ if (node.getName().equals(""))
+ return null;
+ else
+ return node.getParent();
+ } catch (RepositoryException e) {
+ throw new CmsException("Cannot get elements", e);
+ }
+ }
+
+ @Override
+ public boolean hasChildren(Object element) {
+ try {
+ Node node = (Node) element;
+ return node.hasNodes();
+ } catch (RepositoryException e) {
+ throw new CmsException("Cannot get elements", e);
+ }
+ }
+}
--- /dev/null
+package org.argeo.cms.ui.internal;
+
+import static javax.jcr.nodetype.NodeType.NT_FILE;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.RepositoryException;
+import javax.jcr.nodetype.NodeType;
+
+import org.apache.commons.io.FilenameUtils;
+import org.argeo.api.cms.CmsImageManager;
+import org.argeo.cms.ui.widgets.Img;
+import org.argeo.jcr.JcrException;
+import org.argeo.jcr.JcrUtils;
+import org.eclipse.rap.fileupload.FileDetails;
+import org.eclipse.rap.fileupload.FileUploadReceiver;
+
+public class JcrFileUploadReceiver extends FileUploadReceiver {
+ private Img img;
+ private final Node parentNode;
+ private final String nodeName;
+ private final CmsImageManager imageManager;
+
+ /** If nodeName is null, use the uploaded file name */
+ public JcrFileUploadReceiver(Img img, Node parentNode, String nodeName, CmsImageManager imageManager) {
+ super();
+ this.img = img;
+ this.parentNode = parentNode;
+ this.nodeName = nodeName;
+ this.imageManager = imageManager;
+ }
+
+ @Override
+ public void receive(InputStream stream, FileDetails details) throws IOException {
+ try {
+ String fileName = nodeName != null ? nodeName : details.getFileName();
+ String contentType = details.getContentType();
+ if (isImage(details.getFileName(), contentType)) {
+ imageManager.uploadImage(img.getNode(),parentNode, fileName, stream, contentType);
+ return;
+ }
+
+ Node fileNode;
+ if (parentNode.hasNode(fileName)) {
+ fileNode = parentNode.getNode(fileName);
+ if (!fileNode.isNodeType(NT_FILE))
+ fileNode.remove();
+ }
+ fileNode = JcrUtils.copyStreamAsFile(parentNode, fileName, stream);
+
+ if (contentType != null) {
+ fileNode.addMixin(NodeType.MIX_MIMETYPE);
+ fileNode.setProperty(Property.JCR_MIMETYPE, contentType);
+ }
+ processNewFile(fileNode);
+ fileNode.getSession().save();
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot receive " + details, e);
+ }
+ }
+
+ protected Boolean isImage(String fileName, String contentType) {
+ String ext = FilenameUtils.getExtension(fileName);
+ return ext != null && (ext.equals("png") || ext.equalsIgnoreCase("jpg"));
+ }
+
+ protected void processNewFile(Node node) {
+
+ }
+
+}
--- /dev/null
+package org.argeo.cms.ui.internal;
+
+import javax.jcr.RepositoryException;
+
+import org.argeo.api.cms.Cms2DSize;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.ui.util.CmsUiUtils;
+import org.argeo.cms.ui.widgets.EditableImage;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Text;
+
+/** NOT working yet. */
+public class SimpleEditableImage extends EditableImage {
+ private static final long serialVersionUID = -5689145523114022890L;
+
+ private String src;
+ private Cms2DSize imageSize;
+
+ public SimpleEditableImage(Composite parent, int swtStyle) {
+ super(parent, swtStyle);
+ // load(getControl());
+ getParent().layout();
+ }
+
+ public SimpleEditableImage(Composite parent, int swtStyle, String src, Cms2DSize imageSize) {
+ super(parent, swtStyle);
+ this.src = src;
+ this.imageSize = imageSize;
+ }
+
+ @Override
+ protected Control createControl(Composite box, String style) {
+ if (isEditing()) {
+ return createText(box, style);
+ } else {
+ return createLabel(box, style);
+ }
+ }
+
+ protected String createImgTag() throws RepositoryException {
+ String imgTag;
+ if (src != null)
+ imgTag = CmsUiUtils.img(src, imageSize);
+ else
+ imgTag = CmsUiUtils.noImg(imageSize != null ? imageSize : NO_IMAGE_SIZE);
+ return imgTag;
+ }
+
+ protected Text createText(Composite box, String style) {
+ Text text = new Text(box, getStyle());
+ CmsSwtUtils.style(text, style);
+ return text;
+ }
+
+ public String getSrc() {
+ return src;
+ }
+
+ public void setSrc(String src) {
+ this.src = src;
+ }
+
+ public Cms2DSize getImageSize() {
+ return imageSize;
+ }
+
+ public void setImageSize(Cms2DSize imageSize) {
+ this.imageSize = imageSize;
+ }
+
+}
--- /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.eclipse.ui.EclipseUiException;
+import org.argeo.eclipse.ui.TreeParent;
+
+/** 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.eclipse.ui.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.cms.ui.CmsUiConstants;
+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;
+
+ // Utils
+ protected DateFormat timeFormatter = new SimpleDateFormat(CmsUiConstants.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.eclipse.ui.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.eclipse.ui.EclipseUiException;
+import org.argeo.eclipse.ui.TreeParent;
+
+/** 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.eclipse.ui.EclipseUiException;
+import org.argeo.eclipse.ui.TreeParent;
+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.eclipse.ui.EclipseUiException;
+import org.argeo.eclipse.ui.TreeParent;
+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.eclipse.ui.EclipseUiException;
+import org.argeo.eclipse.ui.TreeParent;
+
+/**
+ * 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.eclipse.ui.EclipseUiException;
+import org.argeo.eclipse.ui.TreeParent;
+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
+/** SWT/JFace components for Argeo CMS. */
+package org.argeo.cms.ui;
\ No newline at end of file
--- /dev/null
+package org.argeo.cms.ui.util;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+
+import org.argeo.api.cms.CmsStyle;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.cms.auth.CurrentUser;
+import org.argeo.cms.jcr.CmsJcrUtils;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.ui.CmsUiProvider;
+import org.argeo.jcr.JcrException;
+import org.eclipse.rap.rwt.RWT;
+import org.eclipse.rap.rwt.service.ResourceManager;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.MouseListener;
+import org.eclipse.swt.graphics.ImageData;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+import org.osgi.framework.BundleContext;
+
+/** A link to an internal or external location. */
+public class CmsLink implements CmsUiProvider {
+ private final static CmsLog log = CmsLog.getLog(CmsLink.class);
+ private BundleContext bundleContext;
+
+ private String label;
+ private String style;
+ private String target;
+ private String image;
+ private boolean openNew = false;
+ private MouseListener mouseListener;
+
+ private int horizontalAlignment = SWT.CENTER;
+ private int verticalAlignment = SWT.CENTER;
+
+ private String loggedInLabel = null;
+ private String loggedInTarget = null;
+
+ // internal
+ // private Boolean isUrl = false;
+ private Integer imageWidth, imageHeight;
+
+ public CmsLink() {
+ super();
+ }
+
+ public CmsLink(String label, String target) {
+ this(label, target, (String) null);
+ }
+
+ public CmsLink(String label, String target, CmsStyle style) {
+ this(label, target, style != null ? style.style() : null);
+ }
+
+ public CmsLink(String label, String target, String style) {
+ super();
+ this.label = label;
+ this.target = target;
+ this.style = style;
+ init();
+ }
+
+ public void init() {
+ if (image != null) {
+ ImageData image = loadImage();
+ if (imageHeight == null && imageWidth == null) {
+ imageWidth = image.width;
+ imageHeight = image.height;
+ } else if (imageHeight == null) {
+ imageHeight = (imageWidth * image.height) / image.width;
+ } else if (imageWidth == null) {
+ imageWidth = (imageHeight * image.width) / image.height;
+ }
+ }
+ }
+
+ /** @return {@link Composite} with a single {@link Label} child. */
+ @Override
+ public Control createUi(final Composite parent, Node context) {
+// if (image != null && (imageWidth == null || imageHeight == null)) {
+// throw new CmsException("Image is not properly configured."
+// + " Make sure bundleContext property is set and init() method has been called.");
+// }
+
+ Composite comp = new Composite(parent, SWT.NONE);
+ comp.setLayout(CmsSwtUtils.noSpaceGridLayout());
+
+ Label link = new Label(comp, SWT.NONE);
+ CmsSwtUtils.markup(link);
+ GridData layoutData = new GridData(horizontalAlignment, verticalAlignment, false, false);
+ if (image != null) {
+ if (imageHeight != null)
+ layoutData.heightHint = imageHeight;
+ if (label == null)
+ if (imageWidth != null)
+ layoutData.widthHint = imageWidth;
+ }
+
+ link.setLayoutData(layoutData);
+ CmsSwtUtils.style(comp, style != null ? style : getDefaultStyle());
+ CmsSwtUtils.style(link, style != null ? style : getDefaultStyle());
+
+ // label
+ StringBuilder labelText = new StringBuilder();
+ if (loggedInTarget != null && isLoggedIn()) {
+ labelText.append("<a style='color:inherit;text-decoration:inherit;' href=\"");
+ if (loggedInTarget.equals("")) {
+ try {
+ Node homeNode = CmsJcrUtils.getUserHome(context.getSession());
+ String homePath = homeNode.getPath();
+ labelText.append("/#" + homePath);
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot get home path", e);
+ }
+ } else {
+ labelText.append(loggedInTarget);
+ }
+ labelText.append("\">");
+ } else if (target != null) {
+ labelText.append("<a style='color:inherit;text-decoration:inherit;' href='");
+ labelText.append(target).append("'");
+ if (openNew) {
+ labelText.append(" target='_blank'");
+ }
+ labelText.append(">");
+ }
+ if (image != null) {
+ registerImageIfNeeded();
+ String imageLocation = RWT.getResourceManager().getLocation(image);
+ labelText.append("<img");
+ if (imageWidth != null)
+ labelText.append(" width='").append(imageWidth).append('\'');
+ if (imageHeight != null)
+ labelText.append(" height='").append(imageHeight).append('\'');
+ labelText.append(" src=\"").append(imageLocation).append("\"/>");
+
+ }
+
+ if (loggedInLabel != null && isLoggedIn()) {
+ labelText.append(' ').append(loggedInLabel);
+ } else if (label != null) {
+ labelText.append(' ').append(label);
+ }
+
+ if ((loggedInTarget != null && isLoggedIn()) || target != null)
+ labelText.append("</a>");
+
+ link.setText(labelText.toString());
+
+ if (mouseListener != null)
+ link.addMouseListener(mouseListener);
+
+ return comp;
+ }
+
+ private void registerImageIfNeeded() {
+ ResourceManager resourceManager = RWT.getResourceManager();
+ if (!resourceManager.isRegistered(image)) {
+ URL res = getImageUrl();
+ try (InputStream inputStream = res.openStream()) {
+ resourceManager.register(image, inputStream);
+ if (log.isTraceEnabled())
+ log.trace("Registered image " + image);
+ } catch (IOException e) {
+ throw new RuntimeException("Cannot load image " + image, e);
+ }
+ }
+ }
+
+ private ImageData loadImage() {
+ URL url = getImageUrl();
+ ImageData result = null;
+ try (InputStream inputStream = url.openStream()) {
+ result = new ImageData(inputStream);
+ if (log.isTraceEnabled())
+ log.trace("Loaded image " + image);
+ } catch (IOException e) {
+ throw new RuntimeException("Cannot load image " + image, e);
+ }
+ return result;
+ }
+
+ private URL getImageUrl() {
+ URL url;
+ try {
+ // pure URL
+ url = new URL(image);
+ } catch (MalformedURLException e1) {
+ url = bundleContext.getBundle().getResource(image);
+ }
+
+ if (url == null)
+ throw new IllegalStateException("No image " + image + " available.");
+
+ return url;
+ }
+
+ public void setBundleContext(BundleContext bundleContext) {
+ this.bundleContext = bundleContext;
+ }
+
+ public void setLabel(String label) {
+ this.label = label;
+ }
+
+ public void setStyle(String style) {
+ this.style = style;
+ }
+
+ /** @deprecated Use {@link #setStyle(String)} instead. */
+ @Deprecated
+ public void setCustom(String custom) {
+ this.style = custom;
+ }
+
+ public void setTarget(String target) {
+ this.target = target;
+ // try {
+ // new URL(target);
+ // isUrl = true;
+ // } catch (MalformedURLException e1) {
+ // isUrl = false;
+ // }
+ }
+
+ public void setImage(String image) {
+ this.image = image;
+ }
+
+ public void setLoggedInLabel(String loggedInLabel) {
+ this.loggedInLabel = loggedInLabel;
+ }
+
+ public void setLoggedInTarget(String loggedInTarget) {
+ this.loggedInTarget = loggedInTarget;
+ }
+
+ public void setMouseListener(MouseListener mouseListener) {
+ this.mouseListener = mouseListener;
+ }
+
+ public void setvAlign(String vAlign) {
+ if ("bottom".equals(vAlign)) {
+ verticalAlignment = SWT.BOTTOM;
+ } else if ("top".equals(vAlign)) {
+ verticalAlignment = SWT.TOP;
+ } else if ("center".equals(vAlign)) {
+ verticalAlignment = SWT.CENTER;
+ } else {
+ throw new IllegalArgumentException(
+ "Unsupported vertical alignment " + vAlign + " (must be: top, bottom or center)");
+ }
+ }
+
+ protected boolean isLoggedIn() {
+ return !CurrentUser.isAnonymous();
+ }
+
+ public void setImageWidth(Integer imageWidth) {
+ this.imageWidth = imageWidth;
+ }
+
+ public void setImageHeight(Integer imageHeight) {
+ this.imageHeight = imageHeight;
+ }
+
+ public void setOpenNew(boolean openNew) {
+ this.openNew = openNew;
+ }
+
+ protected String getDefaultStyle() {
+ return SimpleStyle.link.name();
+ }
+}
--- /dev/null
+package org.argeo.cms.ui.util;
+
+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.layout.RowLayout;
+import org.eclipse.swt.widgets.Composite;
+
+/** The main pane of a CMS display, with QA and support areas. */
+public class CmsPane {
+
+ private Composite mainArea;
+ private Composite qaArea;
+ private Composite supportArea;
+
+ public CmsPane(Composite parent, int style) {
+ parent.setLayout(CmsSwtUtils.noSpaceGridLayout());
+
+// qaArea = new Composite(parent, SWT.NONE);
+// qaArea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
+// RowLayout qaLayout = new RowLayout();
+// qaLayout.spacing = 0;
+// qaArea.setLayout(qaLayout);
+
+ mainArea = new Composite(parent, SWT.NONE);
+ mainArea.setLayout(new GridLayout());
+ mainArea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
+
+// supportArea = new Composite(parent, SWT.NONE);
+// supportArea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
+// RowLayout supportLayout = new RowLayout();
+// supportLayout.spacing = 0;
+// supportArea.setLayout(supportLayout);
+ }
+
+ public Composite getMainArea() {
+ return mainArea;
+ }
+
+ public Composite getQaArea() {
+ return qaArea;
+ }
+
+ public Composite getSupportArea() {
+ return supportArea;
+ }
+
+}
--- /dev/null
+package org.argeo.cms.ui.util;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.StringTokenizer;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.servlet.http.HttpServletRequest;
+
+import org.argeo.api.cms.Cms2DSize;
+import org.argeo.api.cms.CmsView;
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.cms.jcr.CmsJcrUtils;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.ui.CmsUiConstants;
+import org.argeo.jcr.JcrUtils;
+import org.eclipse.rap.rwt.RWT;
+import org.eclipse.rap.rwt.service.ResourceManager;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.ImageData;
+import org.eclipse.swt.layout.RowData;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Table;
+
+/** Static utilities for the CMS framework. */
+public class CmsUiUtils {
+ // private final static Log log = LogFactory.getLog(CmsUiUtils.class);
+
+ /*
+ * CMS VIEW
+ */
+
+ /**
+ * The CMS view related to this display, or null if none is available from this
+ * call.
+ *
+ * @deprecated Use {@link CmsSwtUtils#getCmsView(Composite)} instead.
+ */
+ @Deprecated
+ public static CmsView getCmsView() {
+// return UiContext.getData(CmsView.class.getName());
+ return CmsSwtUtils.getCmsView(Display.getCurrent().getActiveShell());
+ }
+
+ public static StringBuilder getServerBaseUrl(HttpServletRequest request) {
+ try {
+ URL url = new URL(request.getRequestURL().toString());
+ StringBuilder buf = new StringBuilder();
+ buf.append(url.getProtocol()).append("://").append(url.getHost());
+ if (url.getPort() != -1)
+ buf.append(':').append(url.getPort());
+ return buf;
+ } catch (MalformedURLException e) {
+ throw new IllegalArgumentException("Cannot extract server base URL from " + request.getRequestURL(), e);
+ }
+ }
+
+ //
+ public static String getDataUrl(Node node, HttpServletRequest request) {
+ try {
+ StringBuilder buf = getServerBaseUrl(request);
+ buf.append(getDataPath(node));
+ return new URL(buf.toString()).toString();
+ } catch (MalformedURLException e) {
+ throw new IllegalArgumentException("Cannot build data URL for " + node, e);
+ }
+ }
+
+ /** A path in the node repository */
+ public static String getDataPath(Node node) {
+ return getDataPath(CmsConstants.EGO_REPOSITORY, node);
+ }
+
+ public static String getDataPath(String cn, Node node) {
+ return CmsJcrUtils.getDataPath(cn, node);
+ }
+
+ /** Clean reserved URL characters for use in HTTP links. */
+ public static String getDataPathForUrl(Node node) {
+ return cleanPathForUrl(getDataPath(node));
+ }
+
+ /** Clean reserved URL characters for use in HTTP links. */
+ public static String cleanPathForUrl(String path) {
+ StringTokenizer st = new StringTokenizer(path, "/");
+ StringBuilder sb = new StringBuilder();
+ while (st.hasMoreElements()) {
+ sb.append('/');
+ String encoded = URLEncoder.encode(st.nextToken(), StandardCharsets.UTF_8);
+ encoded = encoded.replace("+", "%20");
+ sb.append(encoded);
+
+ }
+ return sb.toString();
+ }
+
+ /** @deprecated Use rowData16px() instead. GridData should not be reused. */
+ @Deprecated
+ public static RowData ROW_DATA_16px = new RowData(16, 16);
+
+
+
+ /*
+ * FORM LAYOUT
+ */
+
+
+
+ @Deprecated
+ public static void setItemHeight(Table table, int height) {
+ table.setData(CmsUiConstants.ITEM_HEIGHT, height);
+ }
+
+ //
+ // JCR
+ //
+ public static Node getOrAddEmptyFile(Node parent, Enum<?> child) throws RepositoryException {
+ if (has(parent, child))
+ return child(parent, child);
+ return JcrUtils.copyBytesAsFile(parent, child.name(), new byte[0]);
+ }
+
+ public static Node child(Node parent, Enum<?> en) throws RepositoryException {
+ return parent.getNode(en.name());
+ }
+
+ public static Boolean has(Node parent, Enum<?> en) throws RepositoryException {
+ return parent.hasNode(en.name());
+ }
+
+ public static Node getOrAdd(Node parent, Enum<?> en) throws RepositoryException {
+ return getOrAdd(parent, en, null);
+ }
+
+ public static Node getOrAdd(Node parent, Enum<?> en, String primaryType) throws RepositoryException {
+ if (has(parent, en))
+ return child(parent, en);
+ else if (primaryType == null)
+ return parent.addNode(en.name());
+ else
+ return parent.addNode(en.name(), primaryType);
+ }
+
+ // IMAGES
+
+ public static String img(Node fileNode, String width, String height) {
+ return img(null, fileNode, width, height);
+ }
+
+ public static String img(String serverBase, Node fileNode, String width, String height) {
+// String src = (serverBase != null ? serverBase : "") + NodeUtils.getDataPath(fileNode);
+ String src;
+ src = (serverBase != null ? serverBase : "") + getDataPathForUrl(fileNode);
+ return imgBuilder(src, width, height).append("/>").toString();
+ }
+
+ public static String img(String src, String width, String height) {
+ return imgBuilder(src, width, height).append("/>").toString();
+ }
+
+ public static String img(String src, Cms2DSize size) {
+ return img(src, Integer.toString(size.getWidth()), Integer.toString(size.getHeight()));
+ }
+
+ public static StringBuilder imgBuilder(String src, String width, String height) {
+ return new StringBuilder(64).append("<img width='").append(width).append("' height='").append(height)
+ .append("' src='").append(src).append("'");
+ }
+
+ public static String noImg(Cms2DSize size) {
+ ResourceManager rm = RWT.getResourceManager();
+ return CmsUiUtils.img(rm.getLocation(CmsUiConstants.NO_IMAGE), size);
+ }
+
+ public static String noImg() {
+ return noImg(CmsUiConstants.NO_IMAGE_SIZE);
+ }
+
+ public static Image noImage(Cms2DSize size) {
+ ResourceManager rm = RWT.getResourceManager();
+ InputStream in = null;
+ try {
+ in = rm.getRegisteredContent(CmsUiConstants.NO_IMAGE);
+ ImageData id = new ImageData(in);
+ ImageData scaled = id.scaledTo(size.getWidth(), size.getHeight());
+ Image image = new Image(Display.getCurrent(), scaled);
+ return image;
+ } finally {
+ try {
+ in.close();
+ } catch (IOException e) {
+ // silent
+ }
+ }
+ }
+
+ /** Lorem ipsum text to be used during development. */
+ public final static String LOREM_IPSUM = "Lorem ipsum dolor sit amet, consectetur adipiscing elit."
+ + " Etiam eleifend hendrerit sem, ac ultricies massa ornare ac."
+ + " Cras aliquam sodales risus, vitae varius lacus molestie quis."
+ + " Vivamus consequat, leo id lacinia volutpat, eros diam efficitur urna, finibus interdum risus turpis at nisi."
+ + " Curabitur vulputate nulla quis scelerisque fringilla. Integer consectetur turpis id lobortis accumsan."
+ + " Pellentesque commodo turpis ac diam ultricies dignissim."
+ + " Curabitur sit amet dolor volutpat lacus aliquam ornare quis sed velit."
+ + " Integer varius quis est et tristique."
+ + " Suspendisse pharetra porttitor purus, eget condimentum magna."
+ + " Duis vitae turpis eros. Sed tincidunt lacinia rutrum."
+ + " Aliquam velit velit, rutrum ut augue sed, condimentum lacinia augue.";
+
+ /** Singleton. */
+ private CmsUiUtils() {
+ }
+
+}
--- /dev/null
+package org.argeo.cms.ui.util;
+
+import static javax.jcr.Node.JCR_CONTENT;
+import static javax.jcr.Property.JCR_DATA;
+import static javax.jcr.nodetype.NodeType.NT_FILE;
+import static javax.jcr.nodetype.NodeType.NT_RESOURCE;
+import static org.argeo.cms.ui.CmsUiConstants.NO_IMAGE_SIZE;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+
+import javax.jcr.Binary;
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.RepositoryException;
+
+import org.apache.commons.io.IOUtils;
+import org.argeo.api.cms.Cms2DSize;
+import org.argeo.api.cms.CmsImageManager;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.jcr.JcrException;
+import org.argeo.jcr.JcrUtils;
+import org.eclipse.rap.rwt.RWT;
+import org.eclipse.rap.rwt.service.ResourceManager;
+import org.eclipse.rap.rwt.widgets.FileUpload;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.ImageData;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Label;
+
+/** Manages only public images so far. */
+public class DefaultImageManager implements CmsImageManager<Control, Node> {
+ private final static CmsLog log = CmsLog.getLog(DefaultImageManager.class);
+// private MimetypesFileTypeMap fileTypeMap = new MimetypesFileTypeMap();
+
+ public Boolean load(Node node, Control control, Cms2DSize preferredSize) {
+ Cms2DSize imageSize = getImageSize(node);
+ Cms2DSize size;
+ String imgTag = null;
+ if (preferredSize == null || imageSize.getWidth() == 0 || imageSize.getHeight() == 0
+ || (preferredSize.getWidth() == 0 && preferredSize.getHeight() == 0)) {
+ if (imageSize.getWidth() != 0 && imageSize.getHeight() != 0) {
+ // actual image size if completely known
+ size = imageSize;
+ } else {
+ // no image if not completely known
+ size = resizeTo(NO_IMAGE_SIZE, preferredSize != null ? preferredSize : imageSize);
+ imgTag = CmsUiUtils.noImg(size);
+ }
+
+ } else if (preferredSize.getWidth() != 0 && preferredSize.getHeight() != 0) {
+ // given size if completely provided
+ size = preferredSize;
+ } else {
+ // at this stage :
+ // image is completely known
+ assert imageSize.getWidth() != 0 && imageSize.getHeight() != 0;
+ // one and only one of the dimension as been specified
+ assert preferredSize.getWidth() == 0 || preferredSize.getHeight() == 0;
+ size = resizeTo(imageSize, preferredSize);
+ }
+
+ boolean loaded = false;
+ if (control == null)
+ return loaded;
+
+ if (control instanceof Label) {
+ if (imgTag == null) {
+ // IMAGE RETRIEVED HERE
+ imgTag = getImageTag(node, size);
+ //
+ if (imgTag == null)
+ imgTag = CmsUiUtils.noImg(size);
+ else
+ loaded = true;
+ }
+
+ Label lbl = (Label) control;
+ lbl.setText(imgTag);
+ // lbl.setSize(size);
+ } else if (control instanceof FileUpload) {
+ FileUpload lbl = (FileUpload) control;
+ lbl.setImage(CmsUiUtils.noImage(size));
+ lbl.setSize(new Point(size.getWidth(), size.getHeight()));
+ return loaded;
+ } else
+ loaded = false;
+
+ return loaded;
+ }
+
+ private Cms2DSize resizeTo(Cms2DSize orig, Cms2DSize constraints) {
+ if (constraints.getWidth() != 0 && constraints.getHeight() != 0) {
+ return constraints;
+ } else if (constraints.getWidth() == 0 && constraints.getHeight() == 0) {
+ return orig;
+ } else if (constraints.getHeight() == 0) {// force width
+ return new Cms2DSize(constraints.getWidth(),
+ scale(orig.getHeight(), orig.getWidth(), constraints.getWidth()));
+ } else if (constraints.getWidth() == 0) {// force height
+ return new Cms2DSize(scale(orig.getWidth(), orig.getHeight(), constraints.getHeight()),
+ constraints.getHeight());
+ }
+ throw new IllegalArgumentException("Cannot resize " + orig + " to " + constraints);
+ }
+
+ private int scale(int origDimension, int otherDimension, int otherConstraint) {
+ return Math.round(origDimension * divide(otherConstraint, otherDimension));
+ }
+
+ private float divide(int a, int b) {
+ return ((float) a) / ((float) b);
+ }
+
+ public Cms2DSize getImageSize(Node node) {
+ // TODO optimise
+ Image image = getSwtImage(node);
+ return new Cms2DSize(image.getBounds().width, image.getBounds().height);
+ }
+
+ /** @return null if not available */
+ @Override
+ public String getImageTag(Node node) {
+ return getImageTag(node, getImageSize(node));
+ }
+
+ private String getImageTag(Node node, Cms2DSize size) {
+ StringBuilder buf = getImageTagBuilder(node, size);
+ if (buf == null)
+ return null;
+ return buf.append("/>").toString();
+ }
+
+ /** @return null if not available */
+ @Override
+ public StringBuilder getImageTagBuilder(Node node, Cms2DSize size) {
+ return getImageTagBuilder(node, Integer.toString(size.getWidth()), Integer.toString(size.getHeight()));
+ }
+
+ /** @return null if not available */
+ private StringBuilder getImageTagBuilder(Node node, String width, String height) {
+ String url = getImageUrl(node);
+ if (url == null)
+ return null;
+ return CmsUiUtils.imgBuilder(url, width, height);
+ }
+
+ /** @return null if not available */
+ @Override
+ public String getImageUrl(Node node) {
+ return CmsUiUtils.getDataPathForUrl(node);
+ }
+
+ protected String getResourceName(Node node) {
+ try {
+ String workspace = node.getSession().getWorkspace().getName();
+ if (node.hasNode(JCR_CONTENT))
+ return workspace + '_' + node.getNode(JCR_CONTENT).getIdentifier();
+ else
+ return workspace + '_' + node.getIdentifier();
+ } catch (RepositoryException e) {
+ throw new JcrException(e);
+ }
+ }
+
+ public Binary getImageBinary(Node node) {
+ try {
+ if (node.isNodeType(NT_FILE)) {
+ return node.getNode(JCR_CONTENT).getProperty(JCR_DATA).getBinary();
+ } else {
+ return null;
+ }
+ } catch (RepositoryException e) {
+ throw new JcrException(e);
+ }
+ }
+
+ public Image getSwtImage(Node node) {
+ InputStream inputStream = null;
+ Binary binary = getImageBinary(node);
+ if (binary == null)
+ return null;
+ try {
+ inputStream = binary.getStream();
+ return new Image(Display.getCurrent(), inputStream);
+ } catch (RepositoryException e) {
+ throw new JcrException(e);
+ } finally {
+ IOUtils.closeQuietly(inputStream);
+ JcrUtils.closeQuietly(binary);
+ }
+ }
+
+ @Override
+ public String uploadImage(Node context, Node parentNode, String fileName, InputStream in, String contentType) {
+ InputStream inputStream = null;
+ try {
+ String previousResourceName = null;
+ if (parentNode.hasNode(fileName)) {
+ Node node = parentNode.getNode(fileName);
+ previousResourceName = getResourceName(node);
+ if (node.hasNode(JCR_CONTENT)) {
+ node.getNode(JCR_CONTENT).remove();
+ node.addNode(JCR_CONTENT, NT_RESOURCE);
+ }
+ }
+
+ byte[] arr = IOUtils.toByteArray(in);
+ Node fileNode = JcrUtils.copyBytesAsFile(parentNode, fileName, arr);
+ inputStream = new ByteArrayInputStream(arr);
+ ImageData id = new ImageData(inputStream);
+ processNewImageFile(context, fileNode, id);
+
+ String mime = contentType != null ? contentType : Files.probeContentType(Paths.get(fileName));
+ if (mime != null) {
+ fileNode.getNode(JCR_CONTENT).setProperty(Property.JCR_MIMETYPE, mime);
+ }
+ fileNode.getSession().save();
+
+ // reset resource manager
+ ResourceManager resourceManager = RWT.getResourceManager();
+ if (previousResourceName != null && resourceManager.isRegistered(previousResourceName)) {
+ resourceManager.unregister(previousResourceName);
+ if (log.isDebugEnabled())
+ log.debug("Unregistered image " + previousResourceName);
+ }
+ return CmsUiUtils.getDataPath(fileNode);
+ } catch (IOException e) {
+ throw new RuntimeException("Cannot upload image " + fileName + " in " + parentNode, e);
+ } catch (RepositoryException e) {
+ throw new JcrException(e);
+ } finally {
+ IOUtils.closeQuietly(inputStream);
+ }
+ }
+
+ /** Does nothing by default. */
+ protected void processNewImageFile(Node context, Node fileNode, ImageData id)
+ throws RepositoryException, IOException {
+ }
+}
--- /dev/null
+package org.argeo.cms.ui.util;
+
+import org.argeo.cms.swt.CmsStyles;
+
+/**
+ * Convenience class setting the custom style {@link CmsStyles#CMS_MENU_LINK} on
+ * a {@link CmsLink} when simple menus are used.
+ */
+public class MenuLink extends CmsLink {
+ public MenuLink() {
+ setCustom(CmsStyles.CMS_MENU_LINK);
+ }
+
+ public MenuLink(String label, String target, String custom) {
+ super(label, target, custom);
+ }
+
+ public MenuLink(String label, String target) {
+ super(label, target, CmsStyles.CMS_MENU_LINK);
+ }
+
+}
--- /dev/null
+package org.argeo.cms.ui.util;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+
+import org.argeo.cms.CmsException;
+import org.argeo.cms.swt.CmsStyles;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.ui.CmsUiProvider;
+import org.eclipse.rap.rwt.RWT;
+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;
+
+/** A header in three parts */
+public class SimpleCmsHeader implements CmsUiProvider {
+ private List<CmsUiProvider> lead = new ArrayList<CmsUiProvider>();
+ private List<CmsUiProvider> center = new ArrayList<CmsUiProvider>();
+ private List<CmsUiProvider> end = new ArrayList<CmsUiProvider>();
+
+ private Boolean subPartsSameWidth = false;
+
+ @Override
+ public Control createUi(Composite parent, Node context) throws RepositoryException {
+ Composite header = new Composite(parent, SWT.NONE);
+ header.setData(RWT.CUSTOM_VARIANT, CmsStyles.CMS_HEADER);
+ header.setBackgroundMode(SWT.INHERIT_DEFAULT);
+ header.setLayout(CmsSwtUtils.noSpaceGridLayout(new GridLayout(3, false)));
+
+ configurePart(context, header, lead);
+ configurePart(context, header, center);
+ configurePart(context, header, end);
+ return header;
+ }
+
+ protected void configurePart(Node context, Composite parent, List<CmsUiProvider> partProviders)
+ throws RepositoryException {
+ final int style;
+ final String custom;
+ if (lead == partProviders) {
+ style = SWT.LEAD;
+ custom = CmsStyles.CMS_HEADER_LEAD;
+ } else if (center == partProviders) {
+ style = SWT.CENTER;
+ custom = CmsStyles.CMS_HEADER_CENTER;
+ } else if (end == partProviders) {
+ style = SWT.END;
+ custom = CmsStyles.CMS_HEADER_END;
+ } else {
+ throw new CmsException("Unsupported part providers " + partProviders);
+ }
+
+ Composite part = new Composite(parent, SWT.NONE);
+ part.setData(RWT.CUSTOM_VARIANT, custom);
+ GridData gridData = new GridData(style, SWT.FILL, true, true);
+ part.setLayoutData(gridData);
+ part.setLayout(CmsSwtUtils.noSpaceGridLayout(new GridLayout(partProviders.size(), subPartsSameWidth)));
+ for (CmsUiProvider uiProvider : partProviders) {
+ Control subPart = uiProvider.createUi(part, context);
+ subPart.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
+ }
+ }
+
+ public void setLead(List<CmsUiProvider> lead) {
+ this.lead = lead;
+ }
+
+ public void setCenter(List<CmsUiProvider> center) {
+ this.center = center;
+ }
+
+ public void setEnd(List<CmsUiProvider> end) {
+ this.end = end;
+ }
+
+ public void setSubPartsSameWidth(Boolean subPartsSameWidth) {
+ this.subPartsSameWidth = subPartsSameWidth;
+ }
+
+ public List<CmsUiProvider> getLead() {
+ return lead;
+ }
+
+ public List<CmsUiProvider> getCenter() {
+ return center;
+ }
+
+ public List<CmsUiProvider> getEnd() {
+ return end;
+ }
+
+}
--- /dev/null
+package org.argeo.cms.ui.util;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+
+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.cms.CmsException;
+import org.argeo.cms.ui.CmsUiProvider;
+import org.argeo.jcr.JcrUtils;
+import org.eclipse.rap.rwt.RWT;
+import org.eclipse.swt.SWT;
+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 SimpleDynamicPages implements CmsUiProvider {
+
+ @Override
+ public Control createUi(Composite parent, Node context)
+ throws RepositoryException {
+ if (context == null)
+ throw new CmsException("Context cannot be null");
+ parent.setLayout(new GridLayout(2, false));
+
+ // parent
+ if (!context.getPath().equals("/")) {
+ new CmsLink("..", context.getParent().getPath()).createUi(parent,
+ context);
+ new Label(parent, SWT.NONE).setText(context.getParent()
+ .getPrimaryNodeType().getName());
+ }
+
+ // context
+ Label contextL = new Label(parent, SWT.NONE);
+ contextL.setData(RWT.MARKUP_ENABLED, true);
+ contextL.setText("<b>" + context.getName() + "</b>");
+ new Label(parent, SWT.NONE).setText(context.getPrimaryNodeType()
+ .getName());
+
+ // children
+ // Label childrenL = new Label(parent, SWT.NONE);
+ // childrenL.setData(RWT.MARKUP_ENABLED, true);
+ // childrenL.setText("<i>Children:</i>");
+ // childrenL.setLayoutData(new GridData(SWT.LEAD, SWT.CENTER, false,
+ // false, 2, 1));
+
+ for (NodeIterator nIt = context.getNodes(); nIt.hasNext();) {
+ Node child = nIt.nextNode();
+ new CmsLink(child.getName(), child.getPath()).createUi(parent,
+ context);
+
+ new Label(parent, SWT.NONE).setText(child.getPrimaryNodeType()
+ .getName());
+ }
+
+ // properties
+ // Label propsL = new Label(parent, SWT.NONE);
+ // propsL.setData(RWT.MARKUP_ENABLED, true);
+ // propsL.setText("<i>Properties:</i>");
+ // propsL.setLayoutData(new GridData(SWT.LEAD, SWT.CENTER, false, false,
+ // 2, 1));
+ 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 String getPropAsString(Property property)
+ throws RepositoryException {
+ String result = "";
+ DateFormat timeFormatter = new SimpleDateFormat("");
+ 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();
+ }
+}
\ No newline at end of file
--- /dev/null
+package org.argeo.cms.ui.util;
+
+public class SimpleImageManager extends DefaultImageManager {
+
+}
--- /dev/null
+package org.argeo.cms.ui.util;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+
+import org.argeo.cms.swt.CmsStyles;
+import org.argeo.cms.ui.CmsUiProvider;
+import org.eclipse.rap.rwt.RWT;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+
+public class SimpleStaticPage implements CmsUiProvider {
+ private String text;
+
+ @Override
+ public Control createUi(Composite parent, Node context)
+ throws RepositoryException {
+ Label textC = new Label(parent, SWT.WRAP);
+ textC.setData(RWT.CUSTOM_VARIANT, CmsStyles.CMS_STATIC_TEXT);
+ textC.setData(RWT.MARKUP_ENABLED, Boolean.TRUE);
+ textC.setText(text);
+
+ return textC;
+ }
+
+ public void setText(String text) {
+ this.text = text;
+ }
+
+}
--- /dev/null
+package org.argeo.cms.ui.util;
+
+import org.argeo.api.cms.CmsStyle;
+
+/** Simple styles used by the CMS UI utilities. */
+public enum SimpleStyle implements CmsStyle {
+ link;
+}
--- /dev/null
+package org.argeo.cms.ui.util;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.apache.commons.io.IOUtils;
+import org.argeo.cms.CmsException;
+import org.eclipse.rap.rwt.service.ResourceLoader;
+import org.osgi.framework.Bundle;
+
+/** {@link ResourceLoader} caching stylesheets. */
+public class StyleSheetResourceLoader implements ResourceLoader {
+ private Bundle themeBundle;
+ private Map<String, StyleSheet> stylesheets = new LinkedHashMap<String, StyleSheet>();
+
+ public StyleSheetResourceLoader(Bundle themeBundle) {
+ this.themeBundle = themeBundle;
+ }
+
+ @Override
+ public InputStream getResourceAsStream(String resourceName) throws IOException {
+ if (!stylesheets.containsKey(resourceName)) {
+ // TODO deal with other bundles
+ // Bundle bundle = bundleContext.getBundle();
+ // String location =
+ // bundle.getLocation().substring("initial@reference:".length());
+ // if (location.startsWith("file:")) {
+ // Path path = null;
+ // try {
+ // path = Paths.get(new URI(location));
+ // } catch (URISyntaxException e) {
+ // e.printStackTrace();
+ // }
+ // if (path != null) {
+ // Path resourcePath = path.resolve(resourceName);
+ // if (Files.exists(resourcePath))
+ // return Files.newInputStream(resourcePath);
+ // }
+ // }
+
+ URL res = themeBundle.getEntry(resourceName);
+ if (res == null)
+ throw new CmsException(
+ "Entry " + resourceName + " not found in bundle " + themeBundle.getSymbolicName());
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ IOUtils.copy(res.openStream(), out);
+ stylesheets.put(resourceName, new StyleSheet(out.toByteArray()));
+ }
+ return new ByteArrayInputStream(stylesheets.get(resourceName).getData());
+ // return res.openStream();
+ }
+
+ private class StyleSheet {
+ private byte[] data;
+
+ public StyleSheet(byte[] data) {
+ super();
+ this.data = data;
+ }
+
+ public byte[] getData() {
+ return data;
+ }
+
+ }
+}
--- /dev/null
+package org.argeo.cms.ui.util;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import org.apache.commons.io.IOUtils;
+import org.argeo.cms.CmsException;
+import org.argeo.cms.swt.CmsStyles;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.eclipse.rap.rwt.RWT;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.MouseEvent;
+import org.eclipse.swt.events.MouseListener;
+import org.eclipse.swt.events.ShellAdapter;
+import org.eclipse.swt.events.ShellEvent;
+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.Shell;
+
+/** Shell displaying system notifications such as exceptions */
+public class SystemNotifications extends Shell implements CmsStyles,
+ MouseListener {
+ private static final long serialVersionUID = -8129377525216022683L;
+
+ private Control source;
+
+ public SystemNotifications(Control source) {
+ super(source.getDisplay(), SWT.NO_TRIM | SWT.BORDER | SWT.ON_TOP);
+ setData(RWT.CUSTOM_VARIANT, CMS_USER_MENU);
+
+ this.source = source;
+
+ // TODO UI
+ // setLocation(source.toDisplay(source.getSize().x - getSize().x,
+ // source.getSize().y));
+ setLayout(new GridLayout());
+ addMouseListener(this);
+
+ addShellListener(new ShellAdapter() {
+ private static final long serialVersionUID = 5178980294808435833L;
+
+ @Override
+ public void shellDeactivated(ShellEvent e) {
+ close();
+ dispose();
+ }
+ });
+
+ }
+
+ public void notifyException(Throwable exception) {
+ Composite pane = this;
+
+ Label lbl = new Label(pane, SWT.NONE);
+ lbl.setText(exception.getLocalizedMessage()
+ + (exception instanceof CmsException ? "" : "("
+ + exception.getClass().getName() + ")") + "\n");
+ lbl.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
+ lbl.addMouseListener(this);
+ if (exception.getCause() != null)
+ appendCause(pane, exception.getCause());
+
+ StringBuilder mailToUrl = new StringBuilder("mailto:?");
+ try {
+ mailToUrl.append("subject=").append(
+ URLEncoder.encode(
+ "Exception "
+ + new SimpleDateFormat("yyyy-MM-dd hh:mm")
+ .format(new Date()), "UTF-8")
+ .replace("+", "%20"));
+
+ StringWriter sw = new StringWriter();
+ exception.printStackTrace(new PrintWriter(sw));
+ IOUtils.closeQuietly(sw);
+
+ // see
+ // http://stackoverflow.com/questions/4737841/urlencoder-not-able-to-translate-space-character
+ String encoded = URLEncoder.encode(sw.toString(), "UTF-8").replace(
+ "+", "%20");
+ mailToUrl.append("&body=").append(encoded);
+ } catch (UnsupportedEncodingException e) {
+ mailToUrl.append("&body=").append("Could not encode: ")
+ .append(e.getMessage());
+ }
+ Label mailTo = new Label(pane, SWT.NONE);
+ CmsSwtUtils.markup(mailTo);
+ mailTo.setText("<a href=\"" + mailToUrl + "\">Send details</a>");
+ mailTo.setLayoutData(new GridData(SWT.END, SWT.FILL, true, false));
+
+ pack();
+ layout();
+
+ setLocation(source.toDisplay(source.getSize().x - getSize().x,
+ source.getSize().y - getSize().y));
+ open();
+ }
+
+ private void appendCause(Composite parent, Throwable e) {
+ Label lbl = new Label(parent, SWT.NONE);
+ lbl.setText(" caused by: " + e.getLocalizedMessage() + " ("
+ + e.getClass().getName() + ")" + "\n");
+ lbl.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
+ lbl.addMouseListener(this);
+ if (e.getCause() != null)
+ appendCause(parent, e.getCause());
+ }
+
+ @Override
+ public void mouseDoubleClick(MouseEvent e) {
+ }
+
+ @Override
+ public void mouseDown(MouseEvent e) {
+ close();
+ dispose();
+ }
+
+ @Override
+ public void mouseUp(MouseEvent e) {
+ }
+
+}
--- /dev/null
+package org.argeo.cms.ui.util;
+
+import javax.jcr.Node;
+
+import org.argeo.cms.CmsException;
+import org.argeo.cms.swt.auth.CmsLoginShell;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.ShellAdapter;
+import org.eclipse.swt.events.ShellEvent;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Shell;
+
+/** The site-related user menu */
+public class UserMenu extends CmsLoginShell {
+ private final Control source;
+ private final Node context;
+
+ public UserMenu(Control source, Node context) {
+ super(CmsUiUtils.getCmsView());
+ this.context = context;
+ createUi();
+ if (source == null)
+ throw new CmsException("Source control cannot be null.");
+ this.source = source;
+ open();
+ }
+
+ @Override
+ protected Shell createShell() {
+ return new Shell(Display.getCurrent(), SWT.NO_TRIM | SWT.BORDER | SWT.ON_TOP);
+ }
+
+ @Override
+ public void open() {
+ Shell shell = getShell();
+ shell.pack();
+ shell.layout();
+ shell.setLocation(source.toDisplay(source.getSize().x - shell.getSize().x, source.getSize().y));
+ shell.addShellListener(new ShellAdapter() {
+ private static final long serialVersionUID = 5178980294808435833L;
+
+ @Override
+ public void shellDeactivated(ShellEvent e) {
+ closeShell();
+ }
+ });
+ super.open();
+ }
+
+ protected Node getContext() {
+ return context;
+ }
+
+}
--- /dev/null
+package org.argeo.cms.ui.util;
+
+import javax.jcr.Node;
+
+import org.argeo.cms.CmsMsg;
+import org.argeo.cms.auth.CurrentUser;
+import org.argeo.cms.swt.CmsStyles;
+import org.argeo.cms.swt.auth.CmsLoginShell;
+import org.eclipse.swt.events.DisposeEvent;
+import org.eclipse.swt.events.DisposeListener;
+import org.eclipse.swt.events.MouseEvent;
+import org.eclipse.swt.events.MouseListener;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+
+/** Open the user menu when clicked */
+public class UserMenuLink extends MenuLink {
+
+ public UserMenuLink() {
+ setCustom(CmsStyles.CMS_USER_MENU_LINK);
+ }
+
+ @Override
+ public Control createUi(Composite parent, Node context) {
+ if (CurrentUser.isAnonymous())
+ setLabel(CmsMsg.login.lead());
+ else {
+ setLabel(CurrentUser.getDisplayName());
+ }
+ Label link = (Label) ((Composite) super.createUi(parent, context)).getChildren()[0];
+ link.addMouseListener(new UserMenuLinkController(context));
+ return link.getParent();
+ }
+
+ protected CmsLoginShell createUserMenu(Control source, Node context) {
+ return new UserMenu(source.getParent(), context);
+ }
+
+ private class UserMenuLinkController implements MouseListener, DisposeListener {
+ private static final long serialVersionUID = 3634864186295639792L;
+
+ private CmsLoginShell userMenu = null;
+ private long lastDisposeTS = 0l;
+
+ private final Node context;
+
+ public UserMenuLinkController(Node context) {
+ this.context = context;
+ }
+
+ //
+ // MOUSE LISTENER
+ //
+ @Override
+ public void mouseDown(MouseEvent e) {
+ if (e.button == 1) {
+ Control source = (Control) e.getSource();
+ if (userMenu == null) {
+ long durationSinceLastDispose = System.currentTimeMillis() - lastDisposeTS;
+ // avoid to reopen the menu, if one has clicked gain
+ if (durationSinceLastDispose > 200) {
+ userMenu = createUserMenu(source, context);
+ userMenu.getShell().addDisposeListener(this);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void mouseDoubleClick(MouseEvent e) {
+ }
+
+ @Override
+ public void mouseUp(MouseEvent e) {
+ }
+
+ @Override
+ public void widgetDisposed(DisposeEvent event) {
+ userMenu = null;
+ lastDisposeTS = System.currentTimeMillis();
+ }
+ }
+}
--- /dev/null
+package org.argeo.cms.ui.util;
+
+import java.util.ArrayList;
+import java.util.List;
+
+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.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+
+public class VerticalMenu implements CmsUiProvider {
+ private List<CmsUiProvider> items = new ArrayList<CmsUiProvider>();
+
+ @Override
+ public Control createUi(Composite parent, Node context) throws RepositoryException {
+ Composite part = new Composite(parent, SWT.NONE);
+ part.setLayoutData(new GridData(SWT.LEAD, SWT.TOP, false, false));
+// part.setData(RWT.CUSTOM_VARIANT, custom);
+ part.setLayout(CmsSwtUtils.noSpaceGridLayout());
+ for (CmsUiProvider uiProvider : items) {
+ Control subPart = uiProvider.createUi(part, context);
+ subPart.setLayoutData(new GridData(SWT.LEAD, SWT.TOP, false, false));
+ }
+ return part;
+ }
+
+ public void add(CmsUiProvider uiProvider) {
+ items.add(uiProvider);
+ }
+
+ public List<CmsUiProvider> getItems() {
+ return items;
+ }
+
+ public void setItems(List<CmsUiProvider> items) {
+ this.items = items;
+ }
+
+}
--- /dev/null
+/** Argeo CMS UI utilities. */
+package org.argeo.cms.ui.util;
\ No newline at end of file
--- /dev/null
+package org.argeo.cms.ui.viewers;
+
+import java.security.AccessControlContext;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.Observable;
+import java.util.Observer;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.security.auth.Subject;
+
+import org.argeo.api.cms.CmsEditable;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.cms.ui.widgets.ScrolledPage;
+import org.argeo.jcr.JcrException;
+import org.eclipse.jface.viewers.ContentViewer;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.jface.viewers.StructuredSelection;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.FocusEvent;
+import org.eclipse.swt.events.FocusListener;
+import org.eclipse.swt.events.MouseAdapter;
+import org.eclipse.swt.events.MouseListener;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Widget;
+import org.xml.sax.SAXParseException;
+
+/** Base class for viewers related to a page */
+public abstract class AbstractPageViewer extends ContentViewer implements Observer {
+ private static final long serialVersionUID = 5438688173410341485L;
+
+ private final static CmsLog log = CmsLog.getLog(AbstractPageViewer.class);
+
+ private final boolean readOnly;
+ /** The basis for the layouts, typically a ScrolledPage. */
+ private final Composite page;
+ private final CmsEditable cmsEditable;
+
+ private MouseListener mouseListener;
+ private FocusListener focusListener;
+
+ private EditablePart edited;
+ private ISelection selection = StructuredSelection.EMPTY;
+
+ private AccessControlContext accessControlContext;
+
+ protected AbstractPageViewer(Section parent, int style, CmsEditable cmsEditable) {
+ // read only at UI level
+ readOnly = SWT.READ_ONLY == (style & SWT.READ_ONLY);
+
+ this.cmsEditable = cmsEditable == null ? CmsEditable.NON_EDITABLE : cmsEditable;
+ if (this.cmsEditable instanceof Observable)
+ ((Observable) this.cmsEditable).addObserver(this);
+
+ if (cmsEditable.canEdit()) {
+ mouseListener = createMouseListener();
+ focusListener = createFocusListener();
+ }
+ page = findPage(parent);
+ accessControlContext = AccessController.getContext();
+ }
+
+ /**
+ * Can be called to simplify the called to isModelInitialized() and initModel()
+ */
+ protected void initModelIfNeeded(Node node) {
+ try {
+ if (!isModelInitialized(node))
+ if (getCmsEditable().canEdit()) {
+ initModel(node);
+ node.getSession().save();
+ }
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot initialize model", e);
+ }
+ }
+
+ /** Called if user can edit and model is not initialized */
+ protected Boolean isModelInitialized(Node node) throws RepositoryException {
+ return true;
+ }
+
+ /** Called if user can edit and model is not initialized */
+ protected void initModel(Node node) throws RepositoryException {
+ }
+
+ /** Create (retrieve) the MouseListener to use. */
+ protected MouseListener createMouseListener() {
+ return new MouseAdapter() {
+ private static final long serialVersionUID = 1L;
+ };
+ }
+
+ /** Create (retrieve) the FocusListener to use. */
+ protected FocusListener createFocusListener() {
+ return new FocusListener() {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public void focusLost(FocusEvent event) {
+ }
+
+ @Override
+ public void focusGained(FocusEvent event) {
+ }
+ };
+ }
+
+ protected Composite findPage(Composite composite) {
+ if (composite instanceof ScrolledPage) {
+ return (ScrolledPage) composite;
+ } else {
+ if (composite.getParent() == null)
+ return composite;
+ return findPage(composite.getParent());
+ }
+ }
+
+ public void layoutPage() {
+ if (page != null)
+ page.layout(true, true);
+ }
+
+ protected void showControl(Control control) {
+ if (page != null && (page instanceof ScrolledPage))
+ ((ScrolledPage) page).showControl(control);
+ }
+
+ @Override
+ public void update(Observable o, Object arg) {
+ if (o == cmsEditable)
+ editingStateChanged(cmsEditable);
+ }
+
+ /** To be overridden in order to provide the actual refresh */
+ protected void refresh(Control control) throws RepositoryException {
+ }
+
+ /** To be overridden.Save the edited part. */
+ protected void save(EditablePart part) throws RepositoryException {
+ }
+
+ /** Prepare the edited part */
+ protected void prepare(EditablePart part, Object caretPosition) {
+ }
+
+ /** Notified when the editing state changed. Does nothing, to be overridden */
+ protected void editingStateChanged(CmsEditable cmsEditable) {
+ }
+
+ @Override
+ public void refresh() {
+ // TODO check actual context in order to notice a discrepancy
+ Subject viewerSubject = getViewerSubject();
+ Subject.doAs(viewerSubject, (PrivilegedAction<Void>) () -> {
+ try {
+ if (cmsEditable.canEdit() && !readOnly)
+ mouseListener = createMouseListener();
+ else
+ mouseListener = null;
+ refresh(getControl());
+ // layout(getControl());
+ if (!getControl().isDisposed())
+ layoutPage();
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot refresh", e);
+ }
+ return null;
+ });
+ }
+
+ @Override
+ public void setSelection(ISelection selection, boolean reveal) {
+ this.selection = selection;
+ }
+
+ protected void updateContent(EditablePart part) throws RepositoryException {
+ }
+
+ // LOW LEVEL EDITION
+ protected void edit(EditablePart part, Object caretPosition) {
+ try {
+ if (edited == part)
+ return;
+
+ if (edited != null && edited != part) {
+ EditablePart previouslyEdited = edited;
+ try {
+ stopEditing(true);
+ } catch (Exception e) {
+ notifyEditionException(e);
+ edit(previouslyEdited, caretPosition);
+ return;
+ }
+ }
+
+ part.startEditing();
+ edited = part;
+ updateContent(part);
+ prepare(part, caretPosition);
+ edited.getControl().addFocusListener(new FocusListener() {
+ private static final long serialVersionUID = 6883521812717097017L;
+
+ @Override
+ public void focusLost(FocusEvent event) {
+ stopEditing(true);
+ }
+
+ @Override
+ public void focusGained(FocusEvent event) {
+ }
+ });
+
+ layout(part.getControl());
+ showControl(part.getControl());
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot edit " + part, e);
+ }
+ }
+
+ protected void stopEditing(Boolean save) {
+ if (edited instanceof Widget && ((Widget) edited).isDisposed()) {
+ edited = null;
+ return;
+ }
+
+ assert edited != null;
+ if (edited == null) {
+ if (log.isTraceEnabled())
+ log.warn("Told to stop editing while not editing anything");
+ return;
+ }
+
+ try {
+ if (save)
+ save(edited);
+
+ edited.stopEditing();
+ EditablePart editablePart = edited;
+ Control control = ((EditablePart) edited).getControl();
+ edited = null;
+ // TODO make edited state management more robust
+ updateContent(editablePart);
+ layout(control);
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot stop editing", e);
+ } finally {
+ edited = null;
+ }
+ }
+
+ // METHODS AVAILABLE TO EXTENDING CLASSES
+ protected void saveEdit() {
+ if (edited != null)
+ stopEditing(true);
+ }
+
+ protected void cancelEdit() {
+ if (edited != null)
+ stopEditing(false);
+ }
+
+ /** Layout this controls from the related base page. */
+ public void layout(Control... controls) {
+ page.layout(controls);
+ }
+
+ /**
+ * Find the first {@link EditablePart} in the parents hierarchy of this control
+ */
+ protected EditablePart findDataParent(Control parent) {
+ if (parent instanceof EditablePart) {
+ return (EditablePart) parent;
+ }
+ if (parent.getParent() != null)
+ return findDataParent(parent.getParent());
+ else
+ throw new IllegalStateException("No data parent found");
+ }
+
+ // UTILITIES
+ /** Check whether the edited part is in a proper state */
+ protected void checkEdited() {
+ if (edited == null || (edited instanceof Widget) && ((Widget) edited).isDisposed())
+ throw new IllegalStateException("Edited should not be null or disposed at this stage");
+ }
+
+ /** Persist all changes. */
+ protected void persistChanges(Session session) throws RepositoryException {
+ session.save();
+ session.refresh(false);
+ // TODO notify that changes have been persisted
+ }
+
+ /** Convenience method using a Node in order to save the underlying session. */
+ protected void persistChanges(Node anyNode) throws RepositoryException {
+ persistChanges(anyNode.getSession());
+ }
+
+ /** Notify edition exception */
+ protected void notifyEditionException(Throwable e) {
+ Throwable eToLog = e;
+ if (e instanceof IllegalArgumentException)
+ if (e.getCause() instanceof SAXParseException)
+ eToLog = e.getCause();
+ log.error(eToLog.getMessage(), eToLog);
+// if (log.isTraceEnabled())
+// log.trace("Full stack of " + eToLog.getMessage(), e);
+ // TODO Light error notification popup
+ }
+
+ protected Subject getViewerSubject() {
+ Subject res = null;
+ if (accessControlContext != null) {
+ res = Subject.getSubject(accessControlContext);
+ }
+ if (res == null)
+ throw new IllegalStateException("No subject associated with this viewer");
+ return res;
+ }
+
+ // GETTERS / SETTERS
+ public boolean isReadOnly() {
+ return readOnly;
+ }
+
+ protected EditablePart getEdited() {
+ return edited;
+ }
+
+ public MouseListener getMouseListener() {
+ return mouseListener;
+ }
+
+ public FocusListener getFocusListener() {
+ return focusListener;
+ }
+
+ public CmsEditable getCmsEditable() {
+ return cmsEditable;
+ }
+
+ @Override
+ public ISelection getSelection() {
+ return selection;
+ }
+}
\ No newline at end of file
--- /dev/null
+package org.argeo.cms.ui.viewers;
+
+import org.eclipse.swt.widgets.Control;
+
+/** Manages whether an editable or non editable control is shown. */
+public interface EditablePart {
+ public void startEditing();
+
+ public void stopEditing();
+
+ public Control getControl();
+}
--- /dev/null
+package org.argeo.cms.ui.viewers;
+
+import javax.jcr.Item;
+import javax.jcr.RepositoryException;
+
+/** An editable part related to a JCR Item */
+public interface ItemPart<T extends Item> {
+ public Item getItem() throws RepositoryException;
+}
--- /dev/null
+package org.argeo.cms.ui.viewers;
+
+import java.util.Observable;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.nodetype.NodeType;
+import javax.jcr.version.VersionManager;
+
+import org.argeo.api.cms.CmsEditable;
+import org.argeo.cms.CmsException;
+import org.argeo.cms.ui.CmsEditionEvent;
+import org.eclipse.rap.rwt.RWT;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Event;
+import org.eclipse.swt.widgets.Listener;
+
+/** Provides the CmsEditable semantic based on JCR versioning. */
+public class JcrVersionCmsEditable extends Observable implements CmsEditable {
+ private final String nodePath;// cache
+ private final VersionManager versionManager;
+ private final Boolean canEdit;
+
+ public JcrVersionCmsEditable(Node node) throws RepositoryException {
+ this.nodePath = node.getPath();
+ if (node.getSession().hasPermission(node.getPath(),
+ Session.ACTION_SET_PROPERTY)) {
+ // was Session.ACTION_ADD_NODE
+ canEdit = true;
+ if (!node.isNodeType(NodeType.MIX_VERSIONABLE)) {
+ node.addMixin(NodeType.MIX_VERSIONABLE);
+ node.getSession().save();
+ }
+ versionManager = node.getSession().getWorkspace()
+ .getVersionManager();
+ } else {
+ canEdit = false;
+ versionManager = null;
+ }
+
+ // bind keys
+ if (canEdit) {
+ Display display = Display.getCurrent();
+ display.setData(RWT.ACTIVE_KEYS, new String[] { "CTRL+RETURN",
+ "CTRL+E" });
+ display.addFilter(SWT.KeyDown, new Listener() {
+ private static final long serialVersionUID = -4378653870463187318L;
+
+ public void handleEvent(Event e) {
+ boolean ctrlPressed = (e.stateMask & SWT.CTRL) != 0;
+ if (ctrlPressed && e.keyCode == '\r')
+ stopEditing();
+ else if (ctrlPressed && e.keyCode == 'E')
+ stopEditing();
+ }
+ });
+ }
+ }
+
+ @Override
+ public Boolean canEdit() {
+ return canEdit;
+ }
+
+ public Boolean isEditing() {
+ try {
+ if (!canEdit())
+ return false;
+ return versionManager.isCheckedOut(nodePath);
+ } catch (RepositoryException e) {
+ throw new CmsException("Cannot check whether " + nodePath
+ + " is editing", e);
+ }
+ }
+
+ @Override
+ public void startEditing() {
+ try {
+ versionManager.checkout(nodePath);
+ setChanged();
+ } catch (RepositoryException e1) {
+ throw new CmsException("Cannot publish " + nodePath);
+ }
+ notifyObservers(new CmsEditionEvent(nodePath,
+ CmsEditionEvent.START_EDITING));
+ }
+
+ @Override
+ public void stopEditing() {
+ try {
+ versionManager.checkin(nodePath);
+ setChanged();
+ } catch (RepositoryException e1) {
+ throw new CmsException("Cannot publish " + nodePath, e1);
+ }
+ notifyObservers(new CmsEditionEvent(nodePath,
+ CmsEditionEvent.STOP_EDITING));
+ }
+}
--- /dev/null
+package org.argeo.cms.ui.viewers;
+
+import javax.jcr.Node;
+
+/** An editable part related to a node */
+public interface NodePart extends ItemPart<Node> {
+ public Node getNode();
+}
--- /dev/null
+package org.argeo.cms.ui.viewers;
+
+import javax.jcr.Property;
+
+/** An editable part related to a JCR Property */
+public interface PropertyPart extends ItemPart<Property> {
+ public Property getProperty();
+}
--- /dev/null
+package org.argeo.cms.ui.viewers;
+
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.ui.widgets.JcrComposite;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+
+/** A structured UI related to a JCR context. */
+public class Section extends JcrComposite {
+ private static final long serialVersionUID = -5933796173755739207L;
+
+ private final Section parentSection;
+ private Composite sectionHeader;
+ private final Integer relativeDepth;
+
+ public Section(Composite parent, int style, Node node) {
+ this(parent, findSection(parent), style, node);
+ }
+
+ public Section(Section section, int style, Node node) {
+ this(section, section, style, node);
+ }
+
+ protected Section(Composite parent, Section parentSection, int style, Node node) {
+ super(parent, style, node);
+ try {
+ this.parentSection = parentSection;
+ if (parentSection != null) {
+ relativeDepth = getNode().getDepth() - parentSection.getNode().getDepth();
+ } else {
+ relativeDepth = 0;
+ }
+ setLayout(CmsSwtUtils.noSpaceGridLayout());
+ } catch (RepositoryException e) {
+ throw new IllegalStateException("Cannot create section from " + node, e);
+ }
+ }
+
+ public Map<String, Section> getSubSections() throws RepositoryException {
+ LinkedHashMap<String, Section> result = new LinkedHashMap<String, Section>();
+ for (Control child : getChildren()) {
+ if (child instanceof Composite) {
+ collectDirectSubSections((Composite) child, result);
+ }
+ }
+ return Collections.unmodifiableMap(result);
+ }
+
+ private void collectDirectSubSections(Composite composite, LinkedHashMap<String, Section> subSections)
+ throws RepositoryException {
+ if (composite == sectionHeader || composite instanceof EditablePart)
+ return;
+ if (composite instanceof Section) {
+ Section section = (Section) composite;
+ subSections.put(section.getNodeId(), section);
+ return;
+ }
+
+ for (Control child : composite.getChildren())
+ if (child instanceof Composite)
+ collectDirectSubSections((Composite) child, subSections);
+ }
+
+ public Composite createHeader() {
+ return createHeader(this);
+ }
+
+ public Composite createHeader(Composite parent) {
+ if (sectionHeader != null)
+ sectionHeader.dispose();
+
+ sectionHeader = new Composite(parent, SWT.NONE);
+ sectionHeader.setLayoutData(CmsSwtUtils.fillWidth());
+ sectionHeader.setLayout(CmsSwtUtils.noSpaceGridLayout());
+ // sectionHeader.moveAbove(null);
+ // layout();
+ return sectionHeader;
+ }
+
+ public Composite getHeader() {
+ if (sectionHeader != null && sectionHeader.isDisposed())
+ sectionHeader = null;
+ return sectionHeader;
+ }
+
+ // SECTION PARTS
+ public SectionPart getSectionPart(String partId) {
+ for (Control child : getChildren()) {
+ if (child instanceof SectionPart) {
+ SectionPart sectionPart = (SectionPart) child;
+ if (sectionPart.getPartId().equals(partId))
+ return sectionPart;
+ }
+ }
+ return null;
+ }
+
+ public SectionPart nextSectionPart(SectionPart sectionPart) {
+ Control[] children = getChildren();
+ for (int i = 0; i < children.length; i++) {
+ if (sectionPart == children[i]) {
+ for (int j = i + 1; j < children.length; j++) {
+ if (children[i + 1] instanceof SectionPart) {
+ return (SectionPart) children[i + 1];
+ }
+ }
+
+// if (i + 1 < children.length) {
+// Composite next = (Composite) children[i + 1];
+// return (SectionPart) next;
+// } else {
+// // next section
+// }
+ }
+ }
+ return null;
+ }
+
+ public SectionPart previousSectionPart(SectionPart sectionPart) {
+ Control[] children = getChildren();
+ for (int i = 0; i < children.length; i++) {
+ if (sectionPart == children[i])
+ if (i != 0) {
+ Composite previous = (Composite) children[i - 1];
+ return (SectionPart) previous;
+ } else {
+ // previous section
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public String toString() {
+ if (parentSection == null)
+ return "Main section " + getNode();
+ return "Section " + getNode();
+ }
+
+ public Section getParentSection() {
+ return parentSection;
+ }
+
+ public Integer getRelativeDepth() {
+ return relativeDepth;
+ }
+
+ /** Recursively finds the related section in the parents (can be itself) */
+ public static Section findSection(Control control) {
+ if (control == null)
+ return null;
+ if (control instanceof Section)
+ return (Section) control;
+ else
+ return findSection(control.getParent());
+ }
+}
--- /dev/null
+package org.argeo.cms.ui.viewers;
+
+
+/** An editable part dynamically related to a Section */
+public interface SectionPart extends EditablePart, NodePart {
+ public String getPartId();
+
+ public Section getSection();
+}
--- /dev/null
+/** Argeo CMS generic viewers, based on JFace. */
+package org.argeo.cms.ui.viewers;
\ No newline at end of file
--- /dev/null
+package org.argeo.cms.ui.widgets;
+
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.ShellAdapter;
+import org.eclipse.swt.events.ShellEvent;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Shell;
+
+/**
+ * Manages a lightweight shell which is related to a {@link Control}, typically
+ * in order to reproduce a dropdown semantic, but with more flexibility.
+ */
+public class ContextOverlay extends ScrolledPage {
+ private static final long serialVersionUID = 6702077429573324009L;
+
+// private Shell shell;
+ private Control control;
+
+ private int maxHeight = 400;
+
+ public ContextOverlay(Control control, int style) {
+ super(createShell(control, style), SWT.NONE);
+ Shell shell = getShell();
+ setLayoutData(CmsSwtUtils.fillAll());
+ // TODO make autohide configurable?
+ //shell.addShellListener(new AutoHideShellListener());
+ this.control = control;
+ control.addDisposeListener((e) -> {
+ dispose();
+ shell.dispose();
+ });
+ }
+
+ private static Composite createShell(Control control, int style) {
+ if (control == null)
+ throw new IllegalArgumentException("Control cannot be null");
+ if (control.isDisposed())
+ throw new IllegalArgumentException("Control is disposed");
+ Shell shell = new Shell(control.getShell(), SWT.NO_TRIM);
+ shell.setLayout(CmsSwtUtils.noSpaceGridLayout());
+ Composite placeholder = new Composite(shell, SWT.BORDER);
+ placeholder.setLayoutData(CmsSwtUtils.fillAll());
+ placeholder.setLayout(CmsSwtUtils.noSpaceGridLayout());
+ return placeholder;
+ }
+
+ public void show() {
+ Point relativeControlLocation = control.getLocation();
+ Point controlLocation = control.toDisplay(relativeControlLocation.x, relativeControlLocation.y);
+
+ int controlWidth = control.getBounds().width;
+
+ Shell shell = getShell();
+
+ layout(true, true);
+ shell.pack();
+ shell.layout(true, true);
+ int targetShellWidth = shell.getSize().x < controlWidth ? controlWidth : shell.getSize().x;
+ if (shell.getSize().y > maxHeight) {
+ shell.setSize(targetShellWidth, maxHeight);
+ } else {
+ shell.setSize(targetShellWidth, shell.getSize().y);
+ }
+
+ int shellHeight = shell.getSize().y;
+ int controlHeight = control.getBounds().height;
+ Point shellLocation = new Point(controlLocation.x, controlLocation.y + controlHeight);
+ int displayHeight = shell.getDisplay().getBounds().height;
+ if (shellLocation.y + shellHeight > displayHeight) {// bottom of page
+ shellLocation = new Point(controlLocation.x, controlLocation.y - shellHeight);
+ }
+ shell.setLocation(shellLocation);
+
+ if (getChildren().length != 0)
+ shell.open();
+ if (!control.isDisposed())
+ control.setFocus();
+ }
+
+ public void hide() {
+ getShell().setVisible(false);
+ onHide();
+ }
+
+ public boolean isShellVisible() {
+ if (isDisposed())
+ return false;
+ return getShell().isVisible();
+ }
+
+ /** to be overridden */
+ protected void onHide() {
+ // does nothing by default.
+ }
+
+ private class AutoHideShellListener extends ShellAdapter {
+ private static final long serialVersionUID = 7743287433907938099L;
+
+ @Override
+ public void shellDeactivated(ShellEvent e) {
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e1) {
+ // silent
+ }
+ if (!control.isDisposed() && !control.isFocusControl())
+ hide();
+ }
+ }
+}
--- /dev/null
+package org.argeo.cms.ui.widgets;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+
+import org.argeo.api.cms.Cms2DSize;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.ui.util.CmsUiUtils;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Text;
+
+/** A stylable and editable image. */
+public abstract class EditableImage extends StyledControl {
+ private static final long serialVersionUID = -5689145523114022890L;
+ private final static CmsLog log = CmsLog.getLog(EditableImage.class);
+
+ private Cms2DSize preferredImageSize;
+ private Boolean loaded = false;
+
+ public EditableImage(Composite parent, int swtStyle) {
+ super(parent, swtStyle);
+ }
+
+ public EditableImage(Composite parent, int swtStyle, Cms2DSize preferredImageSize) {
+ super(parent, swtStyle);
+ this.preferredImageSize = preferredImageSize;
+ }
+
+ public EditableImage(Composite parent, int style, Node node, boolean cacheImmediately, Cms2DSize preferredImageSize)
+ throws RepositoryException {
+ super(parent, style, node, cacheImmediately);
+ this.preferredImageSize = preferredImageSize;
+ }
+
+ @Override
+ protected void setContainerLayoutData(Composite composite) {
+ // composite.setLayoutData(fillWidth());
+ }
+
+ @Override
+ protected void setControlLayoutData(Control control) {
+ // control.setLayoutData(fillWidth());
+ }
+
+ /** To be overriden. */
+ protected String createImgTag() throws RepositoryException {
+ return CmsUiUtils
+ .noImg(preferredImageSize != null ? preferredImageSize : new Cms2DSize(getSize().x, getSize().y));
+ }
+
+ protected Label createLabel(Composite box, String style) {
+ Label lbl = new Label(box, getStyle());
+ // lbl.setLayoutData(CmsUiUtils.fillWidth());
+ CmsSwtUtils.markup(lbl);
+ CmsSwtUtils.style(lbl, style);
+ if (mouseListener != null)
+ lbl.addMouseListener(mouseListener);
+ load(lbl);
+ return lbl;
+ }
+
+ /** To be overriden. */
+ protected synchronized Boolean load(Control control) {
+ String imgTag;
+ try {
+ imgTag = createImgTag();
+ } catch (Exception e) {
+ // throw new CmsException("Cannot retrieve image", e);
+ log.error("Cannot retrieve image", e);
+ imgTag = CmsUiUtils.noImg(preferredImageSize);
+ loaded = false;
+ }
+
+ if (imgTag == null) {
+ loaded = false;
+ imgTag = CmsUiUtils.noImg(preferredImageSize);
+ } else
+ loaded = true;
+ if (control != null) {
+ ((Label) control).setText(imgTag);
+ control.setSize(preferredImageSize != null
+ ? new Point(preferredImageSize.getWidth(), preferredImageSize.getHeight())
+ : getSize());
+ } else {
+ loaded = false;
+ }
+ getParent().layout();
+ return loaded;
+ }
+
+ public void setPreferredSize(Cms2DSize size) {
+ this.preferredImageSize = size;
+ if (!loaded) {
+ load((Label) getControl());
+ }
+ }
+
+ protected Text createText(Composite box, String style) {
+ Text text = new Text(box, getStyle());
+ CmsSwtUtils.style(text, style);
+ return text;
+ }
+
+ public Cms2DSize getPreferredImageSize() {
+ return preferredImageSize;
+ }
+
+}
--- /dev/null
+package org.argeo.cms.ui.widgets;
+
+import javax.jcr.Item;
+import javax.jcr.RepositoryException;
+
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Text;
+
+/** Editable text part displaying styled text. */
+public class EditableText extends StyledControl {
+ private static final long serialVersionUID = -6372283442330912755L;
+
+ private boolean editable = true;
+
+ private Color highlightColor;
+ private Composite highlight;
+
+ private boolean useTextAsLabel = false;
+
+ public EditableText(Composite parent, int style) {
+ super(parent, style);
+ editable = !(SWT.READ_ONLY == (style & SWT.READ_ONLY));
+ highlightColor = parent.getDisplay().getSystemColor(SWT.COLOR_GRAY);
+ }
+
+ public EditableText(Composite parent, int style, Item item) throws RepositoryException {
+ this(parent, style, item, false);
+ }
+
+ public EditableText(Composite parent, int style, Item item, boolean cacheImmediately) throws RepositoryException {
+ super(parent, style, item, cacheImmediately);
+ editable = !(SWT.READ_ONLY == (style & SWT.READ_ONLY));
+ highlightColor = parent.getDisplay().getSystemColor(SWT.COLOR_GRAY);
+ }
+
+ @Override
+ protected Control createControl(Composite box, String style) {
+ if (isEditing() && getEditable()) {
+ return createText(box, style, true);
+ } else {
+ if (useTextAsLabel) {
+ return createTextLabel(box, style);
+ } else {
+ return createLabel(box, style);
+ }
+ }
+ }
+
+ protected Label createLabel(Composite box, String style) {
+ Label lbl = new Label(box, getStyle() | SWT.WRAP);
+ lbl.setLayoutData(CmsSwtUtils.fillWidth());
+ if (style != null)
+ CmsSwtUtils.style(lbl, style);
+ CmsSwtUtils.markup(lbl);
+ if (mouseListener != null)
+ lbl.addMouseListener(mouseListener);
+ return lbl;
+ }
+
+ protected Text createTextLabel(Composite box, String style) {
+ Text lbl = new Text(box, getStyle() | SWT.MULTI);
+ lbl.setEditable(false);
+ lbl.setLayoutData(CmsSwtUtils.fillWidth());
+ if (style != null)
+ CmsSwtUtils.style(lbl, style);
+ CmsSwtUtils.markup(lbl);
+ if (mouseListener != null)
+ lbl.addMouseListener(mouseListener);
+ return lbl;
+ }
+
+ protected Text createText(Composite box, String style, boolean editable) {
+ highlight = new Composite(box, SWT.NONE);
+ highlight.setBackground(highlightColor);
+ GridData highlightGd = new GridData(SWT.FILL, SWT.FILL, false, false);
+ highlightGd.widthHint = 5;
+ highlightGd.heightHint = 3;
+ highlight.setLayoutData(highlightGd);
+
+ final Text text = new Text(box, getStyle() | SWT.MULTI | SWT.WRAP);
+ text.setEditable(editable);
+ GridData textLayoutData = CmsSwtUtils.fillWidth();
+ // textLayoutData.heightHint = preferredHeight;
+ text.setLayoutData(textLayoutData);
+ if (style != null)
+ CmsSwtUtils.style(text, style);
+ text.setFocus();
+ return text;
+ }
+
+ @Override
+ protected void clear(boolean deep) {
+ if (highlight != null)
+ highlight.dispose();
+ super.clear(deep);
+ }
+
+ public void setText(String text) {
+ Control child = getControl();
+ if (child instanceof Label)
+ ((Label) child).setText(text);
+ else if (child instanceof Text)
+ ((Text) child).setText(text);
+ }
+
+ public Text getAsText() {
+ return (Text) getControl();
+ }
+
+ public Label getAsLabel() {
+ return (Label) getControl();
+ }
+
+ public String getText() {
+ Control child = getControl();
+
+ if (child instanceof Label)
+ return ((Label) child).getText();
+ else if (child instanceof Text)
+ return ((Text) child).getText();
+ else
+ throw new IllegalStateException("Unsupported control " + child.getClass());
+ }
+
+ /** @deprecated Use {@link #isEditable()} instead. */
+ @Deprecated
+ public boolean getEditable() {
+ return isEditable();
+ }
+
+ public boolean isEditable() {
+ return editable;
+ }
+
+ public void setUseTextAsLabel(boolean useTextAsLabel) {
+ this.useTextAsLabel = useTextAsLabel;
+ }
+
+}
--- /dev/null
+package org.argeo.cms.ui.widgets;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+
+import org.argeo.api.cms.Cms2DSize;
+import org.argeo.api.cms.CmsImageManager;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.ui.internal.JcrFileUploadReceiver;
+import org.argeo.cms.ui.viewers.NodePart;
+import org.argeo.cms.ui.viewers.Section;
+import org.argeo.cms.ui.viewers.SectionPart;
+import org.argeo.jcr.Jcr;
+import org.argeo.jcr.JcrException;
+import org.eclipse.rap.fileupload.FileUploadHandler;
+import org.eclipse.rap.fileupload.FileUploadListener;
+import org.eclipse.rap.fileupload.FileUploadReceiver;
+import org.eclipse.rap.rwt.service.ServerPushSession;
+import org.eclipse.rap.rwt.widgets.FileUpload;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+
+/** An image within the Argeo Text framework */
+public class Img extends EditableImage implements SectionPart, NodePart {
+ private static final long serialVersionUID = 6233572783968188476L;
+
+ private final Section section;
+
+ private final CmsImageManager<Control, Node> imageManager;
+ private FileUploadHandler currentUploadHandler = null;
+ private FileUploadListener fileUploadListener;
+
+ public Img(Composite parent, int swtStyle, Node imgNode, Cms2DSize preferredImageSize) throws RepositoryException {
+ this(Section.findSection(parent), parent, swtStyle, imgNode, preferredImageSize, null);
+ setStyle(TextStyles.TEXT_IMAGE);
+ }
+
+ public Img(Composite parent, int swtStyle, Node imgNode) throws RepositoryException {
+ this(Section.findSection(parent), parent, swtStyle, imgNode, null, null);
+ setStyle(TextStyles.TEXT_IMAGE);
+ }
+
+ public Img(Composite parent, int swtStyle, Node imgNode, CmsImageManager<Control, Node> imageManager)
+ throws RepositoryException {
+ this(Section.findSection(parent), parent, swtStyle, imgNode, null, imageManager);
+ setStyle(TextStyles.TEXT_IMAGE);
+ }
+
+ Img(Section section, Composite parent, int swtStyle, Node imgNode, Cms2DSize preferredImageSize,
+ CmsImageManager<Control, Node> imageManager) throws RepositoryException {
+ super(parent, swtStyle, imgNode, false, preferredImageSize);
+ this.section = section;
+ this.imageManager = imageManager != null ? imageManager
+ : (CmsImageManager<Control, Node>) CmsSwtUtils.getCmsView(section).getImageManager();
+ CmsSwtUtils.style(this, TextStyles.TEXT_IMG);
+ }
+
+ @Override
+ protected Control createControl(Composite box, String style) {
+ if (isEditing()) {
+ try {
+ return createImageChooser(box, style);
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot create image chooser", e);
+ }
+ } else {
+ return createLabel(box, style);
+ }
+ }
+
+ @Override
+ public synchronized void stopEditing() {
+ super.stopEditing();
+ fileUploadListener = null;
+ }
+
+ @Override
+ protected synchronized Boolean load(Control lbl) {
+ Node imgNode = getNode();
+ boolean loaded = imageManager.load(imgNode, lbl, getPreferredImageSize());
+ // getParent().layout();
+ return loaded;
+ }
+
+ protected Node getUploadFolder() {
+ return Jcr.getParent(getNode());
+ }
+
+ protected String getUploadName() {
+ Node node = getNode();
+ return Jcr.getName(node) + '[' + Jcr.getIndex(node) + ']';
+ }
+
+ protected CmsImageManager<Control, Node> getImageManager() {
+ return imageManager;
+ }
+
+ protected Control createImageChooser(Composite box, String style) throws RepositoryException {
+ JcrFileUploadReceiver receiver = new JcrFileUploadReceiver(this, getUploadFolder(), getUploadName(),
+ imageManager);
+ if (currentUploadHandler != null)
+ currentUploadHandler.dispose();
+ currentUploadHandler = prepareUpload(receiver);
+ final ServerPushSession pushSession = new ServerPushSession();
+ final FileUpload fileUpload = new FileUpload(box, SWT.NONE);
+ CmsSwtUtils.style(fileUpload, style);
+ fileUpload.addSelectionListener(new SelectionAdapter() {
+ private static final long serialVersionUID = -9158471843941668562L;
+
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ pushSession.start();
+ fileUpload.submit(currentUploadHandler.getUploadUrl());
+ }
+ });
+ return fileUpload;
+ }
+
+ protected FileUploadHandler prepareUpload(FileUploadReceiver receiver) {
+ final FileUploadHandler uploadHandler = new FileUploadHandler(receiver);
+ if (fileUploadListener != null)
+ uploadHandler.addUploadListener(fileUploadListener);
+ return uploadHandler;
+ }
+
+ @Override
+ public Section getSection() {
+ return section;
+ }
+
+ public void setFileUploadListener(FileUploadListener fileUploadListener) {
+ this.fileUploadListener = fileUploadListener;
+ if (currentUploadHandler != null)
+ currentUploadHandler.addUploadListener(fileUploadListener);
+ }
+
+ @Override
+ public Node getItem() throws RepositoryException {
+ return getNode();
+ }
+
+ @Override
+ public String getPartId() {
+ return getNodeId();
+ }
+
+ @Override
+ public String toString() {
+ return "Img #" + getPartId();
+ }
+
+}
--- /dev/null
+package org.argeo.cms.ui.widgets;
+
+import javax.jcr.Item;
+import javax.jcr.ItemNotFoundException;
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.jcr.JcrException;
+import org.eclipse.swt.widgets.Composite;
+
+/** A composite which can (optionally) manage a JCR Item. */
+public class JcrComposite extends Composite {
+ private static final long serialVersionUID = -1447009015451153367L;
+
+ private Session session;
+
+ private String nodeId;
+ private String property = null;
+ private Node cache;
+
+ /** Regular composite constructor. No layout is set. */
+ public JcrComposite(Composite parent, int style) {
+ super(parent, style);
+ session = null;
+ nodeId = null;
+ }
+
+ public JcrComposite(Composite parent, int style, Item item) {
+ this(parent, style, item, false);
+ }
+
+ public JcrComposite(Composite parent, int style, Item item, boolean cacheImmediately) {
+ super(parent, style);
+ if (item != null)
+ try {
+ this.session = item.getSession();
+// if (!cacheImmediately && (SWT.READ_ONLY == (style & SWT.READ_ONLY))) {
+// // (useless?) optimization: we only save a pointer to the session,
+// // not even a reference to the item
+// this.nodeId = null;
+// } else {
+ Node node;
+ Property property = null;
+ if (item instanceof Node) {
+ node = (Node) item;
+ } else {// Property
+ property = (Property) item;
+ if (property.isMultiple())// TODO manage property index
+ throw new UnsupportedOperationException("Multiple properties not supported yet.");
+ this.property = property.getName();
+ node = property.getParent();
+ }
+ this.nodeId = node.getIdentifier();
+ if (cacheImmediately)
+ this.cache = node;
+// }
+ setLayout(CmsSwtUtils.noSpaceGridLayout());
+ } catch (RepositoryException e) {
+ throw new IllegalStateException("Cannot create composite from " + item, e);
+ }
+ }
+
+ public synchronized Node getNode() {
+ try {
+ if (!itemIsNode())
+ throw new IllegalStateException("Item is not a Node");
+ return getNodeInternal();
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot get node " + nodeId, e);
+ }
+ }
+
+ private synchronized Node getNodeInternal() throws RepositoryException {
+ if (cache != null)
+ return cache;
+ else if (session != null)
+ if (nodeId != null)
+ return session.getNodeByIdentifier(nodeId);
+ else
+ return null;
+ else
+ return null;
+ }
+
+ public synchronized String getPropertyName() {
+ try {
+ return getProperty().getName();
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot get property name", e);
+ }
+ }
+
+ public synchronized Node getPropertyNode() {
+ try {
+ return getProperty().getNode();
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot get property name", e);
+ }
+ }
+
+ public synchronized Property getProperty() {
+ try {
+ if (itemIsNode())
+ throw new IllegalStateException("Item is not a Property");
+ Node node = getNodeInternal();
+ if (!node.hasProperty(property))
+ throw new IllegalStateException("Property " + property + " is not set on " + node);
+ return node.getProperty(property);
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot get property " + property + " from node " + nodeId, e);
+ }
+ }
+
+ public synchronized boolean itemIsNode() {
+ return property == null;
+ }
+
+ public synchronized boolean itemExists() {
+ if (session == null)
+ return false;
+ try {
+ Node n = session.getNodeByIdentifier(nodeId);
+ if (!itemIsNode())
+ return n.hasProperty(property);
+ else
+ return true;
+ } catch (ItemNotFoundException e) {
+ return false;
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot check whether node exists", e);
+ }
+ }
+
+ /** Set/update the cache or change the node */
+ public synchronized void setNode(Node node) {
+ if (!itemIsNode())
+ throw new IllegalArgumentException("Cannot set a Node on a Property");
+
+ if (node == null) {// clear cache
+ this.cache = null;
+ return;
+ }
+
+ try {
+// if (session != null || session != node.getSession())// check session
+// throw new IllegalArgumentException("Uncompatible session");
+// if (session == null)
+ session = node.getSession();
+ if (nodeId == null || !nodeId.equals(node.getIdentifier())) {
+ nodeId = node.getIdentifier();
+ cache = node;
+ itemUpdated();
+ } else {
+ cache = node;// set/update cache
+ }
+ } catch (RepositoryException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ /** Set/update the cache or change the property */
+ public synchronized void setProperty(Property prop) {
+ if (itemIsNode())
+ throw new IllegalArgumentException("Cannot set a Property on a Node");
+
+ if (prop == null) {// clear cache
+ this.cache = null;
+ return;
+ }
+
+ try {
+ if (session == null || session != prop.getSession())// check session
+ throw new IllegalArgumentException("Uncompatible session");
+
+ Node node = prop.getNode();
+ if (nodeId == null || !nodeId.equals(node.getIdentifier()) || !property.equals(prop.getName())) {
+ nodeId = node.getIdentifier();
+ property = prop.getName();
+ cache = node;
+ itemUpdated();
+ } else {
+ cache = node;// set/update cache
+ }
+ } catch (RepositoryException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ public synchronized String getNodeId() {
+ return nodeId;
+ }
+
+ /** Change the node, does nothing if same. */
+ public synchronized void setNodeId(String nodeId) throws RepositoryException {
+ if (this.nodeId != null && this.nodeId.equals(nodeId))
+ return;
+ this.nodeId = nodeId;
+ if (cache != null)
+ cache = session.getNodeByIdentifier(this.nodeId);
+ itemUpdated();
+ }
+
+ protected synchronized void itemUpdated() {
+ layout();
+ }
+
+ public Session getSession() {
+ return session;
+ }
+}
--- /dev/null
+package org.argeo.cms.ui.widgets;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.ScrolledComposite;
+import org.eclipse.swt.events.ControlEvent;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+
+/**
+ * A composite that can be scrolled vertically. It wraps a
+ * {@link ScrolledComposite} (and is being wrapped by it), simplifying its
+ * configuration.
+ */
+public class ScrolledPage extends Composite {
+ private static final long serialVersionUID = 1593536965663574437L;
+
+ private ScrolledComposite scrolledComposite;
+
+ public ScrolledPage(Composite parent, int style) {
+ this(parent, style, false);
+ }
+
+ public ScrolledPage(Composite parent, int style, boolean alwaysShowScroll) {
+ super(createScrolledComposite(parent, alwaysShowScroll), style);
+ scrolledComposite = (ScrolledComposite) getParent();
+ scrolledComposite.setContent(this);
+
+ scrolledComposite.setExpandVertical(true);
+ scrolledComposite.setExpandHorizontal(true);
+ scrolledComposite.addControlListener(new ScrollControlListener());
+ }
+
+ private static ScrolledComposite createScrolledComposite(Composite parent, boolean alwaysShowScroll) {
+ ScrolledComposite scrolledComposite = new ScrolledComposite(parent, SWT.V_SCROLL);
+ scrolledComposite.setAlwaysShowScrollBars(alwaysShowScroll);
+ return scrolledComposite;
+ }
+
+ @Override
+ public void layout(boolean changed, boolean all) {
+ updateScroll();
+ super.layout(changed, all);
+ }
+
+ public void showControl(Control control) {
+ scrolledComposite.showControl(control);
+ }
+
+ protected void updateScroll() {
+ Rectangle r = scrolledComposite.getClientArea();
+ Point preferredSize = computeSize(r.width, SWT.DEFAULT);
+ scrolledComposite.setMinHeight(preferredSize.y);
+ }
+
+ // public ScrolledComposite getScrolledComposite() {
+ // return this.scrolledComposite;
+ // }
+
+ /** Set it on the wrapping scrolled composite */
+ @Override
+ public void setLayoutData(Object layoutData) {
+ scrolledComposite.setLayoutData(layoutData);
+ }
+
+ private class ScrollControlListener extends org.eclipse.swt.events.ControlAdapter {
+ private static final long serialVersionUID = -3586986238567483316L;
+
+ public void controlResized(ControlEvent e) {
+ updateScroll();
+ }
+ }
+}
--- /dev/null
+package org.argeo.cms.ui.widgets;
+
+import javax.jcr.Item;
+
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.ui.CmsUiConstants;
+import org.argeo.eclipse.ui.specific.EclipseUiSpecificUtils;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.FocusListener;
+import org.eclipse.swt.events.MouseListener;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+
+/** Editable text part displaying styled text. */
+public abstract class StyledControl extends JcrComposite implements CmsUiConstants {
+ private static final long serialVersionUID = -6372283442330912755L;
+ private Control control;
+
+ private Composite container;
+ private Composite box;
+
+ protected MouseListener mouseListener;
+ protected FocusListener focusListener;
+
+ private Boolean editing = Boolean.FALSE;
+
+ private Composite ancestorToLayout;
+
+ public StyledControl(Composite parent, int swtStyle) {
+ super(parent, swtStyle);
+ setLayout(CmsSwtUtils.noSpaceGridLayout());
+ }
+
+ public StyledControl(Composite parent, int style, Item item) {
+ super(parent, style, item);
+ }
+
+ public StyledControl(Composite parent, int style, Item item, boolean cacheImmediately) {
+ super(parent, style, item, cacheImmediately);
+ }
+
+ protected abstract Control createControl(Composite box, String style);
+
+ protected Composite createBox() {
+ Composite box = new Composite(container, SWT.INHERIT_DEFAULT);
+ setContainerLayoutData(box);
+ box.setLayout(CmsSwtUtils.noSpaceGridLayout(3));
+ return box;
+ }
+
+ protected Composite createContainer() {
+ Composite container = new Composite(this, SWT.INHERIT_DEFAULT);
+ setContainerLayoutData(container);
+ container.setLayout(CmsSwtUtils.noSpaceGridLayout());
+ return container;
+ }
+
+ public Control getControl() {
+ return control;
+ }
+
+ protected synchronized Boolean isEditing() {
+ return editing;
+ }
+
+ public synchronized void startEditing() {
+ assert !isEditing();
+ editing = true;
+ // int height = control.getSize().y;
+ String style = (String) EclipseUiSpecificUtils.getStyleData(control);
+ clear(false);
+ refreshControl(style);
+
+ // add the focus listener to the newly created edition control
+ if (focusListener != null)
+ control.addFocusListener(focusListener);
+ }
+
+ public synchronized void stopEditing() {
+ assert isEditing();
+ editing = false;
+ String style = (String) EclipseUiSpecificUtils.getStyleData(control);
+ clear(false);
+ refreshControl(style);
+ }
+
+ protected void refreshControl(String style) {
+ control = createControl(box, style);
+ setControlLayoutData(control);
+ if (ancestorToLayout != null)
+ ancestorToLayout.layout(true, true);
+ else
+ getParent().layout(true, true);
+ }
+
+ public void setStyle(String style) {
+ Object currentStyle = null;
+ if (control != null)
+ currentStyle = EclipseUiSpecificUtils.getStyleData(control);
+ if (currentStyle != null && currentStyle.equals(style))
+ return;
+
+ clear(true);
+ refreshControl(style);
+
+ if (style != null) {
+ CmsSwtUtils.style(box, style + "_box");
+ CmsSwtUtils.style(container, style + "_container");
+ }
+ }
+
+ /** To be overridden */
+ protected void setControlLayoutData(Control control) {
+ control.setLayoutData(CmsSwtUtils.fillWidth());
+ }
+
+ /** To be overridden */
+ protected void setContainerLayoutData(Composite composite) {
+ composite.setLayoutData(CmsSwtUtils.fillWidth());
+ }
+
+ protected void clear(boolean deep) {
+ if (deep) {
+ for (Control control : getChildren())
+ control.dispose();
+ container = createContainer();
+ box = createBox();
+ } else {
+ control.dispose();
+ }
+ }
+
+ public void setMouseListener(MouseListener mouseListener) {
+ if (this.mouseListener != null && control != null)
+ control.removeMouseListener(this.mouseListener);
+ this.mouseListener = mouseListener;
+ if (control != null && this.mouseListener != null)
+ control.addMouseListener(mouseListener);
+ }
+
+ public void setFocusListener(FocusListener focusListener) {
+ if (this.focusListener != null && control != null)
+ control.removeFocusListener(this.focusListener);
+ this.focusListener = focusListener;
+ if (control != null && this.focusListener != null)
+ control.addFocusListener(focusListener);
+ }
+
+ public void setAncestorToLayout(Composite ancestorToLayout) {
+ this.ancestorToLayout = ancestorToLayout;
+ }
+
+}
--- /dev/null
+package org.argeo.cms.ui.widgets;
+
+/** Styles references in the CSS. */
+public interface TextStyles {
+ /** The whole page area */
+ public final static String TEXT_AREA = "text_area";
+ /** Area providing controls for editing text */
+ public final static String TEXT_EDITOR_HEADER = "text_editor_header";
+ /** The styled composite for editing the text */
+ public final static String TEXT_STYLED_COMPOSITE = "text_styled_composite";
+ /** A section */
+ public final static String TEXT_SECTION = "text_section";
+ /** A paragraph */
+ public final static String TEXT_PARAGRAPH = "text_paragraph";
+ /** An image */
+ public final static String TEXT_IMG = "text_img";
+ /** The dialog to edit styled paragraph */
+ public final static String TEXT_STYLED_TOOLS_DIALOG = "text_styled_tools_dialog";
+
+ /*
+ * DEFAULT TEXT STYLES
+ */
+ /** Default style for text body */
+ public final static String TEXT_DEFAULT = "text_default";
+ /** Fixed-width, typically code */
+ public final static String TEXT_PRE = "text_pre";
+ /** Quote */
+ public final static String TEXT_QUOTE = "text_quote";
+ /** Title */
+ public final static String TEXT_TITLE = "text_title";
+ /** Header (to be dynamically completed with the depth, e.g. text_h1) */
+ public final static String TEXT_H = "text_h";
+
+ /** Default style for images */
+ public final static String TEXT_IMAGE = "text_image";
+
+}
--- /dev/null
+/** Argeo CMS generic widgets, based on SWT. */
+package org.argeo.cms.ui.widgets;
\ No newline at end of file
--- /dev/null
+package org.argeo.eclipse.ui.jcr;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.argeo.api.cms.CmsLog;
+import org.argeo.eclipse.ui.AbstractTreeContentProvider;
+import org.argeo.eclipse.ui.EclipseUiException;
+
+/** Canonical implementation of tree content provider manipulating JCR nodes. */
+public abstract class AbstractNodeContentProvider extends
+ AbstractTreeContentProvider {
+ private static final long serialVersionUID = -4905836490027272569L;
+
+ private final static CmsLog log = CmsLog
+ .getLog(AbstractNodeContentProvider.class);
+
+ private Session session;
+
+ public AbstractNodeContentProvider(Session session) {
+ this.session = session;
+ }
+
+ /**
+ * Whether this path is a base path (and thus has no parent). By default it
+ * returns true if path is '/' (root node)
+ */
+ protected Boolean isBasePath(String path) {
+ // root node
+ return path.equals("/");
+ }
+
+ @Override
+ public Object[] getChildren(Object element) {
+ Object[] children;
+ if (element instanceof Node) {
+ try {
+ Node node = (Node) element;
+ children = getChildren(node);
+ } catch (RepositoryException e) {
+ throw new EclipseUiException("Cannot get children of " + element, e);
+ }
+ } else if (element instanceof WrappedNode) {
+ WrappedNode wrappedNode = (WrappedNode) element;
+ try {
+ children = getChildren(wrappedNode.getNode());
+ } catch (RepositoryException e) {
+ throw new EclipseUiException("Cannot get children of "
+ + wrappedNode, e);
+ }
+ } else if (element instanceof NodesWrapper) {
+ NodesWrapper node = (NodesWrapper) element;
+ children = node.getChildren();
+ } else {
+ children = super.getChildren(element);
+ }
+
+ children = sort(element, children);
+ return children;
+ }
+
+ /** Do not sort by default. To be overidden to provide custom sort. */
+ protected Object[] sort(Object parent, Object[] children) {
+ return children;
+ }
+
+ /**
+ * To be overridden in order to filter out some nodes. Does nothing by
+ * default. The provided list is a temporary one and can thus be modified
+ * directly . (e.g. via an iterator)
+ */
+ protected List<Node> filterChildren(List<Node> children)
+ throws RepositoryException {
+ return children;
+ }
+
+ protected Object[] getChildren(Node node) throws RepositoryException {
+ List<Node> nodes = new ArrayList<Node>();
+ for (NodeIterator nit = node.getNodes(); nit.hasNext();)
+ nodes.add(nit.nextNode());
+ nodes = filterChildren(nodes);
+ return nodes.toArray();
+ }
+
+ @Override
+ public Object getParent(Object element) {
+ if (element instanceof Node) {
+ Node node = (Node) element;
+ try {
+ String path = node.getPath();
+ if (isBasePath(path))
+ return null;
+ else
+ return node.getParent();
+ } catch (RepositoryException e) {
+ log.warn("Cannot get parent of " + element + ": " + e);
+ return null;
+ }
+ } else if (element instanceof WrappedNode) {
+ WrappedNode wrappedNode = (WrappedNode) element;
+ return wrappedNode.getParent();
+ } else if (element instanceof NodesWrapper) {
+ NodesWrapper nodesWrapper = (NodesWrapper) element;
+ return this.getParent(nodesWrapper.getNode());
+ }
+ return super.getParent(element);
+ }
+
+ @Override
+ public boolean hasChildren(Object element) {
+ try {
+ if (element instanceof Node) {
+ Node node = (Node) element;
+ return node.hasNodes();
+ } else if (element instanceof WrappedNode) {
+ WrappedNode wrappedNode = (WrappedNode) element;
+ return wrappedNode.getNode().hasNodes();
+ } else if (element instanceof NodesWrapper) {
+ NodesWrapper nodesWrapper = (NodesWrapper) element;
+ return nodesWrapper.hasChildren();
+ }
+
+ } catch (RepositoryException e) {
+ throw new EclipseUiException("Cannot check whether " + element
+ + " has children", e);
+ }
+ return super.hasChildren(element);
+ }
+
+ public Session getSession() {
+ return session;
+ }
+}
\ No newline at end of file
--- /dev/null
+package org.argeo.eclipse.ui.jcr;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.observation.Event;
+import javax.jcr.observation.EventIterator;
+import javax.jcr.observation.EventListener;
+
+import org.argeo.api.cms.CmsLog;
+import org.argeo.eclipse.ui.EclipseUiException;
+import org.eclipse.swt.widgets.Display;
+
+/**
+ * {@link EventListener} which simplifies running actions within the UI thread.
+ */
+public abstract class AsyncUiEventListener implements EventListener {
+ // private final static Log logSuper = LogFactory
+ // .getLog(AsyncUiEventListener.class);
+ private final CmsLog logThis = CmsLog.getLog(getClass());
+
+ private final Display display;
+
+ public AsyncUiEventListener(Display display) {
+ super();
+ this.display = display;
+ }
+
+ /** Called asynchronously in the UI thread. */
+ protected abstract void onEventInUiThread(List<Event> events) throws RepositoryException;
+
+ /**
+ * Whether these events should be processed in the UI or skipped with no UI
+ * job created.
+ */
+ protected Boolean willProcessInUiThread(List<Event> events) throws RepositoryException {
+ return true;
+ }
+
+ protected CmsLog getLog() {
+ return logThis;
+ }
+
+ public final void onEvent(final EventIterator eventIterator) {
+ final List<Event> events = new ArrayList<Event>();
+ while (eventIterator.hasNext())
+ events.add(eventIterator.nextEvent());
+
+ if (logThis.isTraceEnabled())
+ logThis.trace("Received " + events.size() + " events");
+
+ try {
+ if (!willProcessInUiThread(events))
+ return;
+ } catch (RepositoryException e) {
+ throw new EclipseUiException("Cannot test skip events " + events, e);
+ }
+
+ // Job job = new Job("JCR Events") {
+ // protected IStatus run(IProgressMonitor monitor) {
+ // if (display.isDisposed()) {
+ // logSuper.warn("Display is disposed cannot update UI");
+ // return Status.CANCEL_STATUS;
+ // }
+
+ if (!display.isDisposed())
+ display.asyncExec(new Runnable() {
+ public void run() {
+ try {
+ onEventInUiThread(events);
+ } catch (RepositoryException e) {
+ throw new EclipseUiException("Cannot process events " + events, e);
+ }
+ }
+ });
+
+ // return Status.OK_STATUS;
+ // }
+ // };
+ // job.schedule();
+ }
+}
--- /dev/null
+package org.argeo.eclipse.ui.jcr;
+
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.RepositoryException;
+import javax.jcr.nodetype.NodeType;
+
+import org.argeo.eclipse.ui.EclipseUiException;
+import org.eclipse.jface.viewers.ColumnLabelProvider;
+import org.eclipse.swt.graphics.Image;
+
+/**
+ * Default label provider to manage node and corresponding UI objects. It
+ * provides reasonable overwrite-able default for known JCR types.
+ */
+public class DefaultNodeLabelProvider extends ColumnLabelProvider {
+ private static final long serialVersionUID = 1216182332792151235L;
+
+ public String getText(Object element) {
+ try {
+ if (element instanceof Node) {
+ return getText((Node) element);
+ } else if (element instanceof WrappedNode) {
+ return getText(((WrappedNode) element).getNode());
+ } else if (element instanceof NodesWrapper) {
+ return getText(((NodesWrapper) element).getNode());
+ }
+ return super.getText(element);
+ } catch (RepositoryException e) {
+ throw new EclipseUiException("Cannot get text for of " + element, e);
+ }
+ }
+
+ protected String getText(Node node) throws RepositoryException {
+ if (node.isNodeType(NodeType.MIX_TITLE)
+ && node.hasProperty(Property.JCR_TITLE))
+ return node.getProperty(Property.JCR_TITLE).getString();
+ else
+ return node.getName();
+ }
+
+ @Override
+ public Image getImage(Object element) {
+ try {
+ 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());
+ }
+ } catch (RepositoryException e) {
+ throw new EclipseUiException("Cannot retrieve image for " + element, e);
+ }
+ return super.getImage(element);
+ }
+
+ protected Image getImage(Node node) throws RepositoryException {
+ // FIXME who uses that?
+ return null;
+ }
+
+ @Override
+ public String getToolTipText(Object element) {
+ try {
+ if (element instanceof Node) {
+ return getToolTipText((Node) element);
+ } else if (element instanceof WrappedNode) {
+ return getToolTipText(((WrappedNode) element).getNode());
+ } else if (element instanceof NodesWrapper) {
+ return getToolTipText(((NodesWrapper) element).getNode());
+ }
+ } catch (RepositoryException e) {
+ throw new EclipseUiException("Cannot get tooltip for " + element, e);
+ }
+ return super.getToolTipText(element);
+ }
+
+ protected String getToolTipText(Node node) throws RepositoryException {
+ return null;
+ }
+}
\ No newline at end of file
--- /dev/null
+package org.argeo.eclipse.ui.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.eclipse.ui.jcr;
+
+import java.util.Calendar;
+
+import javax.jcr.Node;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+
+import org.argeo.eclipse.ui.EclipseUiException;
+import org.argeo.eclipse.ui.jcr.lists.NodeViewerComparator;
+import org.argeo.eclipse.ui.jcr.lists.RowViewerComparator;
+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.widgets.Table;
+
+/** Utility methods to simplify UI development using SWT (or RWT), jface and JCR. */
+public class JcrUiUtils {
+
+ /**
+ * Centralizes management of updating property value. Among other to avoid
+ * infinite loop when the new value is the same as the ones that is already
+ * stored in JCR.
+ *
+ * @return true if the value as changed
+ */
+ public static boolean setJcrProperty(Node node, String propName,
+ int propertyType, Object value) {
+ try {
+ switch (propertyType) {
+ case PropertyType.STRING:
+ if ("".equals((String) value)
+ && (!node.hasProperty(propName) || node
+ .hasProperty(propName)
+ && "".equals(node.getProperty(propName)
+ .getString())))
+ // workaround the fact that the Text widget value cannot be
+ // set to null
+ return false;
+ else if (node.hasProperty(propName)
+ && node.getProperty(propName).getString()
+ .equals((String) value))
+ // nothing changed yet
+ return false;
+ else {
+ node.setProperty(propName, (String) value);
+ return true;
+ }
+ case PropertyType.BOOLEAN:
+ if (node.hasProperty(propName)
+ && node.getProperty(propName).getBoolean() == (Boolean) value)
+ // nothing changed yet
+ return false;
+ else {
+ node.setProperty(propName, (Boolean) value);
+ return true;
+ }
+ case PropertyType.DATE:
+ if (node.hasProperty(propName)
+ && node.getProperty(propName).getDate()
+ .equals((Calendar) value))
+ // nothing changed yet
+ return false;
+ else {
+ node.setProperty(propName, (Calendar) value);
+ return true;
+ }
+ case PropertyType.LONG:
+ Long lgValue = (Long) value;
+
+ if (lgValue == null)
+ lgValue = 0L;
+
+ if (node.hasProperty(propName)
+ && node.getProperty(propName).getLong() == lgValue)
+ // nothing changed yet
+ return false;
+ else {
+ node.setProperty(propName, lgValue);
+ return true;
+ }
+
+ default:
+ throw new EclipseUiException("Unimplemented property save");
+ }
+ } catch (RepositoryException re) {
+ throw new EclipseUiException("Unexpected error while setting property",
+ re);
+ }
+ }
+
+ /**
+ * Creates a new selection adapter in order to provide sorting abitily on a
+ * SWT Table that display a row list
+ **/
+ public static SelectionAdapter getRowSelectionAdapter(final int index,
+ final int propertyType, final String selectorName,
+ final String propertyName, final RowViewerComparator comparator,
+ final TableViewer viewer) {
+ SelectionAdapter selectionAdapter = new SelectionAdapter() {
+ private static final long serialVersionUID = -5738918304901437720L;
+
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ Table table = viewer.getTable();
+ comparator.setColumn(propertyType, selectorName, propertyName);
+ int dir = table.getSortDirection();
+ if (table.getSortColumn() == table.getColumn(index)) {
+ dir = dir == SWT.UP ? SWT.DOWN : SWT.UP;
+ } else {
+ dir = SWT.DOWN;
+ }
+ table.setSortDirection(dir);
+ table.setSortColumn(table.getColumn(index));
+ viewer.refresh();
+ }
+ };
+ return selectionAdapter;
+ }
+
+ /**
+ * Creates a new selection adapter in order to provide sorting abitily on a
+ * swt table that display a row list
+ **/
+ public static SelectionAdapter getNodeSelectionAdapter(final int index,
+ final int propertyType, final String propertyName,
+ final NodeViewerComparator comparator, final TableViewer viewer) {
+ SelectionAdapter selectionAdapter = new SelectionAdapter() {
+ private static final long serialVersionUID = -1683220869195484625L;
+
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ Table table = viewer.getTable();
+ comparator.setColumn(propertyType, propertyName);
+ int dir = table.getSortDirection();
+ if (table.getSortColumn() == table.getColumn(index)) {
+ dir = dir == SWT.UP ? SWT.DOWN : SWT.UP;
+ } else {
+ dir = SWT.DOWN;
+ }
+ table.setSortDirection(dir);
+ table.setSortColumn(table.getColumn(index));
+ viewer.refresh();
+ }
+ };
+ return selectionAdapter;
+ }
+}
--- /dev/null
+package org.argeo.eclipse.ui.jcr;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+
+import org.eclipse.jface.viewers.ColumnLabelProvider;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.Font;
+import org.eclipse.swt.graphics.Image;
+
+/** Simplifies writing JCR-based column label provider. */
+public class NodeColumnLabelProvider extends ColumnLabelProvider {
+ private static final long serialVersionUID = -6586692836928505358L;
+
+ protected String getNodeText(Node node) throws RepositoryException {
+ return super.getText(node);
+ }
+
+ protected String getNodeToolTipText(Node node) throws RepositoryException {
+ return super.getToolTipText(node);
+ }
+
+ protected Image getNodeImage(Node node) throws RepositoryException {
+ return super.getImage(node);
+ }
+
+ protected Font getNodeFont(Node node) throws RepositoryException {
+ return super.getFont(node);
+ }
+
+ public Color getNodeBackground(Node node) throws RepositoryException {
+ return super.getBackground(node);
+ }
+
+ public Color getNodeForeground(Node node) throws RepositoryException {
+ return super.getForeground(node);
+ }
+
+ @Override
+ public String getText(Object element) {
+ try {
+ if (element instanceof Node)
+ return getNodeText((Node) element);
+ else if (element instanceof NodeElement)
+ return getNodeText(((NodeElement) element).getNode());
+ else
+ throw new IllegalArgumentException("Unsupported element type " + element.getClass());
+ } catch (RepositoryException e) {
+ throw new IllegalStateException("Repository exception when accessing " + element, e);
+ }
+ }
+
+ @Override
+ public Image getImage(Object element) {
+ try {
+ if (element instanceof Node)
+ return getNodeImage((Node) element);
+ else if (element instanceof NodeElement)
+ return getNodeImage(((NodeElement) element).getNode());
+ else
+ throw new IllegalArgumentException("Unsupported element type " + element.getClass());
+ } catch (RepositoryException e) {
+ throw new IllegalStateException("Repository exception when accessing " + element, e);
+ }
+ }
+
+ @Override
+ public String getToolTipText(Object element) {
+ try {
+ if (element instanceof Node)
+ return getNodeToolTipText((Node) element);
+ else if (element instanceof NodeElement)
+ return getNodeToolTipText(((NodeElement) element).getNode());
+ else
+ throw new IllegalArgumentException("Unsupported element type " + element.getClass());
+ } catch (RepositoryException e) {
+ throw new IllegalStateException("Repository exception when accessing " + element, e);
+ }
+ }
+
+ @Override
+ public Font getFont(Object element) {
+ try {
+ if (element instanceof Node)
+ return getNodeFont((Node) element);
+ else if (element instanceof NodeElement)
+ return getNodeFont(((NodeElement) element).getNode());
+ else
+ throw new IllegalArgumentException("Unsupported element type " + element.getClass());
+ } catch (RepositoryException e) {
+ throw new IllegalStateException("Repository exception when accessing " + element, e);
+ }
+ }
+
+ @Override
+ public Color getBackground(Object element) {
+ try {
+ if (element instanceof Node)
+ return getNodeBackground((Node) element);
+ else if (element instanceof NodeElement)
+ return getNodeBackground(((NodeElement) element).getNode());
+ else
+ throw new IllegalArgumentException("Unsupported element type " + element.getClass());
+ } catch (RepositoryException e) {
+ throw new IllegalStateException("Repository exception when accessing " + element, e);
+ }
+ }
+
+ @Override
+ public Color getForeground(Object element) {
+ try {
+ if (element instanceof Node)
+ return getNodeForeground((Node) element);
+ else if (element instanceof NodeElement)
+ return getNodeForeground(((NodeElement) element).getNode());
+ else
+ throw new IllegalArgumentException("Unsupported element type " + element.getClass());
+ } catch (RepositoryException e) {
+ throw new IllegalStateException("Repository exception when accessing " + element, e);
+ }
+ }
+
+}
--- /dev/null
+package org.argeo.eclipse.ui.jcr;
+
+import javax.jcr.Node;
+
+/** An element which is related to a JCR {@link Node}. */
+public interface NodeElement {
+ Node getNode();
+}
--- /dev/null
+package org.argeo.eclipse.ui.jcr;\r
+\r
+import javax.jcr.Node;\r
+import javax.jcr.RepositoryException;\r
+\r
+import org.argeo.eclipse.ui.EclipseUiException;\r
+import org.eclipse.jface.viewers.IElementComparer;\r
+\r
+/** Element comparer for JCR node, to be used in JFace viewers. */\r
+public class NodeElementComparer implements IElementComparer {\r
+\r
+ public boolean equals(Object a, Object b) {\r
+ try {\r
+ if ((a instanceof Node) && (b instanceof Node)) {\r
+ Node nodeA = (Node) a;\r
+ Node nodeB = (Node) b;\r
+ return nodeA.getIdentifier().equals(nodeB.getIdentifier());\r
+ } else {\r
+ return a.equals(b);\r
+ }\r
+ } catch (RepositoryException e) {\r
+ throw new EclipseUiException("Cannot compare nodes", e);\r
+ }\r
+ }\r
+\r
+ public int hashCode(Object element) {\r
+ try {\r
+ if (element instanceof Node)\r
+ return ((Node) element).getIdentifier().hashCode();\r
+ return element.hashCode();\r
+ } catch (RepositoryException e) {\r
+ throw new EclipseUiException("Cannot get hash code", e);\r
+ }\r
+ }\r
+\r
+}\r
--- /dev/null
+package org.argeo.eclipse.ui.jcr;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.RepositoryException;
+
+import org.argeo.eclipse.ui.EclipseUiException;
+
+/**
+ * Element of tree which is based on a node, but whose children are not
+ * necessarily this node children.
+ */
+public class NodesWrapper {
+ private final Node node;
+
+ public NodesWrapper(Node node) {
+ super();
+ this.node = node;
+ }
+
+ protected NodeIterator getNodeIterator() throws RepositoryException {
+ return node.getNodes();
+ }
+
+ protected List<WrappedNode> getWrappedNodes() throws RepositoryException {
+ List<WrappedNode> nodes = new ArrayList<WrappedNode>();
+ for (NodeIterator nit = getNodeIterator(); nit.hasNext();)
+ nodes.add(new WrappedNode(this, nit.nextNode()));
+ return nodes;
+ }
+
+ public Object[] getChildren() {
+ try {
+ return getWrappedNodes().toArray();
+ } catch (RepositoryException e) {
+ throw new EclipseUiException("Cannot get wrapped children", e);
+ }
+ }
+
+ /**
+ * @return true by default because we don't want to compute the wrapped
+ * nodes twice
+ */
+ public Boolean hasChildren() {
+ return true;
+ }
+
+ public Node getNode() {
+ return node;
+ }
+
+ @Override
+ public int hashCode() {
+ return node.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof NodesWrapper)
+ return node.equals(((NodesWrapper) obj).getNode());
+ else
+ return false;
+ }
+
+ public String toString() {
+ return "nodes wrapper based on " + node;
+ }
+}
--- /dev/null
+package org.argeo.eclipse.ui.jcr;
+
+import javax.jcr.NodeIterator;
+import javax.jcr.RepositoryException;
+import javax.jcr.query.Query;
+
+import org.argeo.jcr.JcrException;
+import org.argeo.jcr.JcrUtils;
+import org.eclipse.jface.viewers.IStructuredContentProvider;
+import org.eclipse.jface.viewers.Viewer;
+
+/** Content provider based on a JCR {@link Query}. */
+public class QueryTableContentProvider implements IStructuredContentProvider {
+ private static final long serialVersionUID = 760371460907204722L;
+
+ @Override
+ public void dispose() {
+ }
+
+ @Override
+ public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
+ }
+
+ @Override
+ public Object[] getElements(Object inputElement) {
+ Query query = (Query) inputElement;
+ try {
+ NodeIterator nit = query.execute().getNodes();
+ return JcrUtils.nodeIteratorToList(nit).toArray();
+ } catch (RepositoryException e) {
+ throw new JcrException(e);
+ }
+ }
+
+}
--- /dev/null
+package org.argeo.eclipse.ui.jcr;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.argeo.eclipse.ui.EclipseUiException;
+import org.argeo.jcr.JcrUtils;
+
+/** Simple JCR node content provider taking a list of String as base path. */
+public class SimpleNodeContentProvider extends AbstractNodeContentProvider {
+ private static final long serialVersionUID = -8245193308831384269L;
+ private final List<String> basePaths;
+ private Boolean mkdirs = false;
+
+ public SimpleNodeContentProvider(Session session, String... basePaths) {
+ this(session, Arrays.asList(basePaths));
+ }
+
+ public SimpleNodeContentProvider(Session session, List<String> basePaths) {
+ super(session);
+ this.basePaths = basePaths;
+ }
+
+ @Override
+ protected Boolean isBasePath(String path) {
+ if (basePaths.contains(path))
+ return true;
+ return super.isBasePath(path);
+ }
+
+ public Object[] getElements(Object inputElement) {
+ try {
+ List<Node> baseNodes = new ArrayList<Node>();
+ for (String basePath : basePaths)
+ if (mkdirs && !getSession().itemExists(basePath))
+ baseNodes.add(JcrUtils.mkdirs(getSession(), basePath));
+ else
+ baseNodes.add(getSession().getNode(basePath));
+ return baseNodes.toArray();
+ } catch (RepositoryException e) {
+ throw new EclipseUiException("Cannot get base nodes for " + basePaths,
+ e);
+ }
+ }
+
+ public List<String> getBasePaths() {
+ return basePaths;
+ }
+
+ public void setMkdirs(Boolean mkdirs) {
+ this.mkdirs = mkdirs;
+ }
+
+}
--- /dev/null
+package org.argeo.eclipse.ui.jcr;
+
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.RepositoryException;
+import javax.jcr.version.Version;
+
+import org.eclipse.jface.viewers.ColumnLabelProvider;
+import org.eclipse.swt.graphics.Image;
+
+/** Simplifies writing JCR-based column label provider. */
+public class VersionColumnLabelProvider extends ColumnLabelProvider {
+ private static final long serialVersionUID = -6117690082313161159L;
+
+ protected String getVersionText(Version version) throws RepositoryException {
+ return super.getText(version);
+ }
+
+ protected String getVersionToolTipText(Version version) throws RepositoryException {
+ return super.getToolTipText(version);
+ }
+
+ protected Image getVersionImage(Version version) throws RepositoryException {
+ return super.getImage(version);
+ }
+
+ protected String getUserName(Version version) throws RepositoryException {
+ Node node = version.getFrozenNode();
+ if(node.hasProperty(Property.JCR_LAST_MODIFIED_BY))
+ return node.getProperty(Property.JCR_LAST_MODIFIED_BY).getString();
+ if(node.hasProperty(Property.JCR_CREATED_BY))
+ return node.getProperty(Property.JCR_CREATED_BY).getString();
+ return null;
+ }
+
+// protected String getActivityTitle(Version version) throws RepositoryException {
+// Node activity = getActivity(version);
+// if (activity == null)
+// return null;
+// if (activity.hasProperty("jcr:activityTitle"))
+// return activity.getProperty("jcr:activityTitle").getString();
+// else
+// return activity.getName();
+// }
+//
+// protected Node getActivity(Version version) throws RepositoryException {
+// if (version.hasProperty(Property.JCR_ACTIVITY)) {
+// return version.getProperty(Property.JCR_ACTIVITY).getNode();
+// } else
+// return null;
+// }
+
+ @Override
+ public String getText(Object element) {
+ try {
+ return getVersionText((Version) element);
+ } catch (RepositoryException e) {
+ throw new RuntimeException("Runtime repository exception when accessing " + element, e);
+ }
+ }
+
+ @Override
+ public Image getImage(Object element) {
+ try {
+ return getVersionImage((Version) element);
+ } catch (RepositoryException e) {
+ throw new RuntimeException("Runtime repository exception when accessing " + element, e);
+ }
+ }
+
+ @Override
+ public String getToolTipText(Object element) {
+ try {
+ return getVersionToolTipText((Version) element);
+ } catch (RepositoryException e) {
+ throw new RuntimeException("Runtime repository exception when accessing " + element, e);
+ }
+ }
+
+}
--- /dev/null
+package org.argeo.eclipse.ui.jcr;
+
+import javax.jcr.version.VersionHistory;
+
+import org.argeo.jcr.Jcr;
+import org.eclipse.jface.viewers.IStructuredContentProvider;
+import org.eclipse.jface.viewers.Viewer;
+
+/** Content provider based on a {@link VersionHistory}. */
+public class VersionHistoryContentProvider implements IStructuredContentProvider {
+ private static final long serialVersionUID = -4921107883428887012L;
+
+ @Override
+ public void dispose() {
+ }
+
+ @Override
+ public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
+ }
+
+ @Override
+ public Object[] getElements(Object inputElement) {
+ VersionHistory versionHistory = (VersionHistory) inputElement;
+ return Jcr.getLinearVersions(versionHistory).toArray();
+ }
+
+}
--- /dev/null
+package org.argeo.eclipse.ui.jcr;
+
+import javax.jcr.Node;
+
+/** Wrap a node (created from a {@link NodesWrapper}) */
+public class WrappedNode {
+ private final NodesWrapper parent;
+ private final Node node;
+
+ public WrappedNode(NodesWrapper parent, Node node) {
+ super();
+ this.parent = parent;
+ this.node = node;
+ }
+
+ public NodesWrapper getParent() {
+ return parent;
+ }
+
+ public Node getNode() {
+ return node;
+ }
+
+ public String toString() {
+ return "wrapped " + node;
+ }
+
+ @Override
+ public int hashCode() {
+ return node.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof WrappedNode)
+ return node.equals(((WrappedNode) obj).getNode());
+ else
+ return false;
+ }
+
+}
--- /dev/null
+package org.argeo.eclipse.ui.jcr.lists;
+
+import javax.jcr.Node;
+import javax.jcr.query.Row;
+
+import org.argeo.eclipse.ui.ColumnDefinition;
+
+/**
+ * Utility object to manage column in various tables and extracts displaying
+ * data from JCR
+ */
+public class JcrColumnDefinition extends ColumnDefinition {
+ private final static int DEFAULT_COLUMN_SIZE = 120;
+
+ private String selectorName;
+ private String propertyName;
+ private int propertyType;
+ private int columnSize;
+
+ /**
+ * Use this kind of columns to configure a table that displays JCR
+ * {@link Row}
+ *
+ * @param selectorName
+ * @param propertyName
+ * @param propertyType
+ * @param headerLabel
+ */
+ public JcrColumnDefinition(String selectorName, String propertyName,
+ int propertyType, String headerLabel) {
+ super(new SimpleJcrRowLabelProvider(selectorName, propertyName),
+ headerLabel);
+ this.selectorName = selectorName;
+ this.propertyName = propertyName;
+ this.propertyType = propertyType;
+ this.columnSize = DEFAULT_COLUMN_SIZE;
+ }
+
+ /**
+ * Use this kind of columns to configure a table that displays JCR
+ * {@link Row}
+ *
+ * @param selectorName
+ * @param propertyName
+ * @param propertyType
+ * @param headerLabel
+ * @param columnSize
+ */
+ public JcrColumnDefinition(String selectorName, String propertyName,
+ int propertyType, String headerLabel, int columnSize) {
+ super(new SimpleJcrRowLabelProvider(selectorName, propertyName),
+ headerLabel, columnSize);
+ this.selectorName = selectorName;
+ this.propertyName = propertyName;
+ this.propertyType = propertyType;
+ this.columnSize = columnSize;
+ }
+
+ /**
+ * Use this kind of columns to configure a table that displays JCR
+ * {@link Node}
+ *
+ * @param propertyName
+ * @param propertyType
+ * @param headerLabel
+ * @param columnSize
+ */
+ public JcrColumnDefinition(String propertyName, int propertyType,
+ String headerLabel, int columnSize) {
+ super(new SimpleJcrNodeLabelProvider(propertyName), headerLabel,
+ columnSize);
+ this.propertyName = propertyName;
+ this.propertyType = propertyType;
+ this.columnSize = columnSize;
+ }
+
+ public String getSelectorName() {
+ return selectorName;
+ }
+
+ public void setSelectorName(String selectorName) {
+ this.selectorName = selectorName;
+ }
+
+ public String getPropertyName() {
+ return propertyName;
+ }
+
+ public void setPropertyName(String propertyName) {
+ this.propertyName = propertyName;
+ }
+
+ public int getPropertyType() {
+ return propertyType;
+ }
+
+ public void setPropertyType(int propertyType) {
+ this.propertyType = propertyType;
+ }
+
+ public int getColumnSize() {
+ return columnSize;
+ }
+
+ public void setColumnSize(int columnSize) {
+ this.columnSize = columnSize;
+ }
+
+ public String getHeaderLabel() {
+ return super.getLabel();
+ }
+
+ public void setHeaderLabel(String headerLabel) {
+ super.setLabel(headerLabel);
+ }
+}
--- /dev/null
+package org.argeo.eclipse.ui.jcr.lists;
+
+import java.math.BigDecimal;
+import java.util.Calendar;
+
+import javax.jcr.Node;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import javax.jcr.Value;
+import javax.jcr.ValueFormatException;
+
+import org.argeo.eclipse.ui.EclipseUiException;
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.jface.viewers.ViewerComparator;
+
+/**
+ * Base comparator to enable ordering on Table or Tree viewer that display Jcr
+ * Nodes.
+ *
+ * Note that the following snippet must be added before setting the comparator
+ * to the corresponding control: <code>
+ * // IMPORTANT: initialize comparator before setting it
+ * JcrColumnDefinition firstCol = colDefs.get(0);
+ * comparator.setColumn(firstCol.getPropertyType(),
+ * firstCol.getPropertyName());
+ * viewer.setComparator(comparator); </code>
+ */
+public class NodeViewerComparator extends ViewerComparator {
+ private static final long serialVersionUID = -7782916140737279027L;
+
+ protected String propertyName;
+
+ protected int propertyType;
+ public static final int ASCENDING = 0, DESCENDING = 1;
+ protected int direction = DESCENDING;
+
+ public NodeViewerComparator() {
+ }
+
+ /**
+ * e1 and e2 must both be Jcr nodes.
+ *
+ * @param viewer
+ * @param e1
+ * @param e2
+ * @return
+ */
+ @Override
+ public int compare(Viewer viewer, Object e1, Object e2) {
+ int rc = 0;
+ long lc = 0;
+
+ try {
+ Node n1 = (Node) e1;
+ Node n2 = (Node) e2;
+
+ Value v1 = null;
+ Value v2 = null;
+ if (n1.hasProperty(propertyName))
+ v1 = n1.getProperty(propertyName).getValue();
+ if (n2.hasProperty(propertyName))
+ v2 = n2.getProperty(propertyName).getValue();
+
+ if (v2 == null && v1 == null)
+ return 0;
+ else if (v2 == null)
+ return -1;
+ else if (v1 == null)
+ return 1;
+
+ switch (propertyType) {
+ case PropertyType.STRING:
+ rc = v1.getString().compareTo(v2.getString());
+ break;
+ case PropertyType.BOOLEAN:
+ boolean b1 = v1.getBoolean();
+ boolean b2 = v2.getBoolean();
+ if (b1 == b2)
+ rc = 0;
+ else
+ // we assume true is greater than false
+ rc = b1 ? 1 : -1;
+ break;
+ case PropertyType.DATE:
+ Calendar c1 = v1.getDate();
+ Calendar c2 = v2.getDate();
+ if (c1 == null || c2 == null)
+ // log.trace("undefined date");
+ ;
+ lc = c1.getTimeInMillis() - c2.getTimeInMillis();
+ if (lc < Integer.MIN_VALUE)
+ rc = -1;
+ else if (lc > Integer.MAX_VALUE)
+ rc = 1;
+ else
+ rc = (int) lc;
+ break;
+ case PropertyType.LONG:
+ long l1;
+ long l2;
+ // TODO Sometimes an empty string is set instead of a long
+ try {
+ l1 = v1.getLong();
+ } catch (ValueFormatException ve) {
+ l1 = 0;
+ }
+ try {
+ l2 = v2.getLong();
+ } catch (ValueFormatException ve) {
+ l2 = 0;
+ }
+
+ lc = l1 - l2;
+ if (lc < Integer.MIN_VALUE)
+ rc = -1;
+ else if (lc > Integer.MAX_VALUE)
+ rc = 1;
+ else
+ rc = (int) lc;
+ break;
+ case PropertyType.DECIMAL:
+ BigDecimal bd1 = v1.getDecimal();
+ BigDecimal bd2 = v2.getDecimal();
+ rc = bd1.compareTo(bd2);
+ break;
+ case PropertyType.DOUBLE:
+ Double d1 = v1.getDouble();
+ Double d2 = v2.getDouble();
+ rc = d1.compareTo(d2);
+ break;
+ default:
+ throw new EclipseUiException(
+ "Unimplemented comparaison for PropertyType "
+ + propertyType);
+ }
+ // If descending order, flip the direction
+ if (direction == DESCENDING) {
+ rc = -rc;
+ }
+
+ } catch (RepositoryException re) {
+ throw new EclipseUiException("Unexpected error "
+ + "while comparing nodes", re);
+ }
+ return rc;
+ }
+
+ /**
+ * @param propertyType
+ * Corresponding JCR type
+ * @param propertyName
+ * name of the property to use.
+ */
+ public void setColumn(int propertyType, String propertyName) {
+ if (this.propertyName != null && this.propertyName.equals(propertyName)) {
+ // Same column as last sort; toggle the direction
+ direction = 1 - direction;
+ } else {
+ // New column; do an ascending sort
+ this.propertyType = propertyType;
+ this.propertyName = propertyName;
+ direction = ASCENDING;
+ }
+ }
+
+ // Getters and setters
+ protected String getPropertyName() {
+ return propertyName;
+ }
+
+ protected void setPropertyName(String propertyName) {
+ this.propertyName = propertyName;
+ }
+
+ protected int getPropertyType() {
+ return propertyType;
+ }
+
+ protected void setPropertyType(int propertyType) {
+ this.propertyType = propertyType;
+ }
+
+ protected int getDirection() {
+ return direction;
+ }
+
+ protected void setDirection(int direction) {
+ this.direction = direction;
+ }
+}
--- /dev/null
+package org.argeo.eclipse.ui.jcr.lists;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.query.Row;
+
+import org.argeo.eclipse.ui.EclipseUiException;
+import org.eclipse.jface.viewers.Viewer;
+
+/**
+ * Base comparator to enable ordering on Table or Tree viewer that display Jcr
+ * rows
+ */
+public class RowViewerComparator extends NodeViewerComparator {
+ private static final long serialVersionUID = 7020939505172625113L;
+ protected String selectorName;
+
+ public RowViewerComparator() {
+ }
+
+ /**
+ * e1 and e2 must both be Jcr rows.
+ *
+ * @param viewer
+ * @param e1
+ * @param e2
+ * @return
+ */
+ @Override
+ public int compare(Viewer viewer, Object e1, Object e2) {
+ try {
+ Node n1 = ((Row) e1).getNode(selectorName);
+ Node n2 = ((Row) e2).getNode(selectorName);
+ return super.compare(viewer, n1, n2);
+ } catch (RepositoryException re) {
+ throw new EclipseUiException("Unexpected error "
+ + "while comparing nodes", re);
+ }
+ }
+
+ /**
+ * @param propertyType
+ * Corresponding JCR type
+ * @param propertyName
+ * name of the property to use.
+ */
+ public void setColumn(int propertyType, String selectorName,
+ String propertyName) {
+ if (this.selectorName != null && getPropertyName() != null
+ && this.selectorName.equals(selectorName)
+ && this.getPropertyName().equals(propertyName)) {
+ // Same column as last sort; toggle the direction
+ setDirection(1 - getDirection());
+ } else {
+ // New column; do a descending sort
+ setPropertyType(propertyType);
+ setPropertyName(propertyName);
+ this.selectorName = selectorName;
+ setDirection(NodeViewerComparator.ASCENDING);
+ }
+ }
+}
--- /dev/null
+package org.argeo.eclipse.ui.jcr.lists;
+
+import java.text.DateFormat;
+import java.text.DecimalFormat;
+import java.text.NumberFormat;
+import java.text.SimpleDateFormat;
+
+import javax.jcr.Node;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import javax.jcr.Value;
+
+import org.argeo.eclipse.ui.EclipseUiException;
+import org.eclipse.jface.viewers.ColumnLabelProvider;
+
+/** Base implementation of a label provider for controls that display JCR Nodes */
+public class SimpleJcrNodeLabelProvider extends ColumnLabelProvider {
+ private static final long serialVersionUID = -5215787695436221993L;
+
+ private final static String DEFAULT_DATE_FORMAT = "EEE, dd MMM yyyy";
+ private final static String DEFAULT_NUMBER_FORMAT = "#,##0.0";
+
+ private DateFormat dateFormat;
+ private NumberFormat numberFormat;
+
+ final private String propertyName;
+
+ /**
+ * Default Label provider for a given property of a node. Using default
+ * pattern for date and number formating
+ */
+ public SimpleJcrNodeLabelProvider(String propertyName) {
+ this.propertyName = propertyName;
+ dateFormat = new SimpleDateFormat(DEFAULT_DATE_FORMAT);
+ numberFormat = DecimalFormat.getInstance();
+ ((DecimalFormat) numberFormat).applyPattern(DEFAULT_NUMBER_FORMAT);
+ }
+
+ /**
+ * Label provider for a given property of a node optionally precising date
+ * and/or number format patterns
+ */
+ public SimpleJcrNodeLabelProvider(String propertyName,
+ String dateFormatPattern, String numberFormatPattern) {
+ this.propertyName = propertyName;
+ dateFormat = new SimpleDateFormat(
+ dateFormatPattern == null ? DEFAULT_DATE_FORMAT
+ : dateFormatPattern);
+ numberFormat = DecimalFormat.getInstance();
+ ((DecimalFormat) numberFormat)
+ .applyPattern(numberFormatPattern == null ? DEFAULT_NUMBER_FORMAT
+ : numberFormatPattern);
+ }
+
+ @Override
+ public String getText(Object element) {
+ try {
+ Node currNode = (Node) element;
+
+ if (currNode.hasProperty(propertyName)) {
+ if (currNode.getProperty(propertyName).isMultiple()) {
+ StringBuilder builder = new StringBuilder();
+ for (Value value : currNode.getProperty(propertyName)
+ .getValues()) {
+ String currStr = getSingleValueAsString(value);
+ if (notEmptyString(currStr))
+ builder.append(currStr).append("; ");
+ }
+ if (builder.length() > 0)
+ builder.deleteCharAt(builder.length() - 2);
+
+ return builder.toString();
+ } else
+ return getSingleValueAsString(currNode.getProperty(
+ propertyName).getValue());
+ } else
+ return "";
+ } catch (RepositoryException re) {
+ throw new EclipseUiException("Unable to get text from row", re);
+ }
+ }
+
+ private String getSingleValueAsString(Value value)
+ throws RepositoryException {
+ switch (value.getType()) {
+ case PropertyType.STRING:
+ return value.getString();
+ case PropertyType.BOOLEAN:
+ return "" + value.getBoolean();
+ case PropertyType.DATE:
+ return dateFormat.format(value.getDate().getTime());
+ case PropertyType.LONG:
+ return "" + value.getLong();
+ case PropertyType.DECIMAL:
+ return numberFormat.format(value.getDecimal());
+ case PropertyType.DOUBLE:
+ return numberFormat.format(value.getDouble());
+ case PropertyType.NAME:
+ return value.getString();
+ default:
+ throw new EclipseUiException("Unimplemented label provider "
+ + "for property type " + value.getType()
+ + " while getting property " + propertyName + " - value: "
+ + value.getString());
+
+ }
+ }
+
+ private boolean notEmptyString(String string) {
+ return string != null && !"".equals(string.trim());
+ }
+
+ public void setDateFormat(String dateFormatPattern) {
+ dateFormat = new SimpleDateFormat(dateFormatPattern);
+ }
+
+ public void setNumberFormat(String numberFormatPattern) {
+ ((DecimalFormat) numberFormat).applyPattern(numberFormatPattern);
+ }
+}
--- /dev/null
+package org.argeo.eclipse.ui.jcr.lists;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.query.Row;
+
+import org.argeo.eclipse.ui.EclipseUiException;
+
+/**
+ * Base implementation of a label provider for widgets that display JCR Rows.
+ */
+public class SimpleJcrRowLabelProvider extends SimpleJcrNodeLabelProvider {
+ private static final long serialVersionUID = -3414654948197181740L;
+
+ final private String selectorName;
+
+ /**
+ * Default Label provider for a given property of a row. Using default
+ * pattern for date and number formating
+ */
+ public SimpleJcrRowLabelProvider(String selectorName, String propertyName) {
+ super(propertyName);
+ this.selectorName = selectorName;
+ }
+
+ /**
+ * Label provider for a given property of a node optionally precising date
+ * and/or number format patterns
+ */
+ public SimpleJcrRowLabelProvider(String selectorName, String propertyName,
+ String dateFormatPattern, String numberFormatPattern) {
+ super(propertyName, dateFormatPattern, numberFormatPattern);
+ this.selectorName = selectorName;
+ }
+
+ @Override
+ public String getText(Object element) {
+ try {
+ Row currRow = (Row) element;
+ Node currNode = currRow.getNode(selectorName);
+ return super.getText(currNode);
+ } catch (RepositoryException re) {
+ throw new EclipseUiException("Unable to get Node " + selectorName
+ + " from row " + element, re);
+ }
+ }
+}
--- /dev/null
+/** Generic SWT/JFace JCR utilities for lists. */
+package org.argeo.eclipse.ui.jcr.lists;
\ No newline at end of file
--- /dev/null
+/** Generic SWT/JFace JCR utilities. */
+package org.argeo.eclipse.ui.jcr;
\ No newline at end of file
--- /dev/null
+package org.argeo.eclipse.ui.jcr.util;
+
+import java.io.InputStream;
+
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.RepositoryException;
+import javax.jcr.nodetype.NodeType;
+
+import org.apache.commons.io.IOUtils;
+import org.argeo.eclipse.ui.EclipseUiException;
+import org.argeo.eclipse.ui.FileProvider;
+
+/**
+ * Implements a FileProvider for UI purposes. Note that it might not be very
+ * reliable as long as we have not fixed login and multi repository issues that
+ * will be addressed in the next version.
+ *
+ * NOTE: id used here is the real id of the JCR Node, not the JCR Path
+ *
+ * Relies on common approach for JCR file handling implementation.
+ *
+ */
+@SuppressWarnings("deprecation")
+public class JcrFileProvider implements FileProvider {
+
+ // private Object[] rootNodes;
+ private Node refNode;
+
+ /**
+ * Must be set in order for the provider to be able to get current session
+ * and thus have the ability to get the file node corresponding to a given
+ * file ID
+ *
+ * @param refNode
+ */
+ public void setReferenceNode(Node refNode) {
+ // FIXME : this introduces some concurrency ISSUES.
+ this.refNode = refNode;
+ }
+
+ public byte[] getByteArrayFileFromId(String fileId) {
+ InputStream fis = null;
+ byte[] ba = null;
+ Node child = getFileNodeFromId(fileId);
+ try {
+ fis = (InputStream) child.getProperty(Property.JCR_DATA).getBinary().getStream();
+ ba = IOUtils.toByteArray(fis);
+
+ } catch (Exception e) {
+ throw new EclipseUiException("Stream error while opening file", e);
+ } finally {
+ IOUtils.closeQuietly(fis);
+ }
+ return ba;
+ }
+
+ public InputStream getInputStreamFromFileId(String fileId) {
+ try {
+ InputStream fis = null;
+
+ Node child = getFileNodeFromId(fileId);
+ fis = (InputStream) child.getProperty(Property.JCR_DATA).getBinary().getStream();
+ return fis;
+ } catch (RepositoryException re) {
+ throw new EclipseUiException("Cannot get stream from file node for Id " + fileId, re);
+ }
+ }
+
+ /**
+ * Throws an exception if the node is not found in the current repository (a
+ * bit like a FileNotFoundException)
+ *
+ * @param fileId
+ * @return Returns the child node of the nt:file node. It is the child node
+ * that have the jcr:data property where actual file is stored.
+ * never null
+ */
+ private Node getFileNodeFromId(String fileId) {
+ try {
+ Node result = refNode.getSession().getNodeByIdentifier(fileId);
+
+ // rootNodes: for (int j = 0; j < rootNodes.length; j++) {
+ // // in case we have a classic JCR Node
+ // if (rootNodes[j] instanceof Node) {
+ // Node curNode = (Node) rootNodes[j];
+ // if (result != null)
+ // break rootNodes;
+ // } // Case of a repository Node
+ // else if (rootNodes[j] instanceof RepositoryNode) {
+ // Object[] nodes = ((RepositoryNode) rootNodes[j])
+ // .getChildren();
+ // for (int i = 0; i < nodes.length; i++) {
+ // Node node = (Node) nodes[i];
+ // result = node.getSession().getNodeByIdentifier(fileId);
+ // if (result != null)
+ // break rootNodes;
+ // }
+ // }
+ // }
+
+ // Sanity checks
+ if (result == null)
+ throw new EclipseUiException("File node not found for ID" + fileId);
+
+ Node child = null;
+
+ boolean isValid = true;
+ if (!result.isNodeType(NodeType.NT_FILE))
+ // useless: mandatory child node
+ // || !result.hasNode(Property.JCR_CONTENT))
+ isValid = false;
+ else {
+ child = result.getNode(Property.JCR_CONTENT);
+ if (!(child.isNodeType(NodeType.NT_RESOURCE) || child.hasProperty(Property.JCR_DATA)))
+ isValid = false;
+ }
+
+ if (!isValid)
+ throw new EclipseUiException("ERROR: In the current implemented model, '" + NodeType.NT_FILE
+ + "' file node must have a child node named jcr:content "
+ + "that has a BINARY Property named jcr:data " + "where the actual data is stored");
+ return child;
+
+ } catch (RepositoryException re) {
+ throw new EclipseUiException("Erreur while getting file node of ID " + fileId, re);
+ }
+ }
+}
--- /dev/null
+package org.argeo.eclipse.ui.jcr.util;
+
+import java.util.Comparator;
+
+import javax.jcr.Item;
+import javax.jcr.RepositoryException;
+
+import org.argeo.eclipse.ui.EclipseUiException;
+
+/** Compares two JCR items (node or properties) based on their names. */
+public class JcrItemsComparator implements Comparator<Item> {
+ public int compare(Item o1, Item o2) {
+ try {
+ // TODO: put folder before files
+ return o1.getName().toLowerCase().compareTo(o2.getName().toLowerCase());
+ } catch (RepositoryException e) {
+ throw new EclipseUiException("Cannot compare " + o1 + " and " + o2, e);
+ }
+ }
+
+}
--- /dev/null
+package org.argeo.eclipse.ui.jcr.util;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+
+import org.argeo.eclipse.ui.EclipseUiException;
+import org.eclipse.jface.viewers.IElementComparer;
+
+/** Compare JCR nodes based on their JCR identifiers, for use in JFace viewers. */
+public class NodeViewerComparer implements IElementComparer {
+
+ // force comparison on Node IDs only.
+ public boolean equals(Object elementA, Object elementB) {
+ if (!(elementA instanceof Node) || !(elementB instanceof Node)) {
+ return elementA == null ? elementB == null : elementA
+ .equals(elementB);
+ } else {
+
+ boolean result = false;
+ try {
+ String idA = ((Node) elementA).getIdentifier();
+ String idB = ((Node) elementB).getIdentifier();
+ result = idA == null ? idB == null : idA.equals(idB);
+ } catch (RepositoryException re) {
+ throw new EclipseUiException("cannot compare nodes", re);
+ }
+
+ return result;
+ }
+ }
+
+ public int hashCode(Object element) {
+ // TODO enhanced this method.
+ return element.getClass().toString().hashCode();
+ }
+}
\ No newline at end of file
--- /dev/null
+package org.argeo.eclipse.ui.jcr.util;
+
+import java.io.InputStream;
+
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.nodetype.NodeType;
+
+import org.apache.commons.io.IOUtils;
+import org.argeo.eclipse.ui.EclipseUiException;
+import org.argeo.eclipse.ui.FileProvider;
+
+/**
+ * Implements a FileProvider for UI purposes. Unlike the
+ * <code> JcrFileProvider </code>, it relies on a single session and manages
+ * nodes with path only.
+ *
+ * Note that considered id is the JCR path
+ *
+ * Relies on common approach for JCR file handling implementation.
+ */
+@SuppressWarnings("deprecation")
+public class SingleSessionFileProvider implements FileProvider {
+
+ private Session session;
+
+ public SingleSessionFileProvider(Session session) {
+ this.session = session;
+ }
+
+ public byte[] getByteArrayFileFromId(String fileId) {
+ InputStream fis = null;
+ byte[] ba = null;
+ Node child = getFileNodeFromId(fileId);
+ try {
+ fis = (InputStream) child.getProperty(Property.JCR_DATA)
+ .getBinary().getStream();
+ ba = IOUtils.toByteArray(fis);
+
+ } catch (Exception e) {
+ throw new EclipseUiException("Stream error while opening file", e);
+ } finally {
+ IOUtils.closeQuietly(fis);
+ }
+ return ba;
+ }
+
+ public InputStream getInputStreamFromFileId(String fileId) {
+ try {
+ InputStream fis = null;
+
+ Node child = getFileNodeFromId(fileId);
+ fis = (InputStream) child.getProperty(Property.JCR_DATA)
+ .getBinary().getStream();
+ return fis;
+ } catch (RepositoryException re) {
+ throw new EclipseUiException("Cannot get stream from file node for Id "
+ + fileId, re);
+ }
+ }
+
+ /**
+ *
+ * @param fileId
+ * @return Returns the child node of the nt:file node. It is the child node
+ * that have the jcr:data property where actual file is stored.
+ * never null
+ */
+ private Node getFileNodeFromId(String fileId) {
+ try {
+ Node result = null;
+ result = session.getNode(fileId);
+
+ // Sanity checks
+ if (result == null)
+ throw new EclipseUiException("File node not found for ID" + fileId);
+
+ // Ensure that the node have the correct type.
+ if (!result.isNodeType(NodeType.NT_FILE))
+ throw new EclipseUiException(
+ "Cannot open file children Node that are not of "
+ + NodeType.NT_RESOURCE + " type.");
+
+ Node child = result.getNodes().nextNode();
+ if (child == null || !child.isNodeType(NodeType.NT_RESOURCE))
+ throw new EclipseUiException(
+ "ERROR: IN the current implemented model, "
+ + NodeType.NT_FILE
+ + " file node must have one and only one child of the nt:ressource, where actual data is stored");
+ return child;
+ } catch (RepositoryException re) {
+ throw new EclipseUiException("Erreur while getting file node of ID "
+ + fileId, re);
+ }
+ }
+}
--- /dev/null
+/** Generic SWT/JFace JCR helpers. */
+package org.argeo.eclipse.ui.jcr.util;
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.argeo.commons</groupId>
+ <artifactId>argeo-commons</artifactId>
+ <version>2.3-SNAPSHOT</version>
+ <relativePath>..</relativePath>
+ </parent>
+ <groupId>org.argeo.commons</groupId>
+ <artifactId>jcr</artifactId>
+ <name>JCR</name>
+ <packaging>pom</packaging>
+ <modules>
+ <module>org.argeo.cms.jcr</module>
+ <module>org.argeo.cms.ui</module>
+ </modules>
+</project>
\ 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-11"/>
- <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.cms.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
-/MANIFEST.MF
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8"?>
-<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="Default CallbackHandler">
- <implementation class="org.argeo.cms.swt.auth.DynamicCallbackHandler"/>
- <service>
- <provide interface="javax.security.auth.callback.CallbackHandler"/>
- </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=home)"/>
- <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.osgi.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
-Service-Component: OSGI-INF/homeRepository.xml,\
-OSGI-INF/userAdminWrapper.xml,\
-OSGI-INF/defaultCallbackHandler.xml
-Bundle-ActivationPolicy: lazy
-
-Import-Package: org.eclipse.swt,\
-org.eclipse.swt.widgets;version="0.0.0",\
-org.eclipse.e4.ui.model.application.ui,\
-org.eclipse.e4.ui.model.application,\
-javax.jcr.nodetype,\
-org.argeo.cms,\
-org.eclipse.core.commands.common,\
-org.eclipse.jface.window,\
-org.argeo.cms.swt.auth,\
-*
+++ /dev/null
-output.. = bin/
-bin.includes = META-INF/,\
- OSGI-INF/,\
- .,\
- OSGI-INF/homeRepository.xml,\
- OSGI-INF/userAdminWrapper.xml,\
- OSGI-INF/defaultCallbackHandler.xml,\
- e4xmi/cms-demo.e4xmi
-source.. = src/
+++ /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.cms.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.cms.e4/org.argeo.cms.e4.users.UsersView" label="Users" iconURI="platform:/plugin/org.argeo.cms.swt/icons/person.png">
- <handlers xmi:id="_0mN68DvjEeiF1foPJZSZkw" elementId="org.argeo.cms.e4.handler.4" contributionURI="bundleclass://org.argeo.cms.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.cms.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.cms.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.cms.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.cms.e4/org.argeo.cms.e4.users.GroupsView" label="Groups" iconURI="platform:/plugin/org.argeo.cms.swt/icons/group.png">
- <handlers xmi:id="_cmShoDvkEeiF1foPJZSZkw" elementId="org.argeo.cms.e4.handler.6" contributionURI="bundleclass://org.argeo.cms.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.cms.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.cms.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.cms.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.cms.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.cms.e4/org.argeo.cms.e4.jcr.JcrBrowserView" label="JCR" iconURI="platform:/plugin/org.argeo.cms.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.cms.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.cms.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.cms.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.cms.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.cms.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.cms.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.cms.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.cms.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.cms.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.cms.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.cms.e4/org.argeo.cms.e4.monitoring.OsgiConfigurationsView" label="OSGi Configurations" iconURI="platform:/plugin/org.argeo.cms.swt/icons/node.gif"/>
- <children xsi:type="basic:Part" xmi:id="_8dM90FhJEeiknZQLx-vtnA" elementId="org.argeo.cms.e4.part.cmsSessions" contributionURI="bundleclass://org.argeo.cms.e4/org.argeo.cms.e4.monitoring.CmsSessionsView" label="CMS Sessions" iconURI="platform:/plugin/org.argeo.cms.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.cms.e4/org.argeo.cms.e4.monitoring.ModulesView" label="Modules" iconURI="platform:/plugin/org.argeo.cms.swt/icons/bundles.gif"/>
- <children xsi:type="basic:Part" xmi:id="_dXtIoFhNEeiknZQLx-vtnA" elementId="org.argeo.cms.e4.part.bundles" contributionURI="bundleclass://org.argeo.cms.e4/org.argeo.cms.e4.monitoring.BundlesView" label="Bundles" iconURI="platform:/plugin/org.argeo.cms.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.cms.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.cms.e4/org.argeo.cms.e4.files.NodeFsBrowserView" label="Files" iconURI="platform:/plugin/org.argeo.cms.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.cms.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.cms.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.cms.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.cms.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.cms.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.cms.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.cms.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.cms.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.cms.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.cms.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.cms.swt/icons/node.gif" allowMultiple="true" category="dataExplorer" closeable="true" contributionURI="bundleclass://org.argeo.cms.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.cms.swt/icons/person.png" allowMultiple="true" category="usersEditorArea" closeable="true" dirtyable="true" contributionURI="bundleclass://org.argeo.cms.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.cms.swt/icons/group.png" allowMultiple="true" category="usersEditorArea" closeable="true" dirtyable="true" contributionURI="bundleclass://org.argeo.cms.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>
+++ /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: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=user,ou=roles,ou=node</tags>
- <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.cms.e4/org.argeo.cms.e4.jcr.JcrBrowserView" label="JCR" iconURI="platform:/plugin/org.argeo.cms.ui.theme/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.cms.ui.theme/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.cms.ui.theme/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.cms.ui.theme/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.cms.ui.theme/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.cms.ui.theme/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.cms.ui.theme/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.cms.ui.theme/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.cms.ui.theme/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.cms.ui.theme/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 xsi:type="basic:Part" xmi:id="_LyT80MKKEeqaPNgZ5fEKYw" elementId="org.argeo.cms.e4.part.egoDashboard" contributionURI="bundleclass://org.argeo.cms.e4/org.argeo.cms.e4.parts.EgoDashboard" label="" iconURI="platform:/plugin/org.argeo.cms.ui.theme/icons/home.png">
- <toolbar xmi:id="_Ut8wMMKMEeqaPNgZ5fEKYw" elementId="org.argeo.cms.e4.toolbar.0">
- <children xsi:type="menu:HandledToolItem" xmi:id="_nElwUMKMEeq1Ytjq4ALs6g" elementId="org.argeo.cms.e4.handledtoolitem.changepassword" label="Change password" iconURI="platform:/plugin/org.argeo.cms.ui.theme/icons/actions/edit.png" command="_jEjCUMKMEeq1Ytjq4ALs6g"/>
- <children xsi:type="menu:HandledToolItem" xmi:id="_WAD4UMKMEeqaPNgZ5fEKYw" elementId="org.argeo.cms.e4.handledtoolitem.logout" label="Log out" iconURI="platform:/plugin/org.argeo.cms.ui.theme/icons/logout.png" command="_PsWd0FhLEeiknZQLx-vtnA"/>
- </toolbar>
- </children>
- </children>
- </children>
- </children>
- <handlers xmi:id="_Xp-P4CqREeidr6NYQH6GbQ" elementId="org.argeo.cms.e4.handler.0" contributionURI="bundleclass://org.argeo.cms.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.cms.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.cms.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.cms.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.cms.e4/org.argeo.cms.e4.jcr.handlers.AddRemoteRepository" command="_ZWpasFgQEeiknZQLx-vtnA"/>
- <handlers xmi:id="_RQyFAFhLEeiknZQLx-vtnA" elementId="org.argeo.cms.e4.handler.logout" contributionURI="bundleclass://org.argeo.cms.e4/org.argeo.cms.e4.handlers.CloseWorkbench" command="_PsWd0FhLEeiknZQLx-vtnA"/>
- <handlers xmi:id="_lN4GUMKMEeq1Ytjq4ALs6g" elementId="org.argeo.suite.e4.handler.changePassword" contributionURI="bundleclass://org.argeo.cms.e4/org.argeo.cms.e4.handlers.ChangePassword" command="_jEjCUMKMEeq1Ytjq4ALs6g"/>
- <descriptors xmi:id="_XzfoMCqlEeidr6NYQH6GbQ" elementId="org.argeo.cms.e4.partdescriptor.nodeEditor" label="Node Editor" iconURI="platform:/plugin/org.argeo.cms.ui.theme/icons/node.gif" allowMultiple="true" category="dataExplorer" closeable="true" contributionURI="bundleclass://org.argeo.cms.e4/org.argeo.cms.e4.jcr.JcrNodeEditor"/>
- <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"/>
- <commands xmi:id="_jEjCUMKMEeq1Ytjq4ALs6g" elementId="org.argeo.cms.e4.command.changePassword" commandName="Change Password"/>
- <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>
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
- <modelVersion>4.0.0</modelVersion>
- <parent>
- <groupId>org.argeo.commons</groupId>
- <artifactId>argeo-commons</artifactId>
- <version>2.3-SNAPSHOT</version>
- <relativePath>..</relativePath>
- </parent>
- <artifactId>org.argeo.cms.e4</artifactId>
- <name>CMS E4</name>
- <packaging>jar</packaging>
- <dependencies>
- <dependency>
- <groupId>org.argeo.commons</groupId>
- <artifactId>org.argeo.cms.ui</artifactId>
- <version>2.3-SNAPSHOT</version>
- </dependency>
-
- <!-- UI -->
- <dependency>
- <groupId>org.argeo.commons.rap</groupId>
- <artifactId>org.argeo.swt.specific.rap</artifactId>
- <version>2.3-SNAPSHOT</version>
- <scope>provided</scope>
- </dependency>
- <dependency>
- <groupId>org.argeo.tp</groupId>
- <artifactId>argeo-tp-rap-e4</artifactId>
- <version>${version.argeo-tp}</version>
- <type>pom</type>
- <scope>provided</scope>
- </dependency>
- </dependencies>
-</project>
\ No newline at end of file
+++ /dev/null
-package org.argeo.cms.e4;
-
-import java.util.List;
-
-import org.argeo.cms.CmsException;
-import org.eclipse.e4.ui.model.application.MApplication;
-import org.eclipse.e4.ui.model.application.commands.MCommand;
-import org.eclipse.e4.ui.model.application.ui.basic.MPart;
-import org.eclipse.e4.ui.model.application.ui.menu.MDirectMenuItem;
-import org.eclipse.e4.ui.model.application.ui.menu.MHandledMenuItem;
-import org.eclipse.e4.ui.workbench.modeling.EModelService;
-import org.eclipse.e4.ui.workbench.modeling.EPartService;
-import org.eclipse.e4.ui.workbench.modeling.EPartService.PartState;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.FrameworkUtil;
-
-/** Static utilities simplifying recurring Eclipse 4 patterns. */
-public class CmsE4Utils {
- /** Open an editor based on its id. */
- public static void openEditor(EPartService partService, String editorId, String key, String state) {
- for (MPart part : partService.getParts()) {
- String id = part.getPersistedState().get(key);
- if (id != null && state.equals(id)) {
- partService.showPart(part, PartState.ACTIVATE);
- return;
- }
- }
-
- // new part
- MPart part = partService.createPart(editorId);
- if (part == null)
- throw new CmsException("No editor found with id " + editorId);
- part.getPersistedState().put(key, state);
- partService.showPart(part, PartState.ACTIVATE);
- }
-
- /** Dynamically creates an handled menu item from a command ID. */
- public static MHandledMenuItem createHandledMenuItem(EModelService modelService, MApplication app,
- String commandId) {
- MCommand command = findCommand(modelService, app, commandId);
- if (command == null)
- return null;
- MHandledMenuItem handledItem = modelService.createModelElement(MHandledMenuItem.class);
- handledItem.setCommand(command);
- return handledItem;
-
- }
-
- /**
- * Finds a command by ID.
- *
- * @return the {@link MCommand} or <code>null</code> if not found.
- */
- public static MCommand findCommand(EModelService modelService, MApplication app, String commandId) {
- List<MCommand> cmds = modelService.findElements(app, null, MCommand.class, null);
- for (MCommand cmd : cmds) {
- if (cmd.getElementId().equals(commandId)) {
- return cmd;
- }
- }
- return null;
- }
-
- /** Dynamically creates a direct menu item from a class. */
- public static MDirectMenuItem createDirectMenuItem(EModelService modelService, Class<?> clss, String label) {
- MDirectMenuItem dynamicItem = modelService.createModelElement(MDirectMenuItem.class);
- dynamicItem.setLabel(label);
- Bundle bundle = FrameworkUtil.getBundle(clss);
- dynamicItem.setContributionURI("bundleclass://" + bundle.getSymbolicName() + "/" + clss.getName());
- return dynamicItem;
- }
-
- /** Singleton. */
- private CmsE4Utils() {
- }
-
-}
+++ /dev/null
-package org.argeo.cms.e4;
-
-import org.argeo.cms.CmsException;
-import org.eclipse.e4.core.contexts.ContextFunction;
-import org.eclipse.e4.core.contexts.IEclipseContext;
-import org.eclipse.e4.core.di.IInjector;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.FrameworkUtil;
-import org.osgi.framework.InvalidSyntaxException;
-import org.osgi.framework.ServiceReference;
-
-/** An Eclipse 4 {@link ContextFunction} based on an OSGi filter. */
-public class OsgiFilterContextFunction extends ContextFunction {
-
- private BundleContext bc = FrameworkUtil.getBundle(OsgiFilterContextFunction.class).getBundleContext();
-
- @Override
- public Object compute(IEclipseContext context, String contextKey) {
- ServiceReference<?>[] srs;
- try {
- srs = bc.getServiceReferences((String) null, contextKey);
- } catch (InvalidSyntaxException e) {
- throw new CmsException("Context key " + contextKey + " must be a valid osgi filter", e);
- }
- if (srs == null || srs.length == 0) {
- return IInjector.NOT_A_VALUE;
- } else {
- // return the first one
- return bc.getService(srs[0]);
- }
- }
-
-}
+++ /dev/null
-package org.argeo.cms.e4;
-
-import java.security.AccessControlContext;
-import java.security.AccessController;
-import java.security.PrivilegedAction;
-
-import javax.security.auth.Subject;
-
-import org.eclipse.core.runtime.IProgressMonitor;
-import org.eclipse.core.runtime.IStatus;
-import org.eclipse.core.runtime.jobs.Job;
-
-/**
- * Propagate authentication to an eclipse job. Typically to execute a privileged
- * action outside the UI thread
- */
-public abstract class PrivilegedJob extends Job {
- private final Subject subject;
-
- public PrivilegedJob(String jobName) {
- this(jobName, AccessController.getContext());
- }
-
- public PrivilegedJob(String jobName,
- AccessControlContext accessControlContext) {
- super(jobName);
- subject = Subject.getSubject(accessControlContext);
-
- // Must be called *before* the job is scheduled,
- // it is required for the progress window to appear
- setUser(true);
- }
-
- @Override
- protected IStatus run(final IProgressMonitor progressMonitor) {
- PrivilegedAction<IStatus> privilegedAction = new PrivilegedAction<IStatus>() {
- public IStatus run() {
- return doRun(progressMonitor);
- }
- };
- return Subject.doAs(subject, privilegedAction);
- }
-
- /**
- * Implement here what should be executed with default context
- * authentication
- */
- protected abstract IStatus doRun(IProgressMonitor progressMonitor);
-}
+++ /dev/null
-package org.argeo.cms.e4.addons;
-
-import java.security.AccessController;
-import java.util.Iterator;
-
-import javax.annotation.PostConstruct;
-import javax.security.auth.Subject;
-import javax.servlet.http.HttpServletRequest;
-
-import org.argeo.api.cms.CmsLog;
-import org.argeo.cms.CmsException;
-import org.argeo.cms.auth.CurrentUser;
-import org.eclipse.e4.ui.model.application.MApplication;
-import org.eclipse.e4.ui.model.application.ui.MElementContainer;
-import org.eclipse.e4.ui.model.application.ui.MUIElement;
-import org.eclipse.e4.ui.model.application.ui.basic.MTrimBar;
-import org.eclipse.e4.ui.model.application.ui.basic.MTrimmedWindow;
-import org.eclipse.e4.ui.model.application.ui.basic.MWindow;
-
-public class AuthAddon {
- private final static CmsLog log = CmsLog.getLog(AuthAddon.class);
-
- public final static String AUTH = "auth.";
-
- @PostConstruct
- void init(MApplication application) {
- Iterator<MWindow> windows = application.getChildren().iterator();
- boolean atLeastOneTopLevelWindowVisible = false;
- windows: while (windows.hasNext()) {
- MWindow window = windows.next();
- // main window
- boolean windowVisible = process(window);
- if (!windowVisible) {
-// windows.remove();
- continue windows;
- }
- atLeastOneTopLevelWindowVisible = true;
- // trim bars
- if (window instanceof MTrimmedWindow) {
- Iterator<MTrimBar> trimBars = ((MTrimmedWindow) window).getTrimBars().iterator();
- while (trimBars.hasNext()) {
- MTrimBar trimBar = trimBars.next();
- if (!process(trimBar)) {
- trimBars.remove();
- }
- }
- }
- }
-
- if (!atLeastOneTopLevelWindowVisible) {
- log.warn("No top-level window is authorized for user " + CurrentUser.getUsername() + ", logging out..");
- logout();
- }
- }
-
- protected boolean process(MUIElement element) {
- for (String tag : element.getTags()) {
- if (tag.startsWith(AUTH)) {
- String role = tag.substring(AUTH.length(), tag.length());
- if (!CurrentUser.isInRole(role)) {
- element.setVisible(false);
- element.setToBeRendered(false);
- return false;
- }
- }
- }
-
- // children
- if (element instanceof MElementContainer) {
- @SuppressWarnings("unchecked")
- MElementContainer<? extends MUIElement> container = (MElementContainer<? extends MUIElement>) element;
- Iterator<? extends MUIElement> children = container.getChildren().iterator();
- while (children.hasNext()) {
- MUIElement child = children.next();
- boolean visible = process(child);
- if (!visible)
- children.remove();
- }
-
- for (Object child : container.getChildren()) {
- if (child instanceof MUIElement) {
- boolean visible = process((MUIElement) child);
- if (!visible)
- container.getChildren().remove(child);
- }
- }
- }
-
- return true;
- }
-
- protected void logout() {
- Subject subject = Subject.getSubject(AccessController.getContext());
- try {
- CurrentUser.logoutCmsSession(subject);
- } catch (Exception e) {
- throw new CmsException("Cannot log out", e);
- }
- HttpServletRequest request = org.argeo.eclipse.ui.specific.UiContext.getHttpRequest();
- if (request != null)
- request.getSession().setMaxInactiveInterval(0);
- }
-
-}
+++ /dev/null
-package org.argeo.cms.e4.addons;
-
-import java.security.AccessController;
-import java.util.List;
-import java.util.Locale;
-import java.util.Set;
-
-import javax.annotation.PostConstruct;
-import javax.security.auth.Subject;
-
-import org.argeo.eclipse.ui.specific.UiContext;
-import org.eclipse.e4.core.services.nls.ILocaleChangeService;
-import org.eclipse.e4.ui.model.application.MApplication;
-import org.eclipse.e4.ui.model.application.ui.basic.MWindow;
-import org.eclipse.e4.ui.workbench.modeling.EModelService;
-import org.eclipse.e4.ui.workbench.modeling.ElementMatcher;
-import org.eclipse.swt.SWT;
-
-/** Integrate workbench with the locale provided at log in. */
-public class LocaleAddon {
- private final static String STYLE_OVERRIDE = "styleOverride";
-
- // Right to left languages
- private final static String ARABIC = "ar";
- private final static String HEBREW = "he";
-
- @PostConstruct
- public void init(ILocaleChangeService localeChangeService, EModelService modelService, MApplication application) {
- Subject subject = Subject.getSubject(AccessController.getContext());
- Set<Locale> locales = subject.getPublicCredentials(Locale.class);
- if (!locales.isEmpty()) {
- Locale locale = locales.iterator().next();
- localeChangeService.changeApplicationLocale(locale);
- UiContext.setLocale(locale);
-
- if (locale.getLanguage().equals(ARABIC) || locale.getLanguage().equals(HEBREW)) {
- List<MWindow> windows = modelService.findElements(application, MWindow.class, EModelService.ANYWHERE,
- new ElementMatcher(null, null, (String) null));
- for (MWindow window : windows) {
- String currentStyle = window.getPersistedState().get(STYLE_OVERRIDE);
- int style = 0;
- if (currentStyle != null) {
- style = Integer.parseInt(currentStyle);
- }
- style = style | SWT.RIGHT_TO_LEFT;
- window.getPersistedState().put(STYLE_OVERRIDE, Integer.toString(style));
- }
- }
- }
- }
-}
+++ /dev/null
-/** Eclipse 4 addons to integrate with Argeo CMS. */
-package org.argeo.cms.e4.addons;
\ 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.cms.CmsException;
-import org.argeo.cms.jcr.CmsJcrUtils;
-import org.argeo.eclipse.ui.fs.AdvancedFsBrowser;
-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 CmsException("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.handlers;
-
-import java.util.Locale;
-
-import org.eclipse.e4.core.di.annotations.Execute;
-import org.eclipse.e4.core.services.nls.ILocaleChangeService;
-
-public class ChangeLanguage {
- @Execute
- public void execute(ILocaleChangeService localeChangeService) {
- localeChangeService.changeApplicationLocale(Locale.FRENCH);
- }
-}
+++ /dev/null
-package org.argeo.cms.e4.handlers;
-
-import static org.argeo.cms.CmsMsg.changePassword;
-import static org.argeo.cms.CmsMsg.currentPassword;
-import static org.argeo.cms.CmsMsg.newPassword;
-import static org.argeo.cms.CmsMsg.passwordChanged;
-import static org.argeo.cms.CmsMsg.repeatNewPassword;
-
-import java.util.Arrays;
-
-import javax.inject.Inject;
-import javax.naming.InvalidNameException;
-import javax.naming.ldap.LdapName;
-
-import org.argeo.cms.CmsException;
-import org.argeo.cms.auth.CurrentUser;
-import org.argeo.cms.security.CryptoKeyring;
-import org.argeo.cms.swt.dialogs.CmsMessageDialog;
-import org.argeo.eclipse.ui.dialogs.ErrorFeedback;
-import org.argeo.osgi.transaction.WorkTransaction;
-import org.eclipse.e4.core.di.annotations.Execute;
-import org.eclipse.e4.core.di.annotations.Optional;
-import org.eclipse.jface.dialogs.Dialog;
-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.Display;
-import org.eclipse.swt.widgets.Label;
-import org.eclipse.swt.widgets.Shell;
-import org.eclipse.swt.widgets.Text;
-import org.osgi.service.useradmin.User;
-import org.osgi.service.useradmin.UserAdmin;
-
-/** Change the password of the logged-in user. */
-public class ChangePassword {
- @Inject
- private UserAdmin userAdmin;
- @Inject
- private WorkTransaction userTransaction;
- @Inject
- @Optional
- private CryptoKeyring keyring = null;
-
- @Execute
- public void execute() {
- ChangePasswordDialog dialog = new ChangePasswordDialog(Display.getCurrent().getActiveShell(), userAdmin);
- if (dialog.open() == Dialog.OK) {
- new CmsMessageDialog(Display.getCurrent().getActiveShell(), passwordChanged.lead(),
- CmsMessageDialog.INFORMATION).open();
- }
- }
-
- protected void changePassword(char[] oldPassword, char[] newPassword) {
- String name = CurrentUser.getUsername();
- LdapName dn;
- try {
- dn = new LdapName(name);
- } catch (InvalidNameException e) {
- throw new CmsException("Invalid user dn " + name, e);
- }
- User user = (User) userAdmin.getRole(dn.toString());
- if (!user.hasCredential(null, oldPassword))
- throw new CmsException("Invalid password");
- if (Arrays.equals(newPassword, new char[0]))
- throw new CmsException("New password empty");
- try {
- userTransaction.begin();
- user.getCredentials().put(null, newPassword);
- if (keyring != null) {
- keyring.changePassword(oldPassword, newPassword);
- // TODO change secret keys in the CMS session
- }
- userTransaction.commit();
- } catch (Exception e) {
- try {
- userTransaction.rollback();
- } catch (Exception e1) {
- e1.printStackTrace();
- }
- if (e instanceof RuntimeException)
- throw (RuntimeException) e;
- else
- throw new CmsException("Cannot change password", e);
- }
- }
-
- class ChangePasswordDialog extends CmsMessageDialog {
- private Text oldPassword, newPassword1, newPassword2;
-
- public ChangePasswordDialog(Shell parentShell, UserAdmin securityService) {
- super(parentShell, changePassword.lead(), CONFIRM);
- }
-
-// protected Point getInitialSize() {
-// return new Point(400, 450);
-// }
-
- 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));
- oldPassword = createLP(composite, currentPassword.lead());
- newPassword1 = createLP(composite, newPassword.lead());
- newPassword2 = createLP(composite, repeatNewPassword.lead());
-
-// parent.pack();
- oldPassword.setFocus();
- return composite;
- }
-
- @Override
- protected void okPressed() {
- try {
- if (!newPassword1.getText().equals(newPassword2.getText()))
- throw new CmsException("New passwords are different");
- changePassword(oldPassword.getTextChars(), newPassword1.getTextChars());
- closeShell(OK);
- } catch (Exception e) {
- ErrorFeedback.show("Cannot change password", e);
- }
- }
-
- /** Creates label and password. */
- protected Text createLP(Composite parent, String label) {
- new Label(parent, SWT.NONE).setText(label);
- Text text = new Text(parent, SWT.SINGLE | SWT.LEAD | SWT.PASSWORD | SWT.BORDER);
- text.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
- return text;
- }
-
- }
-
-}
+++ /dev/null
-package org.argeo.cms.e4.handlers;
-
-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.workbench.modeling.EPartService;
-
-public class CloseAllParts {
-
- @Execute
- void execute(EPartService partService) {
- for (MPart part : partService.getParts()) {
- if (part.isCloseable()) {
- if (part.isDirty()) {
- if (partService.savePart(part, true)) {
- partService.hidePart(part, true);
- }
- } else {
- partService.hidePart(part, true);
- }
- }
- }
- }
-
- @CanExecute
- boolean canExecute(EPartService partService) {
- boolean atLeastOnePart = false;
- for (MPart part : partService.getParts()) {
- if (part.isVisible() && part.isCloseable()) {
- atLeastOnePart = true;
- break;
- }
- }
- return atLeastOnePart;
- }
-
-}
\ No newline at end of file
+++ /dev/null
-package org.argeo.cms.e4.handlers;
-
-import java.security.AccessController;
-
-import javax.security.auth.Subject;
-
-import org.argeo.cms.CmsException;
-import org.argeo.cms.auth.CurrentUser;
-import org.eclipse.e4.core.di.annotations.Execute;
-import org.eclipse.e4.ui.workbench.IWorkbench;
-
-public class CloseWorkbench {
- @Execute
- public void execute(IWorkbench workbench) {
- logout();
- workbench.close();
- }
-
- protected void logout() {
- Subject subject = Subject.getSubject(AccessController.getContext());
- try {
- CurrentUser.logoutCmsSession(subject);
- } catch (Exception e) {
- throw new CmsException("Cannot log out", e);
- }
- }
-
-}
+++ /dev/null
-package org.argeo.cms.e4.handlers;
-
-import org.eclipse.e4.core.di.annotations.Execute;
-
-public class DoNothing {
- @Execute
- public void execute() {
-
- }
-}
+++ /dev/null
-
-package org.argeo.cms.e4.handlers;
-
-import java.util.Date;
-import java.util.List;
-
-import org.eclipse.e4.ui.di.AboutToHide;
-import org.eclipse.e4.ui.di.AboutToShow;
-import org.eclipse.e4.ui.model.application.ui.menu.MDirectMenuItem;
-import org.eclipse.e4.ui.model.application.ui.menu.MMenuElement;
-import org.eclipse.e4.ui.workbench.modeling.EModelService;
-
-public class LanguageMenuContribution {
- @AboutToShow
- public void aboutToShow(List<MMenuElement> items, EModelService modelService) {
- MDirectMenuItem dynamicItem = modelService.createModelElement(MDirectMenuItem.class);
- dynamicItem.setLabel("Dynamic Menu Item (" + new Date() + ")");
- //dynamicItem.setContributorURI("platform:/plugin/org.argeo.cms.e4");
- //dynamicItem.setContributionURI("bundleclass://org.argeo.cms.e4/" + ChangeLanguage.class.getName());
- dynamicItem.setEnabled(true);
- dynamicItem.setContributionURI("bundleclass://org.argeo.cms.e4/org.argeo.cms.e4.handlers.ChangeLanguage");
- items.add(dynamicItem);
- }
-
- @AboutToHide
- public void aboutToHide() {
-
- }
-}
\ No newline at end of file
+++ /dev/null
-package org.argeo.cms.e4.handlers;
-
-import java.util.List;
-
-import javax.inject.Inject;
-import javax.inject.Named;
-
-import org.eclipse.e4.core.di.annotations.Execute;
-import org.eclipse.e4.ui.model.application.MApplication;
-import org.eclipse.e4.ui.model.application.ui.advanced.MPerspective;
-import org.eclipse.e4.ui.workbench.modeling.EModelService;
-import org.eclipse.e4.ui.workbench.modeling.EPartService;
-
-public class OpenPerspective {
- @Inject
- MApplication application;
- @Inject
- EPartService partService;
- @Inject
- EModelService modelService;
-
- @Execute
- public void execute(@Named("perspectiveId") String perspectiveId) {
- List<MPerspective> perspectives = modelService.findElements(application, perspectiveId, MPerspective.class,
- null);
- if (perspectives.size() == 0)
- return;
- MPerspective perspective = perspectives.get(0);
- partService.switchPerspective(perspective);
- }
-}
+++ /dev/null
-package org.argeo.cms.e4.handlers;
-
-import org.eclipse.e4.core.di.annotations.CanExecute;
-import org.eclipse.e4.core.di.annotations.Execute;
-import org.eclipse.e4.ui.workbench.modeling.EPartService;
-
-public class SaveAllParts {
-
- @Execute
- void execute(EPartService partService) {
- partService.saveAll(false);
- }
-
- @CanExecute
- boolean canExecute(EPartService partService) {
- return partService.getDirtyParts().size() > 0;
- }
-
-}
\ No newline at end of file
+++ /dev/null
-package org.argeo.cms.e4.handlers;
-
-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.workbench.modeling.EPartService;
-
-public class SavePart {
- @Execute
- void execute(EPartService partService, MPart part) {
- partService.savePart(part, false);
- }
-
- @CanExecute
- boolean canExecute(MPart part) {
- return part.isDirty();
- }
-}
\ No newline at end of file
+++ /dev/null
-/** Generic Eclipse 4 handlers. */
-package org.argeo.cms.e4.handlers;
\ No newline at end of file
+++ /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.CmsException;
-import org.argeo.cms.security.CryptoKeyring;
-import org.argeo.cms.security.Keyring;
-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.eclipse.ui.EclipseUiException;
-import org.argeo.eclipse.ui.TreeParent;
-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.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.eclipse.ui.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.eclipse.ui.EclipseUiException;
-import org.argeo.eclipse.ui.TreeParent;
-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.eclipse.ui.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.Cms2DSize;
-import org.argeo.cms.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 <br>" + nodeState.getHostname() + "</br>, 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("[" + "<b>" + nodeState.getHostname() + "</b>]# " + "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.eclipse.ui.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 javax.naming.ldap.LdapName;
-
-import org.argeo.api.cms.CmsSession;
-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) {
- LdapName userDn = ((CmsSession) element).getUserDn();
- return userDn.getRdn(userDn.size() - 1).getValue().toString();
- }
-
- 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.eclipse.ui.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.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.eclipse.ui.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
-/** Eclipse 4 user interfaces. */
-package org.argeo.cms.e4;
\ No newline at end of file
+++ /dev/null
-package org.argeo.cms.e4.parts;
-
-import java.security.AccessController;
-import java.time.ZonedDateTime;
-
-import javax.annotation.PostConstruct;
-import javax.security.auth.Subject;
-
-import org.argeo.api.cms.CmsSession;
-import org.argeo.cms.auth.CurrentUser;
-import org.argeo.cms.osgi.CmsOsgiUtils;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.widgets.Composite;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.FrameworkUtil;
-
-/** 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 = CmsOsgiUtils.getCmsSession(bc, subject);
- 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.ui.eclipse.forms.AbstractFormPart;
-import org.argeo.cms.ui.eclipse.forms.IManagedForm;
-import org.argeo.eclipse.ui.ColumnDefinition;
-import org.argeo.eclipse.ui.EclipseUiUtils;
-import org.argeo.eclipse.ui.parts.LdifUsersTable;
-import org.argeo.jcr.JcrException;
-import org.argeo.jcr.JcrUtils;
-import org.argeo.osgi.transaction.WorkTransaction;
-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.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.CmsException;
-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.ui.workbench.WorkbenchUiPlugin;
-//import org.argeo.cms.ui.workbench.internal.useradmin.UiUserAdminListener;
-//import org.argeo.cms.ui.workbench.internal.useradmin.UserAdminWrapper;
-//import org.argeo.cms.ui.workbench.internal.useradmin.providers.CommonNameLP;
-//import org.argeo.cms.ui.workbench.internal.useradmin.providers.DomainNameLP;
-//import org.argeo.cms.ui.workbench.internal.useradmin.providers.RoleIconLP;
-//import org.argeo.cms.ui.workbench.internal.useradmin.providers.UserDragListener;
-//import org.argeo.cms.ui.workbench.internal.useradmin.providers.UserTableDefaultDClickListener;
-import org.argeo.eclipse.ui.ColumnDefinition;
-import org.argeo.eclipse.ui.EclipseUiUtils;
-import org.argeo.eclipse.ui.parts.LdifUsersTable;
-import org.argeo.util.naming.LdapAttrs;
-import org.argeo.util.naming.LdapObjs;
-import org.eclipse.e4.ui.di.Focus;
-import org.eclipse.e4.ui.workbench.modeling.EPartService;
-import org.eclipse.e4.ui.workbench.modeling.ESelectionService;
-import org.eclipse.jface.viewers.ISelectionChangedListener;
-import org.eclipse.jface.viewers.IStructuredSelection;
-import org.eclipse.jface.viewers.SelectionChangedEvent;
-import org.eclipse.jface.viewers.TableViewer;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.dnd.DND;
-import org.eclipse.swt.dnd.TextTransfer;
-import org.eclipse.swt.dnd.Transfer;
-import org.eclipse.swt.events.SelectionAdapter;
-import org.eclipse.swt.events.SelectionEvent;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.widgets.Button;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Display;
-//import org.eclipse.ui.part.ViewPart;
-import org.osgi.framework.InvalidSyntaxException;
-import org.osgi.service.useradmin.Role;
-import org.osgi.service.useradmin.User;
-import org.osgi.service.useradmin.UserAdminEvent;
-import org.osgi.service.useradmin.UserAdminListener;
-
-/** List all groups with filter */
-public class GroupsView {
- private final static CmsLog log = CmsLog.getLog(GroupsView.class);
- // public final static String ID = WorkbenchUiPlugin.PLUGIN_ID + ".groupsView";
-
- @Inject
- private EPartService partService;
- @Inject
- private UserAdminWrapper userAdminWrapper;
-
- // UI Objects
- private LdifUsersTable groupTableViewerCmp;
- private TableViewer userViewer;
- private List<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.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.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.osgi.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.CmsException;
-import org.argeo.osgi.transaction.WorkTransaction;
-import org.argeo.osgi.useradmin.UserAdminConf;
-import org.argeo.osgi.useradmin.UserDirectory;
-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.getBaseDn().toString();
-
- if (onlyWritable && readOnly)
- continue;
- if (baseDn.equalsIgnoreCase(CmsConstants.ROLES_BASEDN))
- continue;
- if (baseDn.equalsIgnoreCase(CmsConstants.TOKENS_BASEDN))
- continue;
- dns.put(baseDn, UserAdminConf.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.CmsException;
-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.eclipse.ui.ColumnDefinition;
-import org.argeo.eclipse.ui.EclipseUiUtils;
-import org.argeo.eclipse.ui.parts.LdifUsersTable;
-import org.argeo.osgi.transaction.WorkTransaction;
-import org.argeo.util.naming.LdapAttrs;
-import org.argeo.util.naming.LdapObjs;
-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.ui.eclipse.forms.AbstractFormPart;
-//import org.argeo.cms.ui.eclipse.forms.FormToolkit;
-import org.argeo.cms.ui.eclipse.forms.IManagedForm;
-import org.argeo.eclipse.ui.ColumnDefinition;
-import org.argeo.eclipse.ui.EclipseUiUtils;
-import org.argeo.eclipse.ui.parts.LdifUsersTable;
-import org.argeo.util.naming.LdapAttrs;
-import org.eclipse.e4.ui.workbench.modeling.EPartService;
-import org.eclipse.jface.action.Action;
-import org.eclipse.jface.action.ToolBarManager;
-import org.eclipse.jface.dialogs.MessageDialog;
-import org.eclipse.jface.dialogs.TrayDialog;
-import org.eclipse.jface.resource.ImageDescriptor;
-import org.eclipse.jface.viewers.ISelection;
-import org.eclipse.jface.viewers.IStructuredSelection;
-import org.eclipse.jface.viewers.TableViewer;
-import org.eclipse.jface.viewers.Viewer;
-import org.eclipse.jface.viewers.ViewerDropAdapter;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.dnd.DND;
-import org.eclipse.swt.dnd.DropTargetEvent;
-import org.eclipse.swt.dnd.TextTransfer;
-import org.eclipse.swt.dnd.Transfer;
-import org.eclipse.swt.dnd.TransferData;
-import org.eclipse.swt.events.ModifyEvent;
-import org.eclipse.swt.events.ModifyListener;
-import org.eclipse.swt.events.SelectionAdapter;
-import org.eclipse.swt.events.SelectionEvent;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.widgets.Button;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Display;
-import org.eclipse.swt.widgets.Link;
-import org.eclipse.swt.widgets.Shell;
-import org.eclipse.swt.widgets.Text;
-import org.eclipse.swt.widgets.ToolBar;
-import org.osgi.service.useradmin.Group;
-import org.osgi.service.useradmin.Role;
-import org.osgi.service.useradmin.User;
-import org.osgi.service.useradmin.UserAdmin;
-import org.osgi.service.useradmin.UserAdminEvent;
-
-/** Display/edit the properties of a given user */
-public class UserEditor extends AbstractRoleEditor {
- // final static String ID = "UserEditor.mainPage";
-
- @Inject
- private EPartService partService;
-
- // private final UserEditor editor;
- // private UserAdminWrapper userAdminWrapper;
-
- // Local configuration
- // private final int PRE_TITLE_INDENT = 10;
-
- // public UserMainPage(FormEditor editor, UserAdminWrapper userAdminWrapper) {
- // super(editor, ID, "Main");
- // this.editor = (UserEditor) editor;
- // this.userAdminWrapper = userAdminWrapper;
- // }
-
- // protected void createFormContent(final IManagedForm mf) {
- // ScrolledForm form = mf.getForm();
- // Composite body = form.getBody();
- // GridLayout mainLayout = new GridLayout();
- // // mainLayout.marginRight = 10;
- // body.setLayout(mainLayout);
- // User user = editor.getDisplayedUser();
- // appendOverviewPart(body, user);
- // // Remove to ability to force the password for his own user. The user
- // // must then use the change pwd feature
- // appendMemberOfPart(body, user);
- // }
-
- @Override
- protected void createUi(Composite body) {
- // Composite body = new Composite(parent, SWT.BORDER);
- GridLayout mainLayout = new GridLayout();
- // mainLayout.marginRight = 10;
- body.setLayout(mainLayout);
- // body.getParent().setLayout(new GridLayout());
- // body.setLayoutData(CmsUiUtils.fillAll());
- User user = getDisplayedUser();
- appendOverviewPart(body, user);
- // Remove to ability to force the password for his own user. The user
- // must then use the change pwd feature
- appendMemberOfPart(body, user);
- }
-
- /** Creates the general section */
- private void appendOverviewPart(final Composite parent, final User user) {
- // FormToolkit tk = getManagedForm().getToolkit();
-
- // Section section = tk.createSection(parent, SWT.NO_FOCUS);
- // GridData gd = EclipseUiUtils.fillWidth();
- // // gd.verticalAlignment = PRE_TITLE_INDENT;
- // section.setLayoutData(gd);
- Composite body = new Composite(parent, SWT.NONE);
- body.setLayoutData(EclipseUiUtils.fillWidth());
- // section.setClient(body);
- // body.setLayout(new GridLayout(6, false));
- body.setLayout(new GridLayout(2, false));
-
- Text commonName = createReadOnlyLT(body, "Name", getProperty(user, cn));
- Text distinguishedName = createReadOnlyLT(body, "Login", getProperty(user, uid));
- Text firstName = createLT(body, "First name", getProperty(user, givenName));
- Text lastName = createLT(body, "Last name", getProperty(user, sn));
- Text email = createLT(body, "Email", getProperty(user, mail));
-
- Link resetPwdLk = new Link(body, SWT.NONE);
- if (!UserAdminUtils.isCurrentUser(user)) {
- resetPwdLk.setText("<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.CmsException;
-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.eclipse.ui.ColumnDefinition;
-import org.argeo.eclipse.ui.EclipseUiUtils;
-import org.argeo.eclipse.ui.parts.LdifUsersTable;
-import org.argeo.util.naming.LdapAttrs;
-import org.argeo.util.naming.LdapObjs;
-import org.eclipse.e4.ui.di.Focus;
-import org.eclipse.e4.ui.workbench.modeling.EPartService;
-import org.eclipse.e4.ui.workbench.modeling.ESelectionService;
-import org.eclipse.jface.viewers.ISelectionChangedListener;
-import org.eclipse.jface.viewers.IStructuredSelection;
-import org.eclipse.jface.viewers.SelectionChangedEvent;
-import org.eclipse.jface.viewers.TableViewer;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.dnd.DND;
-import org.eclipse.swt.dnd.TextTransfer;
-import org.eclipse.swt.dnd.Transfer;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Display;
-import org.osgi.framework.InvalidSyntaxException;
-import org.osgi.service.useradmin.Role;
-import org.osgi.service.useradmin.User;
-import org.osgi.service.useradmin.UserAdminEvent;
-import org.osgi.service.useradmin.UserAdminListener;
-
-/** List all users with filter - based on Ldif userAdmin */
-public class UsersView {
- // private final static Log log = LogFactory.getLog(UsersView.class);
-
- // public final static String ID = WorkbenchUiPlugin.PLUGIN_ID + ".usersView";
-
- @Inject
- private UserAdminWrapper userAdminWrapper;
- @Inject
- private EPartService partService;
-
- // UI Objects
- private LdifUsersTable userTableViewerCmp;
- private TableViewer userViewer;
- private List<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.CmsException;
-import org.argeo.cms.e4.users.UserAdminWrapper;
-import org.argeo.eclipse.ui.EclipseUiUtils;
-import org.argeo.eclipse.ui.dialogs.ErrorFeedback;
-import org.argeo.osgi.useradmin.UserAdminConf;
-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 = UserAdminConf.uriAsProperties(dns.get(bdn));
- String dn = LdapAttrs.cn.name() + "=" + cn + "," + UserAdminConf.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.CmsException;
-import org.argeo.cms.auth.UserAdminUtils;
-import org.argeo.cms.e4.users.UiAdminUtils;
-import org.argeo.cms.e4.users.UserAdminWrapper;
-import org.argeo.eclipse.ui.EclipseUiUtils;
-import org.argeo.eclipse.ui.dialogs.ErrorFeedback;
-import org.argeo.osgi.useradmin.UserAdminConf;
-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 = UserAdminConf.uriAsProperties(dns.get(bdn));
- String dn = LdapAttrs.uid.name() + "=" + uid + "," + UserAdminConf.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.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.CmsException;
-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.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.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
-<?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-11">
- <attributes>
- <attribute name="module" value="true"/>
- </attributes>
- </classpathentry>
- <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.cms.jcr</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
-eclipse.preferences.version=1
-org.eclipse.jdt.core.compiler.annotation.inheritNullAnnotations=disabled
-org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=ignore
-org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jdt.annotation.NonNull
-org.eclipse.jdt.core.compiler.annotation.nonnull.secondary=
-org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jdt.annotation.NonNullByDefault
-org.eclipse.jdt.core.compiler.annotation.nonnullbydefault.secondary=
-org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable
-org.eclipse.jdt.core.compiler.annotation.nullable.secondary=
-org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled
-org.eclipse.jdt.core.compiler.problem.APILeak=warning
-org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning
-org.eclipse.jdt.core.compiler.problem.autoboxing=ignore
-org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning
-org.eclipse.jdt.core.compiler.problem.deadCode=warning
-org.eclipse.jdt.core.compiler.problem.deprecation=warning
-org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled
-org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled
-org.eclipse.jdt.core.compiler.problem.discouragedReference=warning
-org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore
-org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=ignore
-org.eclipse.jdt.core.compiler.problem.fallthroughCase=ignore
-org.eclipse.jdt.core.compiler.problem.fatalOptionalError=disabled
-org.eclipse.jdt.core.compiler.problem.fieldHiding=ignore
-org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning
-org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning
-org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
-org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning
-org.eclipse.jdt.core.compiler.problem.includeNullInfoFromAsserts=disabled
-org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning
-org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=warning
-org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore
-org.eclipse.jdt.core.compiler.problem.localVariableHiding=ignore
-org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning
-org.eclipse.jdt.core.compiler.problem.missingDefaultCase=ignore
-org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=ignore
-org.eclipse.jdt.core.compiler.problem.missingEnumCaseDespiteDefault=disabled
-org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=ignore
-org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=ignore
-org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled
-org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning
-org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore
-org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning
-org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning
-org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore
-org.eclipse.jdt.core.compiler.problem.nonnullParameterAnnotationDropped=warning
-org.eclipse.jdt.core.compiler.problem.nonnullTypeVariableFromLegacyInvocation=warning
-org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=error
-org.eclipse.jdt.core.compiler.problem.nullReference=warning
-org.eclipse.jdt.core.compiler.problem.nullSpecViolation=error
-org.eclipse.jdt.core.compiler.problem.nullUncheckedConversion=warning
-org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning
-org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore
-org.eclipse.jdt.core.compiler.problem.pessimisticNullAnalysisForFreeTypeVariables=warning
-org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=ignore
-org.eclipse.jdt.core.compiler.problem.potentialNullReference=ignore
-org.eclipse.jdt.core.compiler.problem.potentiallyUnclosedCloseable=ignore
-org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning
-org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning
-org.eclipse.jdt.core.compiler.problem.redundantNullCheck=ignore
-org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=ignore
-org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=ignore
-org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore
-org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore
-org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled
-org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning
-org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled
-org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled
-org.eclipse.jdt.core.compiler.problem.syntacticNullAnalysisForFields=disabled
-org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore
-org.eclipse.jdt.core.compiler.problem.terminalDeprecation=warning
-org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning
-org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=enabled
-org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning
-org.eclipse.jdt.core.compiler.problem.unclosedCloseable=warning
-org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore
-org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning
-org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentType=warning
-org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentTypeStrict=disabled
-org.eclipse.jdt.core.compiler.problem.unlikelyEqualsArgumentType=info
-org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore
-org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=ignore
-org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore
-org.eclipse.jdt.core.compiler.problem.unstableAutoModuleName=warning
-org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=ignore
-org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled
-org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled
-org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled
-org.eclipse.jdt.core.compiler.problem.unusedExceptionParameter=ignore
-org.eclipse.jdt.core.compiler.problem.unusedImport=warning
-org.eclipse.jdt.core.compiler.problem.unusedLabel=warning
-org.eclipse.jdt.core.compiler.problem.unusedLocal=warning
-org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=ignore
-org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore
-org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled
-org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled
-org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled
-org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning
-org.eclipse.jdt.core.compiler.problem.unusedTypeParameter=ignore
-org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning
-org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8"?>
-<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="init" deactivate="destroy" name="org.argeo.cms.dataServletContext">
- <implementation class="org.argeo.cms.jcr.internal.servlet.DataServletContext"/>
- <service>
- <provide interface="org.osgi.service.http.context.ServletContextHelper"/>
- </service>
- <property name="osgi.http.whiteboard.context.name" type="String" value="dataServletContext"/>
- <property name="osgi.http.whiteboard.context.path" type="String" value="/data"/>
-</scr:component>
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8"?>
-<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="org.argeo.cms.filesServlet">
- <implementation class="org.argeo.cms.jcr.internal.servlet.CmsWebDavServlet"/>
- <service>
- <provide interface="javax.servlet.Servlet"/>
- </service>
- <property name="osgi.http.whiteboard.servlet.pattern" type="String" value="/*"/>
- <property name="osgi.http.whiteboard.context.select" type="String" value="(osgi.http.whiteboard.context.name=filesServletContext)"/>
- <property name="servlet.init.resource-config" type="String" value="/org/argeo/cms/jcr/internal/servlet/webdav-config.xml"/>
- <property name="servlet.init.resource-path-prefix" type="String" value="/files"/>
- <reference bind="setRepository" cardinality="1..1" interface="javax.jcr.Repository" policy="static" target="(cn=ego)"/>
-</scr:component>
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8"?>
-<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="init" deactivate="destroy" name="org.argeo.cms.filesServletContext">
- <implementation class="org.argeo.cms.jcr.internal.servlet.JcrServletContext"/>
- <service>
- <provide interface="org.osgi.service.http.context.ServletContextHelper"/>
- </service>
- <property name="osgi.http.whiteboard.context.name" type="String" value="filesServletContext"/>
- <property name="osgi.http.whiteboard.context.path" type="String" value="/files"/>
-</scr:component>
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8"?>
-<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="start" deactivate="stop" name="JCR Deployment">
- <implementation class="org.argeo.cms.jcr.internal.CmsJcrDeployment"/>
- <reference bind="setCmsDeployment" cardinality="1..1" interface="org.argeo.api.cms.CmsDeployment" policy="static"/>
-</scr:component>
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8"?>
-<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="JCR FS Provider">
- <implementation class="org.argeo.cms.jcr.internal.CmsJcrFsProvider"/>
- <property name="service.pid" type="String" value="org.argeo.api.fsProvider"/>
- <service>
- <provide interface="java.nio.file.spi.FileSystemProvider"/>
- </service>
- <reference bind="setRepositoryFactory" cardinality="1..1" interface="javax.jcr.RepositoryFactory" name="RepositoryFactory" policy="static"/>
- <reference bind="setRepository" cardinality="1..1" interface="javax.jcr.Repository" name="Repository" policy="static"/>
-</scr:component>
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8"?>
-<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="JCR Repository Factory">
- <implementation class="org.argeo.cms.jcr.internal.JcrRepositoryFactory"/>
- <service>
- <provide interface="javax.jcr.RepositoryFactory"/>
- </service>
- <reference cardinality="1..1" interface="org.argeo.api.cms.CmsState" name="CmsState" policy="static"/>
-</scr:component>
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8"?>
-<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="init" deactivate="destroy" name="org.argeo.cms.jcrServletContext">
- <implementation class="org.argeo.cms.jcr.internal.servlet.JcrServletContext"/>
- <service>
- <provide interface="org.osgi.service.http.context.ServletContextHelper"/>
- </service>
- <property name="osgi.http.whiteboard.context.name" type="String" value="jcrServletContext"/>
- <property name="osgi.http.whiteboard.context.path" type="String" value="/jcr"/>
-</scr:component>
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8"?>
-<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="init" deactivate="destroy" name="Jackrabbit Repository Contexts Factory">
- <implementation class="org.argeo.cms.jcr.internal.RepositoryContextsFactory"/>
- <property name="service.pid" type="String" value="org.argeo.api.repos"/>
- <service>
- <provide interface="org.osgi.service.cm.ManagedServiceFactory"/>
- </service>
- <reference cardinality="1..1" interface="org.argeo.api.cms.CmsState" name="CmsState" policy="static"/>
-</scr:component>
+++ /dev/null
-Bundle-Activator: org.argeo.cms.jcr.internal.osgi.CmsJcrActivator
-
-Provide-Capability:\
-cms.datamodel; name=jcrx; cnd=/org/argeo/jcr/jcrx.cnd; abstract=true,\
-cms.datamodel; name=argeo; cnd=/org/argeo/cms/jcr/argeo.cnd; abstract=true,\
-cms.datamodel;name=ldap; cnd=/org/argeo/cms/jcr/ldap.cnd; abstract=true,\
-osgi.service;objectClass="javax.jcr.Repository"
-
-Import-Package:\
-org.argeo.cms.servlet,\
-javax.jcr.security,\
-org.h2;resolution:=optional,\
-org.postgresql;version="[42,43)";resolution:=optional,\
-org.apache.jackrabbit.webdav.server,\
-org.apache.jackrabbit.webdav.jcr,\
-org.apache.commons.httpclient.cookie;resolution:=optional,\
-org.osgi.framework.namespace;version=0.0.0,\
-org.osgi.*;version=0.0.0,\
-org.osgi.service.http.whiteboard,\
-org.apache.jackrabbit.api,\
-org.apache.jackrabbit.commons,\
-org.apache.jackrabbit.spi,\
-org.apache.jackrabbit.spi2dav,\
-org.apache.jackrabbit.spi2davex,\
-org.apache.jackrabbit.webdav,\
-junit.*;resolution:=optional,\
-*
-
-Service-Component:\
-OSGI-INF/repositoryContextsFactory.xml,\
-OSGI-INF/jcrRepositoryFactory.xml,\
-OSGI-INF/jcrFsProvider.xml,\
-OSGI-INF/jcrDeployment.xml,\
-OSGI-INF/jcrServletContext.xml,\
-OSGI-INF/dataServletContext.xml,\
-OSGI-INF/filesServletContext.xml,\
-OSGI-INF/filesServlet.xml
+++ /dev/null
-output.. = bin/
-bin.includes = META-INF/,\
- .,\
- OSGI-INF/jcrDeployment.xml,\
- OSGI-INF/repositoryContextsFactory.xml,\
- OSGI-INF/jcrRepositoryFactory.xml,\
- OSGI-INF/jcrFsProvider.xml
-source.. = src/
-additional.bundles = org.apache.jackrabbit.core,\
- javax.jcr,\
- org.apache.jackrabbit.api,\
- org.apache.jackrabbit.data,\
- org.apache.jackrabbit.jcr.commons,\
- org.apache.jackrabbit.spi,\
- org.apache.jackrabbit.spi.commons,\
- org.slf4j.api,\
- org.apache.commons.collections,\
- EDU.oswego.cs.dl.util.concurrent,\
- org.apache.lucene,\
- org.apache.tika.core,\
- org.apache.commons.dbcp,\
- org.apache.commons.pool,\
- com.google.guava,\
- org.apache.jackrabbit.jcr2spi,\
- org.apache.jackrabbit.spi2dav,\
- org.apache.httpcomponents.httpclient,\
- org.apache.httpcomponents.httpcore,\
- org.apache.tika.parsers
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
- <modelVersion>4.0.0</modelVersion>
- <parent>
- <groupId>org.argeo.commons</groupId>
- <artifactId>argeo-commons</artifactId>
- <version>2.3-SNAPSHOT</version>
- <relativePath>..</relativePath>
- </parent>
- <artifactId>org.argeo.cms.jcr</artifactId>
- <name>CMS JCR</name>
- <dependencies>
- <dependency>
- <groupId>org.argeo.commons</groupId>
- <artifactId>org.argeo.cms</artifactId>
- <version>2.3-SNAPSHOT</version>
- </dependency>
- <dependency>
- <groupId>org.argeo.commons</groupId>
- <artifactId>org.argeo.cms.servlet</artifactId>
- <version>2.3-SNAPSHOT</version>
- </dependency>
- </dependencies>
-</project>
\ No newline at end of file
+++ /dev/null
-package org.argeo.cms.fs;
-
-import java.io.IOException;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.nio.file.FileSystem;
-import java.nio.file.Path;
-import java.nio.file.spi.FileSystemProvider;
-
-import javax.jcr.NoSuchWorkspaceException;
-import javax.jcr.Node;
-import javax.jcr.NodeIterator;
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.query.Query;
-import javax.jcr.query.QueryManager;
-
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.jcr.Jcr;
-
-/** Utilities around documents. */
-public class CmsFsUtils {
- // TODO make it more robust and configurable
- private static String baseWorkspaceName = CmsConstants.SYS_WORKSPACE;
-
- public static Node getNode(Repository repository, Path path) {
- String workspaceName = path.getNameCount() == 0 ? baseWorkspaceName : path.getName(0).toString();
- String jcrPath = '/' + path.subpath(1, path.getNameCount()).toString();
- try {
- Session newSession;
- try {
- newSession = repository.login(workspaceName);
- } catch (NoSuchWorkspaceException e) {
- // base workspace
- newSession = repository.login(baseWorkspaceName);
- jcrPath = path.toString();
- }
- return newSession.getNode(jcrPath);
- } catch (RepositoryException e) {
- throw new IllegalStateException("Cannot get node from path " + path, e);
- }
- }
-
- public static NodeIterator getLastUpdatedDocuments(Session session) {
- try {
- String qStr = "//element(*, nt:file)";
- qStr += " order by @jcr:lastModified descending";
- QueryManager queryManager = session.getWorkspace().getQueryManager();
- @SuppressWarnings("deprecation")
- Query xpathQuery = queryManager.createQuery(qStr, Query.XPATH);
- xpathQuery.setLimit(8);
- NodeIterator nit = xpathQuery.execute().getNodes();
- return nit;
- } catch (RepositoryException e) {
- throw new IllegalStateException("Unable to retrieve last updated documents", e);
- }
- }
-
- public static Path getPath(FileSystemProvider nodeFileSystemProvider, URI uri) {
- try {
- FileSystem fileSystem = nodeFileSystemProvider.getFileSystem(uri);
- if (fileSystem == null)
- fileSystem = nodeFileSystemProvider.newFileSystem(uri, null);
- String path = uri.getPath();
- return fileSystem.getPath(path);
- } catch (IOException e) {
- throw new IllegalStateException("Unable to initialise file system for " + uri, e);
- }
- }
-
- public static Path getPath(FileSystemProvider nodeFileSystemProvider, Node node) {
- String workspaceName = Jcr.getWorkspaceName(node);
- String fullPath = baseWorkspaceName.equals(workspaceName) ? Jcr.getPath(node)
- : '/' + workspaceName + Jcr.getPath(node);
- URI uri;
- try {
- uri = new URI(CmsConstants.SCHEME_NODE, null, fullPath, null);
- } catch (URISyntaxException e) {
- throw new IllegalArgumentException("Cannot interpret " + fullPath + " as an URI", e);
- }
- return getPath(nodeFileSystemProvider, uri);
- }
-
- /** Singleton. */
- private CmsFsUtils() {
- }
-}
+++ /dev/null
-package org.argeo.cms.internal.jcr;
-
-import java.util.Properties;
-
-import org.apache.jackrabbit.core.config.BeanConfig;
-import org.apache.jackrabbit.core.config.ConfigurationException;
-import org.apache.jackrabbit.core.config.RepositoryConfigurationParser;
-import org.apache.jackrabbit.core.config.WorkspaceSecurityConfig;
-import org.apache.jackrabbit.core.util.db.ConnectionFactory;
-import org.w3c.dom.Element;
-
-/**
- * A {@link RepositoryConfigurationParser} providing more flexibility with
- * classloaders.
- */
-@SuppressWarnings("restriction")
-class CustomRepositoryConfigurationParser extends RepositoryConfigurationParser {
- private ClassLoader classLoader = null;
-
- public CustomRepositoryConfigurationParser(Properties variables) {
- super(variables);
- }
-
- public CustomRepositoryConfigurationParser(Properties variables, ConnectionFactory connectionFactory) {
- super(variables, connectionFactory);
- }
-
- @Override
- protected RepositoryConfigurationParser createSubParser(Properties variables) {
- Properties props = new Properties(getVariables());
- props.putAll(variables);
- CustomRepositoryConfigurationParser subParser = new CustomRepositoryConfigurationParser(props,
- connectionFactory);
- subParser.setClassLoader(classLoader);
- return subParser;
- }
-
- @Override
- public WorkspaceSecurityConfig parseWorkspaceSecurityConfig(Element parent) throws ConfigurationException {
- WorkspaceSecurityConfig workspaceSecurityConfig = super.parseWorkspaceSecurityConfig(parent);
- workspaceSecurityConfig.getAccessControlProviderConfig().setClassLoader(classLoader);
- return workspaceSecurityConfig;
- }
-
- @Override
- protected BeanConfig parseBeanConfig(Element parent, String name) throws ConfigurationException {
- BeanConfig beanConfig = super.parseBeanConfig(parent, name);
- if (beanConfig.getClassName().startsWith("org.argeo")) {
- beanConfig.setClassLoader(classLoader);
- }
- return beanConfig;
- }
-
- public void setClassLoader(ClassLoader classLoader) {
- this.classLoader = classLoader;
- }
-
-}
+++ /dev/null
-package org.argeo.cms.internal.jcr;
-
-/** Pre-defined Jackrabbit repository configurations. */
-enum JackrabbitType {
- /** Local file system */
- localfs,
- /** Embedded Java H2 database */
- h2,
- /** Embedded Java H2 database in PostgreSQL compatibility mode */
- h2_postgresql,
- /** PostgreSQL */
- postgresql,
- /** PostgreSQL with datastore */
- postgresql_ds,
- /** PostgreSQL with cluster */
- postgresql_cluster,
- /** PostgreSQL with cluster and datastore */
- postgresql_cluster_ds,
- /** Memory */
- memory;
-}
+++ /dev/null
-package org.argeo.cms.internal.jcr;
-
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.Dictionary;
-import java.util.HashMap;
-import java.util.Hashtable;
-import java.util.Map;
-
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.RepositoryFactory;
-
-import org.argeo.api.cms.CmsDeployment;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.jackrabbit.client.ClientDavexRepositoryFactory;
-import org.argeo.jcr.JcrException;
-import org.argeo.util.naming.LdapAttrs;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.Constants;
-import org.osgi.framework.FrameworkUtil;
-
-/** JCR specific init utilities. */
-public class JcrInitUtils {
- private final static CmsLog log = CmsLog.getLog(JcrInitUtils.class);
- private final static BundleContext bundleContext = FrameworkUtil.getBundle(JcrInitUtils.class).getBundleContext();
-
- public static void addToDeployment(CmsDeployment nodeDeployment) {
- // node repository
-// Dictionary<String, Object> provided = null;
- Dictionary<String, Object> provided = nodeDeployment.getProps(CmsConstants.NODE_REPOS_FACTORY_PID,
- CmsConstants.NODE);
- Dictionary<String, Object> nodeConfig = JcrInitUtils.getNodeRepositoryConfig(provided);
- // node repository is mandatory
- nodeDeployment.addFactoryDeployConfig(CmsConstants.NODE_REPOS_FACTORY_PID, nodeConfig);
-
- // additional repositories
-// dataModels: for (DataModels.DataModel dataModel : dataModels.getNonAbstractDataModels()) {
-// if (NodeConstants.NODE_REPOSITORY.equals(dataModel.getName()))
-// continue dataModels;
-// Dictionary<String, Object> config = JcrInitUtils.getRepositoryConfig(dataModel.getName(),
-// getProps(NodeConstants.NODE_REPOS_FACTORY_PID, dataModel.getName()));
-// if (config.size() != 0)
-// putFactoryDeployConfig(NodeConstants.NODE_REPOS_FACTORY_PID, config);
-// }
-
- }
-
- /** Override the provided config with the framework properties */
- public static Dictionary<String, Object> getNodeRepositoryConfig(Dictionary<String, Object> provided) {
- Dictionary<String, Object> props = provided != null ? provided : new Hashtable<String, Object>();
- for (RepoConf repoConf : RepoConf.values()) {
- Object value = getFrameworkProp(CmsConstants.NODE_REPO_PROP_PREFIX + repoConf.name());
- if (value != null) {
- props.put(repoConf.name(), value);
- if (log.isDebugEnabled())
- log.debug("Set node repo configuration " + repoConf.name() + " to " + value);
- }
- }
- props.put(CmsConstants.CN, CmsConstants.NODE_REPOSITORY);
- return props;
- }
-
- public static Dictionary<String, Object> getRepositoryConfig(String dataModelName,
- Dictionary<String, Object> provided) {
- if (dataModelName.equals(CmsConstants.NODE_REPOSITORY) || dataModelName.equals(CmsConstants.EGO_REPOSITORY))
- throw new IllegalArgumentException("Data model '" + dataModelName + "' is reserved.");
- Dictionary<String, Object> props = provided != null ? provided : new Hashtable<String, Object>();
- for (RepoConf repoConf : RepoConf.values()) {
- Object value = getFrameworkProp(
- CmsConstants.NODE_REPOS_PROP_PREFIX + dataModelName + '.' + repoConf.name());
- if (value != null) {
- props.put(repoConf.name(), value);
- if (log.isDebugEnabled())
- log.debug("Set " + dataModelName + " repo configuration " + repoConf.name() + " to " + value);
- }
- }
- if (props.size() != 0)
- props.put(CmsConstants.CN, dataModelName);
- return props;
- }
-
- private static void registerRemoteInit(String uri) {
- try {
- Repository repository = createRemoteRepository(new URI(uri));
- Hashtable<String, Object> properties = new Hashtable<>();
- properties.put(CmsConstants.CN, CmsConstants.NODE_INIT);
- properties.put(LdapAttrs.labeledURI.name(), uri);
- properties.put(Constants.SERVICE_RANKING, -1000);
- bundleContext.registerService(Repository.class, repository, properties);
- } catch (RepositoryException e) {
- throw new JcrException(e);
- } catch (URISyntaxException e) {
- throw new IllegalArgumentException(e);
- }
- }
-
- private static Repository createRemoteRepository(URI uri) throws RepositoryException {
- RepositoryFactory repositoryFactory = new ClientDavexRepositoryFactory();
- Map<String, String> params = new HashMap<String, String>();
- params.put(ClientDavexRepositoryFactory.JACKRABBIT_DAVEX_URI, uri.toString());
- // TODO make it configurable
- params.put(ClientDavexRepositoryFactory.JACKRABBIT_REMOTE_DEFAULT_WORKSPACE, CmsConstants.SYS_WORKSPACE);
- return repositoryFactory.getRepository(params);
- }
-
- private static String getFrameworkProp(String key, String def) {
- String value;
- if (bundleContext != null)
- value = bundleContext.getProperty(key);
- else
- value = System.getProperty(key);
- if (value == null)
- return def;
- return value;
- }
-
- private static String getFrameworkProp(String key) {
- return getFrameworkProp(key, null);
- }
-
-}
+++ /dev/null
-package org.argeo.cms.internal.jcr;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.file.Files;
-import java.nio.file.Paths;
-
-import org.apache.jackrabbit.core.data.DataIdentifier;
-import org.apache.jackrabbit.core.data.DataRecord;
-import org.apache.jackrabbit.core.data.DataStoreException;
-import org.apache.jackrabbit.core.data.FileDataStore;
-
-/**
- * <b>experimental</b> Duplicate added entries in another directory (typically a
- * remote mount).
- */
-@SuppressWarnings("restriction")
-public class LocalFsDataStore extends FileDataStore {
- String redundantPath;
- FileDataStore redundantStore;
-
- @Override
- public void init(String homeDir) {
- // init primary first
- super.init(homeDir);
-
- if (redundantPath != null) {
- // redundant directory must be created first
- // TODO implement some polling?
- if (Files.exists(Paths.get(redundantPath))) {
- redundantStore = new FileDataStore();
- redundantStore.setPath(redundantPath);
- redundantStore.init(homeDir);
- }
- }
- }
-
- @Override
- public DataRecord addRecord(InputStream input) throws DataStoreException {
- DataRecord dataRecord = super.addRecord(input);
- syncRedundantRecord(dataRecord);
- return dataRecord;
- }
-
- @Override
- public DataRecord getRecord(DataIdentifier identifier) throws DataStoreException {
- DataRecord dataRecord = super.getRecord(identifier);
- syncRedundantRecord(dataRecord);
- return dataRecord;
- }
-
- protected void syncRedundantRecord(DataRecord dataRecord) throws DataStoreException {
- if (redundantStore == null)
- return;
- if (redundantStore.getRecordIfStored(dataRecord.getIdentifier()) == null) {
- try (InputStream redundant = dataRecord.getStream()) {
- redundantStore.addRecord(redundant);
- } catch (IOException e) {
- throw new DataStoreException("Cannot add redundant record.", e);
- }
- }
- }
-
- public void setRedundantPath(String redundantPath) {
- this.redundantPath = redundantPath;
- }
-
-}
+++ /dev/null
-package org.argeo.cms.internal.jcr;
-
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.osgi.metatype.EnumAD;
-import org.argeo.osgi.metatype.EnumOCD;
-
-/** JCR repository configuration */
-public enum RepoConf implements EnumAD {
- /** Repository type */
- type("h2"),
- /** Default workspace */
- defaultWorkspace(CmsConstants.SYS_WORKSPACE),
- /** Database URL */
- dburl(null),
- /** Database user */
- dbuser(null),
- /** Database password */
- dbpassword(null),
-
- /** The identifier (can be an URL locating the repo) */
- labeledUri(null),
- //
- // JACKRABBIT SPECIFIC
- //
- /** Maximum database pool size */
- maxPoolSize(10),
- /** Maximum cache size in MB */
- maxCacheMB(null),
- /** Bundle cache size in MB */
- bundleCacheMB(8),
- /** Extractor pool size */
- extractorPoolSize(0),
- /** Search cache size */
- searchCacheSize(1000),
- /** Max volatile index size */
- maxVolatileIndexSize(1048576),
- /** Cluster id (if appropriate configuration) */
- clusterId("default"),
- /** Indexes base path */
- indexesBase(null);
-
- /** The default value. */
- private Object def;
- private String oid;
-
- RepoConf(String oid, Object def) {
- this.oid = oid;
- this.def = def;
- }
-
- RepoConf(Object def) {
- this.def = def;
- }
-
- public Object getDefault() {
- return def;
- }
-
- @Override
- public String getID() {
- if (oid != null)
- return oid;
- return EnumAD.super.getID();
- }
-
- public static class OCD extends EnumOCD<RepoConf> {
- public OCD(String locale) {
- super(RepoConf.class, locale);
- }
- }
-
-}
+++ /dev/null
-package org.argeo.cms.internal.jcr;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.Dictionary;
-import java.util.Enumeration;
-import java.util.Properties;
-import java.util.UUID;
-
-import javax.jcr.RepositoryException;
-
-import org.apache.jackrabbit.core.RepositoryContext;
-import org.apache.jackrabbit.core.RepositoryImpl;
-import org.apache.jackrabbit.core.cache.CacheManager;
-import org.apache.jackrabbit.core.config.RepositoryConfig;
-import org.apache.jackrabbit.core.config.RepositoryConfigurationParser;
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.cms.jcr.internal.CmsPaths;
-import org.xml.sax.InputSource;
-
-/** Can interpret properties in order to create an actual JCR repository. */
-public class RepositoryBuilder {
- private final static CmsLog log = CmsLog.getLog(RepositoryBuilder.class);
-
- public RepositoryContext createRepositoryContext(Dictionary<String, ?> properties)
- throws RepositoryException, IOException {
- RepositoryConfig repositoryConfig = createRepositoryConfig(properties);
- RepositoryContext repositoryContext = createJackrabbitRepository(repositoryConfig);
- RepositoryImpl repository = repositoryContext.getRepository();
-
- // cache
- Object maxCacheMbStr = prop(properties, RepoConf.maxCacheMB);
- if (maxCacheMbStr != null) {
- Integer maxCacheMB = Integer.parseInt(maxCacheMbStr.toString());
- CacheManager cacheManager = repository.getCacheManager();
- cacheManager.setMaxMemory(maxCacheMB * 1024l * 1024l);
- cacheManager.setMaxMemoryPerCache((maxCacheMB / 4) * 1024l * 1024l);
- }
-
- return repositoryContext;
- }
-
- RepositoryConfig createRepositoryConfig(Dictionary<String, ?> properties) throws RepositoryException, IOException {
- JackrabbitType type = JackrabbitType.valueOf(prop(properties, RepoConf.type).toString());
- ClassLoader cl = getClass().getClassLoader();
- final String base = "/org/argeo/cms/internal/jcr";
- try (InputStream in = cl.getResourceAsStream(base + "/repository-" + type.name() + ".xml")) {
- if (in == null)
- throw new IllegalArgumentException("Repository configuration not found");
- InputSource config = new InputSource(in);
- Properties jackrabbitVars = getConfigurationProperties(type, properties);
- // RepositoryConfig repositoryConfig = RepositoryConfig.create(config,
- // jackrabbitVars);
-
- // custom configuration parser
- CustomRepositoryConfigurationParser parser = new CustomRepositoryConfigurationParser(jackrabbitVars);
- parser.setClassLoader(cl);
- RepositoryConfig repositoryConfig = parser.parseRepositoryConfig(config);
- repositoryConfig.init();
-
- // set the proper classloaders
- repositoryConfig.getSecurityConfig().getSecurityManagerConfig().setClassLoader(cl);
- repositoryConfig.getSecurityConfig().getAccessManagerConfig().setClassLoader(cl);
-// for (WorkspaceConfig workspaceConfig : repositoryConfig.getWorkspaceConfigs()) {
-// workspaceConfig.getSecurityConfig().getAccessControlProviderConfig().setClassLoader(cl);
-// }
- return repositoryConfig;
- }
- }
-
- private Properties getConfigurationProperties(JackrabbitType type, Dictionary<String, ?> properties) {
- Properties props = new Properties();
- for (Enumeration<String> keys = properties.keys(); keys.hasMoreElements();) {
- String key = keys.nextElement();
- props.put(key, properties.get(key));
- }
-
- // cluster id
- // cf. https://wiki.apache.org/jackrabbit/Clustering
- // TODO deal with multiple repos
- String clusterId = System.getProperty("org.apache.jackrabbit.core.cluster.node_id");
- String clusterIdProp = props.getProperty(RepoConf.clusterId.name());
- if (clusterId != null) {
- if (clusterIdProp != null)
- throw new IllegalArgumentException("Cluster id defined as System properties and in deploy config");
- props.put(RepoConf.clusterId.name(), clusterId);
- } else {
- clusterId = clusterIdProp;
- }
-
- // home
- String homeUri = props.getProperty(RepoConf.labeledUri.name());
- Path homePath;
- if (homeUri == null) {
- String cn = props.getProperty(CmsConstants.CN);
- assert cn != null;
- if (clusterId != null) {
- homePath = CmsPaths.getRepoDirPath(cn + '/' + clusterId);
- } else {
- homePath = CmsPaths.getRepoDirPath(cn);
- }
- } else {
- try {
- URI uri = new URI(homeUri);
- String host = uri.getHost();
- if (host == null || host.trim().equals("")) {
- homePath = Paths.get(uri).toAbsolutePath();
- } else {
- // TODO remote at this stage?
- throw new IllegalArgumentException("Cannot manage repository path for host " + host);
- }
- } catch (URISyntaxException e) {
- throw new IllegalArgumentException("Invalid repository home URI", e);
- }
- }
- // TODO use Jackrabbit API (?)
- Path rootUuidPath = homePath.resolve("repository/meta/rootUUID");
- try {
- if (!Files.exists(rootUuidPath)) {
- Files.createDirectories(rootUuidPath.getParent());
- Files.write(rootUuidPath, UUID.randomUUID().toString().getBytes());
- }
- // File homeDir = homePath.toFile();
- // homeDir.mkdirs();
- } catch (IOException e) {
- throw new RuntimeException("Cannot set up repository home " + homePath, e);
- }
- // home cannot be overridden
- props.put(RepositoryConfigurationParser.REPOSITORY_HOME_VARIABLE, homePath.toString());
-
- setProp(props, RepoConf.indexesBase, CmsPaths.getRepoIndexesBase().toString());
- // common
- setProp(props, RepoConf.defaultWorkspace);
- setProp(props, RepoConf.maxPoolSize);
- // Jackrabbit defaults
- setProp(props, RepoConf.bundleCacheMB);
- // See http://wiki.apache.org/jackrabbit/Search
- setProp(props, RepoConf.extractorPoolSize);
- setProp(props, RepoConf.searchCacheSize);
- setProp(props, RepoConf.maxVolatileIndexSize);
-
- // specific
- String dburl;
- switch (type) {
- case h2:
- dburl = "jdbc:h2:" + homePath.toAbsolutePath() + "/h2/repository";
- setProp(props, RepoConf.dburl, dburl);
- setProp(props, RepoConf.dbuser, "sa");
- setProp(props, RepoConf.dbpassword, "");
- break;
- case h2_postgresql:
- dburl = "jdbc:h2:" + homePath.toAbsolutePath() + "/h2/repository;MODE=PostgreSQL;DATABASE_TO_LOWER=TRUE";
- setProp(props, RepoConf.dburl, dburl);
- setProp(props, RepoConf.dbuser, "sa");
- setProp(props, RepoConf.dbpassword, "");
- break;
- case postgresql:
- case postgresql_ds:
- case postgresql_cluster:
- case postgresql_cluster_ds:
- dburl = "jdbc:postgresql://localhost/demo";
- setProp(props, RepoConf.dburl, dburl);
- setProp(props, RepoConf.dbuser, "argeo");
- setProp(props, RepoConf.dbpassword, "argeo");
- break;
- case memory:
- break;
- case localfs:
- break;
- default:
- throw new IllegalArgumentException("Unsupported node type " + type);
- }
- return props;
- }
-
- private void setProp(Properties props, RepoConf key, String def) {
- Object value = props.get(key.name());
- if (value == null)
- value = def;
- if (value == null)
- value = key.getDefault();
- if (value != null)
- props.put(key.name(), value.toString());
- }
-
- private void setProp(Properties props, RepoConf key) {
- setProp(props, key, null);
- }
-
- private String prop(Dictionary<String, ?> properties, RepoConf key) {
- Object value = properties.get(key.name());
- if (value == null)
- return key.getDefault() != null ? key.getDefault().toString() : null;
- else
- return value.toString();
- }
-
- private RepositoryContext createJackrabbitRepository(RepositoryConfig repositoryConfig) throws RepositoryException {
- ClassLoader currentContextCl = Thread.currentThread().getContextClassLoader();
- Thread.currentThread().setContextClassLoader(RepositoryBuilder.class.getClassLoader());
- try {
- long begin = System.currentTimeMillis();
- //
- // Actual repository creation
- //
- RepositoryContext repositoryContext = RepositoryContext.create(repositoryConfig);
-
- double duration = ((double) (System.currentTimeMillis() - begin)) / 1000;
- if (log.isDebugEnabled())
- log.debug(
- "Created Jackrabbit repository in " + duration + " s, home: " + repositoryConfig.getHomeDir());
-
- return repositoryContext;
- } finally {
- Thread.currentThread().setContextClassLoader(currentContextCl);
- }
- }
-
-}
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE Repository PUBLIC "Jackrabbit 2.6" "http://jackrabbit.apache.org/dtd/repository-2.6.dtd">
-<Repository>
- <!-- Shared datasource -->
- <DataSources>
- <DataSource name="dataSource">
- <param name="driver" value="org.h2.Driver" />
- <param name="url" value="${dburl}" />
- <param name="user" value="${dbuser}" />
- <param name="password" value="${dbpassword}" />
- <param name="databaseType" value="h2" />
- <param name="maxPoolSize" value="${maxPoolSize}" />
- </DataSource>
- </DataSources>
-
- <!-- File system and datastore -->
- <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
- <param name="dataSourceName" value="dataSource" />
- <param name="schema" value="default" />
- <param name="schemaObjectPrefix" value="fs_" />
- </FileSystem>
- <DataStore class="org.apache.jackrabbit.core.data.FileDataStore">
- <param name="path" value="${rep.home}/datastore" />
- </DataStore>
-
- <!-- Workspace templates -->
- <Workspaces rootPath="${rep.home}/workspaces"
- defaultWorkspace="${defaultWorkspace}" />
- <Workspace name="${wsp.name}">
- <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
- <param name="dataSourceName" value="dataSource" />
- <param name="schema" value="default" />
- <param name="schemaObjectPrefix" value="${wsp.name}_fs_" />
- </FileSystem>
- <PersistenceManager
- class="org.apache.jackrabbit.core.persistence.pool.H2PersistenceManager">
- <param name="dataSourceName" value="dataSource" />
- <param name="schemaObjectPrefix" value="${wsp.name}_pm_" />
- <param name="bundleCacheSize" value="${bundleCacheMB}" />
- </PersistenceManager>
- <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
- <param name="path" value="${indexesBase}/${cn}/${wsp.name}/index" />
- <param name="extractorPoolSize" value="${extractorPoolSize}" />
- <param name="cacheSize" value="${searchCacheSize}" />
- <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
-<!-- <param name="tikaConfigPath" value="${indexesBase}/${cn}/tika-config.xml" /> -->
- <param name="supportHighlighting" value="true" />
- </SearchIndex>
- <WorkspaceSecurity>
- <AccessControlProvider
- class="org.argeo.security.jackrabbit.ArgeoAccessControlProvider" />
- </WorkspaceSecurity>
- </Workspace>
-
- <!-- Versioning -->
- <Versioning rootPath="${rep.home}/version">
- <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
- <param name="dataSourceName" value="dataSource" />
- <param name="schema" value="default" />
- <param name="schemaObjectPrefix" value="fs_ver_" />
- </FileSystem>
- <PersistenceManager
- class="org.apache.jackrabbit.core.persistence.pool.H2PersistenceManager">
- <param name="dataSourceName" value="dataSource" />
- <param name="schemaObjectPrefix" value="pm_ver_" />
- <param name="bundleCacheSize" value="${bundleCacheMB}" />
- </PersistenceManager>
- </Versioning>
-
- <!-- Indexing -->
- <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
- <param name="path" value="${indexesBase}/${cn}/index" />
- <param name="extractorPoolSize" value="${extractorPoolSize}" />
- <param name="cacheSize" value="${searchCacheSize}" />
- <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
-<!-- <param name="tikaConfigPath" value="${indexesBase}/${cn}/tika-config.xml" /> -->
- <param name="supportHighlighting" value="true" />
- </SearchIndex>
-
- <!-- Security -->
- <Security appName="Jackrabbit">
- <SecurityManager class="org.argeo.security.jackrabbit.ArgeoSecurityManager"
- workspaceName="security" />
- <AccessManager class="org.argeo.security.jackrabbit.ArgeoAccessManager" />
- </Security>
-</Repository>
\ No newline at end of file
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE Repository PUBLIC "Jackrabbit 2.6" "http://jackrabbit.apache.org/dtd/repository-2.6.dtd">
-<Repository>
- <!-- Shared datasource -->
- <DataSources>
- <DataSource name="dataSource">
- <param name="driver" value="org.h2.Driver" />
- <param name="url" value="${dburl}" />
- <param name="user" value="${dbuser}" />
- <param name="password" value="${dbpassword}" />
- <param name="databaseType" value="postgresql" />
- <param name="maxPoolSize" value="${maxPoolSize}" />
- </DataSource>
- </DataSources>
-
- <!-- File system and datastore -->
- <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
- <param name="dataSourceName" value="dataSource" />
- <param name="schema" value="default" />
- <param name="schemaObjectPrefix" value="fs_" />
- </FileSystem>
- <DataStore class="org.apache.jackrabbit.core.data.FileDataStore">
- <param name="path" value="${rep.home}/datastore" />
- </DataStore>
-
- <!-- Workspace templates -->
- <Workspaces rootPath="${rep.home}/workspaces"
- defaultWorkspace="${defaultWorkspace}" />
- <Workspace name="${wsp.name}">
- <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
- <param name="dataSourceName" value="dataSource" />
- <param name="schema" value="default" />
- <param name="schemaObjectPrefix" value="${wsp.name}_fs_" />
- </FileSystem>
- <PersistenceManager
- class="org.apache.jackrabbit.core.persistence.pool.PostgreSQLPersistenceManager">
- <param name="dataSourceName" value="dataSource" />
- <param name="schemaObjectPrefix" value="${wsp.name}_pm_" />
- <param name="bundleCacheSize" value="${bundleCacheMB}" />
- </PersistenceManager>
- <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
- <param name="path" value="${indexesBase}/${cn}/${wsp.name}/index" />
- <param name="extractorPoolSize" value="${extractorPoolSize}" />
- <param name="cacheSize" value="${searchCacheSize}" />
- <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
-<!-- <param name="tikaConfigPath" value="${indexesBase}/${cn}/tika-config.xml" /> -->
- <param name="supportHighlighting" value="true" />
- </SearchIndex>
- <WorkspaceSecurity>
- <AccessControlProvider
- class="org.argeo.security.jackrabbit.ArgeoAccessControlProvider" />
- </WorkspaceSecurity>
- </Workspace>
-
- <!-- Versioning -->
- <Versioning rootPath="${rep.home}/version">
- <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
- <param name="dataSourceName" value="dataSource" />
- <param name="schema" value="default" />
- <param name="schemaObjectPrefix" value="fs_ver_" />
- </FileSystem>
- <PersistenceManager
- class="org.apache.jackrabbit.core.persistence.pool.PostgreSQLPersistenceManager">
- <param name="dataSourceName" value="dataSource" />
- <param name="schemaObjectPrefix" value="pm_ver_" />
- <param name="bundleCacheSize" value="${bundleCacheMB}" />
- </PersistenceManager>
- </Versioning>
-
- <!-- Indexing -->
- <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
- <param name="path" value="${indexesBase}/${cn}/index" />
- <param name="extractorPoolSize" value="${extractorPoolSize}" />
- <param name="cacheSize" value="${searchCacheSize}" />
- <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
-<!-- <param name="tikaConfigPath" value="${indexesBase}/${cn}/tika-config.xml" /> -->
- <param name="supportHighlighting" value="true" />
- </SearchIndex>
-
- <!-- Security -->
- <Security appName="Jackrabbit">
- <SecurityManager class="org.argeo.security.jackrabbit.ArgeoSecurityManager"
- workspaceName="security" />
- <AccessManager class="org.argeo.security.jackrabbit.ArgeoAccessManager" />
- </Security>
-</Repository>
\ No newline at end of file
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE Repository PUBLIC "Jackrabbit 2.6" "http://jackrabbit.apache.org/dtd/repository-2.6.dtd">
-<Repository>
- <!-- File system and datastore -->
- <FileSystem class="org.apache.jackrabbit.core.fs.local.LocalFileSystem">
- <param name="path" value="${rep.home}/repository" />
- </FileSystem>
- <DataStore class="org.apache.jackrabbit.core.data.FileDataStore">
- <param name="path" value="${rep.home}/datastore" />
- </DataStore>
-
- <!-- Workspace templates -->
- <Workspaces rootPath="${rep.home}/workspaces"
- defaultWorkspace="${defaultWorkspace}" />
- <Workspace name="${wsp.name}">
- <FileSystem class="org.apache.jackrabbit.core.fs.local.LocalFileSystem">
- <param name="path" value="${wsp.home}" />
- </FileSystem>
- <PersistenceManager
- class="org.apache.jackrabbit.core.persistence.bundle.BundleFsPersistenceManager">
- <param name="bundleCacheSize" value="${bundleCacheMB}" />
- </PersistenceManager>
- <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
- <param name="path" value="${indexesBase}/${cn}/${wsp.name}/index" />
- <param name="extractorPoolSize" value="${extractorPoolSize}" />
- <param name="cacheSize" value="${searchCacheSize}" />
- <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
- </SearchIndex>
- <WorkspaceSecurity>
- <AccessControlProvider
- class="org.argeo.security.jackrabbit.ArgeoAccessControlProvider" />
- </WorkspaceSecurity>
- </Workspace>
-
- <!-- Versioning -->
- <Versioning rootPath="${rep.home}/version">
- <FileSystem class="org.apache.jackrabbit.core.fs.local.LocalFileSystem">
- <param name="path" value="${rep.home}/version" />
- </FileSystem>
- <PersistenceManager
- class="org.apache.jackrabbit.core.persistence.bundle.BundleFsPersistenceManager">
- <param name="bundleCacheSize" value="${bundleCacheMB}" />
- </PersistenceManager>
- </Versioning>
-
- <!-- Indexing -->
- <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
- <param name="path" value="${indexesBase}/${cn}/index" />
- <param name="extractorPoolSize" value="${extractorPoolSize}" />
- <param name="cacheSize" value="${searchCacheSize}" />
- <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
- </SearchIndex>
-
- <!-- Security -->
- <Security appName="Jackrabbit">
- <SecurityManager class="org.argeo.security.jackrabbit.ArgeoSecurityManager"
- workspaceName="security" />
- <AccessManager class="org.argeo.security.jackrabbit.ArgeoAccessManager" />
- </Security>
-</Repository>
\ No newline at end of file
+++ /dev/null
-<?xml version="1.0"?>
-<!DOCTYPE Repository PUBLIC "Jackrabbit 2.6" "http://jackrabbit.apache.org/dtd/repository-2.6.dtd">
-<Repository>
- <!-- File system and datastore -->
- <FileSystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
-
- <!-- Workspace templates -->
- <Workspaces rootPath="${rep.home}/workspaces"
- defaultWorkspace="${defaultWorkspace}" configRootPath="/workspaces" />
- <Workspace name="${wsp.name}">
- <FileSystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
- <PersistenceManager
- class="org.apache.jackrabbit.core.persistence.bundle.BundleFsPersistenceManager">
- <param name="blobFSBlockSize" value="1" />
- <param name="bundleCacheSize" value="${bundleCacheMB}" />
- </PersistenceManager>
- <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
- <param name="path" value="${indexesBase}/${cn}/${wsp.name}/index" />
- <param name="directoryManagerClass"
- value="org.apache.jackrabbit.core.query.lucene.directory.RAMDirectoryManager" />
- <param name="extractorPoolSize" value="${extractorPoolSize}" />
- <param name="cacheSize" value="${searchCacheSize}" />
- <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
- <FileSystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
- </SearchIndex>
- <WorkspaceSecurity>
- <AccessControlProvider
- class="org.argeo.security.jackrabbit.ArgeoAccessControlProvider" />
- </WorkspaceSecurity>
- </Workspace>
-
- <!-- Versioning -->
- <Versioning rootPath="${rep.home}/version">
- <FileSystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
- <PersistenceManager
- class="org.apache.jackrabbit.core.persistence.bundle.BundleFsPersistenceManager">
- <param name="blobFSBlockSize" value="1" />
- <param name="bundleCacheSize" value="${bundleCacheMB}" />
- </PersistenceManager>
- </Versioning>
-
- <!-- Indexing -->
- <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
- <param name="path" value="${indexesBase}/${cn}/index" />
- <param name="directoryManagerClass"
- value="org.apache.jackrabbit.core.query.lucene.directory.RAMDirectoryManager" />
- <param name="extractorPoolSize" value="${extractorPoolSize}" />
- <param name="cacheSize" value="${searchCacheSize}" />
- <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
- <FileSystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
- </SearchIndex>
-
- <!-- Security -->
- <Security appName="Jackrabbit">
- <SecurityManager class="org.argeo.security.jackrabbit.ArgeoSecurityManager"
- workspaceName="security" />
- <AccessManager class="org.argeo.security.jackrabbit.ArgeoAccessManager" />
- </Security>
-</Repository>
\ No newline at end of file
+++ /dev/null
-<?xml version="1.0"?>
-<!DOCTYPE Repository PUBLIC "Jackrabbit 2.6" "http://jackrabbit.apache.org/dtd/repository-2.6.dtd">
-<Repository>
- <!-- Shared datasource -->
- <DataSources>
- <DataSource name="dataSource">
- <param name="driver" value="org.postgresql.Driver" />
- <param name="url" value="${dburl}" />
- <param name="user" value="${dbuser}" />
- <param name="password" value="${dbpassword}" />
- <param name="databaseType" value="postgresql" />
- <param name="maxPoolSize" value="${maxPoolSize}" />
- </DataSource>
- </DataSources>
-
- <!-- File system and datastore -->
- <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
- <param name="dataSourceName" value="dataSource" />
- <param name="schema" value="postgresql" />
- <param name="schemaObjectPrefix" value="fs_" />
- </FileSystem>
-
- <!-- Workspace templates -->
- <Workspaces rootPath="${rep.home}/workspaces"
- defaultWorkspace="${defaultWorkspace}" />
- <Workspace name="${wsp.name}">
- <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
- <param name="dataSourceName" value="dataSource" />
- <param name="schema" value="postgresql" />
- <param name="schemaObjectPrefix" value="${wsp.name}_fs_" />
- </FileSystem>
- <PersistenceManager
- class="org.apache.jackrabbit.core.persistence.pool.PostgreSQLPersistenceManager">
- <param name="dataSourceName" value="dataSource" />
- <param name="schemaObjectPrefix" value="${wsp.name}_pm_" />
- <param name="bundleCacheSize" value="${bundleCacheMB}" />
- </PersistenceManager>
- <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
- <param name="path" value="${indexesBase}/${cn}/${wsp.name}/index" />
- <param name="extractorPoolSize" value="${extractorPoolSize}" />
- <param name="cacheSize" value="${searchCacheSize}" />
- <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
- </SearchIndex>
- <WorkspaceSecurity>
- <AccessControlProvider
- class="org.argeo.security.jackrabbit.ArgeoAccessControlProvider" />
- </WorkspaceSecurity>
- </Workspace>
-
- <!-- Versioning -->
- <Versioning rootPath="${rep.home}/version">
- <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
- <param name="dataSourceName" value="dataSource" />
- <param name="schema" value="postgresql" />
- <param name="schemaObjectPrefix" value="fs_ver_" />
- </FileSystem>
- <PersistenceManager
- class="org.apache.jackrabbit.core.persistence.pool.PostgreSQLPersistenceManager">
- <param name="dataSourceName" value="dataSource" />
- <param name="schemaObjectPrefix" value="pm_ver_" />
- <param name="bundleCacheSize" value="${bundleCacheMB}" />
- </PersistenceManager>
- </Versioning>
-
- <!-- Indexing -->
- <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
- <param name="path" value="${indexesBase}/${cn}/index" />
- <param name="extractorPoolSize" value="${extractorPoolSize}" />
- <param name="cacheSize" value="${searchCacheSize}" />
- <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
- </SearchIndex>
-
- <!-- Security -->
- <Security appName="Jackrabbit">
- <SecurityManager class="org.argeo.security.jackrabbit.ArgeoSecurityManager"
- workspaceName="security" />
- <AccessManager class="org.argeo.security.jackrabbit.ArgeoAccessManager" />
- </Security>
-</Repository>
\ No newline at end of file
+++ /dev/null
-<?xml version="1.0"?>
-<!DOCTYPE Repository PUBLIC "Jackrabbit 2.6" "http://jackrabbit.apache.org/dtd/repository-2.6.dtd">
-<Repository>
- <!-- Shared datasource -->
- <DataSources>
- <DataSource name="dataSource">
- <param name="driver" value="org.postgresql.Driver" />
- <param name="url" value="${dburl}" />
- <param name="user" value="${dbuser}" />
- <param name="password" value="${dbpassword}" />
- <param name="databaseType" value="postgresql" />
- <param name="maxPoolSize" value="${maxPoolSize}" />
- </DataSource>
- </DataSources>
-
- <!-- File system and datastore -->
- <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
- <param name="dataSourceName" value="dataSource" />
- <param name="schema" value="postgresql" />
- <param name="schemaObjectPrefix" value="fs_" />
- </FileSystem>
-
- <!-- Workspace templates -->
- <Workspaces rootPath="${rep.home}/workspaces"
- defaultWorkspace="${defaultWorkspace}" />
- <Workspace name="${wsp.name}">
- <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
- <param name="dataSourceName" value="dataSource" />
- <param name="schema" value="postgresql" />
- <param name="schemaObjectPrefix" value="${wsp.name}_fs_" />
- </FileSystem>
- <PersistenceManager
- class="org.apache.jackrabbit.core.persistence.pool.PostgreSQLPersistenceManager">
- <param name="dataSourceName" value="dataSource" />
- <param name="schemaObjectPrefix" value="${wsp.name}_pm_" />
- <param name="bundleCacheSize" value="${bundleCacheMB}" />
- </PersistenceManager>
- <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
- <param name="path" value="${indexesBase}/${cn}/${wsp.name}/index" />
- <param name="extractorPoolSize" value="${extractorPoolSize}" />
- <param name="cacheSize" value="${searchCacheSize}" />
- <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
- </SearchIndex>
- <WorkspaceSecurity>
- <AccessControlProvider
- class="org.argeo.security.jackrabbit.ArgeoAccessControlProvider" />
- </WorkspaceSecurity>
- </Workspace>
-
- <!-- Versioning -->
- <Versioning rootPath="${rep.home}/version">
- <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
- <param name="dataSourceName" value="dataSource" />
- <param name="schema" value="postgresql" />
- <param name="schemaObjectPrefix" value="fs_ver_" />
- </FileSystem>
- <PersistenceManager
- class="org.apache.jackrabbit.core.persistence.pool.PostgreSQLPersistenceManager">
- <param name="dataSourceName" value="dataSource" />
- <param name="schemaObjectPrefix" value="pm_ver_" />
- <param name="bundleCacheSize" value="${bundleCacheMB}" />
- </PersistenceManager>
- </Versioning>
-
- <!-- Indexing -->
- <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
- <param name="path" value="${indexesBase}/${cn}/index" />
- <param name="extractorPoolSize" value="${extractorPoolSize}" />
- <param name="cacheSize" value="${searchCacheSize}" />
- <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
- </SearchIndex>
-
- <!-- Security -->
- <Security appName="Jackrabbit">
- <SecurityManager class="org.argeo.security.jackrabbit.ArgeoSecurityManager"
- workspaceName="security" />
- <AccessManager class="org.argeo.security.jackrabbit.ArgeoAccessManager" />
- </Security>
-
- <!-- Clustering -->
- <Cluster id="${clusterId}">
- <Journal class="org.apache.jackrabbit.core.journal.DatabaseJournal">
- <param name="dataSourceName" value="dataSource" />
- <param name="schemaObjectPrefix" value="journal_" />
- </Journal>
- </Cluster>
-</Repository>
\ No newline at end of file
+++ /dev/null
-<?xml version="1.0"?>
-<!DOCTYPE Repository PUBLIC "Jackrabbit 2.6" "http://jackrabbit.apache.org/dtd/repository-2.6.dtd">
-<Repository>
- <!-- Shared datasource -->
- <DataSources>
- <DataSource name="dataSource">
- <param name="driver" value="org.postgresql.Driver" />
- <param name="url" value="${dburl}" />
- <param name="user" value="${dbuser}" />
- <param name="password" value="${dbpassword}" />
- <param name="databaseType" value="postgresql" />
- <param name="maxPoolSize" value="${maxPoolSize}" />
- </DataSource>
- </DataSources>
-
- <!-- File system and datastore -->
- <FileSystem
- class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
- <param name="dataSourceName" value="dataSource" />
- <param name="schema" value="postgresql" />
- <param name="schemaObjectPrefix" value="fs_" />
- </FileSystem>
- <DataStore
- class="org.argeo.cms.internal.jcr.LocalFsDataStore">
- <param name="path" value="${rep.home}/../datastore" />
- <param name="redundantPath" value="${rep.home}/../datastorer" />
- </DataStore>
-
- <!-- Workspace templates -->
- <Workspaces rootPath="${rep.home}/workspaces"
- defaultWorkspace="${defaultWorkspace}" />
- <Workspace name="${wsp.name}">
- <FileSystem
- class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
- <param name="dataSourceName" value="dataSource" />
- <param name="schema" value="postgresql" />
- <param name="schemaObjectPrefix" value="${wsp.name}_fs_" />
- </FileSystem>
- <PersistenceManager
- class="org.apache.jackrabbit.core.persistence.pool.PostgreSQLPersistenceManager">
- <param name="dataSourceName" value="dataSource" />
- <param name="schemaObjectPrefix" value="${wsp.name}_pm_" />
- <param name="bundleCacheSize" value="${bundleCacheMB}" />
- </PersistenceManager>
- <SearchIndex
- class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
- <param name="path"
- value="${indexesBase}/${cn}/${wsp.name}/index" />
- <param name="extractorPoolSize" value="${extractorPoolSize}" />
- <param name="cacheSize" value="${searchCacheSize}" />
- <param name="maxVolatileIndexSize"
- value="${maxVolatileIndexSize}" />
- </SearchIndex>
- <WorkspaceSecurity>
- <AccessControlProvider
- class="org.argeo.security.jackrabbit.ArgeoAccessControlProvider" />
- </WorkspaceSecurity>
- </Workspace>
-
- <!-- Versioning -->
- <Versioning rootPath="${rep.home}/version">
- <FileSystem
- class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
- <param name="dataSourceName" value="dataSource" />
- <param name="schema" value="postgresql" />
- <param name="schemaObjectPrefix" value="fs_ver_" />
- </FileSystem>
- <PersistenceManager
- class="org.apache.jackrabbit.core.persistence.pool.PostgreSQLPersistenceManager">
- <param name="dataSourceName" value="dataSource" />
- <param name="schemaObjectPrefix" value="pm_ver_" />
- <param name="bundleCacheSize" value="${bundleCacheMB}" />
- </PersistenceManager>
- </Versioning>
-
- <!-- Indexing -->
- <SearchIndex
- class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
- <param name="path" value="${indexesBase}/${cn}/index" />
- <param name="extractorPoolSize" value="${extractorPoolSize}" />
- <param name="cacheSize" value="${searchCacheSize}" />
- <param name="maxVolatileIndexSize"
- value="${maxVolatileIndexSize}" />
- </SearchIndex>
-
- <!-- Security -->
- <Security appName="Jackrabbit">
- <SecurityManager
- class="org.argeo.security.jackrabbit.ArgeoSecurityManager"
- workspaceName="security" />
- <AccessManager
- class="org.argeo.security.jackrabbit.ArgeoAccessManager" />
- </Security>
-
- <!-- Clustering -->
- <Cluster id="${clusterId}" syncDelay="100">
- <Journal
- class="org.apache.jackrabbit.core.journal.DatabaseJournal">
- <param name="dataSourceName" value="dataSource" />
- <param name="schemaObjectPrefix" value="journal_" />
- </Journal>
- </Cluster>
-</Repository>
\ No newline at end of file
+++ /dev/null
-<?xml version="1.0"?>
-<!DOCTYPE Repository PUBLIC "Jackrabbit 2.6" "http://jackrabbit.apache.org/dtd/repository-2.6.dtd">
-<Repository>
- <!-- Shared datasource -->
- <DataSources>
- <DataSource name="dataSource">
- <param name="driver" value="org.postgresql.Driver" />
- <param name="url" value="${dburl}" />
- <param name="user" value="${dbuser}" />
- <param name="password" value="${dbpassword}" />
- <param name="databaseType" value="postgresql" />
- <param name="maxPoolSize" value="${maxPoolSize}" />
- </DataSource>
- </DataSources>
-
- <!-- File system and datastore -->
- <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
- <param name="dataSourceName" value="dataSource" />
- <param name="schema" value="postgresql" />
- <param name="schemaObjectPrefix" value="fs_" />
- </FileSystem>
- <DataStore class="org.apache.jackrabbit.core.data.FileDataStore">
- <param name="path" value="${rep.home}/datastore" />
- </DataStore>
-
- <!-- Workspace templates -->
- <Workspaces rootPath="${rep.home}/workspaces"
- defaultWorkspace="${defaultWorkspace}" />
- <Workspace name="${wsp.name}">
- <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
- <param name="dataSourceName" value="dataSource" />
- <param name="schema" value="postgresql" />
- <param name="schemaObjectPrefix" value="${wsp.name}_fs_" />
- </FileSystem>
- <PersistenceManager
- class="org.apache.jackrabbit.core.persistence.pool.PostgreSQLPersistenceManager">
- <param name="dataSourceName" value="dataSource" />
- <param name="schemaObjectPrefix" value="${wsp.name}_pm_" />
- <param name="bundleCacheSize" value="${bundleCacheMB}" />
- </PersistenceManager>
- <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
- <param name="path" value="${indexesBase}/${cn}/${wsp.name}/index" />
- <param name="extractorPoolSize" value="${extractorPoolSize}" />
- <param name="cacheSize" value="${searchCacheSize}" />
- <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
- </SearchIndex>
- <WorkspaceSecurity>
- <AccessControlProvider
- class="org.argeo.security.jackrabbit.ArgeoAccessControlProvider" />
- </WorkspaceSecurity>
- </Workspace>
-
- <!-- Versioning -->
- <Versioning rootPath="${rep.home}/version">
- <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
- <param name="dataSourceName" value="dataSource" />
- <param name="schema" value="postgresql" />
- <param name="schemaObjectPrefix" value="fs_ver_" />
- </FileSystem>
- <PersistenceManager
- class="org.apache.jackrabbit.core.persistence.pool.PostgreSQLPersistenceManager">
- <param name="dataSourceName" value="dataSource" />
- <param name="schemaObjectPrefix" value="pm_ver_" />
- <param name="bundleCacheSize" value="${bundleCacheMB}" />
- </PersistenceManager>
- </Versioning>
-
- <!-- Indexing -->
- <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
- <param name="path" value="${indexesBase}/${cn}/index" />
- <param name="extractorPoolSize" value="${extractorPoolSize}" />
- <param name="cacheSize" value="${searchCacheSize}" />
- <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
- </SearchIndex>
-
- <!-- Security -->
- <Security appName="Jackrabbit">
- <SecurityManager class="org.argeo.security.jackrabbit.ArgeoSecurityManager"
- workspaceName="security" />
- <AccessManager class="org.argeo.security.jackrabbit.ArgeoAccessManager" />
- </Security>
-</Repository>
\ No newline at end of file
+++ /dev/null
-package org.argeo.cms.jcr;
-
-import java.security.PrivilegedAction;
-import java.util.HashMap;
-import java.util.Map;
-
-import javax.jcr.NoSuchWorkspaceException;
-import javax.jcr.Node;
-import javax.jcr.Property;
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.RepositoryFactory;
-import javax.jcr.Session;
-import javax.naming.InvalidNameException;
-import javax.naming.ldap.LdapName;
-import javax.security.auth.AuthPermission;
-import javax.security.auth.Subject;
-import javax.security.auth.login.LoginContext;
-import javax.security.auth.login.LoginException;
-
-import org.argeo.api.cms.CmsAuth;
-import org.argeo.api.cms.CmsConstants;
-
-/** Utilities related to Argeo model in JCR */
-public class CmsJcrUtils {
- /**
- * Wraps the call to the repository factory based on parameter
- * {@link CmsConstants#CN} in order to simplify it and protect against future
- * API changes.
- */
- public static Repository getRepositoryByAlias(RepositoryFactory repositoryFactory, String alias) {
- try {
- Map<String, String> parameters = new HashMap<String, String>();
- parameters.put(CmsConstants.CN, alias);
- return repositoryFactory.getRepository(parameters);
- } catch (RepositoryException e) {
- throw new RuntimeException("Unexpected exception when trying to retrieve repository with alias " + alias,
- e);
- }
- }
-
- /**
- * Wraps the call to the repository factory based on parameter
- * {@link CmsConstants#LABELED_URI} in order to simplify it and protect against
- * future API changes.
- */
- public static Repository getRepositoryByUri(RepositoryFactory repositoryFactory, String uri) {
- return getRepositoryByUri(repositoryFactory, uri, null);
- }
-
- /**
- * Wraps the call to the repository factory based on parameter
- * {@link CmsConstants#LABELED_URI} in order to simplify it and protect against
- * future API changes.
- */
- public static Repository getRepositoryByUri(RepositoryFactory repositoryFactory, String uri, String alias) {
- try {
- Map<String, String> parameters = new HashMap<String, String>();
- parameters.put(CmsConstants.LABELED_URI, uri);
- if (alias != null)
- parameters.put(CmsConstants.CN, alias);
- return repositoryFactory.getRepository(parameters);
- } catch (RepositoryException e) {
- throw new RuntimeException("Unexpected exception when trying to retrieve repository with uri " + uri, e);
- }
- }
-
- /**
- * Returns the home node of the user or null if none was found.
- *
- * @param session the session to use in order to perform the search, this can
- * be a session with a different user ID than the one searched,
- * typically when a system or admin session is used.
- * @param username the username of the user
- */
- public static Node getUserHome(Session session, String username) {
-// try {
-// QueryObjectModelFactory qomf = session.getWorkspace().getQueryManager().getQOMFactory();
-// Selector sel = qomf.selector(NodeTypes.NODE_USER_HOME, "sel");
-// DynamicOperand dop = qomf.propertyValue(sel.getSelectorName(), NodeNames.LDAP_UID);
-// StaticOperand sop = qomf.literal(session.getValueFactory().createValue(username));
-// Constraint constraint = qomf.comparison(dop, QueryObjectModelFactory.JCR_OPERATOR_EQUAL_TO, sop);
-// Query query = qomf.createQuery(sel, constraint, null, null);
-// return querySingleNode(query);
-// } catch (RepositoryException e) {
-// throw new RuntimeException("Cannot find home for user " + username, e);
-// }
-
- try {
- checkUserWorkspace(session, username);
- String homePath = getHomePath(username);
- if (session.itemExists(homePath))
- return session.getNode(homePath);
- // legacy
- homePath = "/home/" + username;
- if (session.itemExists(homePath))
- return session.getNode(homePath);
- return null;
- } catch (RepositoryException e) {
- throw new RuntimeException("Cannot find home for user " + username, e);
- }
- }
-
- private static String getHomePath(String username) {
- LdapName dn;
- try {
- dn = new LdapName(username);
- } catch (InvalidNameException e) {
- throw new IllegalArgumentException("Invalid name " + username, e);
- }
- String userId = dn.getRdn(dn.size() - 1).getValue().toString();
- return '/' + userId;
- }
-
- private static void checkUserWorkspace(Session session, String username) {
- String workspaceName = session.getWorkspace().getName();
- if (!CmsConstants.HOME_WORKSPACE.equals(workspaceName))
- throw new IllegalArgumentException(workspaceName + " is not the home workspace for user " + username);
- }
-
- /**
- * Returns the home node of the user or null if none was found.
- *
- * @param session the session to use in order to perform the search, this can
- * be a session with a different user ID than the one searched,
- * typically when a system or admin session is used.
- * @param groupname the name of the group
- */
- public static Node getGroupHome(Session session, String groupname) {
-// try {
-// QueryObjectModelFactory qomf = session.getWorkspace().getQueryManager().getQOMFactory();
-// Selector sel = qomf.selector(NodeTypes.NODE_GROUP_HOME, "sel");
-// DynamicOperand dop = qomf.propertyValue(sel.getSelectorName(), NodeNames.LDAP_CN);
-// StaticOperand sop = qomf.literal(session.getValueFactory().createValue(cn));
-// Constraint constraint = qomf.comparison(dop, QueryObjectModelFactory.JCR_OPERATOR_EQUAL_TO, sop);
-// Query query = qomf.createQuery(sel, constraint, null, null);
-// return querySingleNode(query);
-// } catch (RepositoryException e) {
-// throw new RuntimeException("Cannot find home for group " + cn, e);
-// }
-
- try {
- checkGroupWorkspace(session, groupname);
- String homePath = getGroupPath(groupname);
- if (session.itemExists(homePath))
- return session.getNode(homePath);
- // legacy
- homePath = "/groups/" + groupname;
- if (session.itemExists(homePath))
- return session.getNode(homePath);
- return null;
- } catch (RepositoryException e) {
- throw new RuntimeException("Cannot find home for group " + groupname, e);
- }
-
- }
-
- private static String getGroupPath(String groupname) {
- String cn;
- try {
- LdapName dn = new LdapName(groupname);
- cn = dn.getRdn(dn.size() - 1).getValue().toString();
- } catch (InvalidNameException e) {
- cn = groupname;
- }
- return '/' + cn;
- }
-
- private static void checkGroupWorkspace(Session session, String groupname) {
- String workspaceName = session.getWorkspace().getName();
- if (!CmsConstants.SRV_WORKSPACE.equals(workspaceName))
- throw new IllegalArgumentException(workspaceName + " is not the group workspace for group " + groupname);
- }
-
- /**
- * Queries one single node.
- *
- * @return one single node or null if none was found
- * @throws ArgeoJcrException if more than one node was found
- */
-// private static Node querySingleNode(Query query) {
-// NodeIterator nodeIterator;
-// try {
-// QueryResult queryResult = query.execute();
-// nodeIterator = queryResult.getNodes();
-// } catch (RepositoryException e) {
-// throw new RuntimeException("Cannot execute query " + query, e);
-// }
-// Node node;
-// if (nodeIterator.hasNext())
-// node = nodeIterator.nextNode();
-// else
-// return null;
-//
-// if (nodeIterator.hasNext())
-// throw new RuntimeException("Query returned more than one node.");
-// return node;
-// }
-
- /** Returns the home node of the session user or null if none was found. */
- public static Node getUserHome(Session session) {
- String userID = session.getUserID();
- return getUserHome(session, userID);
- }
-
- /** Whether this node is the home of the user of the underlying session. */
- public static boolean isUserHome(Node node) {
- try {
- String userID = node.getSession().getUserID();
- return node.hasProperty(Property.JCR_ID) && node.getProperty(Property.JCR_ID).getString().equals(userID);
- } catch (RepositoryException e) {
- throw new IllegalStateException(e);
- }
- }
-
- /**
- * Translate the path to this node into a path containing the name of the
- * repository and the name of the workspace.
- */
- public static String getDataPath(String cn, Node node) {
- assert node != null;
- StringBuilder buf = new StringBuilder(CmsConstants.PATH_DATA);
- try {
- return buf.append('/').append(cn).append('/').append(node.getSession().getWorkspace().getName())
- .append(node.getPath()).toString();
- } catch (RepositoryException e) {
- throw new IllegalStateException("Cannot get data path for " + node + " in repository " + cn, e);
- }
- }
-
- /**
- * Translate the path to this node into a path containing the name of the
- * repository and the name of the workspace.
- */
- public static String getDataPath(Node node) {
- return getDataPath(CmsConstants.NODE, node);
- }
-
- /**
- * Open a JCR session with full read/write rights on the data, as
- * {@link CmsConstants#ROLE_USER_ADMIN}, using the
- * {@link CmsAuth#LOGIN_CONTEXT_DATA_ADMIN} login context. For security
- * hardened deployement, use {@link AuthPermission} on this login context.
- */
- public static Session openDataAdminSession(Repository repository, String workspaceName) {
- ClassLoader currentCl = Thread.currentThread().getContextClassLoader();
- LoginContext loginContext;
- try {
- loginContext = new LoginContext(CmsAuth.LOGIN_CONTEXT_DATA_ADMIN);
- loginContext.login();
- } catch (LoginException e1) {
- throw new RuntimeException("Could not login as data admin", e1);
- } finally {
- Thread.currentThread().setContextClassLoader(currentCl);
- }
- return Subject.doAs(loginContext.getSubject(), new PrivilegedAction<Session>() {
-
- @Override
- public Session run() {
- try {
- return repository.login(workspaceName);
- } catch (NoSuchWorkspaceException e) {
- throw new IllegalArgumentException("No workspace " + workspaceName + " available", e);
- } catch (RepositoryException e) {
- throw new RuntimeException("Cannot open data admin session", e);
- }
- }
-
- });
- }
-
- /** Singleton. */
- private CmsJcrUtils() {
- }
-
-}
+++ /dev/null
-package org.argeo.cms.jcr.acr;
-
-import java.util.Calendar;
-import java.util.Iterator;
-import java.util.Optional;
-
-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 javax.jcr.nodetype.NodeType;
-import javax.xml.namespace.QName;
-
-import org.argeo.api.acr.Content;
-import org.argeo.api.acr.spi.AbstractContent;
-import org.argeo.api.acr.spi.ProvidedSession;
-import org.argeo.jcr.Jcr;
-import org.argeo.jcr.JcrException;
-
-public class JcrContent extends AbstractContent {
- private Node jcrNode;
-
- private JcrContentProvider provider;
- private ProvidedSession session;
-
- protected JcrContent(ProvidedSession session, JcrContentProvider provider, Node node) {
- this.session = session;
- this.provider = provider;
- this.jcrNode = node;
- }
-
- @Override
- public QName getName() {
- return session.parsePrefixedName(Jcr.getName(jcrNode));
- }
-
- @Override
- public <A> Optional<A> get(QName key, Class<A> clss) {
- if (isDefaultAttrTypeRequested(clss)) {
- return Optional.of((A) get(jcrNode, key.toString()));
- }
- return Optional.of((A) Jcr.get(jcrNode, key.toString()));
- }
-
- @Override
- public Iterator<Content> iterator() {
- try {
- return new JcrContentIterator(jcrNode.getNodes());
- } catch (RepositoryException e) {
- throw new JcrException("Cannot list children of " + jcrNode, e);
- }
- }
-
- @Override
- protected Iterable<QName> keys() {
- return new Iterable<QName>() {
-
- @Override
- public Iterator<QName> iterator() {
- try {
- PropertyIterator propertyIterator = jcrNode.getProperties();
- return new JcrKeyIterator(provider, propertyIterator);
- } catch (RepositoryException e) {
- throw new JcrException("Cannot retrive properties from " + jcrNode, e);
- }
- }
- };
- }
-
- public Node getJcrNode() {
- return jcrNode;
- }
-
- /** Cast to a standard Java object. */
- static Object get(Node node, String property) {
- try {
- Value value = node.getProperty(property).getValue();
- switch (value.getType()) {
- case PropertyType.STRING:
- return value.getString();
- case PropertyType.DOUBLE:
- return (Double) value.getDouble();
- case PropertyType.LONG:
- return (Long) value.getLong();
- case PropertyType.BOOLEAN:
- return (Boolean) value.getBoolean();
- case PropertyType.DATE:
- Calendar calendar = value.getDate();
- return calendar.toInstant();
- case PropertyType.BINARY:
- throw new IllegalArgumentException("Binary is not supported as an attribute");
- default:
- return value.getString();
- }
- } catch (RepositoryException e) {
- throw new JcrException("Cannot cast value from " + property + " of node " + node, e);
- }
- }
-
- class JcrContentIterator implements Iterator<Content> {
- private final NodeIterator nodeIterator;
- // we keep track in order to be able to delete it
- private JcrContent current = null;
-
- protected JcrContentIterator(NodeIterator nodeIterator) {
- this.nodeIterator = nodeIterator;
- }
-
- @Override
- public boolean hasNext() {
- return nodeIterator.hasNext();
- }
-
- @Override
- public Content next() {
- current = new JcrContent(session, provider, nodeIterator.nextNode());
- return current;
- }
-
- @Override
- public void remove() {
- if (current != null) {
- Jcr.remove(current.getJcrNode());
- }
- }
-
- }
-
- @Override
- public Content getParent() {
- return new JcrContent(session, provider, Jcr.getParent(getJcrNode()));
- }
-
- @Override
- public Content add(QName name, QName... classes) {
- if (classes.length > 0) {
- QName primaryType = classes[0];
- Node child = Jcr.addNode(getJcrNode(), name.toString(), primaryType.toString());
- for (int i = 1; i < classes.length; i++) {
- try {
- child.addMixin(classes[i].toString());
- } catch (RepositoryException e) {
- throw new JcrException("Cannot add child to " + getJcrNode(), e);
- }
- }
-
- } else {
- Jcr.addNode(getJcrNode(), name.toString(), NodeType.NT_UNSTRUCTURED);
- }
- return null;
- }
-
- @Override
- public void remove() {
- Jcr.remove(getJcrNode());
- }
-
- @Override
- protected void removeAttr(QName key) {
- Property property = Jcr.getProperty(getJcrNode(), key.toString());
- if (property != null) {
- try {
- property.remove();
- } catch (RepositoryException e) {
- throw new JcrException("Cannot remove property " + key + " from " + getJcrNode(), e);
- }
- }
-
- }
-
- class JcrKeyIterator implements Iterator<QName> {
- private final JcrContentProvider contentSession;
- private final PropertyIterator propertyIterator;
-
- protected JcrKeyIterator(JcrContentProvider contentSession, PropertyIterator propertyIterator) {
- this.contentSession = contentSession;
- this.propertyIterator = propertyIterator;
- }
-
- @Override
- public boolean hasNext() {
- return propertyIterator.hasNext();
- }
-
- @Override
- public QName next() {
- Property property = null;
- try {
- property = propertyIterator.nextProperty();
- // TODO map standard property names
- return session.parsePrefixedName(property.getName());
- } catch (RepositoryException e) {
- throw new JcrException("Cannot retrieve property " + property, null);
- }
- }
-
- }
-}
+++ /dev/null
-package org.argeo.cms.jcr.acr;
-
-import java.util.Arrays;
-import java.util.Iterator;
-
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.xml.namespace.NamespaceContext;
-
-import org.argeo.api.acr.Content;
-import org.argeo.api.acr.spi.ContentProvider;
-import org.argeo.api.acr.spi.ProvidedSession;
-import org.argeo.cms.jcr.CmsJcrUtils;
-import org.argeo.jcr.JcrException;
-import org.argeo.jcr.JcrUtils;
-
-public class JcrContentProvider implements ContentProvider, NamespaceContext {
- private Repository jcrRepository;
- private Session adminSession;
-
- public void init() {
- adminSession = CmsJcrUtils.openDataAdminSession(jcrRepository, null);
- }
-
- public void destroy() {
- JcrUtils.logoutQuietly(adminSession);
- }
-
- public void setJcrRepository(Repository jcrRepository) {
- this.jcrRepository = jcrRepository;
- }
-
- @Override
- public Content get(ProvidedSession session, String mountPath, String relativePath) {
- // TODO Auto-generated method stub
- return null;
- }
-
- /*
- * NAMESPACE CONTEXT
- */
- @Override
- public String getNamespaceURI(String prefix) {
- try {
- return adminSession.getNamespaceURI(prefix);
- } catch (RepositoryException e) {
- throw new JcrException(e);
- }
- }
-
- @Override
- public String getPrefix(String namespaceURI) {
- try {
- return adminSession.getNamespacePrefix(namespaceURI);
- } catch (RepositoryException e) {
- throw new JcrException(e);
- }
- }
-
- @Override
- public Iterator<String> getPrefixes(String namespaceURI) {
- try {
- return Arrays.asList(adminSession.getNamespacePrefix(namespaceURI)).iterator();
- } catch (RepositoryException e) {
- throw new JcrException(e);
- }
- }
-
-}
+++ /dev/null
-<argeo = 'http://www.argeo.org/ns/argeo'>
-
-// GENERIC TYPES
-[argeo:remoteRepository] > nt:unstructured
-- argeo:uri (STRING)
-- argeo:userID (STRING)
-+ argeo:password (argeo:encrypted)
-
-// TABULAR CONTENT
-[argeo:table] > nt:file
-+ * (argeo:column) *
-
-[argeo:column] > mix:title
-- jcr:requiredType (STRING) = 'STRING'
-
-[argeo:csv] > nt:resource
-
-// CRYPTO
-[argeo:encrypted]
-mixin
-// initialization vector used by some algorithms
-- argeo:iv (BINARY)
-
-[argeo:pbeKeySpec]
-mixin
-- argeo:secretKeyFactory (STRING)
-- argeo:salt (BINARY)
-- argeo:iterationCount (LONG)
-- argeo:keyLength (LONG)
-- argeo:secretKeyEncryption (STRING)
-
-[argeo:pbeSpec] > argeo:pbeKeySpec
-mixin
-- argeo:cipher (STRING)
+++ /dev/null
-// DN (see https://tools.ietf.org/html/rfc4514)
-<cn = 'http://www.argeo.org/ns/rfc4514/cn'>
-<l = 'http://www.argeo.org/ns/rfc4514/l'>
-<st = 'http://www.argeo.org/ns/rfc4514/st'>
-<o = 'http://www.argeo.org/ns/rfc4514/o'>
-<ou = 'http://www.argeo.org/ns/rfc4514/ou'>
-<c = 'http://www.argeo.org/ns/rfc4514/c'>
-<street = 'http://www.argeo.org/ns/rfc4514/street'>
-<dc = 'http://www.argeo.org/ns/rfc4514/dc'>
-<uid = 'http://www.argeo.org/ns/rfc4514/uid'>
+++ /dev/null
-package org.argeo.cms.jcr.internal;
-
-import static org.argeo.cms.osgi.DataModelNamespace.CMS_DATA_MODEL_NAMESPACE;
-import static org.osgi.service.http.whiteboard.HttpWhiteboardConstants.HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.Reader;
-import java.net.URL;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.Hashtable;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.security.auth.callback.CallbackHandler;
-import javax.servlet.Servlet;
-
-import org.apache.jackrabbit.commons.cnd.CndImporter;
-import org.apache.jackrabbit.core.RepositoryContext;
-import org.apache.jackrabbit.core.RepositoryImpl;
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.api.cms.CmsDeployment;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.cms.ArgeoNames;
-import org.argeo.cms.internal.jcr.JcrInitUtils;
-import org.argeo.cms.jcr.CmsJcrUtils;
-import org.argeo.cms.jcr.internal.servlet.CmsRemotingServlet;
-import org.argeo.cms.jcr.internal.servlet.CmsWebDavServlet;
-import org.argeo.cms.jcr.internal.servlet.JcrHttpUtils;
-import org.argeo.cms.osgi.DataModelNamespace;
-import org.argeo.cms.security.CryptoKeyring;
-import org.argeo.cms.security.Keyring;
-import org.argeo.jcr.Jcr;
-import org.argeo.jcr.JcrException;
-import org.argeo.jcr.JcrUtils;
-import org.argeo.util.LangUtils;
-import org.argeo.util.naming.LdapAttrs;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.Constants;
-import org.osgi.framework.FrameworkUtil;
-import org.osgi.framework.InvalidSyntaxException;
-import org.osgi.framework.ServiceReference;
-import org.osgi.framework.wiring.BundleCapability;
-import org.osgi.framework.wiring.BundleWire;
-import org.osgi.framework.wiring.BundleWiring;
-import org.osgi.service.cm.ManagedService;
-import org.osgi.service.http.whiteboard.HttpWhiteboardConstants;
-import org.osgi.util.tracker.ServiceTracker;
-
-/** Implementation of a CMS deployment. */
-public class CmsJcrDeployment {
- private final CmsLog log = CmsLog.getLog(getClass());
- private final BundleContext bc = FrameworkUtil.getBundle(getClass()).getBundleContext();
-
- private DataModels dataModels;
- private String webDavConfig = JcrHttpUtils.WEBDAV_CONFIG;
-
- private boolean argeoDataModelExtensionsAvailable = false;
-
- // Readiness
- private boolean nodeAvailable = false;
-
- CmsDeployment cmsDeployment;
-
- public CmsJcrDeployment() {
-// initTrackers();
- }
-
- public void start() {
- dataModels = new DataModels(bc);
-
- ServiceTracker<?, ?> repoContextSt = new RepositoryContextStc();
- repoContextSt.open();
- //KernelUtils.asyncOpen(repoContextSt);
-
-// nodeDeployment = CmsJcrActivator.getService(NodeDeployment.class);
-
- JcrInitUtils.addToDeployment(cmsDeployment);
-
- }
-
- public void stop() {
-// if (nodeHttp != null)
-// nodeHttp.destroy();
-
- try {
- for (ServiceReference<JackrabbitLocalRepository> sr : bc
- .getServiceReferences(JackrabbitLocalRepository.class, null)) {
- bc.getService(sr).destroy();
- }
- } catch (InvalidSyntaxException e1) {
- log.error("Cannot clean repositories", e1);
- }
-
- }
-
- public void setCmsDeployment(CmsDeployment cmsDeployment) {
- this.cmsDeployment = cmsDeployment;
- }
-
- /**
- * Checks whether the deployment is available according to expectations, and
- * mark it as available.
- */
-// private synchronized void checkReadiness() {
-// if (isAvailable())
-// return;
-// if (nodeAvailable && userAdminAvailable && (httpExpected ? httpAvailable : true)) {
-// String data = KernelUtils.getFrameworkProp(KernelUtils.OSGI_INSTANCE_AREA);
-// String state = KernelUtils.getFrameworkProp(KernelUtils.OSGI_CONFIGURATION_AREA);
-// availableSince = System.currentTimeMillis();
-// long jvmUptime = ManagementFactory.getRuntimeMXBean().getUptime();
-// String jvmUptimeStr = " in " + (jvmUptime / 1000) + "." + (jvmUptime % 1000) + "s";
-// log.info("## ARGEO NODE AVAILABLE" + (log.isDebugEnabled() ? jvmUptimeStr : "") + " ##");
-// if (log.isDebugEnabled()) {
-// log.debug("## state: " + state);
-// if (data != null)
-// log.debug("## data: " + data);
-// }
-// long begin = bc.getService(bc.getServiceReference(NodeState.class)).getAvailableSince();
-// long initDuration = System.currentTimeMillis() - begin;
-// if (log.isTraceEnabled())
-// log.trace("Kernel initialization took " + initDuration + "ms");
-// tributeToFreeSoftware(initDuration);
-// }
-// }
-
- private void prepareNodeRepository(Repository deployedNodeRepository, List<String> publishAsLocalRepo) {
-// if (availableSince != null) {
-// throw new IllegalStateException("Deployment is already available");
-// }
-
- // home
- prepareDataModel(CmsConstants.NODE_REPOSITORY, deployedNodeRepository, publishAsLocalRepo);
-
- // init from backup
-// if (deployConfig.isFirstInit()) {
-// Path restorePath = Paths.get(System.getProperty("user.dir"), "restore");
-// if (Files.exists(restorePath)) {
-// if (log.isDebugEnabled())
-// log.debug("Found backup " + restorePath + ", restoring it...");
-// LogicalRestore logicalRestore = new LogicalRestore(bc, deployedNodeRepository, restorePath);
-// KernelUtils.doAsDataAdmin(logicalRestore);
-// log.info("Restored backup from " + restorePath);
-// }
-// }
-
- // init from repository
- Collection<ServiceReference<Repository>> initRepositorySr;
- try {
- initRepositorySr = bc.getServiceReferences(Repository.class,
- "(" + CmsConstants.CN + "=" + CmsConstants.NODE_INIT + ")");
- } catch (InvalidSyntaxException e1) {
- throw new IllegalArgumentException(e1);
- }
- Iterator<ServiceReference<Repository>> it = initRepositorySr.iterator();
- while (it.hasNext()) {
- ServiceReference<Repository> sr = it.next();
- Object labeledUri = sr.getProperties().get(LdapAttrs.labeledURI.name());
- Repository initRepository = bc.getService(sr);
- if (log.isDebugEnabled())
- log.debug("Found init repository " + labeledUri + ", copying it...");
- initFromRepository(deployedNodeRepository, initRepository);
- log.info("Node repository initialised from " + labeledUri);
- }
- }
-
- /** Init from a (typically remote) repository. */
- private void initFromRepository(Repository deployedNodeRepository, Repository initRepository) {
- Session initSession = null;
- try {
- initSession = initRepository.login();
- workspaces: for (String workspaceName : initSession.getWorkspace().getAccessibleWorkspaceNames()) {
- if ("security".equals(workspaceName))
- continue workspaces;
- if (log.isDebugEnabled())
- log.debug("Copying workspace " + workspaceName + " from init repository...");
- long begin = System.currentTimeMillis();
- Session targetSession = null;
- Session sourceSession = null;
- try {
- try {
- targetSession = CmsJcrUtils.openDataAdminSession(deployedNodeRepository, workspaceName);
- } catch (IllegalArgumentException e) {// no such workspace
- Session adminSession = CmsJcrUtils.openDataAdminSession(deployedNodeRepository, null);
- try {
- adminSession.getWorkspace().createWorkspace(workspaceName);
- } finally {
- Jcr.logout(adminSession);
- }
- targetSession = CmsJcrUtils.openDataAdminSession(deployedNodeRepository, workspaceName);
- }
- sourceSession = initRepository.login(workspaceName);
-// JcrUtils.copyWorkspaceXml(sourceSession, targetSession);
- // TODO deal with referenceable nodes
- JcrUtils.copy(sourceSession.getRootNode(), targetSession.getRootNode());
- targetSession.save();
- long duration = System.currentTimeMillis() - begin;
- if (log.isDebugEnabled())
- log.debug("Copied workspace " + workspaceName + " from init repository in " + (duration / 1000)
- + " s");
- } catch (Exception e) {
- log.error("Cannot copy workspace " + workspaceName + " from init repository.", e);
- } finally {
- Jcr.logout(sourceSession);
- Jcr.logout(targetSession);
- }
- }
- } catch (RepositoryException e) {
- throw new JcrException(e);
- } finally {
- Jcr.logout(initSession);
- }
- }
-
- private void prepareHomeRepository(RepositoryImpl deployedRepository) {
- Session adminSession = KernelUtils.openAdminSession(deployedRepository);
- try {
- argeoDataModelExtensionsAvailable = Arrays
- .asList(adminSession.getWorkspace().getNamespaceRegistry().getURIs())
- .contains(ArgeoNames.ARGEO_NAMESPACE);
- } catch (RepositoryException e) {
- log.warn("Cannot check whether Argeo namespace is registered assuming it isn't.", e);
- argeoDataModelExtensionsAvailable = false;
- } finally {
- JcrUtils.logoutQuietly(adminSession);
- }
-
- // Publish home with the highest service ranking
- Hashtable<String, Object> regProps = new Hashtable<>();
- regProps.put(CmsConstants.CN, CmsConstants.EGO_REPOSITORY);
- regProps.put(Constants.SERVICE_RANKING, Integer.MAX_VALUE);
- Repository egoRepository = new EgoRepository(deployedRepository, false);
- bc.registerService(Repository.class, egoRepository, regProps);
- registerRepositoryServlets(CmsConstants.EGO_REPOSITORY, egoRepository);
-
- // Keyring only if Argeo extensions are available
- if (argeoDataModelExtensionsAvailable) {
- new ServiceTracker<CallbackHandler, CallbackHandler>(bc, CallbackHandler.class, null) {
-
- @Override
- public CallbackHandler addingService(ServiceReference<CallbackHandler> reference) {
- NodeKeyRing nodeKeyring = new NodeKeyRing(egoRepository);
- CallbackHandler callbackHandler = bc.getService(reference);
- nodeKeyring.setDefaultCallbackHandler(callbackHandler);
- bc.registerService(LangUtils.names(Keyring.class, CryptoKeyring.class, ManagedService.class),
- nodeKeyring, LangUtils.dict(Constants.SERVICE_PID, CmsConstants.NODE_KEYRING_PID));
- return callbackHandler;
- }
-
- }.open();
- }
- }
-
- /** Session is logged out. */
- private void prepareDataModel(String cn, Repository repository, List<String> publishAsLocalRepo) {
- Session adminSession = KernelUtils.openAdminSession(repository);
- try {
- Set<String> processed = new HashSet<String>();
- bundles: for (Bundle bundle : bc.getBundles()) {
- BundleWiring wiring = bundle.adapt(BundleWiring.class);
- if (wiring == null)
- continue bundles;
- if (CmsConstants.NODE_REPOSITORY.equals(cn))// process all data models
- processWiring(cn, adminSession, wiring, processed, false, publishAsLocalRepo);
- else {
- List<BundleCapability> capabilities = wiring.getCapabilities(CMS_DATA_MODEL_NAMESPACE);
- for (BundleCapability capability : capabilities) {
- String dataModelName = (String) capability.getAttributes().get(DataModelNamespace.NAME);
- if (dataModelName.equals(cn))// process only own data model
- processWiring(cn, adminSession, wiring, processed, false, publishAsLocalRepo);
- }
- }
- }
- } finally {
- JcrUtils.logoutQuietly(adminSession);
- }
- }
-
- private void processWiring(String cn, Session adminSession, BundleWiring wiring, Set<String> processed,
- boolean importListedAbstractModels, List<String> publishAsLocalRepo) {
- // recursively process requirements first
- List<BundleWire> requiredWires = wiring.getRequiredWires(CMS_DATA_MODEL_NAMESPACE);
- for (BundleWire wire : requiredWires) {
- processWiring(cn, adminSession, wire.getProviderWiring(), processed, true, publishAsLocalRepo);
- }
-
- List<BundleCapability> capabilities = wiring.getCapabilities(CMS_DATA_MODEL_NAMESPACE);
- capabilities: for (BundleCapability capability : capabilities) {
- if (!importListedAbstractModels
- && KernelUtils.asBoolean((String) capability.getAttributes().get(DataModelNamespace.ABSTRACT))) {
- continue capabilities;
- }
- boolean publish = registerDataModelCapability(cn, adminSession, capability, processed);
- if (publish)
- publishAsLocalRepo.add((String) capability.getAttributes().get(DataModelNamespace.NAME));
- }
- }
-
- private boolean registerDataModelCapability(String cn, Session adminSession, BundleCapability capability,
- Set<String> processed) {
- Map<String, Object> attrs = capability.getAttributes();
- String name = (String) attrs.get(DataModelNamespace.NAME);
- if (processed.contains(name)) {
- if (log.isTraceEnabled())
- log.trace("Data model " + name + " has already been processed");
- return false;
- }
-
- // CND
- String path = (String) attrs.get(DataModelNamespace.CND);
- if (path != null) {
- File dataModel = bc.getBundle().getDataFile("dataModels/" + path);
- if (!dataModel.exists()) {
- URL url = capability.getRevision().getBundle().getResource(path);
- if (url == null)
- throw new IllegalArgumentException("No data model '" + name + "' found under path " + path);
- try (Reader reader = new InputStreamReader(url.openStream())) {
- CndImporter.registerNodeTypes(reader, adminSession, true);
- processed.add(name);
- dataModel.getParentFile().mkdirs();
- dataModel.createNewFile();
- if (log.isDebugEnabled())
- log.debug("Registered CND " + url);
- } catch (Exception e) {
- log.error("Cannot import CND " + url, e);
- }
- }
- }
-
- if (KernelUtils.asBoolean((String) attrs.get(DataModelNamespace.ABSTRACT)))
- return false;
- // Non abstract
- boolean isStandalone = isStandalone(name);
- boolean publishLocalRepo;
- if (isStandalone && name.equals(cn))// includes the node itself
- publishLocalRepo = true;
- else if (!isStandalone && cn.equals(CmsConstants.NODE_REPOSITORY))
- publishLocalRepo = true;
- else
- publishLocalRepo = false;
-
- return publishLocalRepo;
- }
-
- boolean isStandalone(String dataModelName) {
- return cmsDeployment.getProps(CmsConstants.NODE_REPOS_FACTORY_PID, dataModelName) != null;
- }
-
- private void publishLocalRepo(String dataModelName, Repository repository) {
- Hashtable<String, Object> properties = new Hashtable<>();
- properties.put(CmsConstants.CN, dataModelName);
- LocalRepository localRepository;
- String[] classes;
- if (repository instanceof RepositoryImpl) {
- localRepository = new JackrabbitLocalRepository((RepositoryImpl) repository, dataModelName);
- classes = new String[] { Repository.class.getName(), LocalRepository.class.getName(),
- JackrabbitLocalRepository.class.getName() };
- } else {
- localRepository = new LocalRepository(repository, dataModelName);
- classes = new String[] { Repository.class.getName(), LocalRepository.class.getName() };
- }
- bc.registerService(classes, localRepository, properties);
-
- // TODO make it configurable
- registerRepositoryServlets(dataModelName, localRepository);
- if (log.isTraceEnabled())
- log.trace("Published data model " + dataModelName);
- }
-
-// @Override
-// public synchronized Long getAvailableSince() {
-// return availableSince;
-// }
-//
-// public synchronized boolean isAvailable() {
-// return availableSince != null;
-// }
-
- protected void registerRepositoryServlets(String alias, Repository repository) {
- // FIXME re-enable it with a proper class loader
-// registerRemotingServlet(alias, repository);
-// registerWebdavServlet(alias, repository);
- }
-
- protected void registerWebdavServlet(String alias, Repository repository) {
- CmsWebDavServlet webdavServlet = new CmsWebDavServlet(alias, repository);
- Hashtable<String, String> ip = new Hashtable<>();
- ip.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX + CmsWebDavServlet.INIT_PARAM_RESOURCE_CONFIG, webDavConfig);
- ip.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX + CmsWebDavServlet.INIT_PARAM_RESOURCE_PATH_PREFIX,
- "/" + alias);
-
- ip.put(HttpWhiteboardConstants.HTTP_WHITEBOARD_SERVLET_PATTERN, "/" + alias + "/*");
- ip.put(HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_SELECT,
- "(" + HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_PATH + "=" + CmsConstants.PATH_DATA + ")");
- bc.registerService(Servlet.class, webdavServlet, ip);
- }
-
- protected void registerRemotingServlet(String alias, Repository repository) {
- CmsRemotingServlet remotingServlet = new CmsRemotingServlet(alias, repository);
- Hashtable<String, String> ip = new Hashtable<>();
- ip.put(CmsConstants.CN, alias);
- // Properties ip = new Properties();
- ip.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX + CmsRemotingServlet.INIT_PARAM_RESOURCE_PATH_PREFIX,
- "/" + alias);
- ip.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX + CmsRemotingServlet.INIT_PARAM_AUTHENTICATE_HEADER,
- "Negotiate");
-
- // Looks like a bug in Jackrabbit remoting init
- Path tmpDir;
- try {
- tmpDir = Files.createTempDirectory("remoting_" + alias);
- } catch (IOException e) {
- throw new RuntimeException("Cannot create temp directory for remoting servlet", e);
- }
- ip.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX + CmsRemotingServlet.INIT_PARAM_HOME, tmpDir.toString());
- ip.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX + CmsRemotingServlet.INIT_PARAM_TMP_DIRECTORY,
- "remoting_" + alias);
- ip.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX + CmsRemotingServlet.INIT_PARAM_PROTECTED_HANDLERS_CONFIG,
- JcrHttpUtils.DEFAULT_PROTECTED_HANDLERS);
- ip.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX + CmsRemotingServlet.INIT_PARAM_CREATE_ABSOLUTE_URI, "false");
-
- ip.put(HttpWhiteboardConstants.HTTP_WHITEBOARD_SERVLET_PATTERN, "/" + alias + "/*");
- ip.put(HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_SELECT,
- "(" + HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_PATH + "=" + CmsConstants.PATH_JCR + ")");
- bc.registerService(Servlet.class, remotingServlet, ip);
- }
-
- private class RepositoryContextStc extends ServiceTracker<RepositoryContext, RepositoryContext> {
-
- public RepositoryContextStc() {
- super(bc, RepositoryContext.class, null);
- }
-
- @Override
- public RepositoryContext addingService(ServiceReference<RepositoryContext> reference) {
- RepositoryContext repoContext = bc.getService(reference);
- String cn = (String) reference.getProperty(CmsConstants.CN);
- if (cn != null) {
- List<String> publishAsLocalRepo = new ArrayList<>();
- if (cn.equals(CmsConstants.NODE_REPOSITORY)) {
-// JackrabbitDataModelMigration.clearRepositoryCaches(repoContext.getRepositoryConfig());
- prepareNodeRepository(repoContext.getRepository(), publishAsLocalRepo);
- // TODO separate home repository
- prepareHomeRepository(repoContext.getRepository());
- registerRepositoryServlets(cn, repoContext.getRepository());
- nodeAvailable = true;
-// checkReadiness();
- } else {
- prepareDataModel(cn, repoContext.getRepository(), publishAsLocalRepo);
- }
- // Publish all at once, so that bundles with multiple CNDs are consistent
- for (String dataModelName : publishAsLocalRepo)
- publishLocalRepo(dataModelName, repoContext.getRepository());
- }
- return repoContext;
- }
-
- @Override
- public void modifiedService(ServiceReference<RepositoryContext> reference, RepositoryContext service) {
- }
-
- @Override
- public void removedService(ServiceReference<RepositoryContext> reference, RepositoryContext service) {
- }
-
- }
-
-}
+++ /dev/null
-package org.argeo.cms.jcr.internal;
-
-import java.io.IOException;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.nio.file.FileSystem;
-import java.nio.file.FileSystemAlreadyExistsException;
-import java.nio.file.Path;
-import java.nio.file.spi.FileSystemProvider;
-import java.util.HashMap;
-import java.util.Map;
-
-import javax.jcr.Node;
-import javax.jcr.Property;
-import javax.jcr.Repository;
-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.auth.CurrentUser;
-import org.argeo.cms.jcr.CmsJcrUtils;
-import org.argeo.jackrabbit.fs.AbstractJackrabbitFsProvider;
-import org.argeo.jcr.fs.JcrFileSystem;
-import org.argeo.jcr.fs.JcrFileSystemProvider;
-import org.argeo.jcr.fs.JcrFsException;
-
-/** Implementation of an {@link FileSystemProvider} based on Jackrabbit. */
-public class CmsJcrFsProvider extends AbstractJackrabbitFsProvider {
- private Map<String, CmsFileSystem> fileSystems = new HashMap<>();
-
- private RepositoryFactory repositoryFactory;
- private Repository repository;
-
- @Override
- public String getScheme() {
- return CmsConstants.SCHEME_NODE;
- }
-
- @Override
- public FileSystem newFileSystem(URI uri, Map<String, ?> env) throws IOException {
-// BundleContext bc = FrameworkUtil.getBundle(CmsJcrFsProvider.class).getBundleContext();
- String username = CurrentUser.getUsername();
- if (username == null) {
- // TODO deal with anonymous
- return null;
- }
- if (fileSystems.containsKey(username))
- throw new FileSystemAlreadyExistsException("CMS file system already exists for user " + username);
-
- try {
- String host = uri.getHost();
- if (host != null && !host.trim().equals("")) {
- URI repoUri = new URI("http", uri.getUserInfo(), uri.getHost(), uri.getPort(), "/jcr/node", null, null);
-// RepositoryFactory repositoryFactory = bc.getService(bc.getServiceReference(RepositoryFactory.class));
- Repository repository = CmsJcrUtils.getRepositoryByUri(repositoryFactory, repoUri.toString());
- CmsFileSystem fileSystem = new CmsFileSystem(this, repository);
- fileSystems.put(username, fileSystem);
- return fileSystem;
- } else {
-// Repository repository = bc.getService(
-// bc.getServiceReferences(Repository.class, "(cn=" + CmsConstants.EGO_REPOSITORY + ")")
-// .iterator().next());
-
- // Session session = repository.login();
- CmsFileSystem fileSystem = new CmsFileSystem(this, repository);
- fileSystems.put(username, fileSystem);
- return fileSystem;
- }
- } catch (URISyntaxException e) {
- throw new IllegalArgumentException("Cannot open file system " + uri + " for user " + username, e);
- }
- }
-
- @Override
- public FileSystem getFileSystem(URI uri) {
- return currentUserFileSystem();
- }
-
- @Override
- public Path getPath(URI uri) {
- JcrFileSystem fileSystem = currentUserFileSystem();
- String path = uri.getPath();
- if (fileSystem == null)
- try {
- fileSystem = (JcrFileSystem) newFileSystem(uri, new HashMap<String, Object>());
- } catch (IOException e) {
- throw new JcrFsException("Could not autocreate file system", e);
- }
- return fileSystem.getPath(path);
- }
-
- protected JcrFileSystem currentUserFileSystem() {
- String username = CurrentUser.getUsername();
- return fileSystems.get(username);
- }
-
- public Node getUserHome(Repository repository) {
- try {
- Session session = repository.login(CmsConstants.HOME_WORKSPACE);
- return CmsJcrUtils.getUserHome(session);
- } catch (RepositoryException e) {
- throw new IllegalStateException("Cannot get user home", e);
- }
- }
-
- public void setRepositoryFactory(RepositoryFactory repositoryFactory) {
- this.repositoryFactory = repositoryFactory;
- }
-
- public void setRepository(Repository repository) {
- this.repository = repository;
- }
-
- static class CmsFileSystem extends JcrFileSystem {
- public CmsFileSystem(JcrFileSystemProvider provider, Repository repository) throws IOException {
- super(provider, repository);
- }
-
- public boolean skipNode(Node node) throws RepositoryException {
-// if (node.isNodeType(NodeType.NT_HIERARCHY_NODE) || node.isNodeType(NodeTypes.NODE_USER_HOME)
-// || node.isNodeType(NodeTypes.NODE_GROUP_HOME))
- if (node.isNodeType(NodeType.NT_HIERARCHY_NODE))
- return false;
- // FIXME Better identifies home
- if (node.hasProperty(Property.JCR_ID))
- return false;
- return true;
- }
-
- }
-}
+++ /dev/null
-package org.argeo.cms.jcr.internal;
-
-import java.nio.file.Path;
-
-/** Centralises access to the default node deployment directories. */
-public class CmsPaths {
- public static Path getRepoDirPath(String cn) {
- return KernelUtils.getOsgiInstancePath(KernelConstants.DIR_REPOS + '/' + cn);
- }
-
- public static Path getRepoIndexesBase() {
- return KernelUtils.getOsgiInstancePath(KernelConstants.DIR_INDEXES);
- }
-
- /** Singleton. */
- private CmsPaths() {
- }
-}
+++ /dev/null
-package org.argeo.cms.jcr.internal;
-
-import java.util.GregorianCalendar;
-import java.util.concurrent.LinkedBlockingDeque;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-import javax.jcr.Node;
-import javax.jcr.Property;
-import javax.jcr.PropertyType;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.Value;
-import javax.jcr.nodetype.NodeType;
-import javax.jcr.observation.Event;
-import javax.jcr.observation.EventIterator;
-import javax.jcr.observation.EventListener;
-import javax.jcr.version.VersionManager;
-
-import org.apache.jackrabbit.api.JackrabbitValue;
-import org.apache.jackrabbit.core.RepositoryImpl;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.jcr.JcrUtils;
-
-/** Ensure consistency of files, folder and last modified nodes. */
-class CmsWorkspaceIndexer implements EventListener {
- private final static CmsLog log = CmsLog.getLog(CmsWorkspaceIndexer.class);
-
-// private final static String MIX_ETAG = "mix:etag";
- private final static String JCR_ETAG = "jcr:etag";
-// private final static String JCR_LAST_MODIFIED = "jcr:lastModified";
-// private final static String JCR_LAST_MODIFIED_BY = "jcr:lastModifiedBy";
-// private final static String JCR_MIXIN_TYPES = "jcr:mixinTypes";
- private final static String JCR_DATA = "jcr:data";
- private final static String JCR_CONTENT = "jcr:data";
-
- private String cn;
- private String workspaceName;
- private RepositoryImpl repositoryImpl;
- private Session session;
- private VersionManager versionManager;
-
- private LinkedBlockingDeque<Event> toProcess = new LinkedBlockingDeque<>();
- private IndexingThread indexingThread;
- private AtomicBoolean stopping = new AtomicBoolean(false);
-
- public CmsWorkspaceIndexer(RepositoryImpl repositoryImpl, String cn, String workspaceName)
- throws RepositoryException {
- this.cn = cn;
- this.workspaceName = workspaceName;
- this.repositoryImpl = repositoryImpl;
- }
-
- public void init() {
- session = KernelUtils.openAdminSession(repositoryImpl, workspaceName);
- try {
- String[] nodeTypes = { NodeType.NT_FILE, NodeType.MIX_LAST_MODIFIED };
- session.getWorkspace().getObservationManager().addEventListener(this,
- Event.NODE_ADDED | Event.PROPERTY_CHANGED, "/", true, null, nodeTypes, true);
- versionManager = session.getWorkspace().getVersionManager();
-
- indexingThread = new IndexingThread();
- indexingThread.start();
- } catch (RepositoryException e1) {
- throw new IllegalStateException(e1);
- }
- }
-
- public void destroy() {
- stopping.set(true);
- indexingThread.interrupt();
- // TODO make it configurable
- try {
- indexingThread.join(10 * 60 * 1000);
- } catch (InterruptedException e1) {
- log.warn("Indexing thread interrupted. Will log out session.");
- }
-
- try {
- session.getWorkspace().getObservationManager().removeEventListener(this);
- } catch (RepositoryException e) {
- if (log.isTraceEnabled())
- log.warn("Cannot unregistered JCR event listener", e);
- } finally {
- JcrUtils.logoutQuietly(session);
- }
- }
-
- private synchronized void processEvents(EventIterator events) {
- long begin = System.currentTimeMillis();
- long count = 0;
- while (events.hasNext()) {
- Event event = events.nextEvent();
- try {
- toProcess.put(event);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
-// processEvent(event);
- count++;
- }
- long duration = System.currentTimeMillis() - begin;
- if (log.isTraceEnabled())
- log.trace("Processed " + count + " events in " + duration + " ms");
- notifyAll();
- }
-
- protected synchronized void processEvent(Event event) {
- try {
- String eventPath = event.getPath();
- if (event.getType() == Event.NODE_ADDED) {
- if (!versionManager.isCheckedOut(eventPath))
- return;// ignore checked-in nodes
- if (log.isTraceEnabled())
- log.trace("NODE_ADDED " + eventPath);
-// session.refresh(true);
- session.refresh(false);
- Node node = session.getNode(eventPath);
- Node parentNode = node.getParent();
- if (parentNode.isNodeType(NodeType.NT_FILE)) {
- if (node.isNodeType(NodeType.NT_UNSTRUCTURED)) {
- if (!node.isNodeType(NodeType.MIX_LAST_MODIFIED))
- node.addMixin(NodeType.MIX_LAST_MODIFIED);
- Property property = node.getProperty(Property.JCR_DATA);
- String etag = toEtag(property.getValue());
- session.save();
- node.setProperty(JCR_ETAG, etag);
- if (log.isTraceEnabled())
- log.trace("ETag and last modified added to new " + node);
- } else if (node.isNodeType(NodeType.NT_RESOURCE)) {
-// if (!node.isNodeType(MIX_ETAG))
-// node.addMixin(MIX_ETAG);
-// session.save();
-// Property property = node.getProperty(Property.JCR_DATA);
-// String etag = toEtag(property.getValue());
-// node.setProperty(JCR_ETAG, etag);
-// session.save();
- }
-// setLastModifiedRecursive(parentNode, event);
-// session.save();
-// if (log.isTraceEnabled())
-// log.trace("ETag and last modified added to new " + node);
- }
-
-// if (node.isNodeType(NodeType.NT_FOLDER)) {
-// setLastModifiedRecursive(node, event);
-// session.save();
-// if (log.isTraceEnabled())
-// log.trace("Last modified added to new " + node);
-// }
- } else if (event.getType() == Event.PROPERTY_CHANGED) {
- String propertyName = extractItemName(eventPath);
- // skip if last modified properties are explicitly set
- if (!propertyName.equals(JCR_DATA))
- return;
-// if (propertyName.equals(JCR_LAST_MODIFIED))
-// return;
-// if (propertyName.equals(JCR_LAST_MODIFIED_BY))
-// return;
-// if (propertyName.equals(JCR_MIXIN_TYPES))
-// return;
-// if (propertyName.equals(JCR_ETAG))
-// return;
-
- if (log.isTraceEnabled())
- log.trace("PROPERTY_CHANGED " + eventPath);
-
- if (!session.propertyExists(eventPath))
- return;
- session.refresh(false);
- Property property = session.getProperty(eventPath);
- Node node = property.getParent();
- if (property.getType() == PropertyType.BINARY && propertyName.equals(JCR_DATA)
- && node.isNodeType(NodeType.NT_UNSTRUCTURED)) {
- String etag = toEtag(property.getValue());
- node.setProperty(JCR_ETAG, etag);
- Node parentNode = node.getParent();
- if (parentNode.isNodeType(NodeType.MIX_LAST_MODIFIED)) {
- setLastModified(parentNode, event);
- }
- if (log.isTraceEnabled())
- log.trace("ETag and last modified updated for " + node);
- }
-// setLastModified(node, event);
-// session.save();
-// if (log.isTraceEnabled())
-// log.trace("ETag and last modified updated for " + node);
- } else if (event.getType() == Event.NODE_REMOVED) {
- String removeNodePath = eventPath;
- String nodeName = extractItemName(eventPath);
- if (JCR_CONTENT.equals(nodeName)) // parent is a file, deleted anyhow
- return;
- if (log.isTraceEnabled())
- log.trace("NODE_REMOVED " + eventPath);
-// String parentPath = JcrUtils.parentPath(removeNodePath);
-// session.refresh(true);
-// setLastModified(parentPath, event);
-// session.save();
- if (log.isTraceEnabled())
- log.trace("Last modified updated for parents of removed " + removeNodePath);
- }
- } catch (Exception e) {
- if (log.isTraceEnabled())
- log.warn("Cannot process event " + event, e);
- } finally {
-// try {
-// session.refresh(true);
-// if (session.hasPendingChanges())
-// session.save();
-//// session.refresh(false);
-// } catch (RepositoryException e) {
-// if (log.isTraceEnabled())
-// log.warn("Cannot refresh JCR session", e);
-// }
- }
-
- }
-
- private String extractItemName(String path) {
- if (path == null || path.length() <= 1)
- return null;
- int lastIndex = path.lastIndexOf('/');
- if (lastIndex >= 0) {
- return path.substring(lastIndex + 1);
- } else {
- return path;
- }
- }
-
- @Override
- public void onEvent(EventIterator events) {
- processEvents(events);
-// Runnable toRun = new Runnable() {
-//
-// @Override
-// public void run() {
-// processEvents(events);
-// }
-// };
-// Future<?> future = Activator.getInternalExecutorService().submit(toRun);
-// try {
-// // make the call synchronous
-// future.get(60, TimeUnit.SECONDS);
-// } catch (TimeoutException | ExecutionException | InterruptedException e) {
-// // silent
-// }
- }
-
- static String toEtag(Value v) {
- if (v instanceof JackrabbitValue) {
- JackrabbitValue value = (JackrabbitValue) v;
- return '\"' + value.getContentIdentity() + '\"';
- } else {
- return null;
- }
-
- }
-
- protected synchronized void setLastModified(Node node, Event event) throws RepositoryException {
- GregorianCalendar calendar = new GregorianCalendar();
- calendar.setTimeInMillis(event.getDate());
- node.setProperty(Property.JCR_LAST_MODIFIED, calendar);
- node.setProperty(Property.JCR_LAST_MODIFIED_BY, event.getUserID());
- if (log.isTraceEnabled())
- log.trace("Last modified set on " + node);
- }
-
- /** Recursively set the last updated time on parents. */
- protected synchronized void setLastModifiedRecursive(Node node, Event event) throws RepositoryException {
- if (versionManager.isCheckedOut(node.getPath())) {
- if (node.isNodeType(NodeType.MIX_LAST_MODIFIED)) {
- setLastModified(node, event);
- }
- if (node.isNodeType(NodeType.NT_FOLDER) && !node.isNodeType(NodeType.MIX_LAST_MODIFIED)) {
- node.addMixin(NodeType.MIX_LAST_MODIFIED);
- if (log.isTraceEnabled())
- log.trace("Last modified mix-in added to " + node);
- }
-
- }
-
- // end condition
- if (node.getDepth() == 0) {
-// try {
-// node.getSession().save();
-// } catch (RepositoryException e) {
-// log.warn("Cannot index workspace", e);
-// }
- return;
- } else {
- Node parent = node.getParent();
- setLastModifiedRecursive(parent, event);
- }
- }
-
- /**
- * Recursively set the last updated time on parents. Useful to use paths when
- * dealing with deletions.
- */
- protected synchronized void setLastModifiedRecursive(String path, Event event) throws RepositoryException {
- // root node will always exist, so end condition is delegated to the other
- // recursive setLastModified method
- if (session.nodeExists(path)) {
- setLastModifiedRecursive(session.getNode(path), event);
- } else {
- setLastModifiedRecursive(JcrUtils.parentPath(path), event);
- }
- }
-
- @Override
- public String toString() {
- return "Indexer for workspace " + workspaceName + " of repository " + cn;
- }
-
- class IndexingThread extends Thread {
-
- public IndexingThread() {
- super(CmsWorkspaceIndexer.this.toString());
- // TODO Auto-generated constructor stub
- }
-
- @Override
- public void run() {
- life: while (session != null && session.isLive()) {
- try {
- Event nextEvent = toProcess.take();
- processEvent(nextEvent);
- } catch (InterruptedException e) {
- // silent
- interrupted();
- }
-
- if (stopping.get() && toProcess.isEmpty()) {
- break life;
- }
- }
- if (log.isDebugEnabled())
- log.debug(CmsWorkspaceIndexer.this.toString() + " has shut down.");
- }
-
- }
-
-}
\ No newline at end of file
+++ /dev/null
-package org.argeo.cms.jcr.internal;
-
-import static org.argeo.cms.osgi.DataModelNamespace.CMS_DATA_MODEL_NAMESPACE;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.TreeMap;
-
-import org.argeo.api.cms.CmsLog;
-import org.argeo.cms.osgi.DataModelNamespace;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.BundleEvent;
-import org.osgi.framework.BundleListener;
-import org.osgi.framework.wiring.BundleCapability;
-import org.osgi.framework.wiring.BundleWire;
-import org.osgi.framework.wiring.BundleWiring;
-
-class DataModels implements BundleListener {
- private final static CmsLog log = CmsLog.getLog(DataModels.class);
-
- private Map<String, DataModel> dataModels = new TreeMap<>();
-
- public DataModels(BundleContext bc) {
- for (Bundle bundle : bc.getBundles())
- processBundle(bundle, null);
- bc.addBundleListener(this);
- }
-
- public List<DataModel> getNonAbstractDataModels() {
- List<DataModel> res = new ArrayList<>();
- for (String name : dataModels.keySet()) {
- DataModel dataModel = dataModels.get(name);
- if (!dataModel.isAbstract())
- res.add(dataModel);
- }
- // TODO reorder?
- return res;
- }
-
- @Override
- public void bundleChanged(BundleEvent event) {
- if (event.getType() == Bundle.RESOLVED) {
- processBundle(event.getBundle(), null);
- } else if (event.getType() == Bundle.UNINSTALLED) {
- BundleWiring wiring = event.getBundle().adapt(BundleWiring.class);
- List<BundleCapability> providedDataModels = wiring.getCapabilities(CMS_DATA_MODEL_NAMESPACE);
- if (providedDataModels.size() == 0)
- return;
- for (BundleCapability bundleCapability : providedDataModels) {
- dataModels.remove(bundleCapability.getAttributes().get(DataModelNamespace.NAME));
- }
- }
-
- }
-
- protected void processBundle(Bundle bundle, List<Bundle> scannedBundles) {
- if (scannedBundles != null && scannedBundles.contains(bundle))
- throw new IllegalStateException("Cycle in CMS data model requirements for " + bundle);
- BundleWiring wiring = bundle.adapt(BundleWiring.class);
- if (wiring == null) {
- int bundleState = bundle.getState();
- if (bundleState != Bundle.INSTALLED && bundleState != Bundle.UNINSTALLED) {// ignore unresolved bundles
- log.warn("Bundle " + bundle.getSymbolicName() + " #" + bundle.getBundleId() + " ("
- + bundle.getLocation() + ") cannot be adapted to a wiring");
- } else {
- if (log.isTraceEnabled())
- log.warn("Bundle " + bundle.getSymbolicName() + " is not resolved.");
- }
- return;
- }
- List<BundleCapability> providedDataModels = wiring.getCapabilities(CMS_DATA_MODEL_NAMESPACE);
- if (providedDataModels.size() == 0)
- return;
- List<BundleWire> requiredDataModels = wiring.getRequiredWires(CMS_DATA_MODEL_NAMESPACE);
- // process requirements first
- for (BundleWire bundleWire : requiredDataModels) {
- List<Bundle> nextScannedBundles = new ArrayList<>();
- if (scannedBundles != null)
- nextScannedBundles.addAll(scannedBundles);
- nextScannedBundles.add(bundle);
- Bundle providerBundle = bundleWire.getProvider().getBundle();
- processBundle(providerBundle, nextScannedBundles);
- }
- for (BundleCapability bundleCapability : providedDataModels) {
- String name = (String) bundleCapability.getAttributes().get(DataModelNamespace.NAME);
- assert name != null;
- if (!dataModels.containsKey(name)) {
- DataModel dataModel = new DataModel(name, bundleCapability, requiredDataModels);
- dataModels.put(dataModel.getName(), dataModel);
- }
- }
- }
-
- /** Return a negative depth if dataModel is required by ref, 0 otherwise. */
- static int required(DataModel ref, DataModel dataModel, int depth) {
- for (DataModel dm : ref.getRequired()) {
- if (dm.equals(dataModel))// found here
- return depth - 1;
- int d = required(dm, dataModel, depth - 1);
- if (d != 0)// found deeper
- return d;
- }
- return 0;// not found
- }
-
- class DataModel {
- private final String name;
- private final boolean abstrct;
- // private final boolean standalone;
- private final String cnd;
- private final List<DataModel> required;
-
- private DataModel(String name, BundleCapability bundleCapability, List<BundleWire> requiredDataModels) {
- assert CMS_DATA_MODEL_NAMESPACE.equals(bundleCapability.getNamespace());
- this.name = name;
- Map<String, Object> attrs = bundleCapability.getAttributes();
- abstrct = KernelUtils.asBoolean((String) attrs.get(DataModelNamespace.ABSTRACT));
- // standalone = KernelUtils.asBoolean((String)
- // attrs.get(DataModelNamespace.CAPABILITY_STANDALONE_ATTRIBUTE));
- cnd = (String) attrs.get(DataModelNamespace.CND);
- List<DataModel> req = new ArrayList<>();
- for (BundleWire wire : requiredDataModels) {
- String requiredDataModelName = (String) wire.getCapability().getAttributes()
- .get(DataModelNamespace.NAME);
- assert requiredDataModelName != null;
- DataModel requiredDataModel = dataModels.get(requiredDataModelName);
- if (requiredDataModel == null)
- throw new IllegalStateException("No required data model " + requiredDataModelName);
- req.add(requiredDataModel);
- }
- required = Collections.unmodifiableList(req);
- }
-
- public String getName() {
- return name;
- }
-
- public boolean isAbstract() {
- return abstrct;
- }
-
- // public boolean isStandalone() {
- // return !isAbstract();
- // }
-
- public String getCnd() {
- return cnd;
- }
-
- public List<DataModel> getRequired() {
- return required;
- }
-
- // @Override
- // public int compareTo(DataModel o) {
- // if (equals(o))
- // return 0;
- // int res = required(this, o, 0);
- // if (res != 0)
- // return res;
- // // the other way round
- // res = required(o, this, 0);
- // if (res != 0)
- // return -res;
- // return 0;
- // }
-
- @Override
- public int hashCode() {
- return name.hashCode();
- }
-
- @Override
- public boolean equals(Object obj) {
- if (obj instanceof DataModel)
- return ((DataModel) obj).name.equals(name);
- return false;
- }
-
- @Override
- public String toString() {
- return "Data model " + name;
- }
-
- }
-
-}
+++ /dev/null
-package org.argeo.cms.jcr.internal;
-
-import java.security.PrivilegedAction;
-import java.text.SimpleDateFormat;
-import java.util.HashSet;
-import java.util.Set;
-
-import javax.jcr.Node;
-import javax.jcr.Property;
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.nodetype.NodeType;
-import javax.jcr.security.Privilege;
-import javax.naming.InvalidNameException;
-import javax.naming.ldap.LdapName;
-import javax.security.auth.Subject;
-import javax.security.auth.login.LoginContext;
-
-import org.argeo.api.cms.CmsAuth;
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.cms.CmsException;
-import org.argeo.cms.jcr.CmsJcrUtils;
-import org.argeo.jcr.JcrException;
-import org.argeo.jcr.JcrRepositoryWrapper;
-import org.argeo.jcr.JcrUtils;
-
-/**
- * Make sure each user has a home directory available.
- */
-class EgoRepository extends JcrRepositoryWrapper implements KernelConstants {
-
- /** The home base path. */
-// private String homeBasePath = KernelConstants.DEFAULT_HOME_BASE_PATH;
-// private String usersBasePath = KernelConstants.DEFAULT_USERS_BASE_PATH;
-// private String groupsBasePath = KernelConstants.DEFAULT_GROUPS_BASE_PATH;
-
- private Set<String> checkedUsers = new HashSet<String>();
-
- private SimpleDateFormat usersDatePath = new SimpleDateFormat("YYYY/MM");
-
- private String defaultHomeWorkspace = CmsConstants.HOME_WORKSPACE;
- private String defaultGroupsWorkspace = CmsConstants.SRV_WORKSPACE;
-// private String defaultGuestsWorkspace = NodeConstants.GUESTS_WORKSPACE;
- private final boolean remote;
-
- public EgoRepository(Repository repository, boolean remote) {
- super(repository);
- this.remote = remote;
- putDescriptor(CmsConstants.CN, CmsConstants.EGO_REPOSITORY);
- if (!remote) {
- LoginContext lc;
- try {
- lc = new LoginContext(CmsAuth.LOGIN_CONTEXT_DATA_ADMIN);
- lc.login();
- } catch (javax.security.auth.login.LoginException e1) {
- throw new IllegalStateException("Cannot login as system", e1);
- }
- Subject.doAs(lc.getSubject(), new PrivilegedAction<Void>() {
-
- @Override
- public Void run() {
- loginOrCreateWorkspace(defaultHomeWorkspace);
- loginOrCreateWorkspace(defaultGroupsWorkspace);
- return null;
- }
-
- });
- }
- }
-
- private void loginOrCreateWorkspace(String workspace) {
- Session adminSession = null;
- try {
- adminSession = JcrUtils.loginOrCreateWorkspace(getRepository(workspace), workspace);
-// JcrUtils.addPrivilege(adminSession, "/", NodeConstants.ROLE_USER, Privilege.JCR_READ);
-
-// initJcr(adminSession);
- } catch (RepositoryException e) {
- throw new JcrException("Cannot init JCR home", e);
- } finally {
- JcrUtils.logoutQuietly(adminSession);
- }
- }
-
-// @Override
-// public Session login(Credentials credentials, String workspaceName)
-// throws LoginException, NoSuchWorkspaceException, RepositoryException {
-// if (workspaceName == null) {
-// return super.login(credentials, getUserHomeWorkspace());
-// } else {
-// return super.login(credentials, workspaceName);
-// }
-// }
-
- protected String getUserHomeWorkspace() {
- // TODO base on JAAS Subject metadata
- return defaultHomeWorkspace;
- }
-
- protected String getGroupsWorkspace() {
- // TODO base on JAAS Subject metadata
- return defaultGroupsWorkspace;
- }
-
-// protected String getGuestsWorkspace() {
-// // TODO base on JAAS Subject metadata
-// return defaultGuestsWorkspace;
-// }
-
- @Override
- protected void processNewSession(Session session, String workspaceName) {
- String username = session.getUserID();
- if (username == null || username.toString().equals(""))
- return;
- if (session.getUserID().equals(CmsConstants.ROLE_ANONYMOUS))
- return;
-
- String userHomeWorkspace = getUserHomeWorkspace();
- if (workspaceName == null || !workspaceName.equals(userHomeWorkspace))
- return;
-
- if (checkedUsers.contains(username))
- return;
- Session adminSession = KernelUtils.openAdminSession(getRepository(workspaceName), workspaceName);
- try {
- syncJcr(adminSession, username);
- checkedUsers.add(username);
- } finally {
- JcrUtils.logoutQuietly(adminSession);
- }
- }
-
- /*
- * JCR
- */
- /** Session is logged out. */
- private void initJcr(Session adminSession) {
- try {
-// JcrUtils.mkdirs(adminSession, homeBasePath);
-// JcrUtils.mkdirs(adminSession, groupsBasePath);
- adminSession.save();
-
-// JcrUtils.addPrivilege(adminSession, homeBasePath, NodeConstants.ROLE_USER_ADMIN, Privilege.JCR_READ);
-// JcrUtils.addPrivilege(adminSession, groupsBasePath, NodeConstants.ROLE_USER_ADMIN, Privilege.JCR_READ);
- adminSession.save();
- } catch (RepositoryException e) {
- throw new CmsException("Cannot initialize home repository", e);
- } finally {
- JcrUtils.logoutQuietly(adminSession);
- }
- }
-
- protected synchronized void syncJcr(Session adminSession, String username) {
- // only in the default workspace
-// if (workspaceName != null)
-// return;
- // skip system users
- if (username.endsWith(CmsConstants.ROLES_BASEDN))
- return;
-
- try {
- Node userHome = CmsJcrUtils.getUserHome(adminSession, username);
- if (userHome == null) {
-// String homePath = generateUserPath(username);
- String userId = extractUserId(username);
-// if (adminSession.itemExists(homePath))// duplicate user id
-// userHome = adminSession.getNode(homePath).getParent().addNode(JcrUtils.lastPathElement(homePath));
-// else
-// userHome = JcrUtils.mkdirs(adminSession, homePath);
- userHome = adminSession.getRootNode().addNode(userId);
-// userHome.addMixin(NodeTypes.NODE_USER_HOME);
- userHome.addMixin(NodeType.MIX_CREATED);
- userHome.addMixin(NodeType.MIX_TITLE);
- userHome.setProperty(Property.JCR_ID, username);
- // TODO use display name
- userHome.setProperty(Property.JCR_TITLE, userId);
-// userHome.setProperty(NodeNames.LDAP_UID, username);
- adminSession.save();
-
- JcrUtils.clearAccessControList(adminSession, userHome.getPath(), username);
- JcrUtils.addPrivilege(adminSession, userHome.getPath(), username, Privilege.JCR_ALL);
-// JackrabbitSecurityUtils.denyPrivilege(adminSession, userHome.getPath(), NodeConstants.ROLE_USER,
-// Privilege.JCR_READ);
- }
- if (adminSession.hasPendingChanges())
- adminSession.save();
- } catch (RepositoryException e) {
- JcrUtils.discardQuietly(adminSession);
- throw new JcrException("Cannot sync node security model for " + username, e);
- }
- }
-
- /** Generate path for a new user home */
- private String generateUserPath(String username) {
- LdapName dn;
- try {
- dn = new LdapName(username);
- } catch (InvalidNameException e) {
- throw new CmsException("Invalid name " + username, e);
- }
- String userId = dn.getRdn(dn.size() - 1).getValue().toString();
- return '/' + userId;
-// int atIndex = userId.indexOf('@');
-// if (atIndex < 0) {
-// return homeBasePath+'/' + userId;
-// } else {
-// return usersBasePath + '/' + usersDatePath.format(new Date()) + '/' + userId;
-// }
- }
-
- private String extractUserId(String username) {
- LdapName dn;
- try {
- dn = new LdapName(username);
- } catch (InvalidNameException e) {
- throw new CmsException("Invalid name " + username, e);
- }
- String userId = dn.getRdn(dn.size() - 1).getValue().toString();
- return userId;
-// int atIndex = userId.indexOf('@');
-// if (atIndex < 0) {
-// return homeBasePath+'/' + userId;
-// } else {
-// return usersBasePath + '/' + usersDatePath.format(new Date()) + '/' + userId;
-// }
- }
-
- public void createWorkgroup(LdapName dn) {
- String groupsWorkspace = getGroupsWorkspace();
- Session adminSession = KernelUtils.openAdminSession(getRepository(groupsWorkspace), groupsWorkspace);
- String cn = dn.getRdn(dn.size() - 1).getValue().toString();
- Node newWorkgroup = CmsJcrUtils.getGroupHome(adminSession, cn);
- if (newWorkgroup != null) {
- JcrUtils.logoutQuietly(adminSession);
- throw new CmsException("Workgroup " + newWorkgroup + " already exists for " + dn);
- }
- try {
- // TODO enhance transformation of cn to a valid node name
- // String relPath = cn.replaceAll("[^a-zA-Z0-9]", "_");
- String relPath = JcrUtils.replaceInvalidChars(cn);
- newWorkgroup = adminSession.getRootNode().addNode(relPath, NodeType.NT_UNSTRUCTURED);
-// newWorkgroup = JcrUtils.mkdirs(adminSession.getNode(groupsBasePath), relPath, NodeType.NT_UNSTRUCTURED);
-// newWorkgroup.addMixin(NodeTypes.NODE_GROUP_HOME);
- newWorkgroup.addMixin(NodeType.MIX_CREATED);
- newWorkgroup.addMixin(NodeType.MIX_TITLE);
- newWorkgroup.setProperty(Property.JCR_ID, dn.toString());
- newWorkgroup.setProperty(Property.JCR_TITLE, cn);
-// newWorkgroup.setProperty(NodeNames.LDAP_CN, cn);
- adminSession.save();
- JcrUtils.addPrivilege(adminSession, newWorkgroup.getPath(), dn.toString(), Privilege.JCR_ALL);
- adminSession.save();
- } catch (RepositoryException e) {
- throw new CmsException("Cannot create workgroup", e);
- } finally {
- JcrUtils.logoutQuietly(adminSession);
- }
-
- }
-
- public boolean isRemote() {
- return remote;
- }
-
-}
+++ /dev/null
-package org.argeo.cms.jcr.internal;
-
-import java.util.Map;
-import java.util.TreeMap;
-
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-
-import org.apache.jackrabbit.core.RepositoryImpl;
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.api.cms.CmsLog;
-
-class JackrabbitLocalRepository extends LocalRepository {
- private final static CmsLog log = CmsLog.getLog(JackrabbitLocalRepository.class);
- final String SECURITY_WORKSPACE = "security";
-
- private Map<String, CmsWorkspaceIndexer> workspaceMonitors = new TreeMap<>();
-
- public JackrabbitLocalRepository(RepositoryImpl repository, String cn) {
- super(repository, cn);
-// Session session = KernelUtils.openAdminSession(repository);
-// try {
-// if (NodeConstants.NODE.equals(cn))
-// for (String workspaceName : session.getWorkspace().getAccessibleWorkspaceNames()) {
-// addMonitor(workspaceName);
-// }
-// } catch (RepositoryException e) {
-// throw new IllegalStateException(e);
-// } finally {
-// JcrUtils.logoutQuietly(session);
-// }
- }
-
- protected RepositoryImpl getJackrabbitrepository(String workspaceName) {
- return (RepositoryImpl) getRepository(workspaceName);
- }
-
- @Override
- protected synchronized void processNewSession(Session session, String workspaceName) {
-// String realWorkspaceName = session.getWorkspace().getName();
-// addMonitor(realWorkspaceName);
- }
-
- private void addMonitor(String realWorkspaceName) {
- if (realWorkspaceName.equals(SECURITY_WORKSPACE))
- return;
- if (!CmsConstants.NODE_REPOSITORY.equals(getCn()))
- return;
-
- if (!workspaceMonitors.containsKey(realWorkspaceName)) {
- try {
- CmsWorkspaceIndexer workspaceMonitor = new CmsWorkspaceIndexer(
- getJackrabbitrepository(realWorkspaceName), getCn(), realWorkspaceName);
- workspaceMonitors.put(realWorkspaceName, workspaceMonitor);
- workspaceMonitor.init();
- if (log.isDebugEnabled())
- log.debug("Registered " + workspaceMonitor);
- } catch (RepositoryException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
- }
-
- public void destroy() {
- for (String workspaceName : workspaceMonitors.keySet()) {
- workspaceMonitors.get(workspaceName).destroy();
- }
- }
-
-}
+++ /dev/null
-package org.argeo.cms.jcr.internal;
-
-import java.io.ByteArrayInputStream;
-import java.io.CharArrayReader;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.Reader;
-import java.nio.charset.StandardCharsets;
-import java.security.GeneralSecurityException;
-import java.security.NoSuchAlgorithmException;
-import java.security.Provider;
-import java.security.SecureRandom;
-
-import javax.crypto.Cipher;
-import javax.crypto.CipherInputStream;
-import javax.crypto.NoSuchPaddingException;
-import javax.crypto.SecretKey;
-import javax.crypto.spec.IvParameterSpec;
-import javax.jcr.Binary;
-import javax.jcr.Node;
-import javax.jcr.NodeIterator;
-import javax.jcr.Property;
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.query.Query;
-
-import org.apache.commons.io.IOUtils;
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.cms.ArgeoNames;
-import org.argeo.cms.ArgeoTypes;
-import org.argeo.cms.jcr.CmsJcrUtils;
-import org.argeo.cms.security.AbstractKeyring;
-import org.argeo.cms.security.PBEKeySpecCallback;
-import org.argeo.jcr.JcrException;
-import org.argeo.jcr.JcrUtils;
-
-/** JCR based implementation of a keyring */
-public class JcrKeyring extends AbstractKeyring implements ArgeoNames {
- private final static CmsLog log = CmsLog.getLog(JcrKeyring.class);
- /**
- * Stronger with 256, but causes problem with Oracle JVM, force 128 in this case
- */
- public final static Long DEFAULT_SECRETE_KEY_LENGTH = 256l;
- public final static String DEFAULT_SECRETE_KEY_FACTORY = "PBKDF2WithHmacSHA1";
- public final static String DEFAULT_SECRETE_KEY_ENCRYPTION = "AES";
- public final static String DEFAULT_CIPHER_NAME = "AES/CBC/PKCS5Padding";
-
- private Integer iterationCountFactor = 200;
- private Long secretKeyLength = DEFAULT_SECRETE_KEY_LENGTH;
- private String secretKeyFactoryName = DEFAULT_SECRETE_KEY_FACTORY;
- private String secretKeyEncryption = DEFAULT_SECRETE_KEY_ENCRYPTION;
- private String cipherName = DEFAULT_CIPHER_NAME;
-
- private final Repository repository;
- // TODO remove thread local session ; open a session each time
- private ThreadLocal<Session> sessionThreadLocal = new ThreadLocal<Session>() {
-
- @Override
- protected Session initialValue() {
- return login();
- }
-
- };
-
- // FIXME is it really still needed?
- /**
- * When setup is called the session has not yet been saved and we don't want to
- * save it since there maybe other data which would be inconsistent. So we keep
- * a reference to this node which will then be used (an reset to null) when
- * handling the PBE callback. We keep one per thread in case multiple users are
- * accessing the same instance of a keyring.
- */
- // private ThreadLocal<Node> notYetSavedKeyring = new ThreadLocal<Node>() {
- //
- // @Override
- // protected Node initialValue() {
- // return null;
- // }
- // };
-
- public JcrKeyring(Repository repository) {
- this.repository = repository;
- }
-
- private Session session() {
- Session session = this.sessionThreadLocal.get();
- if (!session.isLive()) {
- session = login();
- sessionThreadLocal.set(session);
- }
- return session;
- }
-
- private Session login() {
- try {
- return repository.login(CmsConstants.HOME_WORKSPACE);
- } catch (RepositoryException e) {
- throw new JcrException("Cannot login key ring session", e);
- }
- }
-
- @Override
- protected synchronized Boolean isSetup() {
- Session session = null;
- try {
- // if (notYetSavedKeyring.get() != null)
- // return true;
- session = session();
- session.refresh(true);
- Node userHome = CmsJcrUtils.getUserHome(session);
- return userHome.hasNode(ARGEO_KEYRING);
- } catch (RepositoryException e) {
- throw new JcrException("Cannot check whether keyring is setup", e);
- } finally {
- JcrUtils.logoutQuietly(session);
- }
- }
-
- @Override
- protected synchronized void setup(char[] password) {
- Binary binary = null;
- // InputStream in = null;
- try {
- session().refresh(true);
- Node userHome = CmsJcrUtils.getUserHome(session());
- Node keyring;
- if (userHome.hasNode(ARGEO_KEYRING)) {
- throw new IllegalArgumentException("Keyring already set up");
- } else {
- keyring = userHome.addNode(ARGEO_KEYRING);
- }
- keyring.addMixin(ArgeoTypes.ARGEO_PBE_SPEC);
-
- // deterministic salt and iteration count based on username
- String username = session().getUserID();
- byte[] salt = new byte[8];
- byte[] usernameBytes = username.getBytes(StandardCharsets.UTF_8);
- for (int i = 0; i < salt.length; i++) {
- if (i < usernameBytes.length)
- salt[i] = usernameBytes[i];
- else
- salt[i] = 0;
- }
- try (InputStream in = new ByteArrayInputStream(salt);) {
- binary = session().getValueFactory().createBinary(in);
- keyring.setProperty(ARGEO_SALT, binary);
- } catch (IOException e) {
- throw new RuntimeException("Cannot set keyring salt", e);
- }
-
- Integer iterationCount = username.length() * iterationCountFactor;
- keyring.setProperty(ARGEO_ITERATION_COUNT, iterationCount);
-
- // default algo
- // TODO check if algo and key length are available, use DES if not
- keyring.setProperty(ARGEO_SECRET_KEY_FACTORY, secretKeyFactoryName);
- keyring.setProperty(ARGEO_KEY_LENGTH, secretKeyLength);
- keyring.setProperty(ARGEO_SECRET_KEY_ENCRYPTION, secretKeyEncryption);
- keyring.setProperty(ARGEO_CIPHER, cipherName);
-
- keyring.getSession().save();
-
- // encrypted password hash
- // IOUtils.closeQuietly(in);
- // JcrUtils.closeQuietly(binary);
- // byte[] btPass = hash(password, salt, iterationCount);
- // in = new ByteArrayInputStream(btPass);
- // binary = session().getValueFactory().createBinary(in);
- // keyring.setProperty(ARGEO_PASSWORD, binary);
-
- // notYetSavedKeyring.set(keyring);
- } catch (RepositoryException e) {
- throw new JcrException("Cannot setup keyring", e);
- } finally {
- JcrUtils.closeQuietly(binary);
- // IOUtils.closeQuietly(in);
- // JcrUtils.discardQuietly(session());
- }
- }
-
- @Override
- protected synchronized void handleKeySpecCallback(PBEKeySpecCallback pbeCallback) {
- Session session = null;
- try {
- session = session();
- session.refresh(true);
- Node userHome = CmsJcrUtils.getUserHome(session);
- Node keyring;
- if (userHome.hasNode(ARGEO_KEYRING))
- keyring = userHome.getNode(ARGEO_KEYRING);
- // else if (notYetSavedKeyring.get() != null)
- // keyring = notYetSavedKeyring.get();
- else
- throw new IllegalStateException("Keyring not setup");
-
- pbeCallback.set(keyring.getProperty(ARGEO_SECRET_KEY_FACTORY).getString(),
- JcrUtils.getBinaryAsBytes(keyring.getProperty(ARGEO_SALT)),
- (int) keyring.getProperty(ARGEO_ITERATION_COUNT).getLong(),
- (int) keyring.getProperty(ARGEO_KEY_LENGTH).getLong(),
- keyring.getProperty(ARGEO_SECRET_KEY_ENCRYPTION).getString());
-
- // if (notYetSavedKeyring.get() != null)
- // notYetSavedKeyring.remove();
- } catch (RepositoryException e) {
- throw new JcrException("Cannot handle key spec callback", e);
- } finally {
- JcrUtils.logoutQuietly(session);
- }
- }
-
- /** The parent node must already exist at this path. */
- @Override
- protected synchronized void encrypt(String path, InputStream unencrypted) {
- // should be called first for lazy initialization
- SecretKey secretKey = getSecretKey(null);
- Cipher cipher = createCipher();
-
- // Binary binary = null;
- // InputStream in = null;
- try {
- session().refresh(true);
- Node node;
- if (!session().nodeExists(path)) {
- String parentPath = JcrUtils.parentPath(path);
- if (!session().nodeExists(parentPath))
- throw new IllegalStateException("No parent node of " + path);
- Node parentNode = session().getNode(parentPath);
- node = parentNode.addNode(JcrUtils.nodeNameFromPath(path));
- } else {
- node = session().getNode(path);
- }
- encrypt(secretKey, cipher, node, unencrypted);
- // node.addMixin(ArgeoTypes.ARGEO_ENCRYPTED);
- // SecureRandom random = new SecureRandom();
- // byte[] iv = new byte[16];
- // random.nextBytes(iv);
- // cipher.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(iv));
- // JcrUtils.setBinaryAsBytes(node, ARGEO_IV, iv);
- //
- // try (InputStream in = new CipherInputStream(unencrypted, cipher);) {
- // binary = session().getValueFactory().createBinary(in);
- // node.setProperty(Property.JCR_DATA, binary);
- // session().save();
- // }
- } catch (RepositoryException e) {
- throw new JcrException("Cannot encrypt", e);
- } finally {
- try {
- unencrypted.close();
- } catch (IOException e) {
- // silent
- }
- // IOUtils.closeQuietly(unencrypted);
- // IOUtils.closeQuietly(in);
- // JcrUtils.closeQuietly(binary);
- JcrUtils.logoutQuietly(session());
- }
- }
-
- protected synchronized void encrypt(SecretKey secretKey, Cipher cipher, Node node, InputStream unencrypted) {
- try {
- node.addMixin(ArgeoTypes.ARGEO_ENCRYPTED);
- SecureRandom random = new SecureRandom();
- byte[] iv = new byte[16];
- random.nextBytes(iv);
- cipher.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(iv));
- JcrUtils.setBinaryAsBytes(node, ARGEO_IV, iv);
-
- Binary binary = null;
- try (InputStream in = new CipherInputStream(unencrypted, cipher);) {
- binary = session().getValueFactory().createBinary(in);
- node.setProperty(Property.JCR_DATA, binary);
- session().save();
- } finally {
- JcrUtils.closeQuietly(binary);
- }
- } catch (RepositoryException e) {
- throw new JcrException("Cannot encrypt", e);
- } catch (GeneralSecurityException | IOException e) {
- throw new RuntimeException("Cannot encrypt", e);
- }
- }
-
- @Override
- protected synchronized InputStream decrypt(String path) {
- Binary binary = null;
- try {
- session().refresh(true);
- if (!session().nodeExists(path)) {
- char[] password = ask();
- Reader reader = new CharArrayReader(password);
- return new ByteArrayInputStream(IOUtils.toByteArray(reader, StandardCharsets.UTF_8));
- } else {
- // should be called first for lazy initialisation
- SecretKey secretKey = getSecretKey(null);
- Cipher cipher = createCipher();
- Node node = session().getNode(path);
- return decrypt(secretKey, cipher, node);
- }
- } catch (RepositoryException e) {
- throw new JcrException("Cannot decrypt", e);
- } catch (GeneralSecurityException | IOException e) {
- throw new RuntimeException("Cannot decrypt", e);
- } finally {
- JcrUtils.closeQuietly(binary);
- JcrUtils.logoutQuietly(session());
- }
- }
-
- protected synchronized InputStream decrypt(SecretKey secretKey, Cipher cipher, Node node)
- throws RepositoryException, GeneralSecurityException {
- if (node.hasProperty(ARGEO_IV)) {
- byte[] iv = JcrUtils.getBinaryAsBytes(node.getProperty(ARGEO_IV));
- cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(iv));
- } else {
- cipher.init(Cipher.DECRYPT_MODE, secretKey);
- }
-
- Binary binary = node.getProperty(Property.JCR_DATA).getBinary();
- InputStream encrypted = binary.getStream();
- return new CipherInputStream(encrypted, cipher);
- }
-
- protected Cipher createCipher() {
- try {
- Node userHome = CmsJcrUtils.getUserHome(session());
- if (!userHome.hasNode(ARGEO_KEYRING))
- throw new IllegalArgumentException("Keyring not setup");
- Node keyring = userHome.getNode(ARGEO_KEYRING);
- String cipherName = keyring.getProperty(ARGEO_CIPHER).getString();
- Provider securityProvider = getSecurityProvider();
- Cipher cipher;
- if (securityProvider == null)// TODO use BC?
- cipher = Cipher.getInstance(cipherName);
- else
- cipher = Cipher.getInstance(cipherName, securityProvider);
- return cipher;
- } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
- throw new IllegalArgumentException("Cannot get cipher", e);
- } catch (RepositoryException e) {
- throw new JcrException("Cannot get cipher", e);
- } finally {
-
- }
- }
-
- public synchronized void changePassword(char[] oldPassword, char[] newPassword) {
- // TODO make it XA compatible
- SecretKey oldSecretKey = getSecretKey(oldPassword);
- SecretKey newSecretKey = getSecretKey(newPassword);
- Session session = session();
- try {
- NodeIterator encryptedNodes = session.getWorkspace().getQueryManager()
- .createQuery("select * from [argeo:encrypted]", Query.JCR_SQL2).execute().getNodes();
- while (encryptedNodes.hasNext()) {
- Node node = encryptedNodes.nextNode();
- InputStream in = decrypt(oldSecretKey, createCipher(), node);
- encrypt(newSecretKey, createCipher(), node, in);
- if (log.isDebugEnabled())
- log.debug("Converted keyring encrypted value of " + node.getPath());
- }
- } catch (GeneralSecurityException e) {
- throw new RuntimeException("Cannot change JCR keyring password", e);
- } catch (RepositoryException e) {
- throw new JcrException("Cannot change JCR keyring password", e);
- } finally {
- JcrUtils.logoutQuietly(session);
- }
- }
-
- // public synchronized void setSession(Session session) {
- // this.session = session;
- // }
-
- public void setIterationCountFactor(Integer iterationCountFactor) {
- this.iterationCountFactor = iterationCountFactor;
- }
-
- public void setSecretKeyLength(Long keyLength) {
- this.secretKeyLength = keyLength;
- }
-
- public void setSecretKeyFactoryName(String secreteKeyFactoryName) {
- this.secretKeyFactoryName = secreteKeyFactoryName;
- }
-
- public void setSecretKeyEncryption(String secreteKeyEncryption) {
- this.secretKeyEncryption = secreteKeyEncryption;
- }
-
- public void setCipherName(String cipherName) {
- this.cipherName = cipherName;
- }
-
-}
\ No newline at end of file
+++ /dev/null
-package org.argeo.cms.jcr.internal;
-
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Map;
-
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.RepositoryFactory;
-
-import org.apache.jackrabbit.jcr2dav.Jcr2davRepositoryFactory;
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.cms.internal.jcr.RepoConf;
-import org.argeo.cms.jcr.internal.osgi.CmsJcrActivator;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.InvalidSyntaxException;
-import org.osgi.framework.ServiceReference;
-
-/**
- * OSGi-aware Jackrabbit repository factory which can retrieve/publish
- * {@link Repository} as OSGi services.
- */
-public class JcrRepositoryFactory implements RepositoryFactory {
- private final CmsLog log = CmsLog.getLog(getClass());
-// private final BundleContext bundleContext = FrameworkUtil.getBundle(getClass()).getBundleContext();
-
- // private Resource fileRepositoryConfiguration = new ClassPathResource(
- // "/org/argeo/cms/internal/kernel/repository-localfs.xml");
-
- protected Repository getRepositoryByAlias(String alias) {
- BundleContext bundleContext = CmsJcrActivator.getBundleContext();
- if (bundleContext != null) {
- try {
- Collection<ServiceReference<Repository>> srs = bundleContext.getServiceReferences(Repository.class,
- "(" + CmsConstants.CN + "=" + alias + ")");
- if (srs.size() == 0)
- throw new IllegalArgumentException("No repository with alias " + alias + " found in OSGi registry");
- else if (srs.size() > 1)
- throw new IllegalArgumentException(
- srs.size() + " repositories with alias " + alias + " found in OSGi registry");
- return bundleContext.getService(srs.iterator().next());
- } catch (InvalidSyntaxException e) {
- throw new IllegalArgumentException("Cannot find repository with alias " + alias, e);
- }
- } else {
- // TODO ability to filter static services
- return null;
- }
- }
-
- // private void publish(String alias, Repository repository, Properties
- // properties) {
- // if (bundleContext != null) {
- // // do not modify reference
- // Hashtable<String, String> props = new Hashtable<String, String>();
- // props.putAll(props);
- // props.put(JCR_REPOSITORY_ALIAS, alias);
- // bundleContext.registerService(Repository.class.getName(), repository,
- // props);
- // }
- // }
-
- @SuppressWarnings({ "rawtypes" })
- public Repository getRepository(Map parameters) throws RepositoryException {
- // // check if can be found by alias
- // Repository repository = super.getRepository(parameters);
- // if (repository != null)
- // return repository;
-
- // check if remote
- Repository repository;
- String uri = null;
- if (parameters.containsKey(RepoConf.labeledUri.name()))
- uri = parameters.get(CmsConstants.LABELED_URI).toString();
- else if (parameters.containsKey(KernelConstants.JACKRABBIT_REPOSITORY_URI))
- uri = parameters.get(KernelConstants.JACKRABBIT_REPOSITORY_URI).toString();
-
- if (uri != null) {
- if (uri.startsWith("http")) {// http, https
- Object defaultWorkspace = parameters.get(RepoConf.defaultWorkspace.name());
- repository = createRemoteRepository(uri, defaultWorkspace != null ? defaultWorkspace.toString() : null);
- } else if (uri.startsWith("file"))// http, https
- repository = createFileRepository(uri, parameters);
- else if (uri.startsWith("vm")) {
- // log.warn("URI " + uri + " should have been managed by generic
- // JCR repository factory");
- repository = getRepositoryByAlias(getAliasFromURI(uri));
- } else
- throw new IllegalArgumentException("Unrecognized URI format " + uri);
-
- }
-
- else if (parameters.containsKey(CmsConstants.CN)) {
- // Properties properties = new Properties();
- // properties.putAll(parameters);
- String alias = parameters.get(CmsConstants.CN).toString();
- // publish(alias, repository, properties);
- // log.info("Registered JCR repository under alias '" + alias + "'
- // with properties " + properties);
- repository = getRepositoryByAlias(alias);
- } else
- throw new IllegalArgumentException("Not enough information in " + parameters);
-
- if (repository == null)
- throw new IllegalArgumentException("Repository not found " + parameters);
-
- return repository;
- }
-
- protected Repository createRemoteRepository(String uri, String defaultWorkspace) throws RepositoryException {
- Map<String, String> params = new HashMap<String, String>();
- params.put(KernelConstants.JACKRABBIT_REPOSITORY_URI, uri);
- if (defaultWorkspace != null)
- params.put(KernelConstants.JACKRABBIT_REMOTE_DEFAULT_WORKSPACE, defaultWorkspace);
- Repository repository = new Jcr2davRepositoryFactory().getRepository(params);
- if (repository == null)
- throw new IllegalArgumentException("Remote Davex repository " + uri + " not found");
- log.info("Initialized remote Jackrabbit repository from uri " + uri);
- return repository;
- }
-
- @SuppressWarnings({ "rawtypes" })
- protected Repository createFileRepository(final String uri, Map parameters) throws RepositoryException {
- throw new UnsupportedOperationException();
- // InputStream configurationIn = null;
- // try {
- // Properties vars = new Properties();
- // vars.putAll(parameters);
- // String dirPath = uri.substring("file:".length());
- // File homeDir = new File(dirPath);
- // if (homeDir.exists() && !homeDir.isDirectory())
- // throw new ArgeoJcrException("Repository home " + dirPath + " is not a
- // directory");
- // if (!homeDir.exists())
- // homeDir.mkdirs();
- // configurationIn = fileRepositoryConfiguration.getInputStream();
- // vars.put(RepositoryConfigurationParser.REPOSITORY_HOME_VARIABLE,
- // homeDir.getCanonicalPath());
- // RepositoryConfig repositoryConfig = RepositoryConfig.create(new
- // InputSource(configurationIn), vars);
- //
- // // TransientRepository repository = new
- // // TransientRepository(repositoryConfig);
- // final RepositoryImpl repository =
- // RepositoryImpl.create(repositoryConfig);
- // Session session = repository.login();
- // // FIXME make it generic
- // org.argeo.jcr.JcrUtils.addPrivilege(session, "/", "ROLE_ADMIN",
- // "jcr:all");
- // org.argeo.jcr.JcrUtils.logoutQuietly(session);
- // Runtime.getRuntime().addShutdownHook(new Thread("Clean JCR repository
- // " + uri) {
- // public void run() {
- // repository.shutdown();
- // log.info("Destroyed repository " + uri);
- // }
- // });
- // log.info("Initialized file Jackrabbit repository from uri " + uri);
- // return repository;
- // } catch (Exception e) {
- // throw new ArgeoJcrException("Cannot create repository " + uri, e);
- // } finally {
- // IOUtils.closeQuietly(configurationIn);
- // }
- }
-
- protected String getAliasFromURI(String uri) {
- try {
- URI uriObj = new URI(uri);
- String alias = uriObj.getPath();
- if (alias.charAt(0) == '/')
- alias = alias.substring(1);
- if (alias.charAt(alias.length() - 1) == '/')
- alias = alias.substring(0, alias.length() - 1);
- return alias;
- } catch (URISyntaxException e) {
- throw new IllegalArgumentException("Cannot interpret URI " + uri, e);
- }
- }
-
- /**
- * Called after the repository has been initialised. Does nothing by default.
- */
- @SuppressWarnings("rawtypes")
- protected void postInitialization(Repository repository, Map parameters) {
-
- }
-}
+++ /dev/null
-package org.argeo.cms.jcr.internal;
-
-import org.argeo.api.cms.CmsConstants;
-
-/** Internal CMS constants. */
-@Deprecated
-public interface KernelConstants {
- // Directories
- String DIR_NODE = "node";
- String DIR_REPOS = "repos";
- String DIR_INDEXES = "indexes";
- String DIR_TRANSACTIONS = "transactions";
-
- // Files
- String DEPLOY_CONFIG_PATH = DIR_NODE + '/' + CmsConstants.DEPLOY_BASEDN + ".ldif";
- String DEFAULT_KEYSTORE_PATH = DIR_NODE + '/' + CmsConstants.NODE + ".p12";
- String DEFAULT_PEM_KEY_PATH = DIR_NODE + '/' + CmsConstants.NODE + ".key";
- String DEFAULT_PEM_CERT_PATH = DIR_NODE + '/' + CmsConstants.NODE + ".crt";
- String NODE_KEY_TAB_PATH = DIR_NODE + "/krb5.keytab";
-
- // Security
- String JAAS_CONFIG = "/org/argeo/cms/internal/kernel/jaas.cfg";
- String JAAS_CONFIG_IPA = "/org/argeo/cms/internal/kernel/jaas-ipa.cfg";
-
- // Java
- String JAAS_CONFIG_PROP = "java.security.auth.login.config";
-
- // DEFAULTS JCR PATH
- String DEFAULT_HOME_BASE_PATH = "/home";
- String DEFAULT_USERS_BASE_PATH = "/users";
- String DEFAULT_GROUPS_BASE_PATH = "/groups";
-
- // KERBEROS
- String DEFAULT_KERBEROS_SERVICE = "HTTP";
-
- // HTTP client
- String COOKIE_POLICY_BROWSER_COMPATIBILITY = "compatibility";
-
- // RWT / RAP
- // String PATH_WORKBENCH = "/ui";
- // String PATH_WORKBENCH_PUBLIC = PATH_WORKBENCH + "/public";
-
- String JETTY_FACTORY_PID = "org.eclipse.equinox.http.jetty.config";
- String WHITEBOARD_PATTERN_PROP = "osgi.http.whiteboard.servlet.pattern";
- // default Jetty server configured via JettyConfigurator
- String DEFAULT_JETTY_SERVER = "default";
- String CMS_JETTY_CUSTOMIZER_CLASS = "org.argeo.equinox.jetty.CmsJettyCustomizer";
-
- // avoid dependencies
- String CONTEXT_NAME_PROP = "contextName";
- String JACKRABBIT_REPOSITORY_URI = "org.apache.jackrabbit.repository.uri";
- String JACKRABBIT_REMOTE_DEFAULT_WORKSPACE = "org.apache.jackrabbit.spi2davex.WorkspaceNameDefault";
-}
+++ /dev/null
-package org.argeo.cms.jcr.internal;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.PrintStream;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.net.URL;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.security.PrivilegedAction;
-import java.security.URIParameter;
-import java.util.Dictionary;
-import java.util.Hashtable;
-import java.util.Properties;
-import java.util.TreeMap;
-import java.util.TreeSet;
-
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.security.auth.Subject;
-import javax.security.auth.login.LoginContext;
-import javax.security.auth.login.LoginException;
-
-import org.argeo.api.cms.CmsAuth;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.cms.jcr.internal.osgi.CmsJcrActivator;
-import org.argeo.cms.osgi.DataModelNamespace;
-import org.osgi.framework.BundleContext;
-import org.osgi.util.tracker.ServiceTracker;
-
-/** Package utilities */
-class KernelUtils implements KernelConstants {
- final static String OSGI_INSTANCE_AREA = "osgi.instance.area";
- final static String OSGI_CONFIGURATION_AREA = "osgi.configuration.area";
-
- static void setJaasConfiguration(URL jaasConfigurationUrl) {
- try {
- URIParameter uriParameter = new URIParameter(jaasConfigurationUrl.toURI());
- javax.security.auth.login.Configuration jaasConfiguration = javax.security.auth.login.Configuration
- .getInstance("JavaLoginConfig", uriParameter);
- javax.security.auth.login.Configuration.setConfiguration(jaasConfiguration);
- } catch (Exception e) {
- throw new IllegalArgumentException("Cannot set configuration " + jaasConfigurationUrl, e);
- }
- }
-
- static Dictionary<String, ?> asDictionary(Properties props) {
- Hashtable<String, Object> hashtable = new Hashtable<String, Object>();
- for (Object key : props.keySet()) {
- hashtable.put(key.toString(), props.get(key));
- }
- return hashtable;
- }
-
- static Dictionary<String, ?> asDictionary(ClassLoader cl, String resource) {
- Properties props = new Properties();
- try {
- props.load(cl.getResourceAsStream(resource));
- } catch (IOException e) {
- throw new IllegalArgumentException("Cannot load " + resource + " from classpath", e);
- }
- return asDictionary(props);
- }
-
- static File getExecutionDir(String relativePath) {
- File executionDir = new File(getFrameworkProp("user.dir"));
- if (relativePath == null)
- return executionDir;
- try {
- return new File(executionDir, relativePath).getCanonicalFile();
- } catch (IOException e) {
- throw new IllegalArgumentException("Cannot get canonical file", e);
- }
- }
-
- static File getOsgiInstanceDir() {
- return new File(getBundleContext().getProperty(OSGI_INSTANCE_AREA).substring("file:".length()))
- .getAbsoluteFile();
- }
-
- static Path getOsgiInstancePath(String relativePath) {
- return Paths.get(getOsgiInstanceUri(relativePath));
- }
-
- static URI getOsgiInstanceUri(String relativePath) {
- String osgiInstanceBaseUri = getFrameworkProp(OSGI_INSTANCE_AREA);
- if (osgiInstanceBaseUri != null)
- return safeUri(osgiInstanceBaseUri + (relativePath != null ? relativePath : ""));
- else
- return Paths.get(System.getProperty("user.dir")).toUri();
- }
-
- static File getOsgiConfigurationFile(String relativePath) {
- try {
- return new File(new URI(getBundleContext().getProperty(OSGI_CONFIGURATION_AREA) + relativePath))
- .getCanonicalFile();
- } catch (Exception e) {
- throw new IllegalArgumentException("Cannot get configuration file for " + relativePath, e);
- }
- }
-
- static String getFrameworkProp(String key, String def) {
- BundleContext bundleContext = CmsJcrActivator.getBundleContext();
- String value;
- if (bundleContext != null)
- value = bundleContext.getProperty(key);
- else
- value = System.getProperty(key);
- if (value == null)
- return def;
- return value;
- }
-
- static String getFrameworkProp(String key) {
- return getFrameworkProp(key, null);
- }
-
- // Security
- // static Subject anonymousLogin() {
- // Subject subject = new Subject();
- // LoginContext lc;
- // try {
- // lc = new LoginContext(NodeConstants.LOGIN_CONTEXT_USER, subject);
- // lc.login();
- // return subject;
- // } catch (LoginException e) {
- // throw new CmsException("Cannot login as anonymous", e);
- // }
- // }
-
- static void logFrameworkProperties(CmsLog log) {
- BundleContext bc = getBundleContext();
- for (Object sysProp : new TreeSet<Object>(System.getProperties().keySet())) {
- log.debug(sysProp + "=" + bc.getProperty(sysProp.toString()));
- }
- // String[] keys = { Constants.FRAMEWORK_STORAGE,
- // Constants.FRAMEWORK_OS_NAME, Constants.FRAMEWORK_OS_VERSION,
- // Constants.FRAMEWORK_PROCESSOR, Constants.FRAMEWORK_SECURITY,
- // Constants.FRAMEWORK_TRUST_REPOSITORIES,
- // Constants.FRAMEWORK_WINDOWSYSTEM, Constants.FRAMEWORK_VENDOR,
- // Constants.FRAMEWORK_VERSION, Constants.FRAMEWORK_STORAGE_CLEAN,
- // Constants.FRAMEWORK_LANGUAGE, Constants.FRAMEWORK_UUID };
- // for (String key : keys)
- // log.debug(key + "=" + bc.getProperty(key));
- }
-
- static void printSystemProperties(PrintStream out) {
- TreeMap<String, String> display = new TreeMap<>();
- for (Object key : System.getProperties().keySet())
- display.put(key.toString(), System.getProperty(key.toString()));
- for (String key : display.keySet())
- out.println(key + "=" + display.get(key));
- }
-
- static Session openAdminSession(Repository repository) {
- return openAdminSession(repository, null);
- }
-
- static Session openAdminSession(final Repository repository, final String workspaceName) {
- LoginContext loginContext = loginAsDataAdmin();
- return Subject.doAs(loginContext.getSubject(), new PrivilegedAction<Session>() {
-
- @Override
- public Session run() {
- try {
- return repository.login(workspaceName);
- } catch (RepositoryException e) {
- throw new IllegalStateException("Cannot open admin session", e);
- } finally {
- try {
- loginContext.logout();
- } catch (LoginException e) {
- throw new IllegalStateException(e);
- }
- }
- }
-
- });
- }
-
- static LoginContext loginAsDataAdmin() {
- ClassLoader currentCl = Thread.currentThread().getContextClassLoader();
- Thread.currentThread().setContextClassLoader(KernelUtils.class.getClassLoader());
- LoginContext loginContext;
- try {
- loginContext = new LoginContext(CmsAuth.LOGIN_CONTEXT_DATA_ADMIN);
- loginContext.login();
- } catch (LoginException e1) {
- throw new IllegalStateException("Could not login as data admin", e1);
- } finally {
- Thread.currentThread().setContextClassLoader(currentCl);
- }
- return loginContext;
- }
-
- static void doAsDataAdmin(Runnable action) {
- LoginContext loginContext = loginAsDataAdmin();
- Subject.doAs(loginContext.getSubject(), new PrivilegedAction<Void>() {
-
- @Override
- public Void run() {
- try {
- action.run();
- return null;
- } finally {
- try {
- loginContext.logout();
- } catch (LoginException e) {
- throw new IllegalStateException(e);
- }
- }
- }
-
- });
- }
-
- static void asyncOpen(ServiceTracker<?, ?> st) {
- Runnable run = new Runnable() {
-
- @Override
- public void run() {
- st.open();
- }
- };
-// Activator.getInternalExecutorService().execute(run);
- new Thread(run, "Open service tracker " + st).start();
- }
-
- static BundleContext getBundleContext() {
- return CmsJcrActivator.getBundleContext();
- }
-
- static boolean asBoolean(String value) {
- if (value == null)
- return false;
- switch (value) {
- case "true":
- return true;
- case "false":
- return false;
- default:
- throw new IllegalArgumentException(
- "Unsupported value for attribute " + DataModelNamespace.ABSTRACT + ": " + value);
- }
- }
-
- private static URI safeUri(String uri) {
- if (uri == null)
- throw new IllegalArgumentException("URI cannot be null");
- try {
- return new URI(uri);
- } catch (URISyntaxException e) {
- throw new IllegalArgumentException("Badly formatted URI " + uri, e);
- }
- }
-
- private KernelUtils() {
-
- }
-}
+++ /dev/null
-package org.argeo.cms.jcr.internal;
-
-import javax.jcr.Repository;
-
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.jcr.JcrRepositoryWrapper;
-
-class LocalRepository extends JcrRepositoryWrapper {
- private final String cn;
-
- public LocalRepository(Repository repository, String cn) {
- super(repository);
- this.cn = cn;
- // Map<String, Object> attrs = dataModelCapability.getAttributes();
- // cn = (String) attrs.get(DataModelNamespace.NAME);
- putDescriptor(CmsConstants.CN, cn);
- }
-
- String getCn() {
- return cn;
- }
-
-}
+++ /dev/null
-package org.argeo.cms.jcr.internal;
-
-import java.util.Dictionary;
-
-import javax.jcr.Repository;
-
-import org.osgi.service.cm.ConfigurationException;
-import org.osgi.service.cm.ManagedService;
-
-class NodeKeyRing extends JcrKeyring implements ManagedService{
-
- public NodeKeyRing(Repository repository) {
- super(repository);
- }
-
- @Override
- public void updated(Dictionary<String, ?> properties) throws ConfigurationException {
- }
-}
+++ /dev/null
-package org.argeo.cms.jcr.internal;
-
-import java.io.IOException;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.Dictionary;
-import java.util.HashMap;
-import java.util.Map;
-
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.RepositoryFactory;
-
-import org.apache.jackrabbit.core.RepositoryContext;
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.cms.internal.jcr.RepoConf;
-import org.argeo.cms.internal.jcr.RepositoryBuilder;
-import org.argeo.cms.jcr.internal.osgi.CmsJcrActivator;
-import org.argeo.util.LangUtils;
-import org.osgi.framework.Constants;
-import org.osgi.service.cm.ConfigurationException;
-import org.osgi.service.cm.ManagedServiceFactory;
-
-/** A {@link ManagedServiceFactory} creating or referencing JCR repositories. */
-public class RepositoryContextsFactory implements ManagedServiceFactory {
- private final static CmsLog log = CmsLog.getLog(RepositoryContextsFactory.class);
-// private final BundleContext bc = FrameworkUtil.getBundle(RepositoryServiceFactory.class).getBundleContext();
-
- private Map<String, RepositoryContext> repositories = new HashMap<String, RepositoryContext>();
- private Map<String, Object> pidToCn = new HashMap<String, Object>();
-
- public void init() {
-
- }
-
- public void destroy() {
- for (String pid : repositories.keySet()) {
- try {
- RepositoryContext repositoryContext = repositories.get(pid);
- // Must start in another thread otherwise shutdown is interrupted
- // TODO use an executor?
- new Thread(() -> {
- repositoryContext.getRepository().shutdown();
- if (log.isDebugEnabled())
- log.debug("Shut down repository " + pid
- + (pidToCn.containsKey(pid) ? " (" + pidToCn.get(pid) + ")" : ""));
- }, "Shutdown JCR repository " + pid).start();
- } catch (Exception e) {
- log.error("Error when shutting down Jackrabbit repository " + pid, e);
- }
- }
- }
-
- @Override
- public String getName() {
- return "Jackrabbit repository service factory";
- }
-
- @Override
- public void updated(String pid, Dictionary<String, ?> properties) throws ConfigurationException {
- if (repositories.containsKey(pid))
- throw new IllegalArgumentException("Already a repository registered for " + pid);
-
- if (properties == null)
- return;
-
- Object cn = properties.get(CmsConstants.CN);
- if (cn != null)
- for (String otherPid : pidToCn.keySet()) {
- Object o = pidToCn.get(otherPid);
- if (cn.equals(o)) {
- RepositoryContext repositoryContext = repositories.remove(otherPid);
- repositories.put(pid, repositoryContext);
- if (log.isDebugEnabled())
- log.debug("Ignoring update of Jackrabbit repository " + cn);
- // FIXME perform a proper update (also of the OSGi service)
- return;
- }
- }
-
- try {
- Object labeledUri = properties.get(RepoConf.labeledUri.name());
- if (labeledUri == null) {
- RepositoryBuilder repositoryBuilder = new RepositoryBuilder();
- RepositoryContext repositoryContext = repositoryBuilder.createRepositoryContext(properties);
- repositories.put(pid, repositoryContext);
- Dictionary<String, Object> props = LangUtils.dict(Constants.SERVICE_PID, pid);
- // props.put(ArgeoJcrConstants.JCR_REPOSITORY_URI,
- // properties.get(RepoConf.labeledUri.name()));
- if (cn != null) {
- props.put(CmsConstants.CN, cn);
- // props.put(NodeConstants.JCR_REPOSITORY_ALIAS, cn);
- pidToCn.put(pid, cn);
- }
- CmsJcrActivator.registerService(RepositoryContext.class, repositoryContext, props);
- } else {
- Object defaultWorkspace = properties.get(RepoConf.defaultWorkspace.name());
- if (defaultWorkspace == null)
- defaultWorkspace = RepoConf.defaultWorkspace.getDefault();
- URI uri = new URI(labeledUri.toString());
-// RepositoryFactory repositoryFactory = bc
-// .getService(bc.getServiceReference(RepositoryFactory.class));
- RepositoryFactory repositoryFactory = CmsJcrActivator.getService(RepositoryFactory.class);
- Map<String, String> parameters = new HashMap<String, String>();
- parameters.put(RepoConf.labeledUri.name(), uri.toString());
- parameters.put(RepoConf.defaultWorkspace.name(), defaultWorkspace.toString());
- Repository repository = repositoryFactory.getRepository(parameters);
- // Repository repository = NodeUtils.getRepositoryByUri(repositoryFactory,
- // uri.toString());
- Dictionary<String, Object> props = LangUtils.dict(Constants.SERVICE_PID, pid);
- props.put(RepoConf.labeledUri.name(),
- new URI(uri.getScheme(), null, uri.getHost(), uri.getPort(), uri.getPath(), null, null)
- .toString());
- if (cn != null) {
- props.put(CmsConstants.CN, cn);
- // props.put(NodeConstants.JCR_REPOSITORY_ALIAS, cn);
- pidToCn.put(pid, cn);
- }
- CmsJcrActivator.registerService(Repository.class, repository, props);
-
- // home
- if (cn.equals(CmsConstants.NODE_REPOSITORY)) {
- Dictionary<String, Object> homeProps = LangUtils.dict(CmsConstants.CN, CmsConstants.EGO_REPOSITORY);
- EgoRepository homeRepository = new EgoRepository(repository, true);
- CmsJcrActivator.registerService(Repository.class, homeRepository, homeProps);
- }
- }
- } catch (RepositoryException | URISyntaxException | IOException e) {
- throw new IllegalStateException("Cannot create Jackrabbit repository " + pid, e);
- }
-
- }
-
- @Override
- public void deleted(String pid) {
- RepositoryContext repositoryContext = repositories.remove(pid);
- repositoryContext.getRepository().shutdown();
- if (log.isDebugEnabled())
- log.debug("Deleted repository " + pid);
- }
-
-}
+++ /dev/null
-package org.argeo.cms.jcr.internal;
-
-import java.io.File;
-import java.lang.management.ManagementFactory;
-
-import org.apache.jackrabbit.api.stats.RepositoryStatistics;
-import org.apache.jackrabbit.stats.RepositoryStatisticsImpl;
-import org.argeo.api.cms.CmsLog;
-
-/**
- * Background thread started by the kernel, which gather statistics and
- * monitor/control other processes.
- */
-public class StatisticsThread extends Thread {
- private final static CmsLog log = CmsLog.getLog(StatisticsThread.class);
-
- private RepositoryStatisticsImpl repoStats;
-
- /** The smallest period of operation, in ms */
- private final long PERIOD = 60 * 1000l;
- /** One ms in ns */
- private final static long m = 1000l * 1000l;
- private final static long M = 1024l * 1024l;
-
- private boolean running = true;
-
- private CmsLog kernelStatsLog = CmsLog.getLog("argeo.stats.kernel");
- private CmsLog nodeStatsLog = CmsLog.getLog("argeo.stats.node");
-
- @SuppressWarnings("unused")
- private long cycle = 0l;
-
- public StatisticsThread(String name) {
- super(name);
- }
-
- private void doSmallestPeriod() {
- // Clean expired sessions
- // FIXME re-enable it in CMS
- //CmsSessionImpl.closeInvalidSessions();
-
- if (kernelStatsLog.isDebugEnabled()) {
- StringBuilder line = new StringBuilder(64);
- line.append("§\t");
- long freeMem = Runtime.getRuntime().freeMemory() / M;
- long totalMem = Runtime.getRuntime().totalMemory() / M;
- long maxMem = Runtime.getRuntime().maxMemory() / M;
- double loadAvg = ManagementFactory.getOperatingSystemMXBean().getSystemLoadAverage();
- // in min
- boolean min = true;
- long uptime = ManagementFactory.getRuntimeMXBean().getUptime() / (1000 * 60);
- if (uptime > 24 * 60) {
- min = false;
- uptime = uptime / 60;
- }
- line.append(uptime).append(min ? " min" : " h").append('\t');
- line.append(loadAvg).append('\t').append(maxMem).append('\t').append(totalMem).append('\t').append(freeMem)
- .append('\t');
- kernelStatsLog.debug(line);
- }
-
- if (nodeStatsLog.isDebugEnabled()) {
- File dataDir = KernelUtils.getOsgiInstanceDir();
- long freeSpace = dataDir.getUsableSpace() / M;
- // File currentRoot = null;
- // for (File root : File.listRoots()) {
- // String rootPath = root.getAbsolutePath();
- // if (dataDir.getAbsolutePath().startsWith(rootPath)) {
- // if (currentRoot == null
- // || (rootPath.length() > currentRoot.getPath()
- // .length())) {
- // currentRoot = root;
- // }
- // }
- // }
- // long totalSpace = currentRoot.getTotalSpace();
- StringBuilder line = new StringBuilder(128);
- line.append("§\t").append(freeSpace).append(" MB left in " + dataDir);
- line.append('\n');
- if (repoStats != null)
- for (RepositoryStatistics.Type type : RepositoryStatistics.Type.values()) {
- long[] vals = repoStats.getTimeSeries(type).getValuePerMinute();
- long val = vals[vals.length - 1];
- line.append(type.name()).append('\t').append(val).append('\n');
- }
- nodeStatsLog.debug(line);
- }
- }
-
- @Override
- public void run() {
- if (log.isTraceEnabled())
- log.trace("Kernel thread started.");
- final long periodNs = PERIOD * m;
- while (running) {
- long beginNs = System.nanoTime();
- doSmallestPeriod();
-
- long waitNs = periodNs - (System.nanoTime() - beginNs);
- if (waitNs < 0)
- continue;
- // wait
- try {
- sleep(waitNs / m, (int) (waitNs % m));
- } catch (InterruptedException e) {
- // silent
- }
- cycle++;
- }
- }
-
- public synchronized void destroyAndJoin() {
- running = false;
- notifyAll();
-// interrupt();
-// try {
-// join(PERIOD * 2);
-// } catch (InterruptedException e) {
-// // throw new CmsException("Kernel thread destruction was interrupted");
-// log.error("Kernel thread destruction was interrupted", e);
-// }
- }
-}
+++ /dev/null
-package org.argeo.cms.jcr.internal.osgi;
-
-import java.util.Dictionary;
-
-import org.argeo.cms.jcr.internal.StatisticsThread;
-import org.osgi.framework.BundleActivator;
-import org.osgi.framework.BundleContext;
-
-public class CmsJcrActivator implements BundleActivator {
- private static BundleContext bundleContext;
-
-// private List<Runnable> stopHooks = new ArrayList<>();
- private StatisticsThread kernelThread;
-
-// private JackrabbitRepositoryContextsFactory repositoryServiceFactory;
-// private CmsJcrDeployment jcrDeployment;
-
- @Override
- public void start(BundleContext context) throws Exception {
- bundleContext = context;
-
- // kernel thread
- kernelThread = new StatisticsThread("Kernel Thread");
- kernelThread.setContextClassLoader(getClass().getClassLoader());
- kernelThread.start();
-
- // JCR
-// repositoryServiceFactory = new JackrabbitRepositoryContextsFactory();
-//// stopHooks.add(() -> repositoryServiceFactory.shutdown());
-// registerService(ManagedServiceFactory.class, repositoryServiceFactory,
-// LangUtils.dict(Constants.SERVICE_PID, CmsConstants.NODE_REPOS_FACTORY_PID));
-
-// JcrRepositoryFactory repositoryFactory = new JcrRepositoryFactory();
-// registerService(RepositoryFactory.class, repositoryFactory, null);
-
- // File System
-// CmsJcrFsProvider cmsFsProvider = new CmsJcrFsProvider();
-// ServiceLoader<FileSystemProvider> fspSl = ServiceLoader.load(FileSystemProvider.class);
-// for (FileSystemProvider fsp : fspSl) {
-// log.debug("FileSystemProvider " + fsp);
-// if (fsp instanceof CmsFsProvider) {
-// cmsFsProvider = (CmsFsProvider) fsp;
-// }
-// }
-// for (FileSystemProvider fsp : FileSystemProvider.installedProviders()) {
-// log.debug("Installed FileSystemProvider " + fsp);
-// }
-// registerService(FileSystemProvider.class, cmsFsProvider,
-// LangUtils.dict(Constants.SERVICE_PID, CmsConstants.NODE_FS_PROVIDER_PID));
-
-// jcrDeployment = new CmsJcrDeployment();
-// jcrDeployment.init();
- }
-
- @Override
- public void stop(BundleContext context) throws Exception {
-// if (jcrDeployment != null)
-// jcrDeployment.destroy();
-
-// if (repositoryServiceFactory != null)
-// repositoryServiceFactory.shutdown();
-
- if (kernelThread != null)
- kernelThread.destroyAndJoin();
-
- bundleContext = null;
- }
-
- @Deprecated
- public static <T> void registerService(Class<T> clss, T service, Dictionary<String, ?> properties) {
- if (bundleContext != null) {
- bundleContext.registerService(clss, service, properties);
- }
-
- }
-
- @Deprecated
- public static BundleContext getBundleContext() {
- return bundleContext;
- }
-
- @Deprecated
- public static <T> T getService(Class<T> clss) {
- if (bundleContext != null) {
- return bundleContext.getService(bundleContext.getServiceReference(clss));
- } else {
- return null;
- }
- }
-
-}
+++ /dev/null
-package org.argeo.cms.jcr.internal.servlet;
-
-import java.util.Map;
-
-import javax.jcr.Repository;
-
-import org.apache.jackrabbit.server.SessionProvider;
-import org.apache.jackrabbit.server.remoting.davex.JcrRemotingServlet;
-import org.argeo.api.cms.CmsConstants;
-
-/** A {@link JcrRemotingServlet} based on {@link CmsSessionProvider}. */
-public class CmsRemotingServlet extends JcrRemotingServlet {
- private static final long serialVersionUID = 6459455509684213633L;
- private Repository repository;
- private SessionProvider sessionProvider;
-
- public CmsRemotingServlet() {
- }
-
- public CmsRemotingServlet(String alias, Repository repository) {
- this.repository = repository;
- this.sessionProvider = new CmsSessionProvider(alias);
- }
-
- @Override
- public Repository getRepository() {
- return repository;
- }
-
- public void setRepository(Repository repository, Map<String, String> properties) {
- this.repository = repository;
- String alias = properties.get(CmsConstants.CN);
- if (alias != null)
- sessionProvider = new CmsSessionProvider(alias);
- else
- throw new IllegalArgumentException("Only aliased repositories are supported");
- }
-
- @Override
- protected SessionProvider getSessionProvider() {
- return sessionProvider;
- }
-
-}
+++ /dev/null
-package org.argeo.cms.jcr.internal.servlet;
-
-import java.io.Serializable;
-import java.security.PrivilegedActionException;
-import java.security.PrivilegedExceptionAction;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.LinkedHashMap;
-import java.util.Map;
-import java.util.Set;
-
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.security.auth.Subject;
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-
-import org.apache.jackrabbit.server.SessionProvider;
-import org.argeo.api.cms.CmsSession;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.jcr.JcrUtils;
-
-/**
- * Implements an open session in view patter: a new JCR session is created for
- * each request
- */
-public class CmsSessionProvider implements SessionProvider, Serializable {
- private static final long serialVersionUID = -1358136599534938466L;
-
- private final static CmsLog log = CmsLog.getLog(CmsSessionProvider.class);
-
- private final String alias;
-
- private LinkedHashMap<Session, CmsDataSession> cmsSessions = new LinkedHashMap<>();
-
- public CmsSessionProvider(String alias) {
- this.alias = alias;
- }
-
- public Session getSession(HttpServletRequest request, Repository rep, String workspace)
- throws javax.jcr.LoginException, ServletException, RepositoryException {
-
- // a client is scanning parent URLs.
-// if (workspace == null)
-// return null;
-
-// CmsSessionImpl cmsSession = WebCmsSessionImpl.getCmsSession(request);
- // FIXME retrieve CMS session
- CmsSession cmsSession = null;
- if (log.isTraceEnabled()) {
- log.trace("Get JCR session from " + cmsSession);
- }
- if (cmsSession == null)
- throw new IllegalStateException("Cannot find a session for request " + request.getRequestURI());
- CmsDataSession cmsDataSession = new CmsDataSession(cmsSession);
- Session session = cmsDataSession.getDataSession(alias, workspace, rep);
- cmsSessions.put(session, cmsDataSession);
- return session;
- }
-
- public void releaseSession(Session session) {
-// JcrUtils.logoutQuietly(session);
- if (cmsSessions.containsKey(session)) {
- CmsDataSession cmsDataSession = cmsSessions.get(session);
- cmsDataSession.releaseDataSession(alias, session);
- } else {
- log.warn("JCR session " + session + " not found in CMS session list. Logging it out...");
- JcrUtils.logoutQuietly(session);
- }
- }
-
- static class CmsDataSession {
- private CmsSession cmsSession;
-
- private Map<String, Session> dataSessions = new HashMap<>();
- private Set<String> dataSessionsInUse = new HashSet<>();
- private Set<Session> additionalDataSessions = new HashSet<>();
-
- private CmsDataSession(CmsSession cmsSession) {
- this.cmsSession = cmsSession;
- }
-
- public Session newDataSession(String cn, String workspace, Repository repository) {
- checkValid();
- return login(repository, workspace);
- }
-
- public synchronized Session getDataSession(String cn, String workspace, Repository repository) {
- checkValid();
- // FIXME make it more robust
- if (workspace == null)
- workspace = CmsConstants.SYS_WORKSPACE;
- String path = cn + '/' + workspace;
- if (dataSessionsInUse.contains(path)) {
- try {
- wait(1000);
- if (dataSessionsInUse.contains(path)) {
- Session session = login(repository, workspace);
- additionalDataSessions.add(session);
- if (log.isTraceEnabled())
- log.trace("Additional data session " + path + " for " + cmsSession.getUserDn());
- return session;
- }
- } catch (InterruptedException e) {
- // silent
- }
- }
-
- Session session = null;
- if (dataSessions.containsKey(path)) {
- session = dataSessions.get(path);
- } else {
- session = login(repository, workspace);
- dataSessions.put(path, session);
- if (log.isTraceEnabled())
- log.trace("New data session " + path + " for " + cmsSession.getUserDn());
- }
- dataSessionsInUse.add(path);
- return session;
- }
-
- private Session login(Repository repository, String workspace) {
- try {
- return Subject.doAs(cmsSession.getSubject(), new PrivilegedExceptionAction<Session>() {
- @Override
- public Session run() throws Exception {
- return repository.login(workspace);
- }
- });
- } catch (PrivilegedActionException e) {
- throw new IllegalStateException("Cannot log in " + cmsSession.getUserDn() + " to JCR", e);
- }
- }
-
- public synchronized void releaseDataSession(String cn, Session session) {
- if (additionalDataSessions.contains(session)) {
- JcrUtils.logoutQuietly(session);
- additionalDataSessions.remove(session);
- if (log.isTraceEnabled())
- log.trace("Remove additional data session " + session);
- return;
- }
- String path = cn + '/' + session.getWorkspace().getName();
- if (!dataSessionsInUse.contains(path))
- log.warn("Data session " + path + " was not in use for " + cmsSession.getUserDn());
- dataSessionsInUse.remove(path);
- Session registeredSession = dataSessions.get(path);
- if (session != registeredSession)
- log.warn("Data session " + path + " not consistent for " + cmsSession.getUserDn());
- if (log.isTraceEnabled())
- log.trace("Released data session " + session + " for " + path);
- notifyAll();
- }
-
- private void checkValid() {
- if (!cmsSession.isValid())
- throw new IllegalStateException(
- "CMS session " + cmsSession.getUuid() + " is not valid since " + cmsSession.getEnd());
- }
-
- private void close() {
- // FIXME class this when CMS session is closed
- synchronized (this) {
- // TODO check data session in use ?
- for (String path : dataSessions.keySet())
- JcrUtils.logoutQuietly(dataSessions.get(path));
- for (Session session : additionalDataSessions)
- JcrUtils.logoutQuietly(session);
- }
- }
- }
-}
+++ /dev/null
-package org.argeo.cms.jcr.internal.servlet;
-
-import java.util.Map;
-
-import javax.jcr.Repository;
-
-import org.apache.jackrabbit.webdav.simple.SimpleWebdavServlet;
-import org.argeo.api.cms.CmsConstants;
-
-/** A {@link SimpleWebdavServlet} based on {@link CmsSessionProvider}. */
-public class CmsWebDavServlet extends SimpleWebdavServlet {
- private static final long serialVersionUID = 7485800288686328063L;
- private Repository repository;
-
- public CmsWebDavServlet() {
- }
-
- public CmsWebDavServlet(String alias, Repository repository) {
- this.repository = repository;
- setSessionProvider(new CmsSessionProvider(alias));
- }
-
- @Override
- public Repository getRepository() {
- return repository;
- }
-
- public void setRepository(Repository repository, Map<String, String> properties) {
- this.repository = repository;
- String alias = properties.get(CmsConstants.CN);
- if (alias != null)
- setSessionProvider(new CmsSessionProvider(alias));
- else
- throw new IllegalArgumentException("Only aliased repositories are supported");
- }
-
-}
+++ /dev/null
-package org.argeo.cms.jcr.internal.servlet;
-
-import org.argeo.cms.servlet.CmsServletContext;
-
-/** Internal subclass, so that config resources can be loaded from our bundle. */
-public class DataServletContext extends CmsServletContext {
-
-}
+++ /dev/null
-package org.argeo.cms.jcr.internal.servlet;
-
-import java.util.Enumeration;
-
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.argeo.api.cms.CmsLog;
-
-public class JcrHttpUtils {
- public final static String HEADER_AUTHORIZATION = "Authorization";
- public final static String HEADER_WWW_AUTHENTICATE = "WWW-Authenticate";
-
- public final static String DEFAULT_PROTECTED_HANDLERS = "/org/argeo/cms/jcr/internal/servlet/protectedHandlers.xml";
- public final static String WEBDAV_CONFIG = "/org/argeo/cms/jcr/internal/servlet/webdav-config.xml";
-
- static boolean isBrowser(String userAgent) {
- return userAgent.contains("webkit") || userAgent.contains("gecko") || userAgent.contains("firefox")
- || userAgent.contains("msie") || userAgent.contains("chrome") || userAgent.contains("chromium")
- || userAgent.contains("opera") || userAgent.contains("browser");
- }
-
- public static void logResponseHeaders(CmsLog log, HttpServletResponse response) {
- if (!log.isDebugEnabled())
- return;
- for (String headerName : response.getHeaderNames()) {
- Object headerValue = response.getHeader(headerName);
- log.debug(headerName + ": " + headerValue);
- }
- }
-
- public static void logRequestHeaders(CmsLog log, HttpServletRequest request) {
- if (!log.isDebugEnabled())
- return;
- for (Enumeration<String> headerNames = request.getHeaderNames(); headerNames.hasMoreElements();) {
- String headerName = headerNames.nextElement();
- Object headerValue = request.getHeader(headerName);
- log.debug(headerName + ": " + headerValue);
- }
- log.debug(request.getRequestURI() + "\n");
- }
-
- public static void logRequest(CmsLog log, HttpServletRequest request) {
- log.debug("contextPath=" + request.getContextPath());
- log.debug("servletPath=" + request.getServletPath());
- log.debug("requestURI=" + request.getRequestURI());
- log.debug("queryString=" + request.getQueryString());
- StringBuilder buf = new StringBuilder();
- // headers
- Enumeration<String> en = request.getHeaderNames();
- while (en.hasMoreElements()) {
- String header = en.nextElement();
- Enumeration<String> values = request.getHeaders(header);
- while (values.hasMoreElements())
- buf.append(" " + header + ": " + values.nextElement());
- buf.append('\n');
- }
-
- // attributed
- Enumeration<String> an = request.getAttributeNames();
- while (an.hasMoreElements()) {
- String attr = an.nextElement();
- Object value = request.getAttribute(attr);
- buf.append(" " + attr + ": " + value);
- buf.append('\n');
- }
- log.debug("\n" + buf);
- }
-
- private JcrHttpUtils() {
-
- }
-}
+++ /dev/null
-package org.argeo.cms.jcr.internal.servlet;
-
-import org.argeo.cms.servlet.PrivateWwwAuthServletContext;
-
-/** Internal subclass, so that config resources can be loaded from our bundle. */
-public class JcrServletContext extends PrivateWwwAuthServletContext {
-
-}
+++ /dev/null
-package org.argeo.cms.jcr.internal.servlet;
-
-import static javax.jcr.Property.JCR_DESCRIPTION;
-import static javax.jcr.Property.JCR_LAST_MODIFIED;
-import static javax.jcr.Property.JCR_TITLE;
-
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.security.PrivilegedExceptionAction;
-import java.util.Calendar;
-import java.util.Collection;
-
-import javax.jcr.Node;
-import javax.jcr.NodeIterator;
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.security.auth.Subject;
-import javax.security.auth.login.LoginContext;
-import javax.security.auth.login.LoginException;
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.argeo.api.cms.CmsAuth;
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.cms.CmsException;
-import org.argeo.cms.jcr.CmsJcrUtils;
-import org.argeo.jcr.JcrUtils;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.FrameworkUtil;
-import org.osgi.framework.ServiceReference;
-
-public class LinkServlet extends HttpServlet {
- private final BundleContext bc = FrameworkUtil.getBundle(getClass()).getBundleContext();
-
- private static final long serialVersionUID = 3749990143146845708L;
-
- @Override
- protected void service(HttpServletRequest request, HttpServletResponse response)
- throws ServletException, IOException {
- String path = request.getPathInfo();
- String userAgent = request.getHeader("User-Agent").toLowerCase();
- boolean isBot = false;
- // boolean isCompatibleBrowser = false;
- if (userAgent.contains("bot") || userAgent.contains("facebook") || userAgent.contains("twitter")) {
- isBot = true;
- }
- // else if (userAgent.contains("webkit") ||
- // userAgent.contains("gecko") || userAgent.contains("firefox")
- // || userAgent.contains("msie") || userAgent.contains("chrome") ||
- // userAgent.contains("chromium")
- // || userAgent.contains("opera") || userAgent.contains("browser"))
- // {
- // isCompatibleBrowser = true;
- // }
-
- if (isBot) {
- // log.warn("# BOT " + request.getHeader("User-Agent"));
- canonicalAnswer(request, response, path);
- return;
- }
-
- // if (isCompatibleBrowser && log.isTraceEnabled())
- // log.trace("# BWS " + request.getHeader("User-Agent"));
- redirectTo(response, "/#" + path);
- }
-
- private void redirectTo(HttpServletResponse response, String location) {
- response.setHeader("Location", location);
- response.setStatus(HttpServletResponse.SC_FOUND);
- }
-
- // private boolean canonicalAnswerNeededBy(HttpServletRequest request) {
- // String userAgent = request.getHeader("User-Agent").toLowerCase();
- // return userAgent.startsWith("facebookexternalhit/");
- // }
-
- /** For bots which don't understand RWT. */
- private void canonicalAnswer(HttpServletRequest request, HttpServletResponse response, String path) {
- Session session = null;
- try {
- PrintWriter writer = response.getWriter();
- session = Subject.doAs(anonymousLogin(), new PrivilegedExceptionAction<Session>() {
-
- @Override
- public Session run() throws Exception {
- Collection<ServiceReference<Repository>> srs = bc.getServiceReferences(Repository.class,
- "(" + CmsConstants.CN + "=" + CmsConstants.EGO_REPOSITORY + ")");
- Repository repository = bc.getService(srs.iterator().next());
- return repository.login();
- }
-
- });
- Node node = session.getNode(path);
- String title = node.hasProperty(JCR_TITLE) ? node.getProperty(JCR_TITLE).getString() : node.getName();
- String desc = node.hasProperty(JCR_DESCRIPTION) ? node.getProperty(JCR_DESCRIPTION).getString() : null;
- Calendar lastUpdate = node.hasProperty(JCR_LAST_MODIFIED) ? node.getProperty(JCR_LAST_MODIFIED).getDate()
- : null;
- String url = getCanonicalUrl(node, request);
- String imgUrl = null;
- // TODO support images
-// loop: for (NodeIterator it = node.getNodes(); it.hasNext();) {
-// // Takes the first found cms:image
-// Node child = it.nextNode();
-// if (child.isNodeType(CMS_IMAGE)) {
-// imgUrl = getDataUrl(child, request);
-// break loop;
-// }
-// }
- StringBuilder buf = new StringBuilder();
- buf.append("<html>");
- buf.append("<head>");
- writeMeta(buf, "og:title", escapeHTML(title));
- writeMeta(buf, "og:type", "website");
- buf.append("<meta name='twitter:card' content='summary' />");
- buf.append("<meta name='twitter:site' content='@argeo_org' />");
- writeMeta(buf, "og:url", url);
- if (desc != null)
- writeMeta(buf, "og:description", escapeHTML(desc));
- if (imgUrl != null)
- writeMeta(buf, "og:image", imgUrl);
- if (lastUpdate != null)
- writeMeta(buf, "og:updated_time", Long.toString(lastUpdate.getTime().getTime()));
- buf.append("</head>");
- buf.append("<body>");
- buf.append("<p><b>!! This page is meant for indexing robots, not for real people," + " visit <a href='/#")
- .append(path).append("'>").append(escapeHTML(title)).append("</a> instead.</b></p>");
- writeCanonical(buf, node);
- buf.append("</body>");
- buf.append("</html>");
- writer.print(buf.toString());
-
- response.setHeader("Content-Type", "text/html");
- writer.flush();
- } catch (Exception e) {
- throw new CmsException("Cannot write canonical answer", e);
- } finally {
- JcrUtils.logoutQuietly(session);
- }
- }
-
- /**
- * From http://stackoverflow.com/questions/1265282/recommended-method-for-
- * escaping-html-in-java (+ escaping '). TODO Use
- * org.apache.commons.lang.StringEscapeUtils
- */
- private String escapeHTML(String s) {
- StringBuilder out = new StringBuilder(Math.max(16, s.length()));
- for (int i = 0; i < s.length(); i++) {
- char c = s.charAt(i);
- if (c > 127 || c == '\'' || c == '"' || c == '<' || c == '>' || c == '&') {
- out.append("&#");
- out.append((int) c);
- out.append(';');
- } else {
- out.append(c);
- }
- }
- return out.toString();
- }
-
- private void writeMeta(StringBuilder buf, String tag, String value) {
- buf.append("<meta property='").append(tag).append("' content='").append(value).append("'/>");
- }
-
- private void writeCanonical(StringBuilder buf, Node node) throws RepositoryException {
- buf.append("<div>");
- if (node.hasProperty(JCR_TITLE))
- buf.append("<p>").append(node.getProperty(JCR_TITLE).getString()).append("</p>");
- if (node.hasProperty(JCR_DESCRIPTION))
- buf.append("<p>").append(node.getProperty(JCR_DESCRIPTION).getString()).append("</p>");
- NodeIterator children = node.getNodes();
- while (children.hasNext()) {
- writeCanonical(buf, children.nextNode());
- }
- buf.append("</div>");
- }
-
- // DATA
- private StringBuilder getServerBaseUrl(HttpServletRequest request) {
- try {
- URL url = new URL(request.getRequestURL().toString());
- StringBuilder buf = new StringBuilder();
- buf.append(url.getProtocol()).append("://").append(url.getHost());
- if (url.getPort() != -1)
- buf.append(':').append(url.getPort());
- return buf;
- } catch (MalformedURLException e) {
- throw new CmsException("Cannot extract server base URL from " + request.getRequestURL(), e);
- }
- }
-
- private String getDataUrl(Node node, HttpServletRequest request) throws RepositoryException {
- try {
- StringBuilder buf = getServerBaseUrl(request);
- buf.append(CmsJcrUtils.getDataPath(CmsConstants.EGO_REPOSITORY, node));
- return new URL(buf.toString()).toString();
- } catch (MalformedURLException e) {
- throw new CmsException("Cannot build data URL for " + node, e);
- }
- }
-
- // public static String getDataPath(Node node) throws
- // RepositoryException {
- // assert node != null;
- // String userId = node.getSession().getUserID();
- //// if (log.isTraceEnabled())
- //// log.trace(userId + " : " + node.getPath());
- // StringBuilder buf = new StringBuilder();
- // boolean isAnonymous =
- // userId.equalsIgnoreCase(NodeConstants.ROLE_ANONYMOUS);
- // if (isAnonymous)
- // buf.append(WEBDAV_PUBLIC);
- // else
- // buf.append(WEBDAV_PRIVATE);
- // Session session = node.getSession();
- // Repository repository = session.getRepository();
- // String cn;
- // if (repository.isSingleValueDescriptor(NodeConstants.CN)) {
- // cn = repository.getDescriptor(NodeConstants.CN);
- // } else {
- //// log.warn("No cn defined in repository, using " +
- // NodeConstants.NODE);
- // cn = NodeConstants.NODE;
- // }
- // return
- // buf.append('/').append(cn).append('/').append(session.getWorkspace().getName()).append(node.getPath())
- // .toString();
- // }
-
- private String getCanonicalUrl(Node node, HttpServletRequest request) throws RepositoryException {
- try {
- StringBuilder buf = getServerBaseUrl(request);
- buf.append('/').append('!').append(node.getPath());
- return new URL(buf.toString()).toString();
- } catch (MalformedURLException e) {
- throw new CmsException("Cannot build data URL for " + node, e);
- }
- // return request.getRequestURL().append('!').append(node.getPath())
- // .toString();
- }
-
- private Subject anonymousLogin() {
- Subject subject = new Subject();
- LoginContext lc;
- try {
- lc = new LoginContext(CmsAuth.LOGIN_CONTEXT_ANONYMOUS, subject);
- lc.login();
- return subject;
- } catch (LoginException e) {
- throw new CmsException("Cannot login as anonymous", e);
- }
- }
-
-}
+++ /dev/null
-<config>
- <protecteditemremovehandler>
- <class name="org.apache.jackrabbit.server.remoting.davex.AclRemoveHandler" />
- </protecteditemremovehandler>
-</config>
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
- Licensed to the Apache Software Foundation (ASF) under one or more
- contributor license agreements. See the NOTICE file distributed with
- this work for additional information regarding copyright ownership.
- The ASF licenses this file to You under the Apache License, Version 2.0
- (the "License"); you may not use this file except in compliance with
- the License. You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- -->
-<!--
-<!DOCTYPE config [
- <!ELEMENT config (iomanager , propertymanager, (collection | noncollection)? , filter?, mimetypeproperties?) >
-
- <!ELEMENT iomanager (class, iohandler*) >
- <!ELEMENT iohandler (class) >
-
- <!ELEMENT propertymanager (class, propertyhandler*) >
- <!ELEMENT propertyhandler (class) >
-
- <!ELEMENT collection (nodetypes) >
- <!ELEMENT noncollection (nodetypes) >
-
- <!ELEMENT filter (class, namespaces?, nodetypes?) >
-
- <!ELEMENT class >
- <!ATTLIST class
- name CDATA #REQUIRED
- >
- <!ELEMENT namespaces (prefix | uri)* >
- <!ELEMENT prefix (CDATA) >
- <!ELEMENT uri (CDATA) >
-
- <!ELEMENT nodetypes (nodetype)* >
- <!ELEMENT nodetype (CDATA) >
-
- <!ELEMENT mimetypeproperties (mimemapping*, defaultmimetype) >
-
- <!ELEMENT mimemapping >
- <!ATTLIST mimemapping
- extension CDATA #REQUIRED
- mimetype CDATA #REQUIRED
- >
-
- <!ELEMENT defaultmimetype (CDATA) >
-]>
--->
-
-<config>
- <!--
- Defines the IOManager implementation that is responsible for passing
- import/export request to the individual IO-handlers.
- -->
- <iomanager>
- <!-- class element defines the manager to be used. The specified class
- must implement the IOManager interface.
- Note, that the handlers are being added and called in the order
- they appear in the configuration.
- -->
- <class name="org.apache.jackrabbit.server.io.IOManagerImpl" />
- <iohandler>
- <class name="org.apache.jackrabbit.server.io.VersionHandler" />
- </iohandler>
- <iohandler>
- <class name="org.apache.jackrabbit.server.io.VersionHistoryHandler" />
- </iohandler>
-<!-- <iohandler> -->
-<!-- <class name="org.apache.jackrabbit.server.io.ZipHandler" /> -->
-<!-- </iohandler> -->
-<!-- <iohandler> -->
-<!-- <class name="org.apache.jackrabbit.server.io.XmlHandler" /> -->
-<!-- </iohandler> -->
- <iohandler>
- <class name="org.apache.jackrabbit.server.io.DirListingExportHandler" />
- </iohandler>
- <iohandler>
- <class name="org.apache.jackrabbit.server.io.DefaultHandler" />
- </iohandler>
- </iomanager>
- <!--
- Example config for iomanager that populates its list of handlers with
- default values. Therefore the 'iohandler' elements are omited.
- -->
- <!--
- <iomanager>
- <class name="org.apache.jackrabbit.server.io.DefaultIOManager" />
- </iomanager>
- -->
- <!--
- Defines the PropertyManager implementation that is responsible for export
- and import of resource properties.
- -->
- <propertymanager>
- <!-- class element defines the manager to be used. The specified class
- must implement the PropertyManager interface.
- Note, that the handlers are being added and called in the order
- they appear in the configuration.
- -->
- <class name="org.apache.jackrabbit.server.io.PropertyManagerImpl" />
- <propertyhandler>
- <class name="org.apache.jackrabbit.server.io.VersionHandler" />
- </propertyhandler>
- <propertyhandler>
- <class name="org.apache.jackrabbit.server.io.VersionHistoryHandler" />
- </propertyhandler>
-<!-- <propertyhandler> -->
-<!-- <class name="org.apache.jackrabbit.server.io.ZipHandler" /> -->
-<!-- </propertyhandler> -->
-<!-- <propertyhandler> -->
-<!-- <class name="org.apache.jackrabbit.server.io.XmlHandler" /> -->
-<!-- </propertyhandler> -->
- <propertyhandler>
- <class name="org.apache.jackrabbit.server.io.DefaultHandler" />
- </propertyhandler>
- </propertymanager>
- <!--
- Define nodetypes, that should never by displayed as 'collection'
- -->
- <noncollection>
- <nodetypes>
- <nodetype>nt:file</nodetype>
- <nodetype>nt:resource</nodetype>
- </nodetypes>
- </noncollection>
- <!--
- Example: Defines nodetypes, that should always be displayed as 'collection'.
- -->
- <!--
- <collection>
- <nodetypes>
- <nodetype>nt:folder</nodetype>
- <nodetype>rep:root</nodetype>
- </nodetypes>
- </collection>
- -->
- <!--
- Filter that allows to prevent certain items from being displayed.
- Please note, that this has an effect on PROPFIND calls only and does not
- provide limited access to those items matching any of the filters.
-
- However specifying a filter may cause problems with PUT or MKCOL if the
- resource to be created is being filtered out, thus resulting in inconsistent
- responses (e.g. PUT followed by PROPFIND on parent).
- -->
- <filter>
- <!-- class element defines the resource filter to be used. The specified class
- must implement the ItemFilter interface -->
- <class name="org.apache.jackrabbit.webdav.simple.DefaultItemFilter" />
- <!--
- Nodetype names to be used to filter child nodes.
- A child node can be filtered if the declaring nodetype of its definition
- is one of the nodetype names specified in the nodetypes Element.
- E.g. defining 'rep:root' as filtered nodetype whould result in jcr:system
- being hidden but no other child node of the root node, since those
- are defined by the nodetype nt:unstructered.
- -->
- <!--
- <nodetypes>
- <nodetype>rep:root</nodetype>
- </nodetypes>
- -->
- <!--
- Namespace prefixes or uris. Items having a name that matches any of the
- entries will be filtered.
- -->
- <namespaces>
- <prefix>rep</prefix>
- <prefix>jcr</prefix>
- <!-- Argeo namespaces -->
- <prefix>node</prefix>
- <prefix>argeo</prefix>
- <prefix>cms</prefix>
- <prefix>slc</prefix>
- <prefix>connect</prefix>
- <prefix>activities</prefix>
- <prefix>people</prefix>
- <prefix>documents</prefix>
- <prefix>tracker</prefix>
- <!--
- <uri>internal</uri>
- <uri>http://www.jcp.org/jcr/1.0</uri>
- -->
- </namespaces>
- </filter>
-
- <!--
- Optional 'mimetypeproperties' element.
- It defines additional or replaces existing mappings for the MimeResolver
- instance created by the ResourceConfig.
- The default mappings are defined in org.apache.jackrabbit.server.io.mimetypes.properties.
- If the default mime type defined by MimeResolver is 'application/octet-stream'.
- -->
- <!--
- <mimetypeproperties>
- <mimemapping extension="rtf" mimetype="application/rtf" />
- <mimemapping extension="ott" mimetype="application/vnd.oasis.opendocument.text-template" />
- <defaultmimetype>text/html</defaultmimetype>
- </mimetypeproperties>
- -->
-</config>
+++ /dev/null
-<ldap = 'http://www.argeo.org/ns/ldap'>
+++ /dev/null
-<node = 'http://www.argeo.org/ns/node'>
-
-[node:userHome]
-mixin
-- ldap:uid (STRING) m
-
-[node:groupHome]
-mixin
-- ldap:cn (STRING) m
+++ /dev/null
-package org.argeo.cms.jcr.tabular;
-
-import java.io.OutputStream;
-
-import org.argeo.cms.tabular.TabularWriter;
-import org.argeo.util.CsvWriter;
-
-/** Write tabular content in a stream as CSV. Wraps a {@link CsvWriter}. */
-public class CsvTabularWriter implements TabularWriter {
- private CsvWriter csvWriter;
-
- public CsvTabularWriter(OutputStream out) {
- this.csvWriter = new CsvWriter(out);
- }
-
- public void appendRow(Object[] row) {
- csvWriter.writeLine(row);
- }
-
- public void close() {
- }
-
-}
+++ /dev/null
-package org.argeo.cms.jcr.tabular;
-
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.ArrayBlockingQueue;
-
-import javax.jcr.Binary;
-import javax.jcr.Node;
-import javax.jcr.NodeIterator;
-import javax.jcr.Property;
-import javax.jcr.PropertyType;
-import javax.jcr.RepositoryException;
-
-import org.apache.commons.io.IOUtils;
-import org.argeo.cms.ArgeoTypes;
-import org.argeo.cms.tabular.ArrayTabularRow;
-import org.argeo.cms.tabular.TabularColumn;
-import org.argeo.cms.tabular.TabularRow;
-import org.argeo.cms.tabular.TabularRowIterator;
-import org.argeo.jcr.JcrException;
-import org.argeo.util.CsvParser;
-
-/** Iterates over the rows of a {@link ArgeoTypes#ARGEO_TABLE} node. */
-public class JcrTabularRowIterator implements TabularRowIterator {
- private Boolean hasNext = null;
- private Boolean parsingCompleted = false;
-
- private Long currentRowNumber = 0l;
-
- private List<TabularColumn> header = new ArrayList<TabularColumn>();
-
- /** referenced so that we can close it */
- private Binary binary;
- private InputStream in;
-
- private CsvParser csvParser;
- private ArrayBlockingQueue<List<String>> textLines;
-
- public JcrTabularRowIterator(Node tableNode) {
- try {
- for (NodeIterator it = tableNode.getNodes(); it.hasNext();) {
- Node node = it.nextNode();
- if (node.isNodeType(ArgeoTypes.ARGEO_COLUMN)) {
- Integer type = PropertyType.valueFromName(node.getProperty(
- Property.JCR_REQUIRED_TYPE).getString());
- TabularColumn tc = new TabularColumn(node.getProperty(
- Property.JCR_TITLE).getString(), type);
- header.add(tc);
- }
- }
- Node contentNode = tableNode.getNode(Property.JCR_CONTENT);
- if (contentNode.isNodeType(ArgeoTypes.ARGEO_CSV)) {
- textLines = new ArrayBlockingQueue<List<String>>(1000);
- csvParser = new CsvParser() {
- protected void processLine(Integer lineNumber,
- List<String> header, List<String> tokens) {
- try {
- textLines.put(tokens);
- } catch (InterruptedException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- // textLines.add(tokens);
- if (hasNext == null) {
- hasNext = true;
- synchronized (JcrTabularRowIterator.this) {
- JcrTabularRowIterator.this.notifyAll();
- }
- }
- }
- };
- csvParser.setNoHeader(true);
- binary = contentNode.getProperty(Property.JCR_DATA).getBinary();
- in = binary.getStream();
- Thread thread = new Thread(contentNode.getPath() + " reader") {
- public void run() {
- try {
- csvParser.parse(in);
- } finally {
- parsingCompleted = true;
- IOUtils.closeQuietly(in);
- }
- }
- };
- thread.start();
- }
- } catch (RepositoryException e) {
- throw new JcrException("Cannot read table " + tableNode, e);
- }
- }
-
- public synchronized boolean hasNext() {
- // we don't know if there is anything available
- // while (hasNext == null)
- // try {
- // wait();
- // } catch (InterruptedException e) {
- // // silent
- // // FIXME better deal with interruption
- // Thread.currentThread().interrupt();
- // break;
- // }
-
- // buffer not empty
- if (!textLines.isEmpty())
- return true;
-
- // maybe the parsing is finished but the flag has not been set
- while (!parsingCompleted && textLines.isEmpty())
- try {
- wait(100);
- } catch (InterruptedException e) {
- // silent
- // FIXME better deal with interruption
- Thread.currentThread().interrupt();
- break;
- }
-
- // buffer not empty
- if (!textLines.isEmpty())
- return true;
-
- // (parsingCompleted && textLines.isEmpty())
- return false;
-
- // if (!hasNext && textLines.isEmpty()) {
- // if (in != null) {
- // IOUtils.closeQuietly(in);
- // in = null;
- // }
- // if (binary != null) {
- // JcrUtils.closeQuietly(binary);
- // binary = null;
- // }
- // return false;
- // } else
- // return true;
- }
-
- public synchronized TabularRow next() {
- try {
- List<String> tokens = textLines.take();
- List<Object> objs = new ArrayList<Object>(tokens.size());
- for (String token : tokens) {
- // TODO convert to other formats using header
- objs.add(token);
- }
- currentRowNumber++;
- return new ArrayTabularRow(objs);
- } catch (InterruptedException e) {
- // silent
- // FIXME better deal with interruption
- }
- return null;
- }
-
- public void remove() {
- throw new UnsupportedOperationException();
- }
-
- public Long getCurrentRowNumber() {
- return currentRowNumber;
- }
-
- public List<TabularColumn> getHeader() {
- return header;
- }
-
-}
+++ /dev/null
-package org.argeo.cms.jcr.tabular;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.InputStream;
-import java.util.List;
-
-import javax.jcr.Binary;
-import javax.jcr.Node;
-import javax.jcr.Property;
-import javax.jcr.PropertyType;
-import javax.jcr.RepositoryException;
-
-import org.apache.commons.io.IOUtils;
-import org.argeo.cms.ArgeoTypes;
-import org.argeo.cms.tabular.TabularColumn;
-import org.argeo.cms.tabular.TabularWriter;
-import org.argeo.jcr.JcrException;
-import org.argeo.jcr.JcrUtils;
-import org.argeo.util.CsvWriter;
-
-/** Write / reference tabular content in a JCR repository. */
-public class JcrTabularWriter implements TabularWriter {
- private Node contentNode;
- private ByteArrayOutputStream out;
- private CsvWriter csvWriter;
-
- @SuppressWarnings("unused")
- private final List<TabularColumn> columns;
-
- /** Creates a table node */
- public JcrTabularWriter(Node tableNode, List<TabularColumn> columns,
- String contentNodeType) {
- try {
- this.columns = columns;
- for (TabularColumn column : columns) {
- String normalized = JcrUtils.replaceInvalidChars(column
- .getName());
- Node columnNode = tableNode.addNode(normalized,
- ArgeoTypes.ARGEO_COLUMN);
- columnNode.setProperty(Property.JCR_TITLE, column.getName());
- if (column.getType() != null)
- columnNode.setProperty(Property.JCR_REQUIRED_TYPE,
- PropertyType.nameFromValue(column.getType()));
- else
- columnNode.setProperty(Property.JCR_REQUIRED_TYPE,
- PropertyType.TYPENAME_STRING);
- }
- contentNode = tableNode.addNode(Property.JCR_CONTENT,
- contentNodeType);
- if (contentNodeType.equals(ArgeoTypes.ARGEO_CSV)) {
- contentNode.setProperty(Property.JCR_MIMETYPE, "text/csv");
- contentNode.setProperty(Property.JCR_ENCODING, "UTF-8");
- out = new ByteArrayOutputStream();
- csvWriter = new CsvWriter(out);
- }
- } catch (RepositoryException e) {
- throw new JcrException("Cannot create table node " + tableNode, e);
- }
- }
-
- public void appendRow(Object[] row) {
- csvWriter.writeLine(row);
- }
-
- public void close() {
- Binary binary = null;
- InputStream in = null;
- try {
- // TODO parallelize with pipes and writing from another thread
- in = new ByteArrayInputStream(out.toByteArray());
- binary = contentNode.getSession().getValueFactory()
- .createBinary(in);
- contentNode.setProperty(Property.JCR_DATA, binary);
- } catch (RepositoryException e) {
- throw new JcrException("Cannot store data in " + contentNode, e);
- } finally {
- IOUtils.closeQuietly(in);
- JcrUtils.closeQuietly(binary);
- }
- }
-}
+++ /dev/null
-/** Argeo CMS implementation of the Argeo Tabular API (CSV, JCR). */
-package org.argeo.cms.jcr.tabular;
\ No newline at end of file
+++ /dev/null
-package org.argeo.jackrabbit;
-
-import java.util.Map;
-
-import javax.security.auth.Subject;
-import javax.security.auth.callback.CallbackHandler;
-import javax.security.auth.login.LoginException;
-import javax.security.auth.spi.LoginModule;
-
-import org.apache.jackrabbit.core.security.SecurityConstants;
-import org.apache.jackrabbit.core.security.principal.AdminPrincipal;
-
-@Deprecated
-public class JackrabbitAdminLoginModule implements LoginModule {
- private Subject subject;
-
- @Override
- public void initialize(Subject subject, CallbackHandler callbackHandler,
- Map<String, ?> sharedState, Map<String, ?> options) {
- this.subject = subject;
- }
-
- @Override
- public boolean login() throws LoginException {
- // TODO check permission?
- return true;
- }
-
- @Override
- public boolean commit() throws LoginException {
- subject.getPrincipals().add(
- new AdminPrincipal(SecurityConstants.ADMIN_ID));
- return true;
- }
-
- @Override
- public boolean abort() throws LoginException {
- return true;
- }
-
- @Override
- public boolean logout() throws LoginException {
- subject.getPrincipals().removeAll(
- subject.getPrincipals(AdminPrincipal.class));
- return true;
- }
-
-}
+++ /dev/null
-package org.argeo.jackrabbit;
-
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.Reader;
-import java.net.URL;
-
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-
-import org.apache.commons.io.IOUtils;
-import org.apache.jackrabbit.commons.cnd.CndImporter;
-import org.apache.jackrabbit.commons.cnd.ParseException;
-import org.apache.jackrabbit.core.config.RepositoryConfig;
-import org.apache.jackrabbit.core.fs.FileSystemException;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.jcr.JcrCallback;
-import org.argeo.jcr.JcrException;
-import org.argeo.jcr.JcrUtils;
-
-/** Migrate the data in a Jackrabbit repository. */
-@Deprecated
-public class JackrabbitDataModelMigration implements Comparable<JackrabbitDataModelMigration> {
- private final static CmsLog log = CmsLog.getLog(JackrabbitDataModelMigration.class);
-
- private String dataModelNodePath;
- private String targetVersion;
- private URL migrationCnd;
- private JcrCallback dataModification;
-
- /**
- * Expects an already started repository with the old data model to migrate.
- * Expects to be run with admin rights (Repository.login() will be used).
- *
- * @return true if a migration was performed and the repository needs to be
- * restarted and its caches cleared.
- */
- public Boolean migrate(Session session) {
- long begin = System.currentTimeMillis();
- Reader reader = null;
- try {
- // check if already migrated
- if (!session.itemExists(dataModelNodePath)) {
-// log.warn("Node " + dataModelNodePath + " does not exist: nothing to migrate.");
- return false;
- }
-// Node dataModelNode = session.getNode(dataModelNodePath);
-// if (dataModelNode.hasProperty(ArgeoNames.ARGEO_DATA_MODEL_VERSION)) {
-// String currentVersion = dataModelNode.getProperty(
-// ArgeoNames.ARGEO_DATA_MODEL_VERSION).getString();
-// if (compareVersions(currentVersion, targetVersion) >= 0) {
-// log.info("Data model at version " + currentVersion
-// + ", no need to migrate.");
-// return false;
-// }
-// }
-
- // apply transitional CND
- if (migrationCnd != null) {
- reader = new InputStreamReader(migrationCnd.openStream());
- CndImporter.registerNodeTypes(reader, session, true);
- session.save();
-// log.info("Registered migration node types from " + migrationCnd);
- }
-
- // modify data
- dataModification.execute(session);
-
- // apply changes
- session.save();
-
- long duration = System.currentTimeMillis() - begin;
-// log.info("Migration of data model " + dataModelNodePath + " to " + targetVersion + " performed in "
-// + duration + "ms");
- return true;
- } catch (RepositoryException e) {
- JcrUtils.discardQuietly(session);
- throw new JcrException("Migration of data model " + dataModelNodePath + " to " + targetVersion + " failed.",
- e);
- } catch (ParseException | IOException e) {
- JcrUtils.discardQuietly(session);
- throw new RuntimeException(
- "Migration of data model " + dataModelNodePath + " to " + targetVersion + " failed.", e);
- } finally {
- JcrUtils.logoutQuietly(session);
- IOUtils.closeQuietly(reader);
- }
- }
-
- protected static int compareVersions(String version1, String version2) {
- // TODO do a proper version analysis and comparison
- return version1.compareTo(version2);
- }
-
- /** To be called on a stopped repository. */
- public static void clearRepositoryCaches(RepositoryConfig repositoryConfig) {
- try {
- String customeNodeTypesPath = "/nodetypes/custom_nodetypes.xml";
- // FIXME causes weird error in Eclipse
- repositoryConfig.getFileSystem().deleteFile(customeNodeTypesPath);
- if (log.isDebugEnabled())
- log.debug("Cleared " + customeNodeTypesPath);
- } catch (RuntimeException e) {
- throw e;
- } catch (RepositoryException e) {
- throw new JcrException(e);
- } catch (FileSystemException e) {
- throw new RuntimeException("Cannot clear node types cache.",e);
- }
-
- // File customNodeTypes = new File(home.getPath()
- // + "/repository/nodetypes/custom_nodetypes.xml");
- // if (customNodeTypes.exists()) {
- // customNodeTypes.delete();
- // if (log.isDebugEnabled())
- // log.debug("Cleared " + customNodeTypes);
- // } else {
- // log.warn("File " + customNodeTypes + " not found.");
- // }
- }
-
- /*
- * FOR USE IN (SORTED) SETS
- */
-
- public int compareTo(JackrabbitDataModelMigration dataModelMigration) {
- // TODO make ordering smarter
- if (dataModelNodePath.equals(dataModelMigration.dataModelNodePath))
- return compareVersions(targetVersion, dataModelMigration.targetVersion);
- else
- return dataModelNodePath.compareTo(dataModelMigration.dataModelNodePath);
- }
-
- @Override
- public boolean equals(Object obj) {
- if (!(obj instanceof JackrabbitDataModelMigration))
- return false;
- JackrabbitDataModelMigration dataModelMigration = (JackrabbitDataModelMigration) obj;
- return dataModelNodePath.equals(dataModelMigration.dataModelNodePath)
- && targetVersion.equals(dataModelMigration.targetVersion);
- }
-
- @Override
- public int hashCode() {
- return targetVersion.hashCode();
- }
-
- public void setDataModelNodePath(String dataModelNodePath) {
- this.dataModelNodePath = dataModelNodePath;
- }
-
- public void setTargetVersion(String targetVersion) {
- this.targetVersion = targetVersion;
- }
-
- public void setMigrationCnd(URL migrationCnd) {
- this.migrationCnd = migrationCnd;
- }
-
- public void setDataModification(JcrCallback dataModification) {
- this.dataModification = dataModification;
- }
-
- public String getDataModelNodePath() {
- return dataModelNodePath;
- }
-
- public String getTargetVersion() {
- return targetVersion;
- }
-
-}
+++ /dev/null
-package org.argeo.jackrabbit.client;
-
-import java.util.Map;
-
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.RepositoryFactory;
-
-import org.apache.jackrabbit.jcr2spi.Jcr2spiRepositoryFactory;
-import org.apache.jackrabbit.jcr2spi.RepositoryImpl;
-import org.apache.jackrabbit.spi.RepositoryServiceFactory;
-
-/** A customised {@link RepositoryFactory} access a remote DAVEX service. */
-public class ClientDavexRepositoryFactory implements RepositoryFactory {
- public final static String JACKRABBIT_DAVEX_URI = ClientDavexRepositoryServiceFactory.PARAM_REPOSITORY_URI;
- public final static String JACKRABBIT_REMOTE_DEFAULT_WORKSPACE = ClientDavexRepositoryServiceFactory.PARAM_WORKSPACE_NAME_DEFAULT;
-
- @SuppressWarnings("rawtypes")
- @Override
- public Repository getRepository(Map parameters) throws RepositoryException {
- RepositoryServiceFactory repositoryServiceFactory = new ClientDavexRepositoryServiceFactory();
- return RepositoryImpl
- .create(new Jcr2spiRepositoryFactory.RepositoryConfigImpl(repositoryServiceFactory, parameters));
- }
-
-}
+++ /dev/null
-package org.argeo.jackrabbit.client;
-
-import javax.jcr.RepositoryException;
-
-import org.apache.http.client.protocol.HttpClientContext;
-import org.apache.http.protocol.HttpContext;
-import org.apache.jackrabbit.spi.SessionInfo;
-import org.apache.jackrabbit.spi2davex.BatchReadConfig;
-import org.apache.jackrabbit.spi2davex.RepositoryServiceImpl;
-
-/**
- * Wrapper for {@link RepositoryServiceImpl} in order to access the underlying
- * {@link HttpClientContext}.
- */
-public class ClientDavexRepositoryService extends RepositoryServiceImpl {
-
- public ClientDavexRepositoryService(String jcrServerURI, BatchReadConfig batchReadConfig)
- throws RepositoryException {
- super(jcrServerURI, batchReadConfig);
- }
-
- public ClientDavexRepositoryService(String jcrServerURI, String defaultWorkspaceName,
- BatchReadConfig batchReadConfig, int itemInfoCacheSize, int maximumHttpConnections)
- throws RepositoryException {
- super(jcrServerURI, defaultWorkspaceName, batchReadConfig, itemInfoCacheSize, maximumHttpConnections);
- }
-
- public ClientDavexRepositoryService(String jcrServerURI, String defaultWorkspaceName,
- BatchReadConfig batchReadConfig, int itemInfoCacheSize) throws RepositoryException {
- super(jcrServerURI, defaultWorkspaceName, batchReadConfig, itemInfoCacheSize);
- }
-
- @Override
- protected HttpContext getContext(SessionInfo sessionInfo) throws RepositoryException {
- HttpClientContext result = HttpClientContext.create();
- result.setAuthCache(new NonSerialBasicAuthCache());
- return result;
- }
-
-}
+++ /dev/null
-package org.argeo.jackrabbit.client;
-
-import java.util.Map;
-
-import javax.jcr.RepositoryException;
-
-import org.apache.jackrabbit.spi.RepositoryService;
-import org.apache.jackrabbit.spi.commons.ItemInfoCacheImpl;
-import org.apache.jackrabbit.spi2davex.BatchReadConfig;
-import org.apache.jackrabbit.spi2davex.Spi2davexRepositoryServiceFactory;
-
-/**
- * Wrapper for {@link Spi2davexRepositoryServiceFactory} in order to create a
- * {@link ClientDavexRepositoryService}.
- */
-public class ClientDavexRepositoryServiceFactory extends Spi2davexRepositoryServiceFactory {
- @Override
- public RepositoryService createRepositoryService(Map<?, ?> parameters) throws RepositoryException {
- // retrieve the repository uri
- String uri;
- if (parameters == null) {
- uri = System.getProperty(PARAM_REPOSITORY_URI);
- } else {
- Object repoUri = parameters.get(PARAM_REPOSITORY_URI);
- uri = (repoUri == null) ? null : repoUri.toString();
- }
- if (uri == null) {
- uri = DEFAULT_REPOSITORY_URI;
- }
-
- // load other optional configuration parameters
- BatchReadConfig brc = null;
- int itemInfoCacheSize = ItemInfoCacheImpl.DEFAULT_CACHE_SIZE;
- int maximumHttpConnections = 0;
-
- // since JCR-4120 the default workspace name is no longer set to 'default'
- // note: if running with JCR Server < 1.5 a default workspace name must
- // therefore be configured
- String workspaceNameDefault = null;
-
- if (parameters != null) {
- // batchRead config
- Object param = parameters.get(PARAM_BATCHREAD_CONFIG);
- if (param != null && param instanceof BatchReadConfig) {
- brc = (BatchReadConfig) param;
- }
-
- // itemCache size config
- param = parameters.get(PARAM_ITEMINFO_CACHE_SIZE);
- if (param != null) {
- try {
- itemInfoCacheSize = Integer.parseInt(param.toString());
- } catch (NumberFormatException e) {
- // ignore, use default
- }
- }
-
- // max connections config
- param = parameters.get(PARAM_MAX_CONNECTIONS);
- if (param != null) {
- try {
- maximumHttpConnections = Integer.parseInt(param.toString());
- } catch (NumberFormatException e) {
- // using default
- }
- }
-
- param = parameters.get(PARAM_WORKSPACE_NAME_DEFAULT);
- if (param != null) {
- workspaceNameDefault = param.toString();
- }
- }
-
- if (maximumHttpConnections > 0) {
- return new ClientDavexRepositoryService(uri, workspaceNameDefault, brc, itemInfoCacheSize,
- maximumHttpConnections);
- } else {
- return new ClientDavexRepositoryService(uri, workspaceNameDefault, brc, itemInfoCacheSize);
- }
- }
-
-}
+++ /dev/null
-package org.argeo.jackrabbit.client;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.HashMap;
-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.apache.http.client.protocol.HttpClientContext;
-import org.apache.http.protocol.HttpContext;
-import org.apache.jackrabbit.jcr2dav.Jcr2davRepositoryFactory;
-import org.apache.jackrabbit.jcr2spi.Jcr2spiRepositoryFactory;
-import org.apache.jackrabbit.jcr2spi.RepositoryImpl;
-import org.apache.jackrabbit.spi.RepositoryService;
-import org.apache.jackrabbit.spi.RepositoryServiceFactory;
-import org.apache.jackrabbit.spi.SessionInfo;
-import org.apache.jackrabbit.spi.commons.ItemInfoCacheImpl;
-import org.apache.jackrabbit.spi2davex.BatchReadConfig;
-import org.apache.jackrabbit.spi2davex.RepositoryServiceImpl;
-import org.apache.jackrabbit.spi2davex.Spi2davexRepositoryServiceFactory;
-import org.argeo.jcr.JcrUtils;
-
-/** Minimal client to test JCR DAVEX connectivity. */
-public class JackrabbitClient {
- final static String JACKRABBIT_REPOSITORY_URI = "org.apache.jackrabbit.repository.uri";
- final static String JACKRABBIT_DAVEX_URI = "org.apache.jackrabbit.spi2davex.uri";
- final static String JACKRABBIT_REMOTE_DEFAULT_WORKSPACE = "org.apache.jackrabbit.spi2davex.WorkspaceNameDefault";
-
- public static void main(String[] args) {
- String repoUri = args.length == 0 ? "http://root:demo@localhost:7070/jcr/ego" : args[0];
- String workspace = args.length < 2 ? "home" : args[1];
-
- Repository repository = null;
- Session session = null;
-
- URI uri;
- try {
- uri = new URI(repoUri);
- } catch (URISyntaxException e1) {
- throw new IllegalArgumentException(e1);
- }
-
- if (uri.getScheme().equals("http") || uri.getScheme().equals("https")) {
-
- RepositoryFactory repositoryFactory = new Jcr2davRepositoryFactory() {
- @SuppressWarnings("rawtypes")
- public Repository getRepository(Map parameters) throws RepositoryException {
- RepositoryServiceFactory repositoryServiceFactory = new Spi2davexRepositoryServiceFactory() {
-
- @Override
- public RepositoryService createRepositoryService(Map<?, ?> parameters)
- throws RepositoryException {
- Object uri = parameters.get(JACKRABBIT_DAVEX_URI);
- Object defaultWorkspace = parameters.get(JACKRABBIT_REMOTE_DEFAULT_WORKSPACE);
- BatchReadConfig brc = null;
- return new RepositoryServiceImpl(uri.toString(), defaultWorkspace.toString(), brc,
- ItemInfoCacheImpl.DEFAULT_CACHE_SIZE) {
-
- @Override
- protected HttpContext getContext(SessionInfo sessionInfo) throws RepositoryException {
- HttpClientContext result = HttpClientContext.create();
- result.setAuthCache(new NonSerialBasicAuthCache());
- return result;
- }
-
- };
- }
- };
- return RepositoryImpl.create(
- new Jcr2spiRepositoryFactory.RepositoryConfigImpl(repositoryServiceFactory, parameters));
- }
- };
- Map<String, String> params = new HashMap<String, String>();
- params.put(JACKRABBIT_DAVEX_URI, repoUri.toString());
- // FIXME make it configurable
- params.put(JACKRABBIT_REMOTE_DEFAULT_WORKSPACE, "sys");
-
- try {
- repository = repositoryFactory.getRepository(params);
- if (repository != null)
- session = repository.login(workspace);
- else
- throw new IllegalArgumentException("Repository " + repoUri + " not found");
- } catch (RepositoryException e) {
- e.printStackTrace();
- }
-
- } else {
- Path path = Paths.get(uri.getPath());
- }
-
- try {
- Node rootNode = session.getRootNode();
- NodeIterator nit = rootNode.getNodes();
- while (nit.hasNext()) {
- System.out.println(nit.nextNode().getPath());
- }
-
- Node newNode = JcrUtils.mkdirs(rootNode, "dir/subdir");
- System.out.println("Created folder " + newNode.getPath());
- Node newFile = JcrUtils.copyBytesAsFile(newNode, "test.txt", "TEST".getBytes());
- System.out.println("Created file " + newFile.getPath());
- try (BufferedReader reader = new BufferedReader(new InputStreamReader(JcrUtils.getFileAsStream(newFile)))) {
- System.out.println("Read " + reader.readLine());
- } catch (IOException e) {
- e.printStackTrace();
- }
- newNode.getParent().remove();
- System.out.println("Removed new nodes");
- } catch (RepositoryException e) {
- e.printStackTrace();
- }
- }
-}
+++ /dev/null
-package org.argeo.jackrabbit.client;
-
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-
-import org.apache.http.HttpHost;
-import org.apache.http.auth.AuthScheme;
-import org.apache.http.client.AuthCache;
-
-/**
- * Implementation of {@link AuthCache} which doesn't use serialization, as it is
- * not supported by GraalVM at this stage.
- */
-public class NonSerialBasicAuthCache implements AuthCache {
- private final Map<HttpHost, AuthScheme> cache;
-
- public NonSerialBasicAuthCache() {
- cache = new ConcurrentHashMap<HttpHost, AuthScheme>();
- }
-
- @Override
- public void put(HttpHost host, AuthScheme authScheme) {
- cache.put(host, authScheme);
- }
-
- @Override
- public AuthScheme get(HttpHost host) {
- return cache.get(host);
- }
-
- @Override
- public void remove(HttpHost host) {
- cache.remove(host);
- }
-
- @Override
- public void clear() {
- cache.clear();
- }
-
-}
+++ /dev/null
-package org.argeo.jackrabbit.fs;
-
-import org.argeo.jcr.fs.JcrFileSystemProvider;
-
-public abstract class AbstractJackrabbitFsProvider extends JcrFileSystemProvider {
-
-}
+++ /dev/null
-package org.argeo.jackrabbit.fs;
-
-import java.io.IOException;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.nio.file.DirectoryStream;
-import java.nio.file.FileSystem;
-import java.nio.file.FileSystemAlreadyExistsException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.HashMap;
-import java.util.Map;
-
-import javax.jcr.Repository;
-import javax.jcr.RepositoryFactory;
-import javax.jcr.Session;
-
-import org.argeo.jackrabbit.client.ClientDavexRepositoryFactory;
-import org.argeo.jcr.fs.JcrFileSystem;
-import org.argeo.jcr.fs.JcrFsException;
-
-/**
- * A file system provider based on a JCR repository remotely accessed via the
- * DAVEX protocol.
- */
-public class DavexFsProvider extends AbstractJackrabbitFsProvider {
- final static String DEFAULT_JACKRABBIT_REMOTE_DEFAULT_WORKSPACE = "sys";
-
- private Map<String, JcrFileSystem> fileSystems = new HashMap<>();
-
- @Override
- public String getScheme() {
- return "davex";
- }
-
- @Override
- public FileSystem newFileSystem(URI uri, Map<String, ?> env) throws IOException {
- if (uri.getHost() == null)
- throw new IllegalArgumentException("An host should be provided");
- try {
- URI repoUri = new URI("http", uri.getUserInfo(), uri.getHost(), uri.getPort(), uri.getPath(), null, null);
- String repoKey = repoUri.toString();
- if (fileSystems.containsKey(repoKey))
- throw new FileSystemAlreadyExistsException("CMS file system already exists for " + repoKey);
- RepositoryFactory repositoryFactory = new ClientDavexRepositoryFactory();
- return tryGetRepo(repositoryFactory, repoUri, "home");
- } catch (URISyntaxException e) {
- throw new IllegalArgumentException("Cannot open file system " + uri, e);
- }
- }
-
- private JcrFileSystem tryGetRepo(RepositoryFactory repositoryFactory, URI repoUri, String workspace)
- throws IOException {
- Map<String, String> params = new HashMap<String, String>();
- params.put(ClientDavexRepositoryFactory.JACKRABBIT_DAVEX_URI, repoUri.toString());
- // TODO better integrate with OSGi or other configuration than system
- // properties.
- String remoteDefaultWorkspace = System.getProperty(
- ClientDavexRepositoryFactory.JACKRABBIT_REMOTE_DEFAULT_WORKSPACE,
- DEFAULT_JACKRABBIT_REMOTE_DEFAULT_WORKSPACE);
- params.put(ClientDavexRepositoryFactory.JACKRABBIT_REMOTE_DEFAULT_WORKSPACE, remoteDefaultWorkspace);
- Repository repository = null;
- Session session = null;
- try {
- repository = repositoryFactory.getRepository(params);
- if (repository != null)
- session = repository.login(workspace);
- } catch (Exception e) {
- // silent
- }
-
- if (session == null) {
- if (repoUri.getPath() == null || repoUri.getPath().equals("/"))
- return null;
- String repoUriStr = repoUri.toString();
- if (repoUriStr.endsWith("/"))
- repoUriStr = repoUriStr.substring(0, repoUriStr.length() - 1);
- String nextRepoUriStr = repoUriStr.substring(0, repoUriStr.lastIndexOf('/'));
- String nextWorkspace = repoUriStr.substring(repoUriStr.lastIndexOf('/') + 1);
- URI nextUri;
- try {
- nextUri = new URI(nextRepoUriStr);
- } catch (URISyntaxException e) {
- throw new IllegalArgumentException("Badly formatted URI", e);
- }
- return tryGetRepo(repositoryFactory, nextUri, nextWorkspace);
- } else {
- JcrFileSystem fileSystem = new JcrFileSystem(this, repository);
- fileSystems.put(repoUri.toString() + "/" + workspace, fileSystem);
- return fileSystem;
- }
- }
-
- @Override
- public FileSystem getFileSystem(URI uri) {
- return currentUserFileSystem(uri);
- }
-
- @Override
- public Path getPath(URI uri) {
- JcrFileSystem fileSystem = currentUserFileSystem(uri);
- if (fileSystem == null)
- try {
- fileSystem = (JcrFileSystem) newFileSystem(uri, new HashMap<String, Object>());
- if (fileSystem == null)
- throw new IllegalArgumentException("No file system found for " + uri);
- } catch (IOException e) {
- throw new JcrFsException("Could not autocreate file system", e);
- }
- URI repoUri = null;
- try {
- repoUri = new URI("http", uri.getUserInfo(), uri.getHost(), uri.getPort(), uri.getPath(), null, null);
- } catch (URISyntaxException e) {
- throw new IllegalArgumentException(e);
- }
- String uriStr = repoUri.toString();
- String localPath = null;
- for (String key : fileSystems.keySet()) {
- if (uriStr.startsWith(key)) {
- localPath = uriStr.toString().substring(key.length());
- }
- }
- if ("".equals(localPath))
- localPath = "/";
- return fileSystem.getPath(localPath);
- }
-
- private JcrFileSystem currentUserFileSystem(URI uri) {
- for (String key : fileSystems.keySet()) {
- if (uri.toString().startsWith(key))
- return fileSystems.get(key);
- }
- return null;
- }
-
- public static void main(String args[]) {
- try {
- DavexFsProvider fsProvider = new DavexFsProvider();
- Path path = fsProvider.getPath(new URI("davex://root:demo@localhost:7070/jcr/ego/"));
- System.out.println(path);
- DirectoryStream<Path> ds = Files.newDirectoryStream(path);
- for (Path p : ds) {
- System.out.println("- " + p);
- }
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
-}
+++ /dev/null
-package org.argeo.jackrabbit.fs;
-
-import java.io.IOException;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.net.URL;
-import java.nio.file.FileSystem;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.HashMap;
-import java.util.Map;
-
-import javax.jcr.Credentials;
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.SimpleCredentials;
-
-import org.apache.jackrabbit.core.RepositoryImpl;
-import org.apache.jackrabbit.core.config.RepositoryConfig;
-import org.argeo.jcr.fs.JcrFileSystem;
-import org.argeo.jcr.fs.JcrFsException;
-
-public class JackrabbitMemoryFsProvider extends AbstractJackrabbitFsProvider {
- private RepositoryImpl repository;
- private JcrFileSystem fileSystem;
-
- private Credentials credentials;
-
- public JackrabbitMemoryFsProvider() {
- String username = System.getProperty("user.name");
- credentials = new SimpleCredentials(username, username.toCharArray());
- }
-
- @Override
- public String getScheme() {
- return "jcr+memory";
- }
-
- @Override
- public FileSystem newFileSystem(URI uri, Map<String, ?> env) throws IOException {
- try {
- Path tempDir = Files.createTempDirectory("fs-memory");
- URL confUrl = JackrabbitMemoryFsProvider.class.getResource("fs-memory.xml");
- RepositoryConfig repositoryConfig = RepositoryConfig.create(confUrl.toURI(), tempDir.toString());
- repository = RepositoryImpl.create(repositoryConfig);
- postRepositoryCreation(repository);
- fileSystem = new JcrFileSystem(this, repository, credentials);
- return fileSystem;
- } catch (RepositoryException | URISyntaxException e) {
- throw new IOException("Cannot login to repository", e);
- }
- }
-
- @Override
- public FileSystem getFileSystem(URI uri) {
- return fileSystem;
- }
-
- @Override
- public Path getPath(URI uri) {
- String path = uri.getPath();
- if (fileSystem == null)
- try {
- newFileSystem(uri, new HashMap<String, Object>());
- } catch (IOException e) {
- throw new JcrFsException("Could not autocreate file system", e);
- }
- return fileSystem.getPath(path);
- }
-
- public Repository getRepository() {
- return repository;
- }
-
- public Session login() throws RepositoryException {
- return getRepository().login(credentials);
- }
-
- /**
- * Called after the repository has been created and before the file system is
- * created.
- */
- protected void postRepositoryCreation(RepositoryImpl repositoryImpl) throws RepositoryException {
-
- }
-}
+++ /dev/null
-<?xml version="1.0"?>
-<!DOCTYPE Repository PUBLIC "Jackrabbit 2.6" "http://jackrabbit.apache.org/dtd/repository-2.6.dtd">
-<Repository>
- <!-- File system and datastore -->
- <FileSystem
- class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
-
- <!-- Workspace templates -->
- <Workspaces rootPath="${rep.home}/workspaces"
- defaultWorkspace="main" configRootPath="/workspaces" />
- <Workspace name="${wsp.name}">
- <FileSystem
- class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
- <PersistenceManager
- class="org.apache.jackrabbit.core.persistence.bundle.BundleFsPersistenceManager">
- </PersistenceManager>
- <SearchIndex
- class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
- <param name="path" value="${wsp.home}/index" />
- <param name="directoryManagerClass"
- value="org.apache.jackrabbit.core.query.lucene.directory.RAMDirectoryManager" />
- <param name="extractorPoolSize" value="0" />
- <FileSystem
- class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
- </SearchIndex>
- </Workspace>
-
- <!-- Versioning -->
- <Versioning rootPath="${rep.home}/version">
- <FileSystem
- class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
- <PersistenceManager
- class="org.apache.jackrabbit.core.persistence.bundle.BundleFsPersistenceManager">
- </PersistenceManager>
- </Versioning>
-
- <!-- Indexing -->
- <SearchIndex
- class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
- <param name="path" value="${rep.home}/index" />
- <param name="directoryManagerClass"
- value="org.apache.jackrabbit.core.query.lucene.directory.RAMDirectoryManager" />
- <param name="extractorPoolSize" value="0" />
- <FileSystem
- class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
- </SearchIndex>
-
- <!-- Security -->
- <Security appName="Jackrabbit">
- <LoginModule
- class="org.apache.jackrabbit.core.security.SimpleLoginModule" />
- <!-- <SecurityManager class="org.argeo.security.jackrabbit.ArgeoSecurityManager" -->
- <!-- workspaceName="security" /> -->
- <!-- <AccessManager class="org.argeo.security.jackrabbit.ArgeoAccessManager"
- /> -->
- </Security>
-</Repository>
\ No newline at end of file
+++ /dev/null
-/** Java NIO file system implementation based on Jackrabbit. */
-package org.argeo.jackrabbit.fs;
\ No newline at end of file
+++ /dev/null
-/** Generic Jackrabbit utilities. */
-package org.argeo.jackrabbit;
\ No newline at end of file
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE Repository PUBLIC "Jackrabbit 2.6" "http://jackrabbit.apache.org/dtd/repository-2.6.dtd">
-<Repository>
- <!-- Shared datasource -->
- <DataSources>
- <DataSource name="dataSource">
- <param name="driver" value="org.h2.Driver" />
- <param name="url" value="${dburl}" />
- <param name="user" value="${dbuser}" />
- <param name="password" value="${dbpassword}" />
- <param name="databaseType" value="h2" />
- <param name="maxPoolSize" value="${maxPoolSize}" />
- </DataSource>
- </DataSources>
-
- <!-- File system and datastore -->
- <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
- <param name="dataSourceName" value="dataSource" />
- <param name="schema" value="default" />
- <param name="schemaObjectPrefix" value="fs_" />
- </FileSystem>
- <DataStore class="org.apache.jackrabbit.core.data.FileDataStore">
- <param name="path" value="${rep.home}/datastore" />
- </DataStore>
-
- <!-- Workspace templates -->
- <Workspaces rootPath="${rep.home}/workspaces"
- defaultWorkspace="${defaultWorkspace}" />
- <Workspace name="${wsp.name}">
- <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
- <param name="dataSourceName" value="dataSource" />
- <param name="schema" value="default" />
- <param name="schemaObjectPrefix" value="${wsp.name}_fs_" />
- </FileSystem>
- <PersistenceManager
- class="org.apache.jackrabbit.core.persistence.pool.H2PersistenceManager">
- <param name="dataSourceName" value="dataSource" />
- <param name="schemaObjectPrefix" value="${wsp.name}_pm_" />
- <param name="bundleCacheSize" value="${bundleCacheMB}" />
- </PersistenceManager>
- <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
- <param name="path" value="${wsp.home}/index" />
- <param name="extractorPoolSize" value="${extractorPoolSize}" />
- <param name="cacheSize" value="${searchCacheSize}" />
- <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
- </SearchIndex>
- <WorkspaceSecurity>
- <AccessControlProvider
- class="org.argeo.security.jackrabbit.ArgeoAccessControlProvider" />
- </WorkspaceSecurity>
- </Workspace>
-
- <!-- Versioning -->
- <Versioning rootPath="${rep.home}/version">
- <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
- <param name="dataSourceName" value="dataSource" />
- <param name="schema" value="default" />
- <param name="schemaObjectPrefix" value="fs_ver_" />
- </FileSystem>
- <PersistenceManager
- class="org.apache.jackrabbit.core.persistence.pool.H2PersistenceManager">
- <param name="dataSourceName" value="dataSource" />
- <param name="schemaObjectPrefix" value="pm_ver_" />
- <param name="bundleCacheSize" value="${bundleCacheMB}" />
- </PersistenceManager>
- </Versioning>
-
- <!-- Indexing -->
- <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
- <param name="path" value="${rep.home}/index" />
- <param name="extractorPoolSize" value="${extractorPoolSize}" />
- <param name="cacheSize" value="${searchCacheSize}" />
- <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
- </SearchIndex>
-
- <!-- Security -->
- <Security appName="Jackrabbit">
- <SecurityManager class="org.argeo.security.jackrabbit.ArgeoSecurityManager"
- workspaceName="security" />
- <AccessManager class="org.argeo.security.jackrabbit.ArgeoAccessManager" />
- </Security>
-</Repository>
\ No newline at end of file
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE Repository PUBLIC "Jackrabbit 2.6" "http://jackrabbit.apache.org/dtd/repository-2.6.dtd">
-<Repository>
- <!-- File system and datastore -->
- <FileSystem class="org.apache.jackrabbit.core.fs.local.LocalFileSystem">
- <param name="path" value="${rep.home}/repository" />
- </FileSystem>
- <DataStore class="org.apache.jackrabbit.core.data.FileDataStore">
- <param name="path" value="${rep.home}/datastore" />
- </DataStore>
-
- <!-- Workspace templates -->
- <Workspaces rootPath="${rep.home}/workspaces"
- defaultWorkspace="${defaultWorkspace}" />
- <Workspace name="${wsp.name}">
- <FileSystem class="org.apache.jackrabbit.core.fs.local.LocalFileSystem">
- <param name="path" value="${wsp.home}" />
- </FileSystem>
- <PersistenceManager
- class="org.apache.jackrabbit.core.persistence.bundle.BundleFsPersistenceManager">
- <param name="bundleCacheSize" value="${bundleCacheMB}" />
- </PersistenceManager>
- <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
- <param name="path" value="${wsp.home}/index" />
- <param name="extractorPoolSize" value="${extractorPoolSize}" />
- <param name="cacheSize" value="${searchCacheSize}" />
- <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
- </SearchIndex>
- <WorkspaceSecurity>
- <AccessControlProvider
- class="org.argeo.security.jackrabbit.ArgeoAccessControlProvider" />
- </WorkspaceSecurity>
- </Workspace>
-
- <!-- Versioning -->
- <Versioning rootPath="${rep.home}/version">
- <FileSystem class="org.apache.jackrabbit.core.fs.local.LocalFileSystem">
- <param name="path" value="${rep.home}/version" />
- </FileSystem>
- <PersistenceManager
- class="org.apache.jackrabbit.core.persistence.bundle.BundleFsPersistenceManager">
- <param name="bundleCacheSize" value="${bundleCacheMB}" />
- </PersistenceManager>
- </Versioning>
-
- <!-- Indexing -->
- <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
- <param name="path" value="${rep.home}/index" />
- <param name="extractorPoolSize" value="${extractorPoolSize}" />
- <param name="cacheSize" value="${searchCacheSize}" />
- <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
- </SearchIndex>
-
- <!-- Security -->
- <Security appName="Jackrabbit">
- <SecurityManager class="org.argeo.security.jackrabbit.ArgeoSecurityManager"
- workspaceName="security" />
- <AccessManager class="org.argeo.security.jackrabbit.ArgeoAccessManager" />
- </Security>
-</Repository>
\ No newline at end of file
+++ /dev/null
-<?xml version="1.0"?>
-<!DOCTYPE Repository PUBLIC "Jackrabbit 2.6" "http://jackrabbit.apache.org/dtd/repository-2.6.dtd">
-<Repository>
- <!-- File system and datastore -->
- <FileSystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
-
- <!-- Workspace templates -->
- <Workspaces rootPath="${rep.home}/workspaces"
- defaultWorkspace="${defaultWorkspace}" configRootPath="/workspaces" />
- <Workspace name="${wsp.name}">
- <FileSystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
- <PersistenceManager
- class="org.apache.jackrabbit.core.persistence.bundle.BundleFsPersistenceManager">
- <param name="blobFSBlockSize" value="1" />
- <param name="bundleCacheSize" value="${bundleCacheMB}" />
- </PersistenceManager>
- <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
- <param name="path" value="${wsp.home}/index" />
- <param name="directoryManagerClass"
- value="org.apache.jackrabbit.core.query.lucene.directory.RAMDirectoryManager" />
- <param name="extractorPoolSize" value="${extractorPoolSize}" />
- <param name="cacheSize" value="${searchCacheSize}" />
- <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
- <FileSystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
- </SearchIndex>
- </Workspace>
-
- <!-- Versioning -->
- <Versioning rootPath="${rep.home}/version">
- <FileSystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
- <PersistenceManager
- class="org.apache.jackrabbit.core.persistence.bundle.BundleFsPersistenceManager">
- <param name="blobFSBlockSize" value="1" />
- <param name="bundleCacheSize" value="${bundleCacheMB}" />
- </PersistenceManager>
- </Versioning>
-
- <!-- Indexing -->
- <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
- <param name="path" value="${rep.home}/index" />
- <param name="directoryManagerClass"
- value="org.apache.jackrabbit.core.query.lucene.directory.RAMDirectoryManager" />
- <param name="extractorPoolSize" value="${extractorPoolSize}" />
- <param name="cacheSize" value="${searchCacheSize}" />
- <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
- <FileSystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
- </SearchIndex>
-
- <!-- Security -->
- <Security appName="Jackrabbit">
- <SecurityManager class="org.argeo.security.jackrabbit.ArgeoSecurityManager"
- workspaceName="security" />
- <AccessManager class="org.argeo.security.jackrabbit.ArgeoAccessManager" />
- </Security>
-</Repository>
\ No newline at end of file
+++ /dev/null
-<?xml version="1.0"?>
-<!DOCTYPE Repository PUBLIC "Jackrabbit 2.6" "http://jackrabbit.apache.org/dtd/repository-2.6.dtd">
-<Repository>
- <!-- Shared datasource -->
- <DataSources>
- <DataSource name="dataSource">
- <param name="driver" value="org.postgresql.Driver" />
- <param name="url" value="${dburl}" />
- <param name="user" value="${dbuser}" />
- <param name="password" value="${dbpassword}" />
- <param name="databaseType" value="postgresql" />
- <param name="maxPoolSize" value="${maxPoolSize}" />
- </DataSource>
- </DataSources>
-
- <!-- File system and datastore -->
- <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
- <param name="dataSourceName" value="dataSource" />
- <param name="schema" value="postgresql" />
- <param name="schemaObjectPrefix" value="fs_" />
- </FileSystem>
- <DataStore class="org.apache.jackrabbit.core.data.FileDataStore">
- <param name="path" value="${rep.home}/datastore" />
- </DataStore>
-
- <!-- Workspace templates -->
- <Workspaces rootPath="${rep.home}/workspaces"
- defaultWorkspace="${defaultWorkspace}" />
- <Workspace name="${wsp.name}">
- <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
- <param name="dataSourceName" value="dataSource" />
- <param name="schema" value="postgresql" />
- <param name="schemaObjectPrefix" value="${wsp.name}_fs_" />
- </FileSystem>
- <PersistenceManager
- class="org.apache.jackrabbit.core.persistence.pool.PostgreSQLPersistenceManager">
- <param name="dataSourceName" value="dataSource" />
- <param name="schemaObjectPrefix" value="${wsp.name}_pm_" />
- <param name="bundleCacheSize" value="${bundleCacheMB}" />
- </PersistenceManager>
- <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
- <param name="path" value="${wsp.home}/index" />
- <param name="extractorPoolSize" value="${extractorPoolSize}" />
- <param name="cacheSize" value="${searchCacheSize}" />
- <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
- </SearchIndex>
- <WorkspaceSecurity>
- <AccessControlProvider
- class="org.argeo.security.jackrabbit.ArgeoAccessControlProvider" />
- </WorkspaceSecurity>
- </Workspace>
-
- <!-- Versioning -->
- <Versioning rootPath="${rep.home}/version">
- <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
- <param name="dataSourceName" value="dataSource" />
- <param name="schema" value="postgresql" />
- <param name="schemaObjectPrefix" value="fs_ver_" />
- </FileSystem>
- <PersistenceManager
- class="org.apache.jackrabbit.core.persistence.pool.PostgreSQLPersistenceManager">
- <param name="dataSourceName" value="dataSource" />
- <param name="schemaObjectPrefix" value="pm_ver_" />
- <param name="bundleCacheSize" value="${bundleCacheMB}" />
- </PersistenceManager>
- </Versioning>
-
- <!-- Indexing -->
- <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
- <param name="path" value="${rep.home}/index" />
- <param name="extractorPoolSize" value="${extractorPoolSize}" />
- <param name="cacheSize" value="${searchCacheSize}" />
- <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
- </SearchIndex>
-
- <!-- Security -->
- <Security appName="Jackrabbit">
- <SecurityManager class="org.argeo.security.jackrabbit.ArgeoSecurityManager"
- workspaceName="security" />
- <AccessManager class="org.argeo.security.jackrabbit.ArgeoAccessManager" />
- </Security>
-</Repository>
\ No newline at end of file
+++ /dev/null
-<?xml version="1.0"?>
-<!DOCTYPE Repository PUBLIC "Jackrabbit 2.6" "http://jackrabbit.apache.org/dtd/repository-2.6.dtd">
-<Repository>
- <!-- Shared datasource -->
- <DataSources>
- <DataSource name="dataSource">
- <param name="driver" value="org.postgresql.Driver" />
- <param name="url" value="${dburl}" />
- <param name="user" value="${dbuser}" />
- <param name="password" value="${dbpassword}" />
- <param name="databaseType" value="postgresql" />
- <param name="maxPoolSize" value="${maxPoolSize}" />
- </DataSource>
- </DataSources>
-
- <!-- File system and datastore -->
- <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
- <param name="dataSourceName" value="dataSource" />
- <param name="schema" value="postgresql" />
- <param name="schemaObjectPrefix" value="fs_" />
- </FileSystem>
-
- <!-- Workspace templates -->
- <Workspaces rootPath="${rep.home}/workspaces"
- defaultWorkspace="${defaultWorkspace}" />
- <Workspace name="${wsp.name}">
- <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
- <param name="dataSourceName" value="dataSource" />
- <param name="schema" value="postgresql" />
- <param name="schemaObjectPrefix" value="${wsp.name}_fs_" />
- </FileSystem>
- <PersistenceManager
- class="org.apache.jackrabbit.core.persistence.pool.PostgreSQLPersistenceManager">
- <param name="dataSourceName" value="dataSource" />
- <param name="schemaObjectPrefix" value="${wsp.name}_pm_" />
- <param name="bundleCacheSize" value="${bundleCacheMB}" />
- </PersistenceManager>
- <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
- <param name="path" value="${wsp.home}/index" />
- <param name="extractorPoolSize" value="${extractorPoolSize}" />
- <param name="cacheSize" value="${searchCacheSize}" />
- <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
- </SearchIndex>
- <WorkspaceSecurity>
- <AccessControlProvider
- class="org.argeo.security.jackrabbit.ArgeoAccessControlProvider" />
- </WorkspaceSecurity>
- </Workspace>
-
- <!-- Versioning -->
- <Versioning rootPath="${rep.home}/version">
- <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
- <param name="dataSourceName" value="dataSource" />
- <param name="schema" value="postgresql" />
- <param name="schemaObjectPrefix" value="fs_ver_" />
- </FileSystem>
- <PersistenceManager
- class="org.apache.jackrabbit.core.persistence.pool.PostgreSQLPersistenceManager">
- <param name="dataSourceName" value="dataSource" />
- <param name="schemaObjectPrefix" value="pm_ver_" />
- <param name="bundleCacheSize" value="${bundleCacheMB}" />
- </PersistenceManager>
- </Versioning>
-
- <!-- Indexing -->
- <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
- <param name="path" value="${rep.home}/index" />
- <param name="extractorPoolSize" value="${extractorPoolSize}" />
- <param name="cacheSize" value="${searchCacheSize}" />
- <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
- </SearchIndex>
-
- <!-- Security -->
- <Security appName="Jackrabbit">
- <SecurityManager class="org.argeo.security.jackrabbit.ArgeoSecurityManager"
- workspaceName="security" />
- <AccessManager class="org.argeo.security.jackrabbit.ArgeoAccessManager" />
- </Security>
-</Repository>
\ No newline at end of file
+++ /dev/null
-package org.argeo.jackrabbit.security;
-
-import java.security.Principal;
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.security.Privilege;
-
-import org.apache.jackrabbit.api.security.JackrabbitAccessControlList;
-import org.apache.jackrabbit.api.security.JackrabbitAccessControlManager;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.jcr.JcrUtils;
-
-/** Utilities around Jackrabbit security extensions. */
-public class JackrabbitSecurityUtils {
- private final static CmsLog log = CmsLog.getLog(JackrabbitSecurityUtils.class);
-
- /**
- * Convenience method for denying a single privilege to a principal (user or
- * role), typically jcr:all
- */
- public synchronized static void denyPrivilege(Session session, String path, String principal, String privilege)
- throws RepositoryException {
- List<Privilege> privileges = new ArrayList<Privilege>();
- privileges.add(session.getAccessControlManager().privilegeFromName(privilege));
- denyPrivileges(session, path, () -> principal, privileges);
- }
-
- /**
- * Deny privileges on a path to a {@link Principal}. The path must already
- * exist. Session is saved. Synchronized to prevent concurrent modifications of
- * the same node.
- */
- public synchronized static Boolean denyPrivileges(Session session, String path, Principal principal,
- List<Privilege> privs) throws RepositoryException {
- // make sure the session is in line with the persisted state
- session.refresh(false);
- JackrabbitAccessControlManager acm = (JackrabbitAccessControlManager) session.getAccessControlManager();
- JackrabbitAccessControlList acl = (JackrabbitAccessControlList) JcrUtils.getAccessControlList(acm, path);
-
-// accessControlEntries: for (AccessControlEntry ace : acl.getAccessControlEntries()) {
-// Principal currentPrincipal = ace.getPrincipal();
-// if (currentPrincipal.getName().equals(principal.getName())) {
-// Privilege[] currentPrivileges = ace.getPrivileges();
-// if (currentPrivileges.length != privs.size())
-// break accessControlEntries;
-// for (int i = 0; i < currentPrivileges.length; i++) {
-// Privilege currP = currentPrivileges[i];
-// Privilege p = privs.get(i);
-// if (!currP.getName().equals(p.getName())) {
-// break accessControlEntries;
-// }
-// }
-// return false;
-// }
-// }
-
- Privilege[] privileges = privs.toArray(new Privilege[privs.size()]);
- acl.addEntry(principal, privileges, false);
- acm.setPolicy(path, acl);
- if (log.isDebugEnabled()) {
- StringBuffer privBuf = new StringBuffer();
- for (Privilege priv : privs)
- privBuf.append(priv.getName());
- log.debug("Denied privileges " + privBuf + " to " + principal.getName() + " on " + path + " in '"
- + session.getWorkspace().getName() + "'");
- }
- session.refresh(true);
- session.save();
- return true;
- }
-
- /** Singleton. */
- private JackrabbitSecurityUtils() {
-
- }
-}
+++ /dev/null
-/** Generic Jackrabbit security utilities. */
-package org.argeo.jackrabbit.security;
\ No newline at end of file
+++ /dev/null
-package org.argeo.jackrabbit.unit;
-
-import java.net.URL;
-
-import javax.jcr.Repository;
-
-import org.apache.commons.io.FileUtils;
-import org.apache.jackrabbit.core.RepositoryImpl;
-import org.apache.jackrabbit.core.config.RepositoryConfig;
-import org.argeo.jcr.unit.AbstractJcrTestCase;
-
-/** Factorizes configuration of an in memory transient repository */
-public abstract class AbstractJackrabbitTestCase extends AbstractJcrTestCase {
- protected RepositoryImpl repositoryImpl;
-
- // protected File getRepositoryFile() throws Exception {
- // Resource res = new ClassPathResource(
- // "org/argeo/jackrabbit/unit/repository-memory.xml");
- // return res.getFile();
- // }
-
- public AbstractJackrabbitTestCase() {
- URL url = AbstractJackrabbitTestCase.class.getResource("jaas.config");
- assert url != null;
- System.setProperty("java.security.auth.login.config", url.toString());
- }
-
- protected Repository createRepository() throws Exception {
- // Repository repository = new TransientRepository(getRepositoryFile(),
- // getHomeDir());
- RepositoryConfig repositoryConfig = RepositoryConfig.create(
- AbstractJackrabbitTestCase.class
- .getResourceAsStream(getRepositoryConfigResource()),
- getHomeDir().getAbsolutePath());
- RepositoryImpl repositoryImpl = RepositoryImpl.create(repositoryConfig);
- return repositoryImpl;
- }
-
- protected String getRepositoryConfigResource() {
- return "repository-memory.xml";
- }
-
- @Override
- protected void clearRepository(Repository repository) throws Exception {
- RepositoryImpl repositoryImpl = (RepositoryImpl) repository;
- if (repositoryImpl != null)
- repositoryImpl.shutdown();
- FileUtils.deleteDirectory(getHomeDir());
- }
-
-}
+++ /dev/null
-TEST_JACKRABBIT_ADMIN {
- org.argeo.cms.auth.DataAdminLoginModule requisite;
-};
-
-Jackrabbit {
- org.argeo.security.jackrabbit.SystemJackrabbitLoginModule requisite;
-};
+++ /dev/null
-/** Helpers for unit tests with Jackrabbit repositories. */
-package org.argeo.jackrabbit.unit;
\ No newline at end of file
+++ /dev/null
-<?xml version="1.0"?>
-<!DOCTYPE Repository PUBLIC "-//The Apache Software Foundation//DTD Jackrabbit 1.6//EN"
- "http://jackrabbit.apache.org/dtd/repository-2.0.dtd">
-<Repository>
- <!-- Shared datasource -->
- <DataSources>
- <DataSource name="dataSource">
- <param name="driver" value="org.h2.Driver" />
- <param name="url" value="jdbc:h2:mem:jackrabbit" />
- <param name="user" value="sa" />
- <param name="password" value="" />
- <param name="databaseType" value="h2" />
- <param name="maxPoolSize" value="10" />
- </DataSource>
- </DataSources>
-
- <!-- File system and datastore -->
- <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
- <param name="dataSourceName" value="dataSource" />
- <param name="schema" value="default" />
- <param name="schemaObjectPrefix" value="fs_" />
- </FileSystem>
- <DataStore class="org.apache.jackrabbit.core.data.db.DbDataStore">
- <param name="dataSourceName" value="dataSource" />
- <param name="schemaObjectPrefix" value="ds_" />
- </DataStore>
-
- <!-- Workspace templates -->
- <Workspaces rootPath="${rep.home}/workspaces"
- defaultWorkspace="dev" />
- <Workspace name="${wsp.name}">
- <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
- <param name="dataSourceName" value="dataSource" />
- <param name="schema" value="default" />
- <param name="schemaObjectPrefix" value="${wsp.name}_fs_" />
- </FileSystem>
- <PersistenceManager
- class="org.apache.jackrabbit.core.persistence.pool.H2PersistenceManager">
- <param name="dataSourceName" value="dataSource" />
- <param name="schemaObjectPrefix" value="${wsp.name}_pm_" />
- </PersistenceManager>
- <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
- <param name="path" value="${wsp.home}/index" />
- </SearchIndex>
- </Workspace>
-
- <!-- Versioning -->
- <Versioning rootPath="${rep.home}/version">
- <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
- <param name="dataSourceName" value="dataSource" />
- <param name="schema" value="default" />
- <param name="schemaObjectPrefix" value="fs_ver_" />
- </FileSystem>
- <PersistenceManager
- class="org.apache.jackrabbit.core.persistence.pool.H2PersistenceManager">
- <param name="dataSourceName" value="dataSource" />
- <param name="schemaObjectPrefix" value="pm_ver_" />
- </PersistenceManager>
- </Versioning>
-
- <!-- Indexing -->
- <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
- <param name="path" value="${rep.home}/repository/index" />
- <param name="extractorPoolSize" value="2" />
- <param name="supportHighlighting" value="true" />
- </SearchIndex>
-
- <!-- Security -->
- <Security appName="Jackrabbit">
- <SecurityManager
- class="org.apache.jackrabbit.core.security.simple.SimpleSecurityManager"
- workspaceName="security" />
- <AccessManager
- class="org.apache.jackrabbit.core.security.simple.SimpleAccessManager" />
- <LoginModule
- class="org.apache.jackrabbit.core.security.simple.SimpleLoginModule">
- <param name="anonymousId" value="anonymous" />
- <param name="adminId" value="admin" />
- </LoginModule>
- </Security>
-</Repository>
\ No newline at end of file
+++ /dev/null
-<?xml version="1.0"?>
-<!--
-
- Copyright (C) 2007-2012 Argeo GmbH
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
-
--->
-<!DOCTYPE Repository PUBLIC "-//The Apache Software Foundation//DTD Jackrabbit 1.6//EN"
- "http://jackrabbit.apache.org/dtd/repository-2.0.dtd">
-<Repository>
- <!-- File system and datastore -->
- <FileSystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
-
- <!-- Workspace templates -->
- <Workspaces rootPath="${rep.home}/workspaces"
- defaultWorkspace="main" configRootPath="/workspaces" />
- <Workspace name="${wsp.name}">
- <FileSystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
- <PersistenceManager
- class="org.apache.jackrabbit.core.persistence.bundle.BundleFsPersistenceManager">
- <param name="blobFSBlockSize" value="1" />
- </PersistenceManager>
- <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
- <param name="path" value="${rep.home}/repository/index" />
- <param name="directoryManagerClass"
- value="org.apache.jackrabbit.core.query.lucene.directory.RAMDirectoryManager" />
- <FileSystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
- </SearchIndex>
- </Workspace>
-
- <!-- Versioning -->
- <Versioning rootPath="${rep.home}/version">
- <FileSystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
- <PersistenceManager
- class="org.apache.jackrabbit.core.persistence.bundle.BundleFsPersistenceManager">
- <param name="blobFSBlockSize" value="1" />
- </PersistenceManager>
- </Versioning>
-
- <!-- Indexing -->
- <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
- <param name="path" value="${rep.home}/repository/index" />
- <param name="directoryManagerClass"
- value="org.apache.jackrabbit.core.query.lucene.directory.RAMDirectoryManager" />
- <FileSystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
- </SearchIndex>
-
- <!-- Security -->
- <Security appName="Jackrabbit">
- <SecurityManager
- class="org.apache.jackrabbit.core.security.simple.SimpleSecurityManager"
- workspaceName="security" />
- <AccessManager
- class="org.apache.jackrabbit.core.security.simple.SimpleAccessManager" />
- <LoginModule
- class="org.apache.jackrabbit.core.security.simple.SimpleLoginModule">
- <param name="anonymousId" value="anonymous" />
- <param name="adminId" value="admin" />
- </LoginModule>
- </Security>
-</Repository>
\ No newline at end of file
+++ /dev/null
-package org.argeo.jcr;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-import javax.jcr.Binary;
-import javax.jcr.Property;
-import javax.jcr.RepositoryException;
-
-/**
- * A {@link Binary} wrapper implementing {@link AutoCloseable} for ease of use
- * in try/catch blocks.
- */
-public class Bin implements Binary, AutoCloseable {
- private final Binary wrappedBinary;
-
- public Bin(Property property) throws RepositoryException {
- this(property.getBinary());
- }
-
- public Bin(Binary wrappedBinary) {
- if (wrappedBinary == null)
- throw new IllegalArgumentException("Wrapped binary cannot be null");
- this.wrappedBinary = wrappedBinary;
- }
-
- // private static Binary getBinary(Property property) throws IOException {
- // try {
- // return property.getBinary();
- // } catch (RepositoryException e) {
- // throw new IOException("Cannot get binary from property " + property, e);
- // }
- // }
-
- @Override
- public void close() {
- dispose();
- }
-
- @Override
- public InputStream getStream() throws RepositoryException {
- return wrappedBinary.getStream();
- }
-
- @Override
- public int read(byte[] b, long position) throws IOException, RepositoryException {
- return wrappedBinary.read(b, position);
- }
-
- @Override
- public long getSize() throws RepositoryException {
- return wrappedBinary.getSize();
- }
-
- @Override
- public void dispose() {
- wrappedBinary.dispose();
- }
-
-}
+++ /dev/null
-package org.argeo.jcr;
-
-import java.util.Collection;
-import java.util.Iterator;
-import java.util.NoSuchElementException;
-
-import javax.jcr.Node;
-import javax.jcr.NodeIterator;
-
-/** Wraps a collection of nodes in order to read it as a {@link NodeIterator} */
-public class CollectionNodeIterator implements NodeIterator {
- private final Long collectionSize;
- private final Iterator<Node> iterator;
- private Integer position = 0;
-
- public CollectionNodeIterator(Collection<Node> nodes) {
- super();
- this.collectionSize = (long) nodes.size();
- this.iterator = nodes.iterator();
- }
-
- public void skip(long skipNum) {
- if (skipNum < 0)
- throw new IllegalArgumentException(
- "Skip count has to be positive: " + skipNum);
-
- for (long i = 0; i < skipNum; i++) {
- if (!hasNext())
- throw new NoSuchElementException("Last element past (position="
- + getPosition() + ")");
- nextNode();
- }
- }
-
- public long getSize() {
- return collectionSize;
- }
-
- public long getPosition() {
- return position;
- }
-
- public boolean hasNext() {
- return iterator.hasNext();
- }
-
- public Object next() {
- return nextNode();
- }
-
- public void remove() {
- iterator.remove();
- }
-
- public Node nextNode() {
- Node node = iterator.next();
- position++;
- return node;
- }
-
-}
+++ /dev/null
-package org.argeo.jcr;
-
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.observation.Event;
-import javax.jcr.observation.EventIterator;
-import javax.jcr.observation.EventListener;
-import javax.jcr.observation.ObservationManager;
-
-import org.argeo.api.cms.CmsLog;
-
-/** To be overridden */
-public class DefaultJcrListener implements EventListener {
- private final static CmsLog log = CmsLog.getLog(DefaultJcrListener.class);
- private Session session;
- private String path = "/";
- private Boolean deep = true;
-
- public void start() {
- try {
- addEventListener(session().getWorkspace().getObservationManager());
- if (log.isDebugEnabled())
- log.debug("Registered JCR event listener on " + path);
- } catch (RepositoryException e) {
- throw new JcrException("Cannot register event listener", e);
- }
- }
-
- public void stop() {
- try {
- session().getWorkspace().getObservationManager()
- .removeEventListener(this);
- if (log.isDebugEnabled())
- log.debug("Unregistered JCR event listener on " + path);
- } catch (RepositoryException e) {
- throw new JcrException("Cannot unregister event listener", e);
- }
- }
-
- /** Default is listen to all events */
- protected Integer getEvents() {
- return Event.NODE_ADDED | Event.NODE_REMOVED | Event.PROPERTY_ADDED
- | Event.PROPERTY_CHANGED | Event.PROPERTY_REMOVED;
- }
-
- /** To be overidden */
- public void onEvent(EventIterator events) {
- while (events.hasNext()) {
- Event event = events.nextEvent();
- log.debug(event);
- }
- }
-
- /** To be overidden */
- protected void addEventListener(ObservationManager observationManager)
- throws RepositoryException {
- observationManager.addEventListener(this, getEvents(), path, deep,
- null, null, false);
- }
-
- private Session session() {
- return session;
- }
-
- public void setPath(String path) {
- this.path = path;
- }
-
- public void setDeep(Boolean deep) {
- this.deep = deep;
- }
-
- public void setSession(Session session) {
- this.session = session;
- }
-
-}
+++ /dev/null
-package org.argeo.jcr;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.math.BigDecimal;
-import java.text.MessageFormat;
-import java.time.Instant;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Calendar;
-import java.util.Collections;
-import java.util.Date;
-import java.util.GregorianCalendar;
-import java.util.Iterator;
-import java.util.List;
-
-import javax.jcr.Binary;
-import javax.jcr.ItemNotFoundException;
-import javax.jcr.Node;
-import javax.jcr.NodeIterator;
-import javax.jcr.Property;
-import javax.jcr.PropertyType;
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.Value;
-import javax.jcr.Workspace;
-import javax.jcr.nodetype.NodeType;
-import javax.jcr.query.Query;
-import javax.jcr.query.QueryManager;
-import javax.jcr.security.Privilege;
-import javax.jcr.version.Version;
-import javax.jcr.version.VersionHistory;
-import javax.jcr.version.VersionIterator;
-import javax.jcr.version.VersionManager;
-
-import org.apache.commons.io.IOUtils;
-
-/**
- * Utility class whose purpose is to make using JCR less verbose by
- * systematically using unchecked exceptions and returning <code>null</code>
- * when something is not found. This is especially useful when writing user
- * interfaces (such as with SWT) where listeners and callbacks expect unchecked
- * exceptions. Loosely inspired by Java's <code>Files</code> singleton.
- */
-public class Jcr {
- /**
- * The name of a node which will be serialized as XML text, as per section 7.3.1
- * of the JCR 2.0 specifications.
- */
- public final static String JCR_XMLTEXT = "jcr:xmltext";
- /**
- * The name of a property which will be serialized as XML text, as per section
- * 7.3.1 of the JCR 2.0 specifications.
- */
- public final static String JCR_XMLCHARACTERS = "jcr:xmlcharacters";
- /**
- * <code>jcr:name</code>, when used in another context than
- * {@link Property#JCR_NAME}, typically to name a node rather than a property.
- */
- public final static String JCR_NAME = "jcr:name";
- /**
- * <code>jcr:path</code>, when used in another context than
- * {@link Property#JCR_PATH}, typically to name a node rather than a property.
- */
- public final static String JCR_PATH = "jcr:path";
- /**
- * <code>jcr:primaryType</code> with prefix instead of namespace (as in
- * {@link Property#JCR_PRIMARY_TYPE}.
- */
- public final static String JCR_PRIMARY_TYPE = "jcr:primaryType";
- /**
- * <code>jcr:mixinTypes</code> with prefix instead of namespace (as in
- * {@link Property#JCR_MIXIN_TYPES}.
- */
- public final static String JCR_MIXIN_TYPES = "jcr:mixinTypes";
- /**
- * <code>jcr:uuid</code> with prefix instead of namespace (as in
- * {@link Property#JCR_UUID}.
- */
- public final static String JCR_UUID = "jcr:uuid";
- /**
- * <code>jcr:created</code> with prefix instead of namespace (as in
- * {@link Property#JCR_CREATED}.
- */
- public final static String JCR_CREATED = "jcr:created";
- /**
- * <code>jcr:createdBy</code> with prefix instead of namespace (as in
- * {@link Property#JCR_CREATED_BY}.
- */
- public final static String JCR_CREATED_BY = "jcr:createdBy";
- /**
- * <code>jcr:lastModified</code> with prefix instead of namespace (as in
- * {@link Property#JCR_LAST_MODIFIED}.
- */
- public final static String JCR_LAST_MODIFIED = "jcr:lastModified";
- /**
- * <code>jcr:lastModifiedBy</code> with prefix instead of namespace (as in
- * {@link Property#JCR_LAST_MODIFIED_BY}.
- */
- public final static String JCR_LAST_MODIFIED_BY = "jcr:lastModifiedBy";
-
- /**
- * @see Node#isNodeType(String)
- * @throws JcrException caused by {@link RepositoryException}
- */
- public static boolean isNodeType(Node node, String nodeTypeName) {
- try {
- return node.isNodeType(nodeTypeName);
- } catch (RepositoryException e) {
- throw new JcrException("Cannot get whether " + node + " is of type " + nodeTypeName, e);
- }
- }
-
- /**
- * @see Node#hasNodes()
- * @throws JcrException caused by {@link RepositoryException}
- */
- public static boolean hasNodes(Node node) {
- try {
- return node.hasNodes();
- } catch (RepositoryException e) {
- throw new JcrException("Cannot get whether " + node + " has children.", e);
- }
- }
-
- /**
- * @see Node#getParent()
- * @throws JcrException caused by {@link RepositoryException}
- */
- public static Node getParent(Node node) {
- try {
- return isRoot(node) ? null : node.getParent();
- } catch (RepositoryException e) {
- throw new JcrException("Cannot get parent of " + node, e);
- }
- }
-
- /**
- * @see Node#getParent()
- * @throws JcrException caused by {@link RepositoryException}
- */
- public static String getParentPath(Node node) {
- return getPath(getParent(node));
- }
-
- /**
- * Whether this node is the root node.
- *
- * @throws JcrException caused by {@link RepositoryException}
- */
- public static boolean isRoot(Node node) {
- try {
- return node.getDepth() == 0;
- } catch (RepositoryException e) {
- throw new JcrException("Cannot get depth of " + node, e);
- }
- }
-
- /**
- * @see Node#getPath()
- * @throws JcrException caused by {@link RepositoryException}
- */
- public static String getPath(Node node) {
- try {
- return node.getPath();
- } catch (RepositoryException e) {
- throw new JcrException("Cannot get path of " + node, e);
- }
- }
-
- /**
- * @see Node#getSession()
- * @see Session#getWorkspace()
- * @see Workspace#getName()
- */
- public static String getWorkspaceName(Node node) {
- return session(node).getWorkspace().getName();
- }
-
- /**
- * @see Node#getIdentifier()
- * @throws JcrException caused by {@link RepositoryException}
- */
- public static String getIdentifier(Node node) {
- try {
- return node.getIdentifier();
- } catch (RepositoryException e) {
- throw new JcrException("Cannot get identifier of " + node, e);
- }
- }
-
- /**
- * @see Node#getName()
- * @throws JcrException caused by {@link RepositoryException}
- */
- public static String getName(Node node) {
- try {
- return node.getName();
- } catch (RepositoryException e) {
- throw new JcrException("Cannot get name of " + node, e);
- }
- }
-
- /**
- * Returns the node name with its current index (useful for re-ordering).
- *
- * @see Node#getName()
- * @see Node#getIndex()
- * @throws JcrException caused by {@link RepositoryException}
- */
- public static String getIndexedName(Node node) {
- try {
- return node.getName() + "[" + node.getIndex() + "]";
- } catch (RepositoryException e) {
- throw new JcrException("Cannot get name of " + node, e);
- }
- }
-
- /**
- * @see Node#getProperty(String)
- * @throws JcrException caused by {@link RepositoryException}
- */
- public static Property getProperty(Node node, String property) {
- try {
- if (node.hasProperty(property))
- return node.getProperty(property);
- else
- return null;
- } catch (RepositoryException e) {
- throw new JcrException("Cannot get property " + property + " of " + node, e);
- }
- }
-
- /**
- * @see Node#getIndex()
- * @throws JcrException caused by {@link RepositoryException}
- */
- public static int getIndex(Node node) {
- try {
- return node.getIndex();
- } catch (RepositoryException e) {
- throw new JcrException("Cannot get index of " + node, e);
- }
- }
-
- /**
- * If node has mixin {@link NodeType#MIX_TITLE}, return
- * {@link Property#JCR_TITLE}, otherwise return {@link #getName(Node)}.
- */
- public static String getTitle(Node node) {
- if (Jcr.isNodeType(node, NodeType.MIX_TITLE))
- return get(node, Property.JCR_TITLE);
- else
- return Jcr.getName(node);
- }
-
- /** Accesses a {@link NodeIterator} as an {@link Iterable}. */
- @SuppressWarnings("unchecked")
- public static Iterable<Node> iterate(NodeIterator nodeIterator) {
- return new Iterable<Node>() {
-
- @Override
- public Iterator<Node> iterator() {
- return nodeIterator;
- }
- };
- }
-
- /**
- * @return the children as an {@link Iterable} for use in for-each llops.
- * @see Node#getNodes()
- * @throws JcrException caused by {@link RepositoryException}
- */
- public static Iterable<Node> nodes(Node node) {
- try {
- return iterate(node.getNodes());
- } catch (RepositoryException e) {
- throw new JcrException("Cannot get children of " + node, e);
- }
- }
-
- /**
- * @return the children as a (possibly empty) {@link List}.
- * @see Node#getNodes()
- * @throws JcrException caused by {@link RepositoryException}
- */
- public static List<Node> getNodes(Node node) {
- List<Node> nodes = new ArrayList<>();
- try {
- if (node.hasNodes()) {
- NodeIterator nit = node.getNodes();
- while (nit.hasNext())
- nodes.add(nit.nextNode());
- return nodes;
- } else
- return nodes;
- } catch (RepositoryException e) {
- throw new JcrException("Cannot get children of " + node, e);
- }
- }
-
- /**
- * @return the child or <code>null</node> if not found
- * @see Node#getNode(String)
- * @throws JcrException caused by {@link RepositoryException}
- */
- public static Node getNode(Node node, String child) {
- try {
- if (node.hasNode(child))
- return node.getNode(child);
- else
- return null;
- } catch (RepositoryException e) {
- throw new JcrException("Cannot get child of " + node, e);
- }
- }
-
- /**
- * @return the node at this path or <code>null</node> if not found
- * @see Session#getNode(String)
- * @throws JcrException caused by {@link RepositoryException}
- */
- public static Node getNode(Session session, String path) {
- try {
- if (session.nodeExists(path))
- return session.getNode(path);
- else
- return null;
- } catch (RepositoryException e) {
- throw new JcrException("Cannot get node " + path, e);
- }
- }
-
- /**
- * Add a node to this parent, setting its primary type and its mixins.
- *
- * @param parent the parent node
- * @param name the name of the node, if <code>null</code>, the primary
- * type will be used (typically for XML structures)
- * @param primaryType the primary type, if <code>null</code>
- * {@link NodeType#NT_UNSTRUCTURED} will be used.
- * @param mixins the mixins
- * @return the created node
- * @see Node#addNode(String, String)
- * @see Node#addMixin(String)
- */
- public static Node addNode(Node parent, String name, String primaryType, String... mixins) {
- if (name == null && primaryType == null)
- throw new IllegalArgumentException("Both node name and primary type cannot be null");
- try {
- Node newNode = parent.addNode(name == null ? primaryType : name,
- primaryType == null ? NodeType.NT_UNSTRUCTURED : primaryType);
- for (String mixin : mixins) {
- newNode.addMixin(mixin);
- }
- return newNode;
- } catch (RepositoryException e) {
- throw new JcrException("Cannot add node " + name + " to " + parent, e);
- }
- }
-
- /**
- * Add an {@link NodeType#NT_BASE} node to this parent.
- *
- * @param parent the parent node
- * @param name the name of the node, cannot be <code>null</code>
- * @return the created node
- *
- * @see Node#addNode(String)
- */
- public static Node addNode(Node parent, String name) {
- if (name == null)
- throw new IllegalArgumentException("Node name cannot be null");
- try {
- Node newNode = parent.addNode(name);
- return newNode;
- } catch (RepositoryException e) {
- throw new JcrException("Cannot add node " + name + " to " + parent, e);
- }
- }
-
- /**
- * Add mixins to a node.
- *
- * @param node the node
- * @param mixins the mixins
- * @return the created node
- * @see Node#addMixin(String)
- */
- public static void addMixin(Node node, String... mixins) {
- try {
- for (String mixin : mixins) {
- node.addMixin(mixin);
- }
- } catch (RepositoryException e) {
- throw new JcrException("Cannot add mixins " + Arrays.asList(mixins) + " to " + node, e);
- }
- }
-
- /**
- * Removes this node.
- *
- * @see Node#remove()
- */
- public static void remove(Node node) {
- try {
- node.remove();
- } catch (RepositoryException e) {
- throw new JcrException("Cannot remove node " + node, e);
- }
- }
-
- /**
- * @return the node with htis id or <code>null</node> if not found
- * @see Session#getNodeByIdentifier(String)
- * @throws JcrException caused by {@link RepositoryException}
- */
- public static Node getNodeById(Session session, String id) {
- try {
- return session.getNodeByIdentifier(id);
- } catch (ItemNotFoundException e) {
- return null;
- } catch (RepositoryException e) {
- throw new JcrException("Cannot get node with id " + id, e);
- }
- }
-
- /**
- * Set a property to the given value, or remove it if the value is
- * <code>null</code>.
- *
- * @throws JcrException caused by {@link RepositoryException}
- */
- public static void set(Node node, String property, Object value) {
- try {
- if (!node.hasProperty(property)) {
- if (value != null) {
- if (value instanceof List) {// multiple
- List<?> lst = (List<?>) value;
- String[] values = new String[lst.size()];
- for (int i = 0; i < lst.size(); i++) {
- values[i] = lst.get(i).toString();
- }
- node.setProperty(property, values);
- } else {
- node.setProperty(property, value.toString());
- }
- }
- return;
- }
- Property prop = node.getProperty(property);
- if (value == null) {
- prop.remove();
- return;
- }
-
- // multiple
- if (value instanceof List) {
- List<?> lst = (List<?>) value;
- String[] values = new String[lst.size()];
- // TODO better cast?
- for (int i = 0; i < lst.size(); i++) {
- values[i] = lst.get(i).toString();
- }
- if (!prop.isMultiple())
- prop.remove();
- node.setProperty(property, values);
- return;
- }
-
- // single
- if (prop.isMultiple()) {
- prop.remove();
- node.setProperty(property, value.toString());
- return;
- }
-
- if (value instanceof String)
- prop.setValue((String) value);
- else if (value instanceof Long)
- prop.setValue((Long) value);
- else if (value instanceof Integer)
- prop.setValue(((Integer) value).longValue());
- else if (value instanceof Double)
- prop.setValue((Double) value);
- else if (value instanceof Float)
- prop.setValue(((Float) value).doubleValue());
- else if (value instanceof Calendar)
- prop.setValue((Calendar) value);
- else if (value instanceof BigDecimal)
- prop.setValue((BigDecimal) value);
- else if (value instanceof Boolean)
- prop.setValue((Boolean) value);
- else if (value instanceof byte[])
- JcrUtils.setBinaryAsBytes(prop, (byte[]) value);
- else if (value instanceof Instant) {
- Instant instant = (Instant) value;
- GregorianCalendar calendar = new GregorianCalendar();
- calendar.setTime(Date.from(instant));
- prop.setValue(calendar);
- } else // try with toString()
- prop.setValue(value.toString());
- } catch (RepositoryException e) {
- throw new JcrException("Cannot set property " + property + " of " + node + " to " + value, e);
- }
- }
-
- /**
- * Get property as {@link String}.
- *
- * @return the value of
- * {@link Node#getProperty(String)}.{@link Property#getString()} or
- * <code>null</code> if the property does not exist.
- * @throws JcrException caused by {@link RepositoryException}
- */
- public static String get(Node node, String property) {
- return get(node, property, null);
- }
-
- /**
- * Get property as a {@link String}. If the property is multiple it returns the
- * first value.
- *
- * @return the value of
- * {@link Node#getProperty(String)}.{@link Property#getString()} or
- * <code>defaultValue</code> if the property does not exist.
- * @throws JcrException caused by {@link RepositoryException}
- */
- public static String get(Node node, String property, String defaultValue) {
- try {
- if (node.hasProperty(property)) {
- Property p = node.getProperty(property);
- if (!p.isMultiple())
- return p.getString();
- else {
- Value[] values = p.getValues();
- if (values.length == 0)
- return defaultValue;
- else
- return values[0].getString();
- }
- } else
- return defaultValue;
- } catch (RepositoryException e) {
- throw new JcrException("Cannot retrieve property " + property + " from " + node, e);
- }
- }
-
- /**
- * Get property as a {@link Value}.
- *
- * @return {@link Node#getProperty(String)} or <code>null</code> if the property
- * does not exist.
- * @throws JcrException caused by {@link RepositoryException}
- */
- public static Value getValue(Node node, String property) {
- try {
- if (node.hasProperty(property))
- return node.getProperty(property).getValue();
- else
- return null;
- } catch (RepositoryException e) {
- throw new JcrException("Cannot retrieve property " + property + " from " + node, e);
- }
- }
-
- /**
- * Get property doing a best effort to cast it as the target object.
- *
- * @return the value of {@link Node#getProperty(String)} or
- * <code>defaultValue</code> if the property does not exist.
- * @throws IllegalArgumentException if the value could not be cast
- * @throws JcrException in case of unexpected
- * {@link RepositoryException}
- */
- @SuppressWarnings("unchecked")
- public static <T> T getAs(Node node, String property, T defaultValue) {
- try {
- // TODO deal with multiple
- if (node.hasProperty(property)) {
- Property p = node.getProperty(property);
- try {
- if (p.isMultiple()) {
- throw new UnsupportedOperationException("Multiple values properties are not supported");
- }
- Value value = p.getValue();
- return (T) get(value);
- } catch (ClassCastException e) {
- throw new IllegalArgumentException(
- "Cannot cast property of type " + PropertyType.nameFromValue(p.getType()), e);
- }
- } else {
- return defaultValue;
- }
- } catch (RepositoryException e) {
- throw new JcrException("Cannot retrieve property " + property + " from " + node, e);
- }
- }
-
- public static <T> T getAs(Node node, String property, Class<T> clss) {
- if(String.class.isAssignableFrom(clss)) {
- return (T)get(node,property);
- } else if(Long.class.isAssignableFrom(clss)) {
- return (T)get(node,property);
- }else {
- throw new IllegalArgumentException("Unsupported format "+clss);
- }
- }
-
- /**
- * Get a multiple property as a list, doing a best effort to cast it as the
- * target list.
- *
- * @return the value of {@link Node#getProperty(String)}.
- * @throws IllegalArgumentException if the value could not be cast
- * @throws JcrException in case of unexpected
- * {@link RepositoryException}
- */
- public static <T> List<T> getMultiple(Node node, String property) {
- try {
- if (node.hasProperty(property)) {
- Property p = node.getProperty(property);
- return getMultiple(p);
- } else {
- return null;
- }
- } catch (RepositoryException e) {
- throw new JcrException("Cannot retrieve multiple values property " + property + " from " + node, e);
- }
- }
-
- /**
- * Get a multiple property as a list, doing a best effort to cast it as the
- * target list.
- */
- @SuppressWarnings("unchecked")
- public static <T> List<T> getMultiple(Property p) {
- try {
- List<T> res = new ArrayList<>();
- if (!p.isMultiple()) {
- res.add((T) get(p.getValue()));
- return res;
- }
- Value[] values = p.getValues();
- for (Value value : values) {
- res.add((T) get(value));
- }
- return res;
- } catch (ClassCastException | RepositoryException e) {
- throw new IllegalArgumentException("Cannot get property " + p, e);
- }
- }
-
- /** Cast a {@link Value} to a standard Java object. */
- public static Object get(Value value) {
- Binary binary = null;
- try {
- switch (value.getType()) {
- case PropertyType.STRING:
- return value.getString();
- case PropertyType.DOUBLE:
- return (Double) value.getDouble();
- case PropertyType.LONG:
- return (Long) value.getLong();
- case PropertyType.BOOLEAN:
- return (Boolean) value.getBoolean();
- case PropertyType.DATE:
- return value.getDate();
- case PropertyType.BINARY:
- binary = value.getBinary();
- byte[] arr = null;
- try (InputStream in = binary.getStream(); ByteArrayOutputStream out = new ByteArrayOutputStream();) {
- IOUtils.copy(in, out);
- arr = out.toByteArray();
- } catch (IOException e) {
- throw new RuntimeException("Cannot read binary from " + value, e);
- }
- return arr;
- default:
- return value.getString();
- }
- } catch (RepositoryException e) {
- throw new JcrException("Cannot cast value from " + value, e);
- } finally {
- if (binary != null)
- binary.dispose();
- }
- }
-
- /**
- * Retrieves the {@link Session} related to this node.
- *
- * @deprecated Use {@link #getSession(Node)} instead.
- */
- @Deprecated
- public static Session session(Node node) {
- return getSession(node);
- }
-
- /** Retrieves the {@link Session} related to this node. */
- public static Session getSession(Node node) {
- try {
- return node.getSession();
- } catch (RepositoryException e) {
- throw new JcrException("Cannot retrieve session related to " + node, e);
- }
- }
-
- /** Retrieves the root node related to this session. */
- public static Node getRootNode(Session session) {
- try {
- return session.getRootNode();
- } catch (RepositoryException e) {
- throw new JcrException("Cannot get root node for " + session, e);
- }
- }
-
- /** Whether this item exists. */
- public static boolean itemExists(Session session, String path) {
- try {
- return session.itemExists(path);
- } catch (RepositoryException e) {
- throw new JcrException("Cannot check whether " + path + " exists", e);
- }
- }
-
- /**
- * Saves the {@link Session} related to this node. Note that all other unrelated
- * modifications in this session will also be saved.
- */
- public static void save(Node node) {
- try {
- Session session = node.getSession();
-// if (node.isNodeType(NodeType.MIX_LAST_MODIFIED)) {
-// set(node, Property.JCR_LAST_MODIFIED, Instant.now());
-// set(node, Property.JCR_LAST_MODIFIED_BY, session.getUserID());
-// }
- if (session.hasPendingChanges())
- session.save();
- } catch (RepositoryException e) {
- throw new JcrException("Cannot save session related to " + node + " in workspace "
- + session(node).getWorkspace().getName(), e);
- }
- }
-
- /** Login to a JCR repository. */
- public static Session login(Repository repository, String workspace) {
- try {
- return repository.login(workspace);
- } catch (RepositoryException e) {
- throw new IllegalArgumentException("Cannot login to repository", e);
- }
- }
-
- /** Safely and silently logs out a session. */
- public static void logout(Session session) {
- try {
- if (session != null)
- if (session.isLive())
- session.logout();
- } catch (Exception e) {
- // silent
- }
- }
-
- /** Safely and silently logs out the underlying session. */
- public static void logout(Node node) {
- Jcr.logout(session(node));
- }
-
- /*
- * SECURITY
- */
- /**
- * Add a single privilege to a node.
- *
- * @see Privilege
- */
- public static void addPrivilege(Node node, String principal, String privilege) {
- try {
- Session session = node.getSession();
- JcrUtils.addPrivilege(session, node.getPath(), principal, privilege);
- } catch (RepositoryException e) {
- throw new JcrException("Cannot add privilege " + privilege + " to " + node, e);
- }
- }
-
- /*
- * VERSIONING
- */
- /** Get checked out status. */
- public static boolean isCheckedOut(Node node) {
- try {
- return node.isCheckedOut();
- } catch (RepositoryException e) {
- throw new JcrException("Cannot retrieve checked out status of " + node, e);
- }
- }
-
- /** @see VersionManager#checkpoint(String) */
- public static void checkpoint(Node node) {
- try {
- versionManager(node).checkpoint(node.getPath());
- } catch (RepositoryException e) {
- throw new JcrException("Cannot check in " + node, e);
- }
- }
-
- /** @see VersionManager#checkin(String) */
- public static void checkin(Node node) {
- try {
- versionManager(node).checkin(node.getPath());
- } catch (RepositoryException e) {
- throw new JcrException("Cannot check in " + node, e);
- }
- }
-
- /** @see VersionManager#checkout(String) */
- public static void checkout(Node node) {
- try {
- versionManager(node).checkout(node.getPath());
- } catch (RepositoryException e) {
- throw new JcrException("Cannot check out " + node, e);
- }
- }
-
- /** Get the {@link VersionManager} related to this node. */
- public static VersionManager versionManager(Node node) {
- try {
- return node.getSession().getWorkspace().getVersionManager();
- } catch (RepositoryException e) {
- throw new JcrException("Cannot get version manager from " + node, e);
- }
- }
-
- /** Get the {@link VersionHistory} related to this node. */
- public static VersionHistory getVersionHistory(Node node) {
- try {
- return versionManager(node).getVersionHistory(node.getPath());
- } catch (RepositoryException e) {
- throw new JcrException("Cannot get version history from " + node, e);
- }
- }
-
- /**
- * The linear versions of this version history in reverse order and without the
- * root version.
- */
- public static List<Version> getLinearVersions(VersionHistory versionHistory) {
- try {
- List<Version> lst = new ArrayList<>();
- VersionIterator vit = versionHistory.getAllLinearVersions();
- while (vit.hasNext())
- lst.add(vit.nextVersion());
- lst.remove(0);
- Collections.reverse(lst);
- return lst;
- } catch (RepositoryException e) {
- throw new JcrException("Cannot get linear versions from " + versionHistory, e);
- }
- }
-
- /** The frozen node related to this {@link Version}. */
- public static Node getFrozenNode(Version version) {
- try {
- return version.getFrozenNode();
- } catch (RepositoryException e) {
- throw new JcrException("Cannot get frozen node from " + version, e);
- }
- }
-
- /** Get the base {@link Version} related to this node. */
- public static Version getBaseVersion(Node node) {
- try {
- return versionManager(node).getBaseVersion(node.getPath());
- } catch (RepositoryException e) {
- throw new JcrException("Cannot get base version from " + node, e);
- }
- }
-
- /*
- * FILES
- */
- /**
- * Returns the size of this file.
- *
- * @see NodeType#NT_FILE
- */
- public static long getFileSize(Node fileNode) {
- try {
- if (!fileNode.isNodeType(NodeType.NT_FILE))
- throw new IllegalArgumentException(fileNode + " must be a file.");
- return getBinarySize(fileNode.getNode(Node.JCR_CONTENT).getProperty(Property.JCR_DATA).getBinary());
- } catch (RepositoryException e) {
- throw new JcrException("Cannot get file size of " + fileNode, e);
- }
- }
-
- /** Returns the size of this {@link Binary}. */
- public static long getBinarySize(Binary binaryArg) {
- try {
- try (Bin binary = new Bin(binaryArg)) {
- return binary.getSize();
- }
- } catch (RepositoryException e) {
- throw new JcrException("Cannot get file size of binary " + binaryArg, e);
- }
- }
-
- // QUERY
- /** Creates a JCR-SQL2 query using {@link MessageFormat}. */
- public static Query createQuery(QueryManager qm, String sql, Object... args) {
- // fix single quotes
- sql = sql.replaceAll("'", "''");
- String query = MessageFormat.format(sql, args);
- try {
- return qm.createQuery(query, Query.JCR_SQL2);
- } catch (RepositoryException e) {
- throw new JcrException("Cannot create JCR-SQL2 query from " + query, e);
- }
- }
-
- /** Executes a JCR-SQL2 query using {@link MessageFormat}. */
- public static NodeIterator executeQuery(QueryManager qm, String sql, Object... args) {
- Query query = createQuery(qm, sql, args);
- try {
- return query.execute().getNodes();
- } catch (RepositoryException e) {
- throw new JcrException("Cannot execute query " + sql + " with arguments " + Arrays.asList(args), e);
- }
- }
-
- /** Executes a JCR-SQL2 query using {@link MessageFormat}. */
- public static NodeIterator executeQuery(Session session, String sql, Object... args) {
- QueryManager queryManager;
- try {
- queryManager = session.getWorkspace().getQueryManager();
- } catch (RepositoryException e) {
- throw new JcrException("Cannot get query manager from session " + session, e);
- }
- return executeQuery(queryManager, sql, args);
- }
-
- /**
- * Executes a JCR-SQL2 query using {@link MessageFormat}, which must return a
- * single node at most.
- *
- * @return the node or <code>null</code> if not found.
- */
- public static Node getNode(QueryManager qm, String sql, Object... args) {
- NodeIterator nit = executeQuery(qm, sql, args);
- if (nit.hasNext()) {
- Node node = nit.nextNode();
- if (nit.hasNext())
- throw new IllegalStateException(
- "Query " + sql + " with arguments " + Arrays.asList(args) + " returned more than one node.");
- return node;
- } else {
- return null;
- }
- }
-
- /**
- * Executes a JCR-SQL2 query using {@link MessageFormat}, which must return a
- * single node at most.
- *
- * @return the node or <code>null</code> if not found.
- */
- public static Node getNode(Session session, String sql, Object... args) {
- QueryManager queryManager;
- try {
- queryManager = session.getWorkspace().getQueryManager();
- } catch (RepositoryException e) {
- throw new JcrException("Cannot get query manager from session " + session, e);
- }
- return getNode(queryManager, sql, args);
- }
-
- /** Singleton. */
- private Jcr() {
-
- }
-}
+++ /dev/null
-package org.argeo.jcr;
-
-import java.security.Principal;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.security.AccessControlManager;
-import javax.jcr.security.Privilege;
-import javax.naming.InvalidNameException;
-import javax.naming.ldap.LdapName;
-
-/** Apply authorizations to a JCR repository. */
-public class JcrAuthorizations implements Runnable {
- // private final static Log log =
- // LogFactory.getLog(JcrAuthorizations.class);
-
- private Repository repository;
- private String workspace = null;
-
- private String securityWorkspace = "security";
-
- /**
- * key := privilege1,privilege2/path/to/node<br/>
- * value := group1,group2,user1
- */
- private Map<String, String> principalPrivileges = new HashMap<String, String>();
-
- public void run() {
- String currentWorkspace = workspace;
- Session session = null;
- try {
- if (workspace != null && workspace.equals("*")) {
- session = repository.login();
- String[] workspaces = session.getWorkspace().getAccessibleWorkspaceNames();
- JcrUtils.logoutQuietly(session);
- for (String wksp : workspaces) {
- currentWorkspace = wksp;
- if (currentWorkspace.equals(securityWorkspace))
- continue;
- session = repository.login(currentWorkspace);
- initAuthorizations(session);
- JcrUtils.logoutQuietly(session);
- }
- } else {
- session = repository.login(workspace);
- initAuthorizations(session);
- }
- } catch (RepositoryException e) {
- JcrUtils.discardQuietly(session);
- throw new JcrException(
- "Cannot set authorizations " + principalPrivileges + " on workspace " + currentWorkspace, e);
- } finally {
- JcrUtils.logoutQuietly(session);
- }
- }
-
- protected void processWorkspace(String workspace) {
- Session session = null;
- try {
- session = repository.login(workspace);
- initAuthorizations(session);
- } catch (RepositoryException e) {
- JcrUtils.discardQuietly(session);
- throw new JcrException(
- "Cannot set authorizations " + principalPrivileges + " on repository " + repository, e);
- } finally {
- JcrUtils.logoutQuietly(session);
- }
- }
-
- /** @deprecated call {@link #run()} instead. */
- @Deprecated
- public void init() {
- run();
- }
-
- protected void initAuthorizations(Session session) throws RepositoryException {
- AccessControlManager acm = session.getAccessControlManager();
-
- for (String privileges : principalPrivileges.keySet()) {
- String path = null;
- int slashIndex = privileges.indexOf('/');
- if (slashIndex == 0) {
- throw new IllegalArgumentException("Privilege " + privileges + " badly formatted it starts with /");
- } else if (slashIndex > 0) {
- path = privileges.substring(slashIndex);
- privileges = privileges.substring(0, slashIndex);
- }
-
- if (path == null)
- path = "/";
-
- List<Privilege> privs = new ArrayList<Privilege>();
- for (String priv : privileges.split(",")) {
- privs.add(acm.privilegeFromName(priv));
- }
-
- String principalNames = principalPrivileges.get(privileges);
- try {
- new LdapName(principalNames);
- // TODO differentiate groups and users ?
- Principal principal = getOrCreatePrincipal(session, principalNames);
- JcrUtils.addPrivileges(session, path, principal, privs);
- } catch (InvalidNameException e) {
- for (String principalName : principalNames.split(",")) {
- Principal principal = getOrCreatePrincipal(session, principalName);
- JcrUtils.addPrivileges(session, path, principal, privs);
- // if (log.isDebugEnabled()) {
- // StringBuffer privBuf = new StringBuffer();
- // for (Privilege priv : privs)
- // privBuf.append(priv.getName());
- // log.debug("Added privileges " + privBuf + " to "
- // + principal.getName() + " on " + path + " in '"
- // + session.getWorkspace().getName() + "'");
- // }
- }
- }
- }
-
- // if (log.isDebugEnabled())
- // log.debug("JCR authorizations applied on '"
- // + session.getWorkspace().getName() + "'");
- }
-
- /**
- * Returns a {@link SimplePrincipal}, does not check whether it exists since
- * such capabilities is not provided by the standard JCR API. Can be
- * overridden to provide smarter handling
- */
- protected Principal getOrCreatePrincipal(Session session, String principalName) throws RepositoryException {
- return new SimplePrincipal(principalName);
- }
-
- // public static void addPrivileges(Session session, Principal principal,
- // String path, List<Privilege> privs) throws RepositoryException {
- // AccessControlManager acm = session.getAccessControlManager();
- // // search for an access control list
- // AccessControlList acl = null;
- // AccessControlPolicyIterator policyIterator = acm
- // .getApplicablePolicies(path);
- // if (policyIterator.hasNext()) {
- // while (policyIterator.hasNext()) {
- // AccessControlPolicy acp = policyIterator
- // .nextAccessControlPolicy();
- // if (acp instanceof AccessControlList)
- // acl = ((AccessControlList) acp);
- // }
- // } else {
- // AccessControlPolicy[] existingPolicies = acm.getPolicies(path);
- // for (AccessControlPolicy acp : existingPolicies) {
- // if (acp instanceof AccessControlList)
- // acl = ((AccessControlList) acp);
- // }
- // }
- //
- // if (acl != null) {
- // acl.addAccessControlEntry(principal,
- // privs.toArray(new Privilege[privs.size()]));
- // acm.setPolicy(path, acl);
- // session.save();
- // if (log.isDebugEnabled()) {
- // StringBuffer buf = new StringBuffer("");
- // for (int i = 0; i < privs.size(); i++) {
- // if (i != 0)
- // buf.append(',');
- // buf.append(privs.get(i).getName());
- // }
- // log.debug("Added privilege(s) '" + buf + "' to '"
- // + principal.getName() + "' on " + path
- // + " from workspace '"
- // + session.getWorkspace().getName() + "'");
- // }
- // } else {
- // throw new ArgeoJcrException("Don't know how to apply privileges "
- // + privs + " to " + principal + " on " + path
- // + " from workspace '" + session.getWorkspace().getName()
- // + "'");
- // }
- // }
-
- @Deprecated
- public void setGroupPrivileges(Map<String, String> groupPrivileges) {
- this.principalPrivileges = groupPrivileges;
- }
-
- public void setPrincipalPrivileges(Map<String, String> principalPrivileges) {
- this.principalPrivileges = principalPrivileges;
- }
-
- public void setRepository(Repository repository) {
- this.repository = repository;
- }
-
- public void setWorkspace(String workspace) {
- this.workspace = workspace;
- }
-
- public void setSecurityWorkspace(String securityWorkspace) {
- this.securityWorkspace = securityWorkspace;
- }
-
-}
+++ /dev/null
-package org.argeo.jcr;
-
-import java.util.function.Function;
-
-import javax.jcr.Session;
-
-/** An arbitrary execution on a JCR session, optionally returning a result. */
-@FunctionalInterface
-public interface JcrCallback extends Function<Session, Object> {
- /** @deprecated Use {@link #apply(Session)} instead. */
- @Deprecated
- public default Object execute(Session session) {
- return apply(session);
- }
-}
+++ /dev/null
-package org.argeo.jcr;
-
-import javax.jcr.RepositoryException;
-
-/**
- * Wraps a {@link RepositoryException} in a {@link RuntimeException}.
- */
-public class JcrException extends IllegalStateException {
- private static final long serialVersionUID = -4530350094877964989L;
-
- public JcrException(String message, RepositoryException e) {
- super(message, e);
- }
-
- public JcrException(RepositoryException e) {
- super(e);
- }
-
- public RepositoryException getRepositoryCause() {
- return (RepositoryException) getCause();
- }
-}
+++ /dev/null
-package org.argeo.jcr;
-
-
-/**
- * Simple monitor abstraction. Inspired by Eclipse IProgressMOnitor, but without
- * dependency to it.
- */
-public interface JcrMonitor {
- /**
- * Constant indicating an unknown amount of work.
- */
- public final static int UNKNOWN = -1;
-
- /**
- * Notifies that the main task is beginning. This must only be called once
- * on a given progress monitor instance.
- *
- * @param name
- * the name (or description) of the main task
- * @param totalWork
- * the total number of work units into which the main task is
- * been subdivided. If the value is <code>UNKNOWN</code> the
- * implementation is free to indicate progress in a way which
- * doesn't require the total number of work units in advance.
- */
- public void beginTask(String name, int totalWork);
-
- /**
- * Notifies that the work is done; that is, either the main task is
- * completed or the user canceled it. This method may be called more than
- * once (implementations should be prepared to handle this case).
- */
- public void done();
-
- /**
- * Returns whether cancelation of current operation has been requested.
- * Long-running operations should poll to see if cancelation has been
- * requested.
- *
- * @return <code>true</code> if cancellation has been requested, and
- * <code>false</code> otherwise
- * @see #setCanceled(boolean)
- */
- public boolean isCanceled();
-
- /**
- * Sets the cancel state to the given value.
- *
- * @param value
- * <code>true</code> indicates that cancelation has been
- * requested (but not necessarily acknowledged);
- * <code>false</code> clears this flag
- * @see #isCanceled()
- */
- public void setCanceled(boolean value);
-
- /**
- * Sets the task name to the given value. This method is used to restore the
- * task label after a nested operation was executed. Normally there is no
- * need for clients to call this method.
- *
- * @param name
- * the name (or description) of the main task
- * @see #beginTask(java.lang.String, int)
- */
- public void setTaskName(String name);
-
- /**
- * Notifies that a subtask of the main task is beginning. Subtasks are
- * optional; the main task might not have subtasks.
- *
- * @param name
- * the name (or description) of the subtask
- */
- public void subTask(String name);
-
- /**
- * Notifies that a given number of work unit of the main task has been
- * completed. Note that this amount represents an installment, as opposed to
- * a cumulative amount of work done to date.
- *
- * @param work
- * a non-negative number of work units just completed
- */
- public void worked(int work);
-
-}
+++ /dev/null
-package org.argeo.jcr;
-
-import java.io.InputStream;
-import java.math.BigDecimal;
-import java.util.Arrays;
-import java.util.Calendar;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import javax.jcr.Binary;
-import javax.jcr.Credentials;
-import javax.jcr.LoginException;
-import javax.jcr.NoSuchWorkspaceException;
-import javax.jcr.PropertyType;
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.Value;
-import javax.jcr.ValueFormatException;
-
-/**
- * Wrapper around a JCR repository which allows to simplify configuration and
- * intercept some actions. It exposes itself as a {@link Repository}.
- */
-public abstract class JcrRepositoryWrapper implements Repository {
- // private final static Log log = LogFactory
- // .getLog(JcrRepositoryWrapper.class);
-
- // wrapped repository
- private Repository repository;
-
- private Map<String, String> additionalDescriptors = new HashMap<>();
-
- private Boolean autocreateWorkspaces = false;
-
- public JcrRepositoryWrapper(Repository repository) {
- setRepository(repository);
- }
-
- /**
- * Empty constructor
- */
- public JcrRepositoryWrapper() {
- }
-
- // /** Initializes */
- // public void init() {
- // }
- //
- // /** Shutdown the repository */
- // public void destroy() throws Exception {
- // }
-
- protected void putDescriptor(String key, String value) {
- if (Arrays.asList(getRepository().getDescriptorKeys()).contains(key))
- throw new IllegalArgumentException("Descriptor key " + key + " is already defined in wrapped repository");
- if (value == null)
- additionalDescriptors.remove(key);
- else
- additionalDescriptors.put(key, value);
- }
-
- /*
- * DELEGATED JCR REPOSITORY METHODS
- */
-
- public String getDescriptor(String key) {
- if (additionalDescriptors.containsKey(key))
- return additionalDescriptors.get(key);
- return getRepository().getDescriptor(key);
- }
-
- public String[] getDescriptorKeys() {
- if (additionalDescriptors.size() == 0)
- return getRepository().getDescriptorKeys();
- List<String> keys = Arrays.asList(getRepository().getDescriptorKeys());
- keys.addAll(additionalDescriptors.keySet());
- return keys.toArray(new String[keys.size()]);
- }
-
- /** Central login method */
- public Session login(Credentials credentials, String workspaceName)
- throws LoginException, NoSuchWorkspaceException, RepositoryException {
- Session session;
- try {
- session = getRepository(workspaceName).login(credentials, workspaceName);
- } catch (NoSuchWorkspaceException e) {
- if (autocreateWorkspaces && workspaceName != null)
- session = createWorkspaceAndLogsIn(credentials, workspaceName);
- else
- throw e;
- }
- processNewSession(session, workspaceName);
- return session;
- }
-
- public Session login() throws LoginException, RepositoryException {
- return login(null, null);
- }
-
- public Session login(Credentials credentials) throws LoginException, RepositoryException {
- return login(credentials, null);
- }
-
- public Session login(String workspaceName) throws LoginException, NoSuchWorkspaceException, RepositoryException {
- return login(null, workspaceName);
- }
-
- /** Called after a session has been created, does nothing by default. */
- protected void processNewSession(Session session, String workspaceName) {
- }
-
- /**
- * Wraps access to the repository, making sure it is available.
- *
- * @deprecated Use {@link #getDefaultRepository()} instead.
- */
- @Deprecated
- protected synchronized Repository getRepository() {
- return getDefaultRepository();
- }
-
- protected synchronized Repository getDefaultRepository() {
- return repository;
- }
-
- protected synchronized Repository getRepository(String workspaceName) {
- return getDefaultRepository();
- }
-
- /**
- * Logs in to the default workspace, creates the required workspace, logs out,
- * logs in to the required workspace.
- */
- protected Session createWorkspaceAndLogsIn(Credentials credentials, String workspaceName)
- throws RepositoryException {
- if (workspaceName == null)
- throw new IllegalArgumentException("No workspace specified.");
- Session session = getRepository(workspaceName).login(credentials);
- session.getWorkspace().createWorkspace(workspaceName);
- session.logout();
- return getRepository(workspaceName).login(credentials, workspaceName);
- }
-
- public boolean isStandardDescriptor(String key) {
- return getRepository().isStandardDescriptor(key);
- }
-
- public boolean isSingleValueDescriptor(String key) {
- if (additionalDescriptors.containsKey(key))
- return true;
- return getRepository().isSingleValueDescriptor(key);
- }
-
- public Value getDescriptorValue(String key) {
- if (additionalDescriptors.containsKey(key))
- return new StrValue(additionalDescriptors.get(key));
- return getRepository().getDescriptorValue(key);
- }
-
- public Value[] getDescriptorValues(String key) {
- return getRepository().getDescriptorValues(key);
- }
-
- public synchronized void setRepository(Repository repository) {
- this.repository = repository;
- }
-
- public void setAutocreateWorkspaces(Boolean autocreateWorkspaces) {
- this.autocreateWorkspaces = autocreateWorkspaces;
- }
-
- protected static class StrValue implements Value {
- private final String str;
-
- public StrValue(String str) {
- this.str = str;
- }
-
- @Override
- public String getString() throws ValueFormatException, IllegalStateException, RepositoryException {
- return str;
- }
-
- @Override
- public InputStream getStream() throws RepositoryException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public Binary getBinary() throws RepositoryException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public long getLong() throws ValueFormatException, RepositoryException {
- try {
- return Long.parseLong(str);
- } catch (NumberFormatException e) {
- throw new ValueFormatException("Cannot convert", e);
- }
- }
-
- @Override
- public double getDouble() throws ValueFormatException, RepositoryException {
- try {
- return Double.parseDouble(str);
- } catch (NumberFormatException e) {
- throw new ValueFormatException("Cannot convert", e);
- }
- }
-
- @Override
- public BigDecimal getDecimal() throws ValueFormatException, RepositoryException {
- try {
- return new BigDecimal(str);
- } catch (NumberFormatException e) {
- throw new ValueFormatException("Cannot convert", e);
- }
- }
-
- @Override
- public Calendar getDate() throws ValueFormatException, RepositoryException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public boolean getBoolean() throws ValueFormatException, RepositoryException {
- try {
- return Boolean.parseBoolean(str);
- } catch (NumberFormatException e) {
- throw new ValueFormatException("Cannot convert", e);
- }
- }
-
- @Override
- public int getType() {
- return PropertyType.STRING;
- }
-
- }
-
-}
+++ /dev/null
-package org.argeo.jcr;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.URL;
-import java.net.URLConnection;
-import java.net.URLStreamHandler;
-
-import javax.jcr.Item;
-import javax.jcr.Node;
-import javax.jcr.Property;
-import javax.jcr.PropertyType;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.nodetype.NodeType;
-
-/** URL stream handler able to deal with nt:file node and properties. NOT FINISHED */
-public class JcrUrlStreamHandler extends URLStreamHandler {
- private final Session session;
-
- public JcrUrlStreamHandler(Session session) {
- this.session = session;
- }
-
- @Override
- protected URLConnection openConnection(final URL u) throws IOException {
- // TODO Auto-generated method stub
- return new URLConnection(u) {
-
- @Override
- public void connect() throws IOException {
- String itemPath = u.getPath();
- try {
- if (!session.itemExists(itemPath))
- throw new IOException("No item under " + itemPath);
-
- Item item = session.getItem(u.getPath());
- if (item.isNode()) {
- // this should be a nt:file node
- Node node = (Node) item;
- if (!node.getPrimaryNodeType().isNodeType(
- NodeType.NT_FILE))
- throw new IOException("Node " + node + " is not a "
- + NodeType.NT_FILE);
-
- } else {
- Property property = (Property) item;
- if(property.getType()==PropertyType.BINARY){
- //Binary binary = property.getBinary();
-
- }
- }
- } catch (RepositoryException e) {
- IOException ioe = new IOException(
- "Unexpected JCR exception");
- ioe.initCause(e);
- throw ioe;
- }
- }
-
- @Override
- public InputStream getInputStream() throws IOException {
- // TODO Auto-generated method stub
- return super.getInputStream();
- }
-
- };
- }
-
-}
+++ /dev/null
-package org.argeo.jcr;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.PipedInputStream;
-import java.io.PipedOutputStream;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.security.Principal;
-import java.text.DateFormat;
-import java.text.ParseException;
-import java.time.Instant;
-import java.util.ArrayList;
-import java.util.Calendar;
-import java.util.Collections;
-import java.util.Date;
-import java.util.GregorianCalendar;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.TreeMap;
-
-import javax.jcr.Binary;
-import javax.jcr.Credentials;
-import javax.jcr.ImportUUIDBehavior;
-import javax.jcr.NamespaceRegistry;
-import javax.jcr.NoSuchWorkspaceException;
-import javax.jcr.Node;
-import javax.jcr.NodeIterator;
-import javax.jcr.Property;
-import javax.jcr.PropertyIterator;
-import javax.jcr.PropertyType;
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.Value;
-import javax.jcr.Workspace;
-import javax.jcr.nodetype.NoSuchNodeTypeException;
-import javax.jcr.nodetype.NodeType;
-import javax.jcr.observation.EventListener;
-import javax.jcr.query.Query;
-import javax.jcr.query.QueryResult;
-import javax.jcr.security.AccessControlEntry;
-import javax.jcr.security.AccessControlList;
-import javax.jcr.security.AccessControlManager;
-import javax.jcr.security.AccessControlPolicy;
-import javax.jcr.security.AccessControlPolicyIterator;
-import javax.jcr.security.Privilege;
-
-import org.apache.commons.io.IOUtils;
-
-/** Utility methods to simplify common JCR operations. */
-public class JcrUtils {
-
-// final private static Log log = LogFactory.getLog(JcrUtils.class);
-
- /**
- * Not complete yet. See
- * http://www.day.com/specs/jcr/2.0/3_Repository_Model.html#3.2.2%20Local
- * %20Names
- */
- public final static char[] INVALID_NAME_CHARACTERS = { '/', ':', '[', ']', '|', '*', /* invalid for XML: */ '<',
- '>', '&' };
-
- /** Prevents instantiation */
- private JcrUtils() {
- }
-
- /**
- * Queries one single node.
- *
- * @return one single node or null if none was found
- * @throws JcrException if more than one node was found
- */
- public static Node querySingleNode(Query query) {
- NodeIterator nodeIterator;
- try {
- QueryResult queryResult = query.execute();
- nodeIterator = queryResult.getNodes();
- } catch (RepositoryException e) {
- throw new JcrException("Cannot execute query " + query, e);
- }
- Node node;
- if (nodeIterator.hasNext())
- node = nodeIterator.nextNode();
- else
- return null;
-
- if (nodeIterator.hasNext())
- throw new IllegalArgumentException("Query returned more than one node.");
- return node;
- }
-
- /** Retrieves the node name from the provided path */
- public static String nodeNameFromPath(String path) {
- if (path.equals("/"))
- return "";
- if (path.charAt(0) != '/')
- throw new IllegalArgumentException("Path " + path + " must start with a '/'");
- String pathT = path;
- if (pathT.charAt(pathT.length() - 1) == '/')
- pathT = pathT.substring(0, pathT.length() - 2);
-
- int index = pathT.lastIndexOf('/');
- return pathT.substring(index + 1);
- }
-
- /** Retrieves the parent path of the provided path */
- public static String parentPath(String path) {
- if (path.equals("/"))
- throw new IllegalArgumentException("Root path '/' has no parent path");
- if (path.charAt(0) != '/')
- throw new IllegalArgumentException("Path " + path + " must start with a '/'");
- String pathT = path;
- if (pathT.charAt(pathT.length() - 1) == '/')
- pathT = pathT.substring(0, pathT.length() - 2);
-
- int index = pathT.lastIndexOf('/');
- return pathT.substring(0, index);
- }
-
- /** The provided data as a path ('/' at the end, not the beginning) */
- public static String dateAsPath(Calendar cal) {
- return dateAsPath(cal, false);
- }
-
- /**
- * Creates a deep path based on a URL:
- * http://subdomain.example.com/to/content?args becomes
- * com/example/subdomain/to/content
- */
- public static String urlAsPath(String url) {
- try {
- URL u = new URL(url);
- StringBuffer path = new StringBuffer(url.length());
- // invert host
- path.append(hostAsPath(u.getHost()));
- // we don't put port since it may not always be there and may change
- path.append(u.getPath());
- return path.toString();
- } catch (MalformedURLException e) {
- throw new IllegalArgumentException("Cannot generate URL path for " + url, e);
- }
- }
-
- /** Set the {@link NodeType#NT_ADDRESS} properties based on this URL. */
- public static void urlToAddressProperties(Node node, String url) {
- try {
- URL u = new URL(url);
- node.setProperty(Property.JCR_PROTOCOL, u.getProtocol());
- node.setProperty(Property.JCR_HOST, u.getHost());
- node.setProperty(Property.JCR_PORT, Integer.toString(u.getPort()));
- node.setProperty(Property.JCR_PATH, normalizePath(u.getPath()));
- } catch (RepositoryException e) {
- throw new JcrException("Cannot set URL " + url + " as nt:address properties", e);
- } catch (MalformedURLException e) {
- throw new IllegalArgumentException("Cannot set URL " + url + " as nt:address properties", e);
- }
- }
-
- /** Build URL based on the {@link NodeType#NT_ADDRESS} properties. */
- public static String urlFromAddressProperties(Node node) {
- try {
- URL u = new URL(node.getProperty(Property.JCR_PROTOCOL).getString(),
- node.getProperty(Property.JCR_HOST).getString(),
- (int) node.getProperty(Property.JCR_PORT).getLong(),
- node.getProperty(Property.JCR_PATH).getString());
- return u.toString();
- } catch (RepositoryException e) {
- throw new JcrException("Cannot get URL from nt:address properties of " + node, e);
- } catch (MalformedURLException e) {
- throw new IllegalArgumentException("Cannot get URL from nt:address properties of " + node, e);
- }
- }
-
- /*
- * PATH UTILITIES
- */
-
- /**
- * Make sure that: starts with '/', do not end with '/', do not have '//'
- */
- public static String normalizePath(String path) {
- List<String> tokens = tokenize(path);
- StringBuffer buf = new StringBuffer(path.length());
- for (String token : tokens) {
- buf.append('/');
- buf.append(token);
- }
- return buf.toString();
- }
-
- /**
- * Creates a path from a FQDN, inverting the order of the component:
- * www.argeo.org becomes org.argeo.www
- */
- public static String hostAsPath(String host) {
- StringBuffer path = new StringBuffer(host.length());
- String[] hostTokens = host.split("\\.");
- for (int i = hostTokens.length - 1; i >= 0; i--) {
- path.append(hostTokens[i]);
- if (i != 0)
- path.append('/');
- }
- return path.toString();
- }
-
- /**
- * Creates a path from a UUID (e.g. 6ebda899-217d-4bf1-abe4-2839085c8f3c becomes
- * 6ebda899-217d/4bf1/abe4/2839085c8f3c/). '/' at the end, not the beginning
- */
- public static String uuidAsPath(String uuid) {
- StringBuffer path = new StringBuffer(uuid.length());
- String[] tokens = uuid.split("-");
- for (int i = 0; i < tokens.length; i++) {
- path.append(tokens[i]);
- if (i != 0)
- path.append('/');
- }
- return path.toString();
- }
-
- /**
- * The provided data as a path ('/' at the end, not the beginning)
- *
- * @param cal the date
- * @param addHour whether to add hour as well
- */
- public static String dateAsPath(Calendar cal, Boolean addHour) {
- StringBuffer buf = new StringBuffer(14);
- buf.append('Y');
- buf.append(cal.get(Calendar.YEAR));
- buf.append('/');
-
- int month = cal.get(Calendar.MONTH) + 1;
- buf.append('M');
- if (month < 10)
- buf.append(0);
- buf.append(month);
- buf.append('/');
-
- int day = cal.get(Calendar.DAY_OF_MONTH);
- buf.append('D');
- if (day < 10)
- buf.append(0);
- buf.append(day);
- buf.append('/');
-
- if (addHour) {
- int hour = cal.get(Calendar.HOUR_OF_DAY);
- buf.append('H');
- if (hour < 10)
- buf.append(0);
- buf.append(hour);
- buf.append('/');
- }
- return buf.toString();
-
- }
-
- /** Converts in one call a string into a gregorian calendar. */
- public static Calendar parseCalendar(DateFormat dateFormat, String value) {
- try {
- Date date = dateFormat.parse(value);
- Calendar calendar = new GregorianCalendar();
- calendar.setTime(date);
- return calendar;
- } catch (ParseException e) {
- throw new IllegalArgumentException("Cannot parse " + value + " with date format " + dateFormat, e);
- }
-
- }
-
- /** The last element of a path. */
- public static String lastPathElement(String path) {
- if (path.charAt(path.length() - 1) == '/')
- throw new IllegalArgumentException("Path " + path + " cannot end with '/'");
- int index = path.lastIndexOf('/');
- if (index < 0)
- return path;
- return path.substring(index + 1);
- }
-
- /**
- * Call {@link Node#getName()} without exceptions (useful in super
- * constructors).
- */
- public static String getNameQuietly(Node node) {
- try {
- return node.getName();
- } catch (RepositoryException e) {
- throw new JcrException("Cannot get name from " + node, e);
- }
- }
-
- /**
- * Call {@link Node#getProperty(String)} without exceptions (useful in super
- * constructors).
- */
- public static String getStringPropertyQuietly(Node node, String propertyName) {
- try {
- return node.getProperty(propertyName).getString();
- } catch (RepositoryException e) {
- throw new JcrException("Cannot get name from " + node, e);
- }
- }
-
-// /**
-// * Routine that get the child with this name, adding it if it does not already
-// * exist
-// */
-// public static Node getOrAdd(Node parent, String name, String primaryNodeType) throws RepositoryException {
-// return parent.hasNode(name) ? parent.getNode(name) : parent.addNode(name, primaryNodeType);
-// }
-
- /**
- * Routine that get the child with this name, adding it if it does not already
- * exist
- */
- public static Node getOrAdd(Node parent, String name, String primaryNodeType, String... mixinNodeTypes)
- throws RepositoryException {
- Node node;
- if (parent.hasNode(name)) {
- node = parent.getNode(name);
- if (primaryNodeType != null && !node.isNodeType(primaryNodeType))
- throw new IllegalArgumentException("Node " + node + " exists but is of primary node type "
- + node.getPrimaryNodeType().getName() + ", not " + primaryNodeType);
- for (String mixin : mixinNodeTypes) {
- if (!node.isNodeType(mixin))
- node.addMixin(mixin);
- }
- return node;
- } else {
- node = primaryNodeType != null ? parent.addNode(name, primaryNodeType) : parent.addNode(name);
- for (String mixin : mixinNodeTypes) {
- node.addMixin(mixin);
- }
- return node;
- }
- }
-
- /**
- * Routine that get the child with this name, adding it if it does not already
- * exist
- */
- public static Node getOrAdd(Node parent, String name) throws RepositoryException {
- return parent.hasNode(name) ? parent.getNode(name) : parent.addNode(name);
- }
-
- /** Convert a {@link NodeIterator} to a list of {@link Node} */
- public static List<Node> nodeIteratorToList(NodeIterator nodeIterator) {
- List<Node> nodes = new ArrayList<Node>();
- while (nodeIterator.hasNext()) {
- nodes.add(nodeIterator.nextNode());
- }
- return nodes;
- }
-
- /*
- * PROPERTIES
- */
-
- /**
- * Concisely get the string value of a property or null if this node doesn't
- * have this property
- */
- public static String get(Node node, String propertyName) {
- try {
- if (!node.hasProperty(propertyName))
- return null;
- return node.getProperty(propertyName).getString();
- } catch (RepositoryException e) {
- throw new JcrException("Cannot get property " + propertyName + " of " + node, e);
- }
- }
-
- /** Concisely get the path of the given node. */
- public static String getPath(Node node) {
- try {
- return node.getPath();
- } catch (RepositoryException e) {
- throw new JcrException("Cannot get path of " + node, e);
- }
- }
-
- /** Concisely get the boolean value of a property */
- public static Boolean check(Node node, String propertyName) {
- try {
- return node.getProperty(propertyName).getBoolean();
- } catch (RepositoryException e) {
- throw new JcrException("Cannot get property " + propertyName + " of " + node, e);
- }
- }
-
- /** Concisely get the bytes array value of a property */
- public static byte[] getBytes(Node node, String propertyName) {
- try {
- return getBinaryAsBytes(node.getProperty(propertyName));
- } catch (RepositoryException e) {
- throw new JcrException("Cannot get property " + propertyName + " of " + node, e);
- }
- }
-
- /*
- * MKDIRS
- */
-
- /**
- * Create sub nodes relative to a parent node
- */
- public static Node mkdirs(Node parentNode, String relativePath) {
- return mkdirs(parentNode, relativePath, null, null);
- }
-
- /**
- * Create sub nodes relative to a parent node
- *
- * @param nodeType the type of the leaf node
- */
- public static Node mkdirs(Node parentNode, String relativePath, String nodeType) {
- return mkdirs(parentNode, relativePath, nodeType, null);
- }
-
- /**
- * Create sub nodes relative to a parent node
- *
- * @param nodeType the type of the leaf node
- */
- public static Node mkdirs(Node parentNode, String relativePath, String nodeType, String intermediaryNodeType) {
- List<String> tokens = tokenize(relativePath);
- Node currParent = parentNode;
- try {
- for (int i = 0; i < tokens.size(); i++) {
- String name = tokens.get(i);
- if (currParent.hasNode(name)) {
- currParent = currParent.getNode(name);
- } else {
- if (i != (tokens.size() - 1)) {// intermediary
- currParent = currParent.addNode(name, intermediaryNodeType);
- } else {// leaf
- currParent = currParent.addNode(name, nodeType);
- }
- }
- }
- return currParent;
- } catch (RepositoryException e) {
- throw new JcrException("Cannot mkdirs relative path " + relativePath + " from " + parentNode, e);
- }
- }
-
- /**
- * Synchronized and save is performed, to avoid race conditions in initializers
- * leading to duplicate nodes.
- */
- public synchronized static Node mkdirsSafe(Session session, String path, String type) {
- try {
- if (session.hasPendingChanges())
- throw new IllegalStateException("Session has pending changes, save them first.");
- Node node = mkdirs(session, path, type);
- session.save();
- return node;
- } catch (RepositoryException e) {
- discardQuietly(session);
- throw new JcrException("Cannot safely make directories", e);
- }
- }
-
- public synchronized static Node mkdirsSafe(Session session, String path) {
- return mkdirsSafe(session, path, null);
- }
-
- /** Creates the nodes making path, if they don't exist. */
- public static Node mkdirs(Session session, String path) {
- return mkdirs(session, path, null, null, false);
- }
-
- /**
- * @param type the type of the leaf node
- */
- public static Node mkdirs(Session session, String path, String type) {
- return mkdirs(session, path, type, null, false);
- }
-
- /**
- * Creates the nodes making path, if they don't exist. This is up to the caller
- * to save the session. Use with caution since it can create duplicate nodes if
- * used concurrently. Requires read access to the root node of the workspace.
- */
- public static Node mkdirs(Session session, String path, String type, String intermediaryNodeType,
- Boolean versioning) {
- try {
- if (path.equals("/"))
- return session.getRootNode();
-
- if (session.itemExists(path)) {
- Node node = session.getNode(path);
- // check type
- if (type != null && !node.isNodeType(type) && !node.getPath().equals("/"))
- throw new IllegalArgumentException("Node " + node + " exists but is of type "
- + node.getPrimaryNodeType().getName() + " not of type " + type);
- // TODO: check versioning
- return node;
- }
-
- // StringBuffer current = new StringBuffer("/");
- // Node currentNode = session.getRootNode();
-
- Node currentNode = findClosestExistingParent(session, path);
- String closestExistingParentPath = currentNode.getPath();
- StringBuffer current = new StringBuffer(closestExistingParentPath);
- if (!closestExistingParentPath.endsWith("/"))
- current.append('/');
- Iterator<String> it = tokenize(path.substring(closestExistingParentPath.length())).iterator();
- while (it.hasNext()) {
- String part = it.next();
- current.append(part).append('/');
- if (!session.itemExists(current.toString())) {
- if (!it.hasNext() && type != null)
- currentNode = currentNode.addNode(part, type);
- else if (it.hasNext() && intermediaryNodeType != null)
- currentNode = currentNode.addNode(part, intermediaryNodeType);
- else
- currentNode = currentNode.addNode(part);
- if (versioning)
- currentNode.addMixin(NodeType.MIX_VERSIONABLE);
-// if (log.isTraceEnabled())
-// log.debug("Added folder " + part + " as " + current);
- } else {
- currentNode = (Node) session.getItem(current.toString());
- }
- }
- return currentNode;
- } catch (RepositoryException e) {
- discardQuietly(session);
- throw new JcrException("Cannot mkdirs " + path, e);
- } finally {
- }
- }
-
- private static Node findClosestExistingParent(Session session, String path) throws RepositoryException {
- int idx = path.lastIndexOf('/');
- if (idx == 0)
- return session.getRootNode();
- String parentPath = path.substring(0, idx);
- if (session.itemExists(parentPath))
- return session.getNode(parentPath);
- else
- return findClosestExistingParent(session, parentPath);
- }
-
- /** Convert a path to the list of its tokens */
- public static List<String> tokenize(String path) {
- List<String> tokens = new ArrayList<String>();
- boolean optimized = false;
- if (!optimized) {
- String[] rawTokens = path.split("/");
- for (String token : rawTokens) {
- if (!token.equals(""))
- tokens.add(token);
- }
- } else {
- StringBuffer curr = new StringBuffer();
- char[] arr = path.toCharArray();
- chars: for (int i = 0; i < arr.length; i++) {
- char c = arr[i];
- if (c == '/') {
- if (i == 0 || (i == arr.length - 1))
- continue chars;
- if (curr.length() > 0) {
- tokens.add(curr.toString());
- curr = new StringBuffer();
- }
- } else
- curr.append(c);
- }
- if (curr.length() > 0) {
- tokens.add(curr.toString());
- curr = new StringBuffer();
- }
- }
- return Collections.unmodifiableList(tokens);
- }
-
- // /**
- // * use {@link #mkdirs(Session, String, String, String, Boolean)} instead.
- // *
- // * @deprecated
- // */
- // @Deprecated
- // public static Node mkdirs(Session session, String path, String type,
- // Boolean versioning) {
- // return mkdirs(session, path, type, type, false);
- // }
-
- /**
- * Safe and repository implementation independent registration of a namespace.
- */
- public static void registerNamespaceSafely(Session session, String prefix, String uri) {
- try {
- registerNamespaceSafely(session.getWorkspace().getNamespaceRegistry(), prefix, uri);
- } catch (RepositoryException e) {
- throw new JcrException("Cannot find namespace registry", e);
- }
- }
-
- /**
- * Safe and repository implementation independent registration of a namespace.
- */
- public static void registerNamespaceSafely(NamespaceRegistry nr, String prefix, String uri) {
- try {
- String[] prefixes = nr.getPrefixes();
- for (String pref : prefixes)
- if (pref.equals(prefix)) {
- String registeredUri = nr.getURI(pref);
- if (!registeredUri.equals(uri))
- throw new IllegalArgumentException("Prefix " + pref + " already registered for URI "
- + registeredUri + " which is different from provided URI " + uri);
- else
- return;// skip
- }
- nr.registerNamespace(prefix, uri);
- } catch (RepositoryException e) {
- throw new JcrException("Cannot register namespace " + uri + " under prefix " + prefix, e);
- }
- }
-
-// /** Recursively outputs the contents of the given node. */
-// public static void debug(Node node) {
-// debug(node, log);
-// }
-//
-// /** Recursively outputs the contents of the given node. */
-// public static void debug(Node node, Log log) {
-// try {
-// // First output the node path
-// log.debug(node.getPath());
-// // Skip the virtual (and large!) jcr:system subtree
-// if (node.getName().equals("jcr:system")) {
-// return;
-// }
-//
-// // Then the children nodes (recursive)
-// NodeIterator it = node.getNodes();
-// while (it.hasNext()) {
-// Node childNode = it.nextNode();
-// debug(childNode, log);
-// }
-//
-// // Then output the properties
-// PropertyIterator properties = node.getProperties();
-// // log.debug("Property are : ");
-//
-// properties: while (properties.hasNext()) {
-// Property property = properties.nextProperty();
-// if (property.getType() == PropertyType.BINARY)
-// continue properties;// skip
-// if (property.getDefinition().isMultiple()) {
-// // A multi-valued property, print all values
-// Value[] values = property.getValues();
-// for (int i = 0; i < values.length; i++) {
-// log.debug(property.getPath() + "=" + values[i].getString());
-// }
-// } else {
-// // A single-valued property
-// log.debug(property.getPath() + "=" + property.getString());
-// }
-// }
-// } catch (Exception e) {
-// log.error("Could not debug " + node, e);
-// }
-//
-// }
-
-// /** Logs the effective access control policies */
-// public static void logEffectiveAccessPolicies(Node node) {
-// try {
-// logEffectiveAccessPolicies(node.getSession(), node.getPath());
-// } catch (RepositoryException e) {
-// log.error("Cannot log effective access policies of " + node, e);
-// }
-// }
-//
-// /** Logs the effective access control policies */
-// public static void logEffectiveAccessPolicies(Session session, String path) {
-// if (!log.isDebugEnabled())
-// return;
-//
-// try {
-// AccessControlPolicy[] effectivePolicies = session.getAccessControlManager().getEffectivePolicies(path);
-// if (effectivePolicies.length > 0) {
-// for (AccessControlPolicy policy : effectivePolicies) {
-// if (policy instanceof AccessControlList) {
-// AccessControlList acl = (AccessControlList) policy;
-// log.debug("Access control list for " + path + "\n" + accessControlListSummary(acl));
-// }
-// }
-// } else {
-// log.debug("No effective access control policy for " + path);
-// }
-// } catch (RepositoryException e) {
-// log.error("Cannot log effective access policies of " + path, e);
-// }
-// }
-
- /** Returns a human-readable summary of this access control list. */
- public static String accessControlListSummary(AccessControlList acl) {
- StringBuffer buf = new StringBuffer("");
- try {
- for (AccessControlEntry ace : acl.getAccessControlEntries()) {
- buf.append('\t').append(ace.getPrincipal().getName()).append('\n');
- for (Privilege priv : ace.getPrivileges())
- buf.append("\t\t").append(priv.getName()).append('\n');
- }
- return buf.toString();
- } catch (RepositoryException e) {
- throw new JcrException("Cannot write summary of " + acl, e);
- }
- }
-
- /** Copy the whole workspace via a system view XML. */
- public static void copyWorkspaceXml(Session fromSession, Session toSession) {
- Workspace fromWorkspace = fromSession.getWorkspace();
- Workspace toWorkspace = toSession.getWorkspace();
- String errorMsg = "Cannot copy workspace " + fromWorkspace + " to " + toWorkspace + " via XML.";
-
- try (PipedInputStream in = new PipedInputStream(1024 * 1024);) {
- new Thread(() -> {
- try (PipedOutputStream out = new PipedOutputStream(in)) {
- fromSession.exportSystemView("/", out, false, false);
- out.flush();
- } catch (IOException e) {
- throw new RuntimeException(errorMsg, e);
- } catch (RepositoryException e) {
- throw new JcrException(errorMsg, e);
- }
- }, "Copy workspace" + fromWorkspace + " to " + toWorkspace).start();
-
- toSession.importXML("/", in, ImportUUIDBehavior.IMPORT_UUID_COLLISION_REPLACE_EXISTING);
- toSession.save();
- } catch (IOException e) {
- throw new RuntimeException(errorMsg, e);
- } catch (RepositoryException e) {
- throw new JcrException(errorMsg, e);
- }
- }
-
- /**
- * Copies recursively the content of a node to another one. Do NOT copy the
- * property values of {@link NodeType#MIX_CREATED} and
- * {@link NodeType#MIX_LAST_MODIFIED}, but update the
- * {@link Property#JCR_LAST_MODIFIED} and {@link Property#JCR_LAST_MODIFIED_BY}
- * properties if the target node has the {@link NodeType#MIX_LAST_MODIFIED}
- * mixin.
- */
- public static void copy(Node fromNode, Node toNode) {
- try {
- if (toNode.getDefinition().isProtected())
- return;
-
- // add mixins
- for (NodeType mixinType : fromNode.getMixinNodeTypes()) {
- try {
- toNode.addMixin(mixinType.getName());
- } catch (NoSuchNodeTypeException e) {
- // ignore unknown mixins
- // TODO log it
- }
- }
-
- // process properties
- PropertyIterator pit = fromNode.getProperties();
- properties: while (pit.hasNext()) {
- Property fromProperty = pit.nextProperty();
- String propertyName = fromProperty.getName();
- if (toNode.hasProperty(propertyName) && toNode.getProperty(propertyName).getDefinition().isProtected())
- continue properties;
-
- if (fromProperty.getDefinition().isProtected())
- continue properties;
-
- if (propertyName.equals("jcr:created") || propertyName.equals("jcr:createdBy")
- || propertyName.equals("jcr:lastModified") || propertyName.equals("jcr:lastModifiedBy"))
- continue properties;
-
- if (fromProperty.isMultiple()) {
- toNode.setProperty(propertyName, fromProperty.getValues());
- } else {
- toNode.setProperty(propertyName, fromProperty.getValue());
- }
- }
-
- // update jcr:lastModified and jcr:lastModifiedBy in toNode in case
- // they existed, before adding the mixins
- updateLastModified(toNode, true);
-
- // process children nodes
- NodeIterator nit = fromNode.getNodes();
- while (nit.hasNext()) {
- Node fromChild = nit.nextNode();
- Integer index = fromChild.getIndex();
- String nodeRelPath = fromChild.getName() + "[" + index + "]";
- Node toChild;
- if (toNode.hasNode(nodeRelPath))
- toChild = toNode.getNode(nodeRelPath);
- else {
- try {
- toChild = toNode.addNode(fromChild.getName(), fromChild.getPrimaryNodeType().getName());
- } catch (NoSuchNodeTypeException e) {
- // ignore unknown primary types
- // TODO log it
- return;
- }
- }
- copy(fromChild, toChild);
- }
- } catch (RepositoryException e) {
- throw new JcrException("Cannot copy " + fromNode + " to " + toNode, e);
- }
- }
-
- /**
- * Check whether all first-level properties (except jcr:* properties) are equal.
- * Skip jcr:* properties
- */
- public static Boolean allPropertiesEquals(Node reference, Node observed, Boolean onlyCommonProperties) {
- try {
- PropertyIterator pit = reference.getProperties();
- props: while (pit.hasNext()) {
- Property propReference = pit.nextProperty();
- String propName = propReference.getName();
- if (propName.startsWith("jcr:"))
- continue props;
-
- if (!observed.hasProperty(propName))
- if (onlyCommonProperties)
- continue props;
- else
- return false;
- // TODO: deal with multiple property values?
- if (!observed.getProperty(propName).getValue().equals(propReference.getValue()))
- return false;
- }
- return true;
- } catch (RepositoryException e) {
- throw new JcrException("Cannot check all properties equals of " + reference + " and " + observed, e);
- }
- }
-
- public static Map<String, PropertyDiff> diffProperties(Node reference, Node observed) {
- Map<String, PropertyDiff> diffs = new TreeMap<String, PropertyDiff>();
- diffPropertiesLevel(diffs, null, reference, observed);
- return diffs;
- }
-
- /**
- * Compare the properties of two nodes. Recursivity to child nodes is not yet
- * supported. Skip jcr:* properties.
- */
- static void diffPropertiesLevel(Map<String, PropertyDiff> diffs, String baseRelPath, Node reference,
- Node observed) {
- try {
- // check removed and modified
- PropertyIterator pit = reference.getProperties();
- props: while (pit.hasNext()) {
- Property p = pit.nextProperty();
- String name = p.getName();
- if (name.startsWith("jcr:"))
- continue props;
-
- if (!observed.hasProperty(name)) {
- String relPath = propertyRelPath(baseRelPath, name);
- PropertyDiff pDiff = new PropertyDiff(PropertyDiff.REMOVED, relPath, p.getValue(), null);
- diffs.put(relPath, pDiff);
- } else {
- if (p.isMultiple()) {
- // FIXME implement multiple
- } else {
- Value referenceValue = p.getValue();
- Value newValue = observed.getProperty(name).getValue();
- if (!referenceValue.equals(newValue)) {
- String relPath = propertyRelPath(baseRelPath, name);
- PropertyDiff pDiff = new PropertyDiff(PropertyDiff.MODIFIED, relPath, referenceValue,
- newValue);
- diffs.put(relPath, pDiff);
- }
- }
- }
- }
- // check added
- pit = observed.getProperties();
- props: while (pit.hasNext()) {
- Property p = pit.nextProperty();
- String name = p.getName();
- if (name.startsWith("jcr:"))
- continue props;
- if (!reference.hasProperty(name)) {
- if (p.isMultiple()) {
- // FIXME implement multiple
- } else {
- String relPath = propertyRelPath(baseRelPath, name);
- PropertyDiff pDiff = new PropertyDiff(PropertyDiff.ADDED, relPath, null, p.getValue());
- diffs.put(relPath, pDiff);
- }
- }
- }
- } catch (RepositoryException e) {
- throw new JcrException("Cannot diff " + reference + " and " + observed, e);
- }
- }
-
- /**
- * Compare only a restricted list of properties of two nodes. No recursivity.
- *
- */
- public static Map<String, PropertyDiff> diffProperties(Node reference, Node observed, List<String> properties) {
- Map<String, PropertyDiff> diffs = new TreeMap<String, PropertyDiff>();
- try {
- Iterator<String> pit = properties.iterator();
-
- props: while (pit.hasNext()) {
- String name = pit.next();
- if (!reference.hasProperty(name)) {
- if (!observed.hasProperty(name))
- continue props;
- Value val = observed.getProperty(name).getValue();
- try {
- // empty String but not null
- if ("".equals(val.getString()))
- continue props;
- } catch (Exception e) {
- // not parseable as String, silent
- }
- PropertyDiff pDiff = new PropertyDiff(PropertyDiff.ADDED, name, null, val);
- diffs.put(name, pDiff);
- } else if (!observed.hasProperty(name)) {
- PropertyDiff pDiff = new PropertyDiff(PropertyDiff.REMOVED, name,
- reference.getProperty(name).getValue(), null);
- diffs.put(name, pDiff);
- } else {
- Value referenceValue = reference.getProperty(name).getValue();
- Value newValue = observed.getProperty(name).getValue();
- if (!referenceValue.equals(newValue)) {
- PropertyDiff pDiff = new PropertyDiff(PropertyDiff.MODIFIED, name, referenceValue, newValue);
- diffs.put(name, pDiff);
- }
- }
- }
- } catch (RepositoryException e) {
- throw new JcrException("Cannot diff " + reference + " and " + observed, e);
- }
- return diffs;
- }
-
- /** Builds a property relPath to be used in the diff. */
- private static String propertyRelPath(String baseRelPath, String propertyName) {
- if (baseRelPath == null)
- return propertyName;
- else
- return baseRelPath + '/' + propertyName;
- }
-
- /**
- * Normalizes a name so that it can be stored in contexts not supporting names
- * with ':' (typically databases). Replaces ':' by '_'.
- */
- public static String normalize(String name) {
- return name.replace(':', '_');
- }
-
- /**
- * Replaces characters which are invalid in a JCR name by '_'. Currently not
- * exhaustive.
- *
- * @see JcrUtils#INVALID_NAME_CHARACTERS
- */
- public static String replaceInvalidChars(String name) {
- return replaceInvalidChars(name, '_');
- }
-
- /**
- * Replaces characters which are invalid in a JCR name. Currently not
- * exhaustive.
- *
- * @see JcrUtils#INVALID_NAME_CHARACTERS
- */
- public static String replaceInvalidChars(String name, char replacement) {
- boolean modified = false;
- char[] arr = name.toCharArray();
- for (int i = 0; i < arr.length; i++) {
- char c = arr[i];
- invalid: for (char invalid : INVALID_NAME_CHARACTERS) {
- if (c == invalid) {
- arr[i] = replacement;
- modified = true;
- break invalid;
- }
- }
- }
- if (modified)
- return new String(arr);
- else
- // do not create new object if unnecessary
- return name;
- }
-
- // /**
- // * Removes forbidden characters from a path, replacing them with '_'
- // *
- // * @deprecated use {@link #replaceInvalidChars(String)} instead
- // */
- // public static String removeForbiddenCharacters(String str) {
- // return str.replace('[', '_').replace(']', '_').replace('/', '_').replace('*',
- // '_');
- //
- // }
-
- /** Cleanly disposes a {@link Binary} even if it is null. */
- public static void closeQuietly(Binary binary) {
- if (binary == null)
- return;
- binary.dispose();
- }
-
- /** Retrieve a {@link Binary} as a byte array */
- public static byte[] getBinaryAsBytes(Property property) {
- try (ByteArrayOutputStream out = new ByteArrayOutputStream();
- Bin binary = new Bin(property);
- InputStream in = binary.getStream()) {
- IOUtils.copy(in, out);
- return out.toByteArray();
- } catch (RepositoryException e) {
- throw new JcrException("Cannot read binary " + property + " as bytes", e);
- } catch (IOException e) {
- throw new RuntimeException("Cannot read binary " + property + " as bytes", e);
- }
- }
-
- /** Writes a {@link Binary} from a byte array */
- public static void setBinaryAsBytes(Node node, String property, byte[] bytes) {
- Binary binary = null;
- try (InputStream in = new ByteArrayInputStream(bytes)) {
- binary = node.getSession().getValueFactory().createBinary(in);
- node.setProperty(property, binary);
- } catch (RepositoryException e) {
- throw new JcrException("Cannot set binary " + property + " as bytes", e);
- } catch (IOException e) {
- throw new RuntimeException("Cannot set binary " + property + " as bytes", e);
- } finally {
- closeQuietly(binary);
- }
- }
-
- /** Writes a {@link Binary} from a byte array */
- public static void setBinaryAsBytes(Property prop, byte[] bytes) {
- Binary binary = null;
- try (InputStream in = new ByteArrayInputStream(bytes)) {
- binary = prop.getSession().getValueFactory().createBinary(in);
- prop.setValue(binary);
- } catch (RepositoryException e) {
- throw new JcrException("Cannot set binary " + prop + " as bytes", e);
- } catch (IOException e) {
- throw new RuntimeException("Cannot set binary " + prop + " as bytes", e);
- } finally {
- closeQuietly(binary);
- }
- }
-
- /**
- * Creates depth from a string (typically a username) by adding levels based on
- * its first characters: "aBcD",2 becomes a/aB
- */
- public static String firstCharsToPath(String str, Integer nbrOfChars) {
- if (str.length() < nbrOfChars)
- throw new IllegalArgumentException("String " + str + " length must be greater or equal than " + nbrOfChars);
- StringBuffer path = new StringBuffer("");
- StringBuffer curr = new StringBuffer("");
- for (int i = 0; i < nbrOfChars; i++) {
- curr.append(str.charAt(i));
- path.append(curr);
- if (i < nbrOfChars - 1)
- path.append('/');
- }
- return path.toString();
- }
-
- /**
- * Discards the current changes in the session attached to this node. To be used
- * typically in a catch block.
- *
- * @see #discardQuietly(Session)
- */
- public static void discardUnderlyingSessionQuietly(Node node) {
- try {
- discardQuietly(node.getSession());
- } catch (RepositoryException e) {
- // silent
- }
- }
-
- /**
- * Discards the current changes in a session by calling
- * {@link Session#refresh(boolean)} with <code>false</code>, only logging
- * potential errors when doing so. To be used typically in a catch block.
- */
- public static void discardQuietly(Session session) {
- try {
- if (session != null)
- session.refresh(false);
- } catch (RepositoryException e) {
- // silent
- }
- }
-
- /**
- * Login to a workspace with implicit credentials, creates the workspace with
- * these credentials if it does not already exist.
- */
- public static Session loginOrCreateWorkspace(Repository repository, String workspaceName)
- throws RepositoryException {
- return loginOrCreateWorkspace(repository, workspaceName, null);
- }
-
- /**
- * Login to a workspace with implicit credentials, creates the workspace with
- * these credentials if it does not already exist.
- */
- public static Session loginOrCreateWorkspace(Repository repository, String workspaceName, Credentials credentials)
- throws RepositoryException {
- Session workspaceSession = null;
- Session defaultSession = null;
- try {
- try {
- workspaceSession = repository.login(credentials, workspaceName);
- } catch (NoSuchWorkspaceException e) {
- // try to create workspace
- defaultSession = repository.login(credentials);
- defaultSession.getWorkspace().createWorkspace(workspaceName);
- workspaceSession = repository.login(credentials, workspaceName);
- }
- return workspaceSession;
- } finally {
- logoutQuietly(defaultSession);
- }
- }
-
- /**
- * Logs out the session, not throwing any exception, even if it is null.
- * {@link Jcr#logout(Session)} should rather be used.
- */
- public static void logoutQuietly(Session session) {
- Jcr.logout(session);
-// try {
-// if (session != null)
-// if (session.isLive())
-// session.logout();
-// } catch (Exception e) {
-// // silent
-// }
- }
-
- /**
- * Convenient method to add a listener. uuids passed as null, deep=true,
- * local=true, only one node type
- */
- public static void addListener(Session session, EventListener listener, int eventTypes, String basePath,
- String nodeType) {
- try {
- session.getWorkspace().getObservationManager().addEventListener(listener, eventTypes, basePath, true, null,
- nodeType == null ? null : new String[] { nodeType }, true);
- } catch (RepositoryException e) {
- throw new JcrException("Cannot add JCR listener " + listener + " to session " + session, e);
- }
- }
-
- /** Removes a listener without throwing exception */
- public static void removeListenerQuietly(Session session, EventListener listener) {
- if (session == null || !session.isLive())
- return;
- try {
- session.getWorkspace().getObservationManager().removeEventListener(listener);
- } catch (RepositoryException e) {
- // silent
- }
- }
-
- /**
- * Quietly unregisters an {@link EventListener} from the udnerlying workspace of
- * this node.
- */
- public static void unregisterQuietly(Node node, EventListener eventListener) {
- try {
- unregisterQuietly(node.getSession().getWorkspace(), eventListener);
- } catch (RepositoryException e) {
- // silent
- }
- }
-
- /** Quietly unregisters an {@link EventListener} from this workspace */
- public static void unregisterQuietly(Workspace workspace, EventListener eventListener) {
- if (eventListener == null)
- return;
- try {
- workspace.getObservationManager().removeEventListener(eventListener);
- } catch (RepositoryException e) {
- // silent
- }
- }
-
- /**
- * Checks whether {@link Property#JCR_LAST_MODIFIED} or (afterwards)
- * {@link Property#JCR_CREATED} are set and returns it as an {@link Instant}.
- */
- public static Instant getModified(Node node) {
- Calendar calendar = null;
- try {
- if (node.hasProperty(Property.JCR_LAST_MODIFIED))
- calendar = node.getProperty(Property.JCR_LAST_MODIFIED).getDate();
- else if (node.hasProperty(Property.JCR_CREATED))
- calendar = node.getProperty(Property.JCR_CREATED).getDate();
- else
- throw new IllegalArgumentException("No modification time found in " + node);
- return calendar.toInstant();
- } catch (RepositoryException e) {
- throw new JcrException("Cannot get modification time for " + node, e);
- }
-
- }
-
- /**
- * Get {@link Property#JCR_CREATED} as an {@link Instant}, if it is set.
- */
- public static Instant getCreated(Node node) {
- Calendar calendar = null;
- try {
- if (node.hasProperty(Property.JCR_CREATED))
- calendar = node.getProperty(Property.JCR_CREATED).getDate();
- else
- throw new IllegalArgumentException("No created time found in " + node);
- return calendar.toInstant();
- } catch (RepositoryException e) {
- throw new JcrException("Cannot get created time for " + node, e);
- }
-
- }
-
- /**
- * Updates the {@link Property#JCR_LAST_MODIFIED} property with the current time
- * and the {@link Property#JCR_LAST_MODIFIED_BY} property with the underlying
- * session user id.
- */
- public static void updateLastModified(Node node) {
- updateLastModified(node, false);
- }
-
- /**
- * Updates the {@link Property#JCR_LAST_MODIFIED} property with the current time
- * and the {@link Property#JCR_LAST_MODIFIED_BY} property with the underlying
- * session user id. In Jackrabbit 2.x,
- * <a href="https://issues.apache.org/jira/browse/JCR-2233">these properties are
- * not automatically updated</a>, hence the need for manual update. The session
- * is not saved.
- */
- public static void updateLastModified(Node node, boolean addMixin) {
- try {
- if (addMixin && !node.isNodeType(NodeType.MIX_LAST_MODIFIED))
- node.addMixin(NodeType.MIX_LAST_MODIFIED);
- node.setProperty(Property.JCR_LAST_MODIFIED, new GregorianCalendar());
- node.setProperty(Property.JCR_LAST_MODIFIED_BY, node.getSession().getUserID());
- } catch (RepositoryException e) {
- throw new JcrException("Cannot update last modified on " + node, e);
- }
- }
-
- /**
- * Update lastModified recursively until this parent.
- *
- * @param node the node
- * @param untilPath the base path, null is equivalent to "/"
- */
- public static void updateLastModifiedAndParents(Node node, String untilPath) {
- updateLastModifiedAndParents(node, untilPath, true);
- }
-
- /**
- * Update lastModified recursively until this parent.
- *
- * @param node the node
- * @param untilPath the base path, null is equivalent to "/"
- */
- public static void updateLastModifiedAndParents(Node node, String untilPath, boolean addMixin) {
- try {
- if (untilPath != null && !node.getPath().startsWith(untilPath))
- throw new IllegalArgumentException(node + " is not under " + untilPath);
- updateLastModified(node, addMixin);
- if (untilPath == null) {
- if (!node.getPath().equals("/"))
- updateLastModifiedAndParents(node.getParent(), untilPath, addMixin);
- } else {
- if (!node.getPath().equals(untilPath))
- updateLastModifiedAndParents(node.getParent(), untilPath, addMixin);
- }
- } catch (RepositoryException e) {
- throw new JcrException("Cannot update lastModified from " + node + " until " + untilPath, e);
- }
- }
-
- /**
- * Returns a String representing the short version (see
- * <a href="http://jackrabbit.apache.org/node-type-notation.html"> Node type
- * Notation </a> attributes grammar) of the main business attributes of this
- * property definition
- *
- * @param prop
- */
- public static String getPropertyDefinitionAsString(Property prop) {
- StringBuffer sbuf = new StringBuffer();
- try {
- if (prop.getDefinition().isAutoCreated())
- sbuf.append("a");
- if (prop.getDefinition().isMandatory())
- sbuf.append("m");
- if (prop.getDefinition().isProtected())
- sbuf.append("p");
- if (prop.getDefinition().isMultiple())
- sbuf.append("*");
- } catch (RepositoryException re) {
- throw new JcrException("unexpected error while getting property definition as String", re);
- }
- return sbuf.toString();
- }
-
- /**
- * Estimate the sub tree size from current node. Computation is based on the Jcr
- * {@link Property#getLength()} method. Note : it is not the exact size used on
- * the disk by the current part of the JCR Tree.
- */
-
- public static long getNodeApproxSize(Node node) {
- long curNodeSize = 0;
- try {
- PropertyIterator pi = node.getProperties();
- while (pi.hasNext()) {
- Property prop = pi.nextProperty();
- if (prop.isMultiple()) {
- int nb = prop.getLengths().length;
- for (int i = 0; i < nb; i++) {
- curNodeSize += (prop.getLengths()[i] > 0 ? prop.getLengths()[i] : 0);
- }
- } else
- curNodeSize += (prop.getLength() > 0 ? prop.getLength() : 0);
- }
-
- NodeIterator ni = node.getNodes();
- while (ni.hasNext())
- curNodeSize += getNodeApproxSize(ni.nextNode());
- return curNodeSize;
- } catch (RepositoryException re) {
- throw new JcrException("Unexpected error while recursively determining node size.", re);
- }
- }
-
- /*
- * SECURITY
- */
-
- /**
- * Convenience method for adding a single privilege to a principal (user or
- * role), typically jcr:all
- */
- public synchronized static void addPrivilege(Session session, String path, String principal, String privilege)
- throws RepositoryException {
- List<Privilege> privileges = new ArrayList<Privilege>();
- privileges.add(session.getAccessControlManager().privilegeFromName(privilege));
- addPrivileges(session, path, new SimplePrincipal(principal), privileges);
- }
-
- /**
- * Add privileges on a path to a {@link Principal}. The path must already exist.
- * Session is saved. Synchronized to prevent concurrent modifications of the
- * same node.
- */
- public synchronized static Boolean addPrivileges(Session session, String path, Principal principal,
- List<Privilege> privs) throws RepositoryException {
- // make sure the session is in line with the persisted state
- session.refresh(false);
- AccessControlManager acm = session.getAccessControlManager();
- AccessControlList acl = getAccessControlList(acm, path);
-
- accessControlEntries: for (AccessControlEntry ace : acl.getAccessControlEntries()) {
- Principal currentPrincipal = ace.getPrincipal();
- if (currentPrincipal.getName().equals(principal.getName())) {
- Privilege[] currentPrivileges = ace.getPrivileges();
- if (currentPrivileges.length != privs.size())
- break accessControlEntries;
- for (int i = 0; i < currentPrivileges.length; i++) {
- Privilege currP = currentPrivileges[i];
- Privilege p = privs.get(i);
- if (!currP.getName().equals(p.getName())) {
- break accessControlEntries;
- }
- }
- return false;
- }
- }
-
- Privilege[] privileges = privs.toArray(new Privilege[privs.size()]);
- acl.addAccessControlEntry(principal, privileges);
- acm.setPolicy(path, acl);
-// if (log.isDebugEnabled()) {
-// StringBuffer privBuf = new StringBuffer();
-// for (Privilege priv : privs)
-// privBuf.append(priv.getName());
-// log.debug("Added privileges " + privBuf + " to " + principal.getName() + " on " + path + " in '"
-// + session.getWorkspace().getName() + "'");
-// }
- session.refresh(true);
- session.save();
- return true;
- }
-
- /**
- * Gets the first available access control list for this path, throws exception
- * if not found
- */
- public synchronized static AccessControlList getAccessControlList(AccessControlManager acm, String path)
- throws RepositoryException {
- // search for an access control list
- AccessControlList acl = null;
- AccessControlPolicyIterator policyIterator = acm.getApplicablePolicies(path);
- applicablePolicies: if (policyIterator.hasNext()) {
- while (policyIterator.hasNext()) {
- AccessControlPolicy acp = policyIterator.nextAccessControlPolicy();
- if (acp instanceof AccessControlList) {
- acl = ((AccessControlList) acp);
- break applicablePolicies;
- }
- }
- } else {
- AccessControlPolicy[] existingPolicies = acm.getPolicies(path);
- existingPolicies: for (AccessControlPolicy acp : existingPolicies) {
- if (acp instanceof AccessControlList) {
- acl = ((AccessControlList) acp);
- break existingPolicies;
- }
- }
- }
- if (acl != null)
- return acl;
- else
- throw new IllegalArgumentException("ACL not found at " + path);
- }
-
- /** Clear authorizations for a user at this path */
- public synchronized static void clearAccessControList(Session session, String path, String username)
- throws RepositoryException {
- AccessControlManager acm = session.getAccessControlManager();
- AccessControlList acl = getAccessControlList(acm, path);
- for (AccessControlEntry ace : acl.getAccessControlEntries()) {
- if (ace.getPrincipal().getName().equals(username)) {
- acl.removeAccessControlEntry(ace);
- }
- }
- // the new access control list must be applied otherwise this call:
- // acl.removeAccessControlEntry(ace); has no effect
- acm.setPolicy(path, acl);
- session.refresh(true);
- session.save();
- }
-
- /*
- * FILES UTILITIES
- */
- /**
- * Creates the nodes making the path as {@link NodeType#NT_FOLDER}
- */
- public static Node mkfolders(Session session, String path) {
- return mkdirs(session, path, NodeType.NT_FOLDER, NodeType.NT_FOLDER, false);
- }
-
- /**
- * Copy only nt:folder and nt:file, without their additional types and
- * properties.
- *
- * @param recursive if true copies folders as well, otherwise only first level
- * files
- * @return how many files were copied
- */
- public static Long copyFiles(Node fromNode, Node toNode, Boolean recursive, JcrMonitor monitor, boolean onlyAdd) {
- long count = 0l;
-
- // Binary binary = null;
- // InputStream in = null;
- try {
- NodeIterator fromChildren = fromNode.getNodes();
- children: while (fromChildren.hasNext()) {
- if (monitor != null && monitor.isCanceled())
- throw new IllegalStateException("Copy cancelled before it was completed");
-
- Node fromChild = fromChildren.nextNode();
- String fileName = fromChild.getName();
- if (fromChild.isNodeType(NodeType.NT_FILE)) {
- if (onlyAdd && toNode.hasNode(fileName)) {
- monitor.subTask("Skip existing " + fileName);
- continue children;
- }
-
- if (monitor != null)
- monitor.subTask("Copy " + fileName);
- try (Bin binary = new Bin(fromChild.getNode(Node.JCR_CONTENT).getProperty(Property.JCR_DATA));
- InputStream in = binary.getStream();) {
- copyStreamAsFile(toNode, fileName, in);
- } catch (IOException e) {
- throw new RuntimeException("Cannot copy " + fileName + " to " + toNode, e);
- }
-
- // save session
- toNode.getSession().save();
- count++;
-
-// if (log.isDebugEnabled())
-// log.debug("Copied file " + fromChild.getPath());
- if (monitor != null)
- monitor.worked(1);
- } else if (fromChild.isNodeType(NodeType.NT_FOLDER) && recursive) {
- Node toChildFolder;
- if (toNode.hasNode(fileName)) {
- toChildFolder = toNode.getNode(fileName);
- if (!toChildFolder.isNodeType(NodeType.NT_FOLDER))
- throw new IllegalArgumentException(toChildFolder + " is not of type nt:folder");
- } else {
- toChildFolder = toNode.addNode(fileName, NodeType.NT_FOLDER);
-
- // save session
- toNode.getSession().save();
- }
- count = count + copyFiles(fromChild, toChildFolder, recursive, monitor, onlyAdd);
- }
- }
- return count;
- } catch (RepositoryException e) {
- throw new JcrException("Cannot copy files between " + fromNode + " and " + toNode, e);
- } finally {
- // in case there was an exception
- // IOUtils.closeQuietly(in);
- // closeQuietly(binary);
- }
- }
-
- /**
- * Iteratively count all file nodes in subtree, inefficient but can be useful
- * when query are poorly supported, such as in remoting.
- */
- public static Long countFiles(Node node) {
- Long localCount = 0l;
- try {
- for (NodeIterator nit = node.getNodes(); nit.hasNext();) {
- Node child = nit.nextNode();
- if (child.isNodeType(NodeType.NT_FOLDER))
- localCount = localCount + countFiles(child);
- else if (child.isNodeType(NodeType.NT_FILE))
- localCount = localCount + 1;
- }
- } catch (RepositoryException e) {
- throw new JcrException("Cannot count all children of " + node, e);
- }
- return localCount;
- }
-
- /**
- * Copy a file as an nt:file, assuming an nt:folder hierarchy. The session is
- * NOT saved.
- *
- * @return the created file node
- */
- @Deprecated
- public static Node copyFile(Node folderNode, File file) {
- try (InputStream in = new FileInputStream(file)) {
- return copyStreamAsFile(folderNode, file.getName(), in);
- } catch (IOException e) {
- throw new RuntimeException("Cannot copy file " + file + " under " + folderNode, e);
- }
- }
-
- /** Copy bytes as an nt:file */
- public static Node copyBytesAsFile(Node folderNode, String fileName, byte[] bytes) {
- // InputStream in = null;
- try (InputStream in = new ByteArrayInputStream(bytes)) {
- // in = new ByteArrayInputStream(bytes);
- return copyStreamAsFile(folderNode, fileName, in);
- } catch (IOException e) {
- throw new RuntimeException("Cannot copy file " + fileName + " under " + folderNode, e);
- // } finally {
- // IOUtils.closeQuietly(in);
- }
- }
-
- /**
- * Copy a stream as an nt:file, assuming an nt:folder hierarchy. The session is
- * NOT saved.
- *
- * @return the created file node
- */
- public static Node copyStreamAsFile(Node folderNode, String fileName, InputStream in) {
- Binary binary = null;
- try {
- Node fileNode;
- Node contentNode;
- if (folderNode.hasNode(fileName)) {
- fileNode = folderNode.getNode(fileName);
- if (!fileNode.isNodeType(NodeType.NT_FILE))
- throw new IllegalArgumentException(fileNode + " is not of type nt:file");
- // we assume that the content node is already there
- contentNode = fileNode.getNode(Node.JCR_CONTENT);
- } else {
- fileNode = folderNode.addNode(fileName, NodeType.NT_FILE);
- contentNode = fileNode.addNode(Node.JCR_CONTENT, NodeType.NT_UNSTRUCTURED);
- }
- binary = contentNode.getSession().getValueFactory().createBinary(in);
- contentNode.setProperty(Property.JCR_DATA, binary);
- updateLastModified(contentNode);
- return fileNode;
- } catch (RepositoryException e) {
- throw new JcrException("Cannot create file node " + fileName + " under " + folderNode, e);
- } finally {
- closeQuietly(binary);
- }
- }
-
- /** Read an an nt:file as an {@link InputStream}. */
- public static InputStream getFileAsStream(Node fileNode) throws RepositoryException {
- return fileNode.getNode(Node.JCR_CONTENT).getProperty(Property.JCR_DATA).getBinary().getStream();
- }
-
- /**
- * Set the properties of {@link NodeType#MIX_MIMETYPE} on the content of this
- * file node.
- */
- public static void setFileMimeType(Node fileNode, String mimeType, String encoding) throws RepositoryException {
- Node contentNode = fileNode.getNode(Node.JCR_CONTENT);
- if (mimeType != null)
- contentNode.setProperty(Property.JCR_MIMETYPE, mimeType);
- if (encoding != null)
- contentNode.setProperty(Property.JCR_ENCODING, encoding);
- // TODO remove properties if args are null?
- }
-
- public static void copyFilesToFs(Node baseNode, Path targetDir, boolean recursive) {
- try {
- Files.createDirectories(targetDir);
- for (NodeIterator nit = baseNode.getNodes(); nit.hasNext();) {
- Node node = nit.nextNode();
- if (node.isNodeType(NodeType.NT_FILE)) {
- Path filePath = targetDir.resolve(node.getName());
- try (OutputStream out = Files.newOutputStream(filePath); InputStream in = getFileAsStream(node)) {
- IOUtils.copy(in, out);
- }
- } else if (recursive && node.isNodeType(NodeType.NT_FOLDER)) {
- Path dirPath = targetDir.resolve(node.getName());
- copyFilesToFs(node, dirPath, true);
- }
- }
- } catch (RepositoryException e) {
- throw new JcrException("Cannot copy " + baseNode + " to " + targetDir, e);
- } catch (IOException e) {
- throw new RuntimeException("Cannot copy " + baseNode + " to " + targetDir, e);
- }
- }
-
- /**
- * Computes the checksum of an nt:file.
- *
- * @deprecated use separate digest utilities
- */
- @Deprecated
- public static String checksumFile(Node fileNode, String algorithm) {
- try (InputStream in = fileNode.getNode(Node.JCR_CONTENT).getProperty(Property.JCR_DATA).getBinary()
- .getStream()) {
- return digest(algorithm, in);
- } catch (IOException e) {
- throw new RuntimeException("Cannot checksum file " + fileNode + " with algorithm " + algorithm, e);
- } catch (RepositoryException e) {
- throw new JcrException("Cannot checksum file " + fileNode + " with algorithm " + algorithm, e);
- }
- }
-
- @Deprecated
- private static String digest(String algorithm, InputStream in) {
- final Integer byteBufferCapacity = 100 * 1024;// 100 KB
- try {
- MessageDigest digest = MessageDigest.getInstance(algorithm);
- byte[] buffer = new byte[byteBufferCapacity];
- int read = 0;
- while ((read = in.read(buffer)) > 0) {
- digest.update(buffer, 0, read);
- }
-
- byte[] checksum = digest.digest();
- String res = encodeHexString(checksum);
- return res;
- } catch (IOException e) {
- throw new RuntimeException("Cannot digest with algorithm " + algorithm, e);
- } catch (NoSuchAlgorithmException e) {
- throw new IllegalArgumentException("Cannot digest with algorithm " + algorithm, e);
- }
- }
-
- /**
- * From
- * http://stackoverflow.com/questions/9655181/how-to-convert-a-byte-array-to
- * -a-hex-string-in-java
- */
- @Deprecated
- private static String encodeHexString(byte[] bytes) {
- final char[] hexArray = "0123456789abcdef".toCharArray();
- char[] hexChars = new char[bytes.length * 2];
- for (int j = 0; j < bytes.length; j++) {
- int v = bytes[j] & 0xFF;
- hexChars[j * 2] = hexArray[v >>> 4];
- hexChars[j * 2 + 1] = hexArray[v & 0x0F];
- }
- return new String(hexChars);
- }
-
- /** Export a subtree as a compact XML without namespaces. */
- public static void toSimpleXml(Node node, StringBuilder sb) throws RepositoryException {
- sb.append('<');
- String nodeName = node.getName();
- int colIndex = nodeName.indexOf(':');
- if (colIndex > 0) {
- nodeName = nodeName.substring(colIndex + 1);
- }
- sb.append(nodeName);
- PropertyIterator pit = node.getProperties();
- properties: while (pit.hasNext()) {
- Property p = pit.nextProperty();
- // skip multiple properties
- if (p.isMultiple())
- continue properties;
- String propertyName = p.getName();
- int pcolIndex = propertyName.indexOf(':');
- // skip properties with namespaces
- if (pcolIndex > 0)
- continue properties;
- // skip binaries
- if (p.getType() == PropertyType.BINARY) {
- continue properties;
- // TODO retrieve identifier?
- }
- sb.append(' ');
- sb.append(propertyName);
- sb.append('=');
- sb.append('\"').append(p.getString()).append('\"');
- }
-
- if (node.hasNodes()) {
- sb.append('>');
- NodeIterator children = node.getNodes();
- while (children.hasNext()) {
- toSimpleXml(children.nextNode(), sb);
- }
- sb.append("</");
- sb.append(nodeName);
- sb.append('>');
- } else {
- sb.append("/>");
- }
- }
-
-}
+++ /dev/null
-package org.argeo.jcr;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-import javax.jcr.Node;
-import javax.jcr.Property;
-import javax.jcr.RepositoryException;
-import javax.jcr.Value;
-
-/** Uilities around the JCR extensions. */
-public class JcrxApi {
- public final static String MD5 = "MD5";
- public final static String SHA1 = "SHA1";
- public final static String SHA256 = "SHA-256";
- public final static String SHA512 = "SHA-512";
-
- public final static String EMPTY_MD5 = "d41d8cd98f00b204e9800998ecf8427e";
- public final static String EMPTY_SHA1 = "da39a3ee5e6b4b0d3255bfef95601890afd80709";
- public final static String EMPTY_SHA256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
- public final static String EMPTY_SHA512 = "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e";
-
- public final static int LENGTH_MD5 = EMPTY_MD5.length();
- public final static int LENGTH_SHA1 = EMPTY_SHA1.length();
- public final static int LENGTH_SHA256 = EMPTY_SHA256.length();
- public final static int LENGTH_SHA512 = EMPTY_SHA512.length();
-
- /*
- * XML
- */
- /**
- * Get the XML text of this child node.
- */
- public static String getXmlValue(Node node, String name) {
- try {
- if (!node.hasNode(name))
- return null;
- Node child = node.getNode(name);
- return getXmlValue(child);
- } catch (RepositoryException e) {
- throw new IllegalStateException("Cannot get " + name + " as XML text", e);
- }
- }
-
- /**
- * Get the XML text of this node.
- */
- public static String getXmlValue(Node node) {
- try {
- if (!node.hasNode(Jcr.JCR_XMLTEXT))
- return null;
- Node xmlText = node.getNode(Jcr.JCR_XMLTEXT);
- if (!xmlText.hasProperty(Jcr.JCR_XMLCHARACTERS))
- throw new IllegalArgumentException(
- "Node " + xmlText + " has no " + Jcr.JCR_XMLCHARACTERS + " property");
- return xmlText.getProperty(Jcr.JCR_XMLCHARACTERS).getString();
- } catch (RepositoryException e) {
- throw new IllegalStateException("Cannot get " + node + " as XML text", e);
- }
- }
-
- /**
- * Set as a subnode which will be exported as an XML element.
- */
- public static void setXmlValue(Node node, String name, String value) {
- try {
- if (node.hasNode(name)) {
- Node child = node.getNode(name);
- setXmlValue(node, child, value);
- } else
- node.addNode(name, JcrxType.JCRX_XMLVALUE).addNode(Jcr.JCR_XMLTEXT, JcrxType.JCRX_XMLTEXT)
- .setProperty(Jcr.JCR_XMLCHARACTERS, value);
- } catch (RepositoryException e) {
- throw new JcrException("Cannot set " + name + " as XML text", e);
- }
- }
-
- public static void setXmlValue(Node node, Node child, String value) {
- try {
- if (!child.hasNode(Jcr.JCR_XMLTEXT))
- child.addNode(Jcr.JCR_XMLTEXT, JcrxType.JCRX_XMLTEXT);
- child.getNode(Jcr.JCR_XMLTEXT).setProperty(Jcr.JCR_XMLCHARACTERS, value);
- } catch (RepositoryException e) {
- throw new JcrException("Cannot set " + child + " as XML text", e);
- }
- }
-
- /**
- * Add a checksum replacing the one which was previously set with the same
- * length.
- */
- public static void addChecksum(Node node, String checksum) {
- try {
- if (!node.hasProperty(JcrxName.JCRX_SUM)) {
- node.setProperty(JcrxName.JCRX_SUM, new String[] { checksum });
- return;
- } else {
- int stringLength = checksum.length();
- Property property = node.getProperty(JcrxName.JCRX_SUM);
- List<Value> values = Arrays.asList(property.getValues());
- Integer indexToRemove = null;
- values: for (int i = 0; i < values.size(); i++) {
- Value value = values.get(i);
- if (value.getString().length() == stringLength) {
- indexToRemove = i;
- break values;
- }
- }
- if (indexToRemove != null)
- values.set(indexToRemove, node.getSession().getValueFactory().createValue(checksum));
- else
- values.add(0, node.getSession().getValueFactory().createValue(checksum));
- property.setValue(values.toArray(new Value[values.size()]));
- }
- } catch (RepositoryException e) {
- throw new JcrException("Cannot set checksum on " + node, e);
- }
- }
-
- /** Replace all checksums. */
- public static void setChecksums(Node node, List<String> checksums) {
- try {
- node.setProperty(JcrxName.JCRX_SUM, checksums.toArray(new String[checksums.size()]));
- } catch (RepositoryException e) {
- throw new JcrException("Cannot set checksums on " + node, e);
- }
- }
-
- /** Replace all checksums. */
- public static List<String> getChecksums(Node node) {
- try {
- List<String> res = new ArrayList<>();
- if (!node.hasProperty(JcrxName.JCRX_SUM))
- return res;
- Property property = node.getProperty(JcrxName.JCRX_SUM);
- for (Value value : property.getValues()) {
- res.add(value.getString());
- }
- return res;
- } catch (RepositoryException e) {
- throw new JcrException("Cannot get checksums from " + node, e);
- }
- }
-
-// /** Replace all checksums with this single one. */
-// public static void setChecksum(Node node, String checksum) {
-// setChecksums(node, Collections.singletonList(checksum));
-// }
-
- /** Retrieves the checksum with this algorithm, or null if not found. */
- public static String getChecksum(Node node, String algorithm) {
- int stringLength;
- switch (algorithm) {
- case MD5:
- stringLength = LENGTH_MD5;
- break;
- case SHA1:
- stringLength = LENGTH_SHA1;
- break;
- case SHA256:
- stringLength = LENGTH_SHA256;
- break;
- case SHA512:
- stringLength = LENGTH_SHA512;
- break;
- default:
- throw new IllegalArgumentException("Unkown algorithm " + algorithm);
- }
- return getChecksum(node, stringLength);
- }
-
- /** Retrieves the checksum with this string length, or null if not found. */
- public static String getChecksum(Node node, int stringLength) {
- try {
- if (!node.hasProperty(JcrxName.JCRX_SUM))
- return null;
- Property property = node.getProperty(JcrxName.JCRX_SUM);
- for (Value value : property.getValues()) {
- String str = value.getString();
- if (str.length() == stringLength)
- return str;
- }
- return null;
- } catch (RepositoryException e) {
- throw new IllegalStateException("Cannot get checksum for " + node, e);
- }
- }
-
-}
+++ /dev/null
-package org.argeo.jcr;
-
-/** Names declared by the JCR extensions. */
-public interface JcrxName {
- /** The multiple property holding various coherent checksums. */
- public final static String JCRX_SUM = "{http://www.argeo.org/ns/jcrx}sum";
-}
+++ /dev/null
-package org.argeo.jcr;
-
-/** Node types declared by the JCR extensions. */
-public interface JcrxType {
- /**
- * Node type for an XML value, which will be serialized in XML as an element
- * containing text.
- */
- public final static String JCRX_XMLVALUE = "{http://www.argeo.org/ns/jcrx}xmlvalue";
-
- /** Node type for the node containing the text. */
- public final static String JCRX_XMLTEXT = "{http://www.argeo.org/ns/jcrx}xmltext";
-
- /** Mixin node type for a set of checksums. */
- public final static String JCRX_CSUM = "{http://www.argeo.org/ns/jcrx}csum";
-
-}
+++ /dev/null
-package org.argeo.jcr;
-
-import javax.jcr.Value;
-
-/** The result of the comparison of two JCR properties. */
-public class PropertyDiff {
- public final static Integer MODIFIED = 0;
- public final static Integer ADDED = 1;
- public final static Integer REMOVED = 2;
-
- private final Integer type;
- private final String relPath;
- private final Value referenceValue;
- private final Value newValue;
-
- public PropertyDiff(Integer type, String relPath, Value referenceValue, Value newValue) {
- super();
-
- if (type == MODIFIED) {
- if (referenceValue == null || newValue == null)
- throw new IllegalArgumentException("Reference and new values must be specified.");
- } else if (type == ADDED) {
- if (referenceValue != null || newValue == null)
- throw new IllegalArgumentException("New value and only it must be specified.");
- } else if (type == REMOVED) {
- if (referenceValue == null || newValue != null)
- throw new IllegalArgumentException("Reference value and only it must be specified.");
- } else {
- throw new IllegalArgumentException("Unkown diff type " + type);
- }
-
- if (relPath == null)
- throw new IllegalArgumentException("Relative path must be specified");
-
- this.type = type;
- this.relPath = relPath;
- this.referenceValue = referenceValue;
- this.newValue = newValue;
- }
-
- public Integer getType() {
- return type;
- }
-
- public String getRelPath() {
- return relPath;
- }
-
- public Value getReferenceValue() {
- return referenceValue;
- }
-
- public Value getNewValue() {
- return newValue;
- }
-
-}
+++ /dev/null
-package org.argeo.jcr;
-
-import java.security.Principal;
-
-/** Canonical implementation of a {@link Principal} */
-class SimplePrincipal implements Principal {
- private final String name;
-
- public SimplePrincipal(String name) {
- if (name == null)
- throw new IllegalArgumentException("Principal name cannot be null");
- this.name = name;
- }
-
- public String getName() {
- return name;
- }
-
- @Override
- public int hashCode() {
- return name.hashCode();
- }
-
- @Override
- public boolean equals(Object obj) {
- if (obj == null)
- return false;
- if (obj instanceof Principal)
- return name.equals((((Principal) obj).getName()));
- return name.equals(obj.toString());
- }
-
- @Override
- protected Object clone() throws CloneNotSupportedException {
- return new SimplePrincipal(name);
- }
-
- @Override
- public String toString() {
- return name;
- }
-
-}
+++ /dev/null
-package org.argeo.jcr;
-
-import java.lang.reflect.InvocationHandler;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.lang.reflect.Proxy;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-
-import javax.jcr.LoginException;
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.SimpleCredentials;
-
-import org.argeo.api.cms.CmsLog;
-
-/** Proxy JCR sessions and attach them to calling threads. */
-@Deprecated
-public abstract class ThreadBoundJcrSessionFactory {
- private final static CmsLog log = CmsLog.getLog(ThreadBoundJcrSessionFactory.class);
-
- private Repository repository;
- /** can be injected as list, only used if repository is null */
- private List<Repository> repositories;
-
- private ThreadLocal<Session> session = new ThreadLocal<Session>();
- private final Session proxiedSession;
- /** If workspace is null, default will be used. */
- private String workspace = null;
-
- private String defaultUsername = "demo";
- private String defaultPassword = "demo";
- private Boolean forceDefaultCredentials = false;
-
- private boolean active = true;
-
- // monitoring
- private final List<Thread> threads = Collections.synchronizedList(new ArrayList<Thread>());
- private final Map<Long, Session> activeSessions = Collections.synchronizedMap(new HashMap<Long, Session>());
- private MonitoringThread monitoringThread;
-
- public ThreadBoundJcrSessionFactory() {
- Class<?>[] interfaces = { Session.class };
- proxiedSession = (Session) Proxy.newProxyInstance(ThreadBoundJcrSessionFactory.class.getClassLoader(),
- interfaces, new JcrSessionInvocationHandler());
- }
-
- /** Logs in to the repository using various strategies. */
- protected synchronized Session login() {
- if (!isActive())
- throw new IllegalStateException("Thread bound session factory inactive");
-
- // discard session previously attached to this thread
- Thread thread = Thread.currentThread();
- if (activeSessions.containsKey(thread.getId())) {
- Session oldSession = activeSessions.remove(thread.getId());
- oldSession.logout();
- session.remove();
- }
-
- Session newSession = null;
- // first try to login without credentials, assuming the underlying login
- // module will have dealt with authentication (typically using Spring
- // Security)
- if (!forceDefaultCredentials)
- try {
- newSession = repository().login(workspace);
- } catch (LoginException e1) {
- log.warn("Cannot login without credentials: " + e1.getMessage());
- // invalid credentials, go to the next step
- } catch (RepositoryException e1) {
- // other kind of exception, fail
- throw new JcrException("Cannot log in to repository", e1);
- }
-
- // log using default username / password (useful for testing purposes)
- if (newSession == null)
- try {
- SimpleCredentials sc = new SimpleCredentials(defaultUsername, defaultPassword.toCharArray());
- newSession = repository().login(sc, workspace);
- } catch (RepositoryException e) {
- throw new JcrException("Cannot log in to repository", e);
- }
-
- session.set(newSession);
- // Log and monitor new session
- if (log.isTraceEnabled())
- log.trace("Logged in to JCR session " + newSession + "; userId=" + newSession.getUserID());
-
- // monitoring
- activeSessions.put(thread.getId(), newSession);
- threads.add(thread);
- return newSession;
- }
-
- public Object getObject() {
- return proxiedSession;
- }
-
- public void init() throws Exception {
- // log.error("SHOULD NOT BE USED ANYMORE");
- monitoringThread = new MonitoringThread();
- monitoringThread.start();
- }
-
- public void dispose() throws Exception {
- // if (activeSessions.size() == 0)
- // return;
-
- if (log.isTraceEnabled())
- log.trace("Cleaning up " + activeSessions.size() + " active JCR sessions...");
-
- deactivate();
- for (Session sess : activeSessions.values()) {
- JcrUtils.logoutQuietly(sess);
- }
- activeSessions.clear();
- }
-
- protected Boolean isActive() {
- return active;
- }
-
- protected synchronized void deactivate() {
- active = false;
- notifyAll();
- }
-
- protected synchronized void removeSession(Thread thread) {
- if (!isActive())
- return;
- activeSessions.remove(thread.getId());
- threads.remove(thread);
- }
-
- protected synchronized void cleanDeadThreads() {
- if (!isActive())
- return;
- Iterator<Thread> it = threads.iterator();
- while (it.hasNext()) {
- Thread thread = it.next();
- if (!thread.isAlive() && isActive()) {
- if (activeSessions.containsKey(thread.getId())) {
- Session session = activeSessions.get(thread.getId());
- activeSessions.remove(thread.getId());
- session.logout();
- if (log.isTraceEnabled())
- log.trace("Cleaned up JCR session (userID=" + session.getUserID() + ") from dead thread "
- + thread.getId());
- }
- it.remove();
- }
- }
- try {
- wait(1000);
- } catch (InterruptedException e) {
- // silent
- }
- }
-
- public Class<? extends Session> getObjectType() {
- return Session.class;
- }
-
- public boolean isSingleton() {
- return true;
- }
-
- /**
- * Called before a method is actually called, allowing to check the session or
- * re-login it (e.g. if authentication has changed). The default implementation
- * returns the session.
- */
- protected Session preCall(Session session) {
- return session;
- }
-
- protected Repository repository() {
- if (repository != null)
- return repository;
- if (repositories != null) {
- // hardened for OSGi dynamic services
- Iterator<Repository> it = repositories.iterator();
- if (it.hasNext())
- return it.next();
- }
- throw new IllegalStateException("No repository injected");
- }
-
- // /** Useful for declarative registration of OSGi services (blueprint) */
- // public void register(Repository repository, Map<?, ?> params) {
- // this.repository = repository;
- // }
- //
- // /** Useful for declarative registration of OSGi services (blueprint) */
- // public void unregister(Repository repository, Map<?, ?> params) {
- // this.repository = null;
- // }
-
- public void setRepository(Repository repository) {
- this.repository = repository;
- }
-
- public void setRepositories(List<Repository> repositories) {
- this.repositories = repositories;
- }
-
- public void setDefaultUsername(String defaultUsername) {
- this.defaultUsername = defaultUsername;
- }
-
- public void setDefaultPassword(String defaultPassword) {
- this.defaultPassword = defaultPassword;
- }
-
- public void setForceDefaultCredentials(Boolean forceDefaultCredentials) {
- this.forceDefaultCredentials = forceDefaultCredentials;
- }
-
- public void setWorkspace(String workspace) {
- this.workspace = workspace;
- }
-
- protected class JcrSessionInvocationHandler implements InvocationHandler {
-
- public Object invoke(Object proxy, Method method, Object[] args) throws Throwable, RepositoryException {
- Session threadSession = session.get();
- if (threadSession == null) {
- if ("logout".equals(method.getName()))// no need to login
- return Void.TYPE;
- else if ("toString".equals(method.getName()))// maybe logging
- return "Uninitialized Argeo thread bound JCR session";
- threadSession = login();
- }
-
- preCall(threadSession);
- Object ret;
- try {
- ret = method.invoke(threadSession, args);
- } catch (InvocationTargetException e) {
- Throwable cause = e.getCause();
- if (cause instanceof RepositoryException)
- throw (RepositoryException) cause;
- else
- throw cause;
- }
- if ("logout".equals(method.getName())) {
- session.remove();
- Thread thread = Thread.currentThread();
- removeSession(thread);
- if (log.isTraceEnabled())
- log.trace("Logged out JCR session (userId=" + threadSession.getUserID() + ") on thread "
- + thread.getId());
- }
- return ret;
- }
- }
-
- /** Monitors registered thread in order to clean up dead ones. */
- private class MonitoringThread extends Thread {
-
- public MonitoringThread() {
- super("ThreadBound JCR Session Monitor");
- }
-
- @Override
- public void run() {
- while (isActive()) {
- cleanDeadThreads();
- }
- }
-
- }
-}
+++ /dev/null
-package org.argeo.jcr;
-
-import java.util.Calendar;
-import java.util.Map;
-
-/**
- * Generic Object that enables the creation of history reports based on a JCR
- * versionable node. userId and creation date are added to the map of
- * PropertyDiff.
- *
- * These two fields might be null
- *
- */
-public class VersionDiff {
-
- private String userId;
- private Map<String, PropertyDiff> diffs;
- private Calendar updateTime;
-
- public VersionDiff(String userId, Calendar updateTime,
- Map<String, PropertyDiff> diffs) {
- this.userId = userId;
- this.updateTime = updateTime;
- this.diffs = diffs;
- }
-
- public String getUserId() {
- return userId;
- }
-
- public Map<String, PropertyDiff> getDiffs() {
- return diffs;
- }
-
- public Calendar getUpdateTime() {
- return updateTime;
- }
-}
+++ /dev/null
-package org.argeo.jcr.fs;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.ByteBuffer;
-import java.nio.channels.Channels;
-import java.nio.channels.FileChannel;
-import java.nio.channels.ReadableByteChannel;
-import java.nio.channels.SeekableByteChannel;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.StandardOpenOption;
-
-import javax.jcr.Binary;
-import javax.jcr.Node;
-import javax.jcr.Property;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.nodetype.NodeType;
-
-import org.argeo.jcr.JcrUtils;
-
-/** A read/write {@link SeekableByteChannel} based on a {@link Binary}. */
-public class BinaryChannel implements SeekableByteChannel {
- private final Node file;
- private Binary binary;
- private boolean open = true;
-
- private long position = 0;
-
- private FileChannel fc = null;
-
- public BinaryChannel(Node file, Path path) throws RepositoryException, IOException {
- this.file = file;
- Session session = file.getSession();
- synchronized (session) {
- if (file.isNodeType(NodeType.NT_FILE)) {
- if (file.hasNode(Node.JCR_CONTENT)) {
- Node data = file.getNode(Property.JCR_CONTENT);
- this.binary = data.getProperty(Property.JCR_DATA).getBinary();
- } else {
- Node data = file.addNode(Node.JCR_CONTENT, NodeType.NT_UNSTRUCTURED);
- data.addMixin(NodeType.MIX_LAST_MODIFIED);
- try (InputStream in = new ByteArrayInputStream(new byte[0])) {
- this.binary = data.getSession().getValueFactory().createBinary(in);
- }
- data.setProperty(Property.JCR_DATA, this.binary);
-
- // MIME type
- String mime = Files.probeContentType(path);
- // String mime = fileTypeMap.getContentType(file.getName());
- data.setProperty(Property.JCR_MIMETYPE, mime);
-
- session.refresh(true);
- session.save();
- session.notifyAll();
- }
- } else {
- throw new IllegalArgumentException(
- "Unsupported file node " + file + " (" + file.getPrimaryNodeType() + ")");
- }
- }
- }
-
- @Override
- public synchronized boolean isOpen() {
- return open;
- }
-
- @Override
- public synchronized void close() throws IOException {
- if (isModified()) {
- Binary newBinary = null;
- try {
- Session session = file.getSession();
- synchronized (session) {
- fc.position(0);
- InputStream in = Channels.newInputStream(fc);
- newBinary = session.getValueFactory().createBinary(in);
- file.getNode(Property.JCR_CONTENT).setProperty(Property.JCR_DATA, newBinary);
- session.refresh(true);
- session.save();
- open = false;
- session.notifyAll();
- }
- } catch (RepositoryException e) {
- throw new IOException("Cannot close " + file, e);
- } finally {
- JcrUtils.closeQuietly(newBinary);
- // IOUtils.closeQuietly(fc);
- if (fc != null) {
- fc.close();
- }
- }
- } else {
- clearReadState();
- open = false;
- }
- }
-
- @Override
- public int read(ByteBuffer dst) throws IOException {
- if (isModified()) {
- return fc.read(dst);
- } else {
-
- try {
- int read;
- byte[] arr = dst.array();
- read = binary.read(arr, position);
-
- if (read != -1)
- position = position + read;
- return read;
- } catch (RepositoryException e) {
- throw new IOException("Cannot read into buffer", e);
- }
- }
- }
-
- @Override
- public int write(ByteBuffer src) throws IOException {
- int written = getFileChannel().write(src);
- return written;
- }
-
- @Override
- public long position() throws IOException {
- if (isModified())
- return getFileChannel().position();
- else
- return position;
- }
-
- @Override
- public SeekableByteChannel position(long newPosition) throws IOException {
- if (isModified()) {
- getFileChannel().position(position);
- } else {
- this.position = newPosition;
- }
- return this;
- }
-
- @Override
- public long size() throws IOException {
- if (isModified()) {
- return getFileChannel().size();
- } else {
- try {
- return binary.getSize();
- } catch (RepositoryException e) {
- throw new IOException("Cannot get size", e);
- }
- }
- }
-
- @Override
- public SeekableByteChannel truncate(long size) throws IOException {
- getFileChannel().truncate(size);
- return this;
- }
-
- private FileChannel getFileChannel() throws IOException {
- try {
- if (fc == null) {
- Path tempPath = Files.createTempFile(getClass().getSimpleName(), null);
- fc = FileChannel.open(tempPath, StandardOpenOption.WRITE, StandardOpenOption.READ,
- StandardOpenOption.DELETE_ON_CLOSE, StandardOpenOption.SPARSE);
- ReadableByteChannel readChannel = Channels.newChannel(binary.getStream());
- fc.transferFrom(readChannel, 0, binary.getSize());
- clearReadState();
- }
- return fc;
- } catch (RepositoryException e) {
- throw new IOException("Cannot get temp file channel", e);
- }
- }
-
- private boolean isModified() {
- return fc != null;
- }
-
- private void clearReadState() {
- position = -1;
- JcrUtils.closeQuietly(binary);
- binary = null;
- }
-}
+++ /dev/null
-package org.argeo.jcr.fs;
-
-import static javax.jcr.Property.JCR_CREATED;
-import static javax.jcr.Property.JCR_LAST_MODIFIED;
-
-import java.nio.file.attribute.FileTime;
-import java.time.Instant;
-
-import javax.jcr.Binary;
-import javax.jcr.Node;
-import javax.jcr.Property;
-import javax.jcr.RepositoryException;
-import javax.jcr.nodetype.NodeType;
-
-import org.argeo.jcr.JcrUtils;
-
-public class JcrBasicfileAttributes implements NodeFileAttributes {
- private final Node node;
-
- private final static FileTime EPOCH = FileTime.fromMillis(0);
-
- public JcrBasicfileAttributes(Node node) {
- if (node == null)
- throw new JcrFsException("Node underlying the attributes cannot be null");
- this.node = node;
- }
-
- @Override
- public FileTime lastModifiedTime() {
- try {
- if (node.hasProperty(JCR_LAST_MODIFIED)) {
- Instant instant = node.getProperty(JCR_LAST_MODIFIED).getDate().toInstant();
- return FileTime.from(instant);
- } else if (node.hasProperty(JCR_CREATED)) {
- Instant instant = node.getProperty(JCR_CREATED).getDate().toInstant();
- return FileTime.from(instant);
- }
-// if (node.isNodeType(NodeType.MIX_LAST_MODIFIED)) {
-// Instant instant = node.getProperty(Property.JCR_LAST_MODIFIED).getDate().toInstant();
-// return FileTime.from(instant);
-// }
- return EPOCH;
- } catch (RepositoryException e) {
- throw new JcrFsException("Cannot get last modified time", e);
- }
- }
-
- @Override
- public FileTime lastAccessTime() {
- return lastModifiedTime();
- }
-
- @Override
- public FileTime creationTime() {
- try {
- if (node.hasProperty(JCR_CREATED)) {
- Instant instant = node.getProperty(JCR_CREATED).getDate().toInstant();
- return FileTime.from(instant);
- } else if (node.hasProperty(JCR_LAST_MODIFIED)) {
- Instant instant = node.getProperty(JCR_LAST_MODIFIED).getDate().toInstant();
- return FileTime.from(instant);
- }
-// if (node.isNodeType(NodeType.MIX_CREATED)) {
-// Instant instant = node.getProperty(JCR_CREATED).getDate().toInstant();
-// return FileTime.from(instant);
-// }
- return EPOCH;
- } catch (RepositoryException e) {
- throw new JcrFsException("Cannot get creation time", e);
- }
- }
-
- @Override
- public boolean isRegularFile() {
- try {
- return node.isNodeType(NodeType.NT_FILE);
- } catch (RepositoryException e) {
- throw new JcrFsException("Cannot check if regular file", e);
- }
- }
-
- @Override
- public boolean isDirectory() {
- try {
- if (node.isNodeType(NodeType.NT_FOLDER))
- return true;
- // all other non file nodes
- return !(node.isNodeType(NodeType.NT_FILE) || node.isNodeType(NodeType.NT_LINKED_FILE));
- } catch (RepositoryException e) {
- throw new JcrFsException("Cannot check if directory", e);
- }
- }
-
- @Override
- public boolean isSymbolicLink() {
- try {
- return node.isNodeType(NodeType.NT_LINKED_FILE);
- } catch (RepositoryException e) {
- throw new JcrFsException("Cannot check if linked file", e);
- }
- }
-
- @Override
- public boolean isOther() {
- return !(isDirectory() || isRegularFile() || isSymbolicLink());
- }
-
- @Override
- public long size() {
- if (isRegularFile()) {
- Binary binary = null;
- try {
- binary = node.getNode(Property.JCR_CONTENT).getProperty(Property.JCR_DATA).getBinary();
- return binary.getSize();
- } catch (RepositoryException e) {
- throw new JcrFsException("Cannot check size", e);
- } finally {
- JcrUtils.closeQuietly(binary);
- }
- }
- return -1;
- }
-
- @Override
- public Object fileKey() {
- try {
- return node.getIdentifier();
- } catch (RepositoryException e) {
- throw new JcrFsException("Cannot get identifier", e);
- }
- }
-
- @Override
- public Node getNode() {
- return node;
- }
-
-}
+++ /dev/null
-package org.argeo.jcr.fs;
-
-import java.io.IOException;
-import java.nio.file.FileStore;
-import java.nio.file.FileSystem;
-import java.nio.file.Path;
-import java.nio.file.PathMatcher;
-import java.nio.file.WatchService;
-import java.nio.file.attribute.UserPrincipalLookupService;
-import java.nio.file.spi.FileSystemProvider;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.TreeMap;
-
-import javax.jcr.Credentials;
-import javax.jcr.Node;
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.nodetype.NodeType;
-
-import org.argeo.api.acr.fs.AbstractFsStore;
-import org.argeo.api.acr.fs.AbstractFsSystem;
-import org.argeo.jcr.Jcr;
-import org.argeo.jcr.JcrUtils;
-
-public class JcrFileSystem extends AbstractFsSystem<WorkspaceFileStore> {
- private final JcrFileSystemProvider provider;
-
- private final Repository repository;
- private Session session;
- private WorkspaceFileStore baseFileStore;
-
- private Map<String, WorkspaceFileStore> mounts = new TreeMap<>();
-
- private String userHomePath = null;
-
- @Deprecated
- public JcrFileSystem(JcrFileSystemProvider provider, Session session) throws IOException {
- super();
- this.provider = provider;
- baseFileStore = new WorkspaceFileStore(null, session.getWorkspace());
- this.session = session;
-// Node userHome = provider.getUserHome(session);
-// if (userHome != null)
-// try {
-// userHomePath = userHome.getPath();
-// } catch (RepositoryException e) {
-// throw new IOException("Cannot retrieve user home path", e);
-// }
- this.repository = null;
- }
-
- public JcrFileSystem(JcrFileSystemProvider provider, Repository repository) throws IOException {
- this(provider, repository, null);
- }
-
- public JcrFileSystem(JcrFileSystemProvider provider, Repository repository, Credentials credentials)
- throws IOException {
- super();
- this.provider = provider;
- this.repository = repository;
- try {
- this.session = credentials == null ? repository.login() : repository.login(credentials);
- baseFileStore = new WorkspaceFileStore(null, session.getWorkspace());
- workspaces: for (String workspaceName : baseFileStore.getWorkspace().getAccessibleWorkspaceNames()) {
- if (workspaceName.equals(baseFileStore.getWorkspace().getName()))
- continue workspaces;// do not mount base
- if (workspaceName.equals("security")) {
- continue workspaces;// do not mount security workspace
- // TODO make it configurable
- }
- Session mountSession = credentials == null ? repository.login(workspaceName)
- : repository.login(credentials, workspaceName);
- String mountPath = JcrPath.separator + workspaceName;
- mounts.put(mountPath, new WorkspaceFileStore(mountPath, mountSession.getWorkspace()));
- }
- } catch (RepositoryException e) {
- throw new IOException("Cannot initialise file system", e);
- }
-
- Node userHome = provider.getUserHome(repository);
- if (userHome != null)
- try {
- userHomePath = toFsPath(userHome);
- } catch (RepositoryException e) {
- throw new IOException("Cannot retrieve user home path", e);
- } finally {
- JcrUtils.logoutQuietly(Jcr.session(userHome));
- }
- }
-
- public String toFsPath(Node node) throws RepositoryException {
- return getFileStore(node).toFsPath(node);
- }
-
- /** Whether this node should be skipped in directory listings */
- public boolean skipNode(Node node) throws RepositoryException {
- if (node.isNodeType(NodeType.NT_HIERARCHY_NODE))
- return false;
- return true;
- }
-
- public String getUserHomePath() {
- return userHomePath;
- }
-
- public WorkspaceFileStore getFileStore(String path) {
- WorkspaceFileStore res = baseFileStore;
- for (String mountPath : mounts.keySet()) {
- if (path.equals(mountPath))
- return mounts.get(mountPath);
- if (path.startsWith(mountPath + JcrPath.separator)) {
- res = mounts.get(mountPath);
- // we keep the last one
- }
- }
- assert res != null;
- return res;
- }
-
- public WorkspaceFileStore getFileStore(Node node) throws RepositoryException {
- String workspaceName = node.getSession().getWorkspace().getName();
- if (workspaceName.equals(baseFileStore.getWorkspace().getName()))
- return baseFileStore;
- for (String mountPath : mounts.keySet()) {
- WorkspaceFileStore fileStore = mounts.get(mountPath);
- if (workspaceName.equals(fileStore.getWorkspace().getName()))
- return fileStore;
- }
- throw new IllegalStateException("No workspace mount found for " + node + " in workspace " + workspaceName);
- }
-
- public Iterator<JcrPath> listDirectMounts(Path base) {
- String baseStr = base.toString();
- Set<JcrPath> res = new HashSet<>();
- mounts: for (String mountPath : mounts.keySet()) {
- if (mountPath.equals(baseStr))
- continue mounts;
- if (mountPath.startsWith(baseStr)) {
- JcrPath path = new JcrPath(this, mountPath);
- Path relPath = base.relativize(path);
- if (relPath.getNameCount() == 1)
- res.add(path);
- }
- }
- return res.iterator();
- }
-
- public WorkspaceFileStore getBaseFileStore() {
- return baseFileStore;
- }
-
- @Override
- public FileSystemProvider provider() {
- return provider;
- }
-
- @Override
- public void close() throws IOException {
- JcrUtils.logoutQuietly(session);
- for (String mountPath : mounts.keySet()) {
- WorkspaceFileStore fileStore = mounts.get(mountPath);
- try {
- fileStore.close();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
-
- @Override
- public boolean isOpen() {
- return session.isLive();
- }
-
- @Override
- public boolean isReadOnly() {
- return false;
- }
-
- @Override
- public String getSeparator() {
- return JcrPath.separator;
- }
-
- @Override
- public Iterable<Path> getRootDirectories() {
- Set<Path> single = new HashSet<>();
- single.add(new JcrPath(this, JcrPath.separator));
- return single;
- }
-
- @Override
- public Iterable<FileStore> getFileStores() {
- List<FileStore> stores = new ArrayList<>();
- stores.add(baseFileStore);
- stores.addAll(mounts.values());
- return stores;
- }
-
- @Override
- public Set<String> supportedFileAttributeViews() {
- try {
- String[] prefixes = session.getNamespacePrefixes();
- Set<String> res = new HashSet<>();
- for (String prefix : prefixes)
- res.add(prefix);
- res.add("basic");
- return res;
- } catch (RepositoryException e) {
- throw new JcrFsException("Cannot get supported file attributes views", e);
- }
- }
-
- @Override
- public Path getPath(String first, String... more) {
- StringBuilder sb = new StringBuilder(first);
- // TODO Make it more robust
- for (String part : more)
- sb.append('/').append(part);
- return new JcrPath(this, sb.toString());
- }
-
- @Override
- public PathMatcher getPathMatcher(String syntaxAndPattern) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public UserPrincipalLookupService getUserPrincipalLookupService() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public WatchService newWatchService() throws IOException {
- throw new UnsupportedOperationException();
- }
-
-// public Session getSession() {
-// return session;
-// }
-
- public Repository getRepository() {
- return repository;
- }
-
-}
+++ /dev/null
-package org.argeo.jcr.fs;
-
-import java.io.IOException;
-import java.nio.channels.SeekableByteChannel;
-import java.nio.file.AccessMode;
-import java.nio.file.CopyOption;
-import java.nio.file.DirectoryNotEmptyException;
-import java.nio.file.DirectoryStream;
-import java.nio.file.DirectoryStream.Filter;
-import java.nio.file.FileStore;
-import java.nio.file.LinkOption;
-import java.nio.file.NoSuchFileException;
-import java.nio.file.OpenOption;
-import java.nio.file.Path;
-import java.nio.file.attribute.BasicFileAttributes;
-import java.nio.file.attribute.FileAttribute;
-import java.nio.file.attribute.FileAttributeView;
-import java.nio.file.spi.FileSystemProvider;
-import java.util.Calendar;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Set;
-
-import javax.jcr.Node;
-import javax.jcr.Property;
-import javax.jcr.PropertyIterator;
-import javax.jcr.PropertyType;
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.nodetype.NodeType;
-import javax.jcr.nodetype.PropertyDefinition;
-
-import org.argeo.jcr.JcrUtils;
-
-/** Operations on a {@link JcrFileSystem}. */
-public abstract class JcrFileSystemProvider extends FileSystemProvider {
-
- @Override
- public SeekableByteChannel newByteChannel(Path path, Set<? extends OpenOption> options, FileAttribute<?>... attrs)
- throws IOException {
- Node node = toNode(path);
- try {
- if (node == null) {
- Node parent = toNode(path.getParent());
- if (parent == null)
- throw new IOException("No parent directory for " + path);
- if (parent.getPrimaryNodeType().isNodeType(NodeType.NT_FILE)
- || parent.getPrimaryNodeType().isNodeType(NodeType.NT_LINKED_FILE))
- throw new IOException(path + " parent is a file");
-
- String fileName = path.getFileName().toString();
- fileName = Text.escapeIllegalJcrChars(fileName);
- node = parent.addNode(fileName, NodeType.NT_FILE);
- node.addMixin(NodeType.MIX_CREATED);
-// node.addMixin(NodeType.MIX_LAST_MODIFIED);
- }
- if (!node.isNodeType(NodeType.NT_FILE))
- throw new UnsupportedOperationException(node + " must be a file");
- return new BinaryChannel(node, path);
- } catch (RepositoryException e) {
- discardChanges(node);
- throw new IOException("Cannot read file", e);
- }
- }
-
- @Override
- public DirectoryStream<Path> newDirectoryStream(Path dir, Filter<? super Path> filter) throws IOException {
- try {
- Node base = toNode(dir);
- if (base == null)
- throw new IOException(dir + " is not a JCR node");
- JcrFileSystem fileSystem = (JcrFileSystem) dir.getFileSystem();
- return new NodeDirectoryStream(fileSystem, base.getNodes(), fileSystem.listDirectMounts(dir), filter);
- } catch (RepositoryException e) {
- throw new IOException("Cannot list directory", e);
- }
- }
-
- @Override
- public void createDirectory(Path dir, FileAttribute<?>... attrs) throws IOException {
- Node node = toNode(dir);
- try {
- if (node == null) {
- Node parent = toNode(dir.getParent());
- if (parent == null)
- throw new IOException("Parent of " + dir + " does not exist");
- Session session = parent.getSession();
- synchronized (session) {
- if (parent.getPrimaryNodeType().isNodeType(NodeType.NT_FILE)
- || parent.getPrimaryNodeType().isNodeType(NodeType.NT_LINKED_FILE))
- throw new IOException(dir + " parent is a file");
- String fileName = dir.getFileName().toString();
- fileName = Text.escapeIllegalJcrChars(fileName);
- node = parent.addNode(fileName, NodeType.NT_FOLDER);
- node.addMixin(NodeType.MIX_CREATED);
- node.addMixin(NodeType.MIX_LAST_MODIFIED);
- save(session);
- }
- } else {
- // if (!node.getPrimaryNodeType().isNodeType(NodeType.NT_FOLDER))
- // throw new FileExistsException(dir + " exists and is not a directory");
- }
- } catch (RepositoryException e) {
- discardChanges(node);
- throw new IOException("Cannot create directory " + dir, e);
- }
- }
-
- @Override
- public void delete(Path path) throws IOException {
- Node node = toNode(path);
- try {
- if (node == null)
- throw new NoSuchFileException(path + " does not exist");
- Session session = node.getSession();
- synchronized (session) {
- session.refresh(false);
- if (node.getPrimaryNodeType().isNodeType(NodeType.NT_FILE))
- node.remove();
- else if (node.getPrimaryNodeType().isNodeType(NodeType.NT_FOLDER)) {
- if (node.hasNodes())// TODO check only files
- throw new DirectoryNotEmptyException(path.toString());
- node.remove();
- }
- save(session);
- }
- } catch (RepositoryException e) {
- discardChanges(node);
- throw new IOException("Cannot delete " + path, e);
- }
-
- }
-
- @Override
- public void copy(Path source, Path target, CopyOption... options) throws IOException {
- Node sourceNode = toNode(source);
- Node targetNode = toNode(target);
- try {
- Session targetSession = targetNode.getSession();
- synchronized (targetSession) {
- JcrUtils.copy(sourceNode, targetNode);
- save(targetSession);
- }
- } catch (RepositoryException e) {
- discardChanges(sourceNode);
- discardChanges(targetNode);
- throw new IOException("Cannot copy from " + source + " to " + target, e);
- }
- }
-
- @Override
- public void move(Path source, Path target, CopyOption... options) throws IOException {
- JcrFileSystem sourceFileSystem = (JcrFileSystem) source.getFileSystem();
- WorkspaceFileStore sourceStore = sourceFileSystem.getFileStore(source.toString());
- WorkspaceFileStore targetStore = sourceFileSystem.getFileStore(target.toString());
- try {
- if (sourceStore.equals(targetStore)) {
- sourceStore.getWorkspace().move(sourceStore.toJcrPath(source.toString()),
- targetStore.toJcrPath(target.toString()));
- } else {
- // TODO implement it
- throw new UnsupportedOperationException("Can only move paths within the same workspace.");
- }
- } catch (RepositoryException e) {
- throw new IOException("Cannot move from " + source + " to " + target, e);
- }
-
-// Node sourceNode = toNode(source);
-// try {
-// Session session = sourceNode.getSession();
-// synchronized (session) {
-// session.move(sourceNode.getPath(), target.toString());
-// save(session);
-// }
-// } catch (RepositoryException e) {
-// discardChanges(sourceNode);
-// throw new IOException("Cannot move from " + source + " to " + target, e);
-// }
- }
-
- @Override
- public boolean isSameFile(Path path, Path path2) throws IOException {
- if (path.getFileSystem() != path2.getFileSystem())
- return false;
- boolean equ = path.equals(path2);
- if (equ)
- return true;
- else {
- try {
- Node node = toNode(path);
- Node node2 = toNode(path2);
- return node.isSame(node2);
- } catch (RepositoryException e) {
- throw new IOException("Cannot check whether " + path + " and " + path2 + " are same", e);
- }
- }
-
- }
-
- @Override
- public boolean isHidden(Path path) throws IOException {
- return path.getFileName().toString().charAt(0) == '.';
- }
-
- @Override
- public FileStore getFileStore(Path path) throws IOException {
- JcrFileSystem fileSystem = (JcrFileSystem) path.getFileSystem();
- return fileSystem.getFileStore(path.toString());
- }
-
- @Override
- public void checkAccess(Path path, AccessMode... modes) throws IOException {
- Node node = toNode(path);
- if (node == null)
- throw new NoSuchFileException(path + " does not exist");
- // TODO check access via JCR api
- }
-
- @Override
- public <V extends FileAttributeView> V getFileAttributeView(Path path, Class<V> type, LinkOption... options) {
- throw new UnsupportedOperationException();
- }
-
- @SuppressWarnings("unchecked")
- @Override
- public <A extends BasicFileAttributes> A readAttributes(Path path, Class<A> type, LinkOption... options)
- throws IOException {
- // TODO check if assignable
- Node node = toNode(path);
- if (node == null) {
- throw new IOException("JCR node not found for " + path);
- }
- return (A) new JcrBasicfileAttributes(node);
- }
-
- @Override
- public Map<String, Object> readAttributes(Path path, String attributes, LinkOption... options) throws IOException {
- try {
- Node node = toNode(path);
- String pattern = attributes.replace(',', '|');
- Map<String, Object> res = new HashMap<String, Object>();
- PropertyIterator it = node.getProperties(pattern);
- props: while (it.hasNext()) {
- Property prop = it.nextProperty();
- PropertyDefinition pd = prop.getDefinition();
- if (pd.isMultiple())
- continue props;
- int requiredType = pd.getRequiredType();
- switch (requiredType) {
- case PropertyType.LONG:
- res.put(prop.getName(), prop.getLong());
- break;
- case PropertyType.DOUBLE:
- res.put(prop.getName(), prop.getDouble());
- break;
- case PropertyType.BOOLEAN:
- res.put(prop.getName(), prop.getBoolean());
- break;
- case PropertyType.DATE:
- res.put(prop.getName(), prop.getDate());
- break;
- case PropertyType.BINARY:
- byte[] arr = JcrUtils.getBinaryAsBytes(prop);
- res.put(prop.getName(), arr);
- break;
- default:
- res.put(prop.getName(), prop.getString());
- }
- }
- return res;
- } catch (RepositoryException e) {
- throw new IOException("Cannot read attributes of " + path, e);
- }
- }
-
- @Override
- public void setAttribute(Path path, String attribute, Object value, LinkOption... options) throws IOException {
- Node node = toNode(path);
- try {
- Session session = node.getSession();
- synchronized (session) {
- if (value instanceof byte[]) {
- JcrUtils.setBinaryAsBytes(node, attribute, (byte[]) value);
- } else if (value instanceof Calendar) {
- node.setProperty(attribute, (Calendar) value);
- } else {
- node.setProperty(attribute, value.toString());
- }
- save(session);
- }
- } catch (RepositoryException e) {
- discardChanges(node);
- throw new IOException("Cannot set attribute " + attribute + " on " + path, e);
- }
- }
-
- protected Node toNode(Path path) {
- try {
- return ((JcrPath) path).getNode();
- } catch (RepositoryException e) {
- throw new JcrFsException("Cannot convert path " + path + " to JCR Node", e);
- }
- }
-
- /** Discard changes in the underlying session */
- protected void discardChanges(Node node) {
- if (node == null)
- return;
- try {
- // discard changes
- node.getSession().refresh(false);
- } catch (RepositoryException e) {
- e.printStackTrace();
- // TODO log out session?
- // TODO use Commons logging?
- }
- }
-
- /** Make sure save is robust. */
- protected void save(Session session) throws RepositoryException {
- session.refresh(true);
- session.save();
- session.notifyAll();
- }
-
- /**
- * To be overriden in order to support the ~ path, with an implementation
- * specific concept of user home.
- *
- * @return null by default
- */
- public Node getUserHome(Repository session) {
- return null;
- }
-
-}
+++ /dev/null
-package org.argeo.jcr.fs;
-
-
-/** Exception related to the JCR FS */
-public class JcrFsException extends RuntimeException {
- private static final long serialVersionUID = -7973896038244922980L;
-
- public JcrFsException(String message, Throwable e) {
- super(message, e);
- }
-
- public JcrFsException(String message) {
- super(message);
- }
-}
+++ /dev/null
-package org.argeo.jcr.fs;
-
-import java.nio.file.Path;
-
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-
-import org.argeo.api.acr.fs.AbstractFsPath;
-
-/** A {@link Path} which contains a reference to a JCR {@link Node}. */
-public class JcrPath extends AbstractFsPath<JcrFileSystem, WorkspaceFileStore> {
- final static String separator = "/";
- final static char separatorChar = '/';
-
-// private final JcrFileSystem fs;
-// /** null for non absolute paths */
-// private final WorkspaceFileStore fileStore;
-// private final String[] path;// null means root
-// private final boolean absolute;
-//
-// // optim
-// private final int hashCode;
-
- public JcrPath(JcrFileSystem filesSystem, String path) {
- super(filesSystem, path);
-// this.fs = filesSystem;
-// if (path == null)
-// throw new JcrFsException("Path cannot be null");
-// if (path.equals(separator)) {// root
-// this.path = null;
-// this.absolute = true;
-// this.hashCode = 0;
-// this.fileStore = fs.getBaseFileStore();
-// return;
-// } else if (path.equals("")) {// empty path
-// this.path = new String[] { "" };
-// this.absolute = false;
-// this.fileStore = null;
-// this.hashCode = "".hashCode();
-// return;
-// }
-//
-// if (path.equals("~")) {// home
-// path = filesSystem.getUserHomePath();
-// if (path == null)
-// throw new JcrFsException("No home directory available");
-// }
-//
-// this.absolute = path.charAt(0) == separatorChar ? true : false;
-//
-// this.fileStore = absolute ? fs.getFileStore(path) : null;
-//
-// String trimmedPath = path.substring(absolute ? 1 : 0,
-// path.charAt(path.length() - 1) == separatorChar ? path.length() - 1 : path.length());
-// this.path = trimmedPath.split(separator);
-// for (int i = 0; i < this.path.length; i++) {
-// this.path[i] = Text.unescapeIllegalJcrChars(this.path[i]);
-// }
-// this.hashCode = this.path[this.path.length - 1].hashCode();
-// assert !(absolute && fileStore == null);
- }
-
- public JcrPath(JcrFileSystem filesSystem, Node node) throws RepositoryException {
- this(filesSystem, filesSystem.getFileStore(node).toFsPath(node));
- }
-
- /** Internal optimisation */
- private JcrPath(JcrFileSystem filesSystem, WorkspaceFileStore fileStore, String[] path, boolean absolute) {
- super(filesSystem, fileStore, path, absolute);
-// this.fs = filesSystem;
-// this.path = path;
-// this.absolute = path == null ? true : absolute;
-// if (this.absolute && fileStore == null)
-// throw new IllegalArgumentException("Absolute path requires a file store");
-// if (!this.absolute && fileStore != null)
-// throw new IllegalArgumentException("A file store should not be provided for a relative path");
-// this.fileStore = fileStore;
-// this.hashCode = path == null ? 0 : path[path.length - 1].hashCode();
-// assert !(absolute && fileStore == null);
- }
-
- protected String cleanUpSegment(String segment) {
- return Text.unescapeIllegalJcrChars(segment);
- }
-
- @Override
- protected JcrPath newInstance(String path) {
- return new JcrPath(getFileSystem(), path);
- }
-
- @Override
- protected JcrPath newInstance(String[] segments, boolean absolute) {
- return new JcrPath(getFileSystem(), getFileStore(), segments, absolute);
-
- }
-
-// @Override
-// public FileSystem getFileSystem() {
-// return fs;
-// }
-//
-// @Override
-// public boolean isAbsolute() {
-// return absolute;
-// }
-//
-// @Override
-// public Path getRoot() {
-// if (path == null)
-// return this;
-// return new JcrPath(fs, separator);
-// }
-//
-// @Override
-// public String toString() {
-// return toFsPath(path);
-// }
-//
-// private String toFsPath(String[] path) {
-// if (path == null)
-// return "/";
-// StringBuilder sb = new StringBuilder();
-// if (isAbsolute())
-// sb.append('/');
-// for (int i = 0; i < path.length; i++) {
-// if (i != 0)
-// sb.append('/');
-// sb.append(path[i]);
-// }
-// return sb.toString();
-// }
-
-// @Deprecated
-// private String toJcrPath() {
-// return toJcrPath(path);
-// }
-//
-// @Deprecated
-// private String toJcrPath(String[] path) {
-// if (path == null)
-// return "/";
-// StringBuilder sb = new StringBuilder();
-// if (isAbsolute())
-// sb.append('/');
-// for (int i = 0; i < path.length; i++) {
-// if (i != 0)
-// sb.append('/');
-// sb.append(Text.escapeIllegalJcrChars(path[i]));
-// }
-// return sb.toString();
-// }
-
-// @Override
-// public Path getFileName() {
-// if (path == null)
-// return null;
-// return new JcrPath(fs, path[path.length - 1]);
-// }
-//
-// @Override
-// public Path getParent() {
-// if (path == null)
-// return null;
-// if (path.length == 1)// root
-// return new JcrPath(fs, separator);
-// String[] parentPath = Arrays.copyOfRange(path, 0, path.length - 1);
-// if (!absolute)
-// return new JcrPath(fs, null, parentPath, absolute);
-// else
-// return new JcrPath(fs, toFsPath(parentPath));
-// }
-//
-// @Override
-// public int getNameCount() {
-// if (path == null)
-// return 0;
-// return path.length;
-// }
-//
-// @Override
-// public Path getName(int index) {
-// if (path == null)
-// return null;
-// return new JcrPath(fs, path[index]);
-// }
-//
-// @Override
-// public Path subpath(int beginIndex, int endIndex) {
-// if (path == null)
-// return null;
-// String[] parentPath = Arrays.copyOfRange(path, beginIndex, endIndex);
-// return new JcrPath(fs, null, parentPath, false);
-// }
-//
-// @Override
-// public boolean startsWith(Path other) {
-// return toString().startsWith(other.toString());
-// }
-//
-// @Override
-// public boolean startsWith(String other) {
-// return toString().startsWith(other);
-// }
-//
-// @Override
-// public boolean endsWith(Path other) {
-// return toString().endsWith(other.toString());
-// }
-//
-// @Override
-// public boolean endsWith(String other) {
-// return toString().endsWith(other);
-// }
-
-// @Override
-// public Path normalize() {
-// // always normalized
-// return this;
-// }
-
-// @Override
-// public Path resolve(Path other) {
-// JcrPath otherPath = (JcrPath) other;
-// if (otherPath.isAbsolute())
-// return other;
-// String[] newPath;
-// if (path == null) {
-// newPath = new String[otherPath.path.length];
-// System.arraycopy(otherPath.path, 0, newPath, 0, otherPath.path.length);
-// } else {
-// newPath = new String[path.length + otherPath.path.length];
-// System.arraycopy(path, 0, newPath, 0, path.length);
-// System.arraycopy(otherPath.path, 0, newPath, path.length, otherPath.path.length);
-// }
-// if (!absolute)
-// return new JcrPath(fs, null, newPath, absolute);
-// else {
-// return new JcrPath(fs, toFsPath(newPath));
-// }
-// }
-//
-// @Override
-// public final Path resolve(String other) {
-// return resolve(getFileSystem().getPath(other));
-// }
-//
-// @Override
-// public final Path resolveSibling(Path other) {
-// if (other == null)
-// throw new NullPointerException();
-// Path parent = getParent();
-// return (parent == null) ? other : parent.resolve(other);
-// }
-//
-// @Override
-// public final Path resolveSibling(String other) {
-// return resolveSibling(getFileSystem().getPath(other));
-// }
-//
-// @Override
-// public final Iterator<Path> iterator() {
-// return new Iterator<Path>() {
-// private int i = 0;
-//
-// @Override
-// public boolean hasNext() {
-// return (i < getNameCount());
-// }
-//
-// @Override
-// public Path next() {
-// if (i < getNameCount()) {
-// Path result = getName(i);
-// i++;
-// return result;
-// } else {
-// throw new NoSuchElementException();
-// }
-// }
-//
-// @Override
-// public void remove() {
-// throw new UnsupportedOperationException();
-// }
-// };
-// }
-//
-// @Override
-// public Path relativize(Path other) {
-// if (equals(other))
-// return new JcrPath(fs, "");
-// if (other.startsWith(this)) {
-// String p1 = toString();
-// String p2 = other.toString();
-// String relative = p2.substring(p1.length(), p2.length());
-// if (relative.charAt(0) == '/')
-// relative = relative.substring(1);
-// return new JcrPath(fs, relative);
-// }
-// throw new IllegalArgumentException(other + " cannot be relativized against " + this);
-// }
-
-// @Override
-// public URI toUri() {
-// try {
-// return new URI(fs.provider().getScheme(), toString(), null);
-// } catch (URISyntaxException e) {
-// throw new JcrFsException("Cannot create URI for " + toString(), e);
-// }
-// }
-//
-// @Override
-// public Path toAbsolutePath() {
-// if (isAbsolute())
-// return this;
-// return new JcrPath(fs, fileStore, path, true);
-// }
-//
-// @Override
-// public Path toRealPath(LinkOption... options) throws IOException {
-// return this;
-// }
-//
-// @Override
-// public File toFile() {
-// throw new UnsupportedOperationException();
-// }
-
- public Node getNode() throws RepositoryException {
- if (!isAbsolute())// TODO default dir
- throw new JcrFsException("Cannot get a JCR node from a relative path");
- assert getFileStore() != null;
- return getFileStore().toNode(getSegments());
-// String pathStr = toJcrPath();
-// Session session = fs.getSession();
-// // TODO synchronize on the session ?
-// if (!session.itemExists(pathStr))
-// return null;
-// return session.getNode(pathStr);
- }
-//
-// @Override
-// public boolean equals(Object obj) {
-// if (!(obj instanceof JcrPath))
-// return false;
-// JcrPath other = (JcrPath) obj;
-//
-// if (path == null) {// root
-// if (other.path == null)// root
-// return true;
-// else
-// return false;
-// } else {
-// if (other.path == null)// root
-// return false;
-// }
-// // non root
-// if (path.length != other.path.length)
-// return false;
-// for (int i = 0; i < path.length; i++) {
-// if (!path[i].equals(other.path[i]))
-// return false;
-// }
-// return true;
-// }
-
-// @Override
-// public int hashCode() {
-// return hashCode;
-// }
-
-// @Override
-// protected Object clone() throws CloneNotSupportedException {
-// return new JcrPath(fs, toString());
-// }
-
-// @Override
-// protected void finalize() throws Throwable {
-// Arrays.fill(path, null);
-// }
-
-
-
-}
+++ /dev/null
-package org.argeo.jcr.fs;
-
-import java.io.IOException;
-import java.nio.file.DirectoryStream;
-import java.nio.file.Path;
-import java.util.Iterator;
-
-import javax.jcr.Node;
-import javax.jcr.NodeIterator;
-
-public class NodeDirectoryStream implements DirectoryStream<Path> {
- private final JcrFileSystem fs;
- private final NodeIterator nodeIterator;
- private final Iterator<JcrPath> additionalPaths;
- private final Filter<? super Path> filter;
-
- public NodeDirectoryStream(JcrFileSystem fs, NodeIterator nodeIterator, Iterator<JcrPath> additionalPaths,
- Filter<? super Path> filter) {
- this.fs = fs;
- this.nodeIterator = nodeIterator;
- this.additionalPaths = additionalPaths;
- this.filter = filter;
- }
-
- @Override
- public void close() throws IOException {
- }
-
- @Override
- public Iterator<Path> iterator() {
- return new Iterator<Path>() {
- private JcrPath next = null;
-
- @Override
- public synchronized boolean hasNext() {
- if (next != null)
- return true;
- nodes: while (nodeIterator.hasNext()) {
- try {
- Node node = nodeIterator.nextNode();
- String nodeName = node.getName();
- if (nodeName.startsWith("rep:") || nodeName.startsWith("jcr:"))
- continue nodes;
- if (fs.skipNode(node))
- continue nodes;
- next = new JcrPath(fs, node);
- if (filter != null) {
- if (filter.accept(next))
- break nodes;
- } else
- break nodes;
- } catch (Exception e) {
- throw new JcrFsException("Could not get next path", e);
- }
- }
-
- if (next == null) {
- if (additionalPaths.hasNext())
- next = additionalPaths.next();
- }
-
- return next != null;
- }
-
- @Override
- public synchronized Path next() {
- if (!hasNext())// make sure has next has been called
- return null;
- JcrPath res = next;
- next = null;
- return res;
- }
-
- };
- }
-
-}
+++ /dev/null
-package org.argeo.jcr.fs;
-
-import java.nio.file.attribute.BasicFileAttributes;
-
-import javax.jcr.Node;
-
-public interface NodeFileAttributes extends BasicFileAttributes {
- public Node getNode();
-}
+++ /dev/null
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.argeo.jcr.fs;
-
-import java.io.ByteArrayOutputStream;
-import java.io.UnsupportedEncodingException;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.util.ArrayList;
-import java.util.BitSet;
-import java.util.Properties;
-
-/**
- * <b>Hacked from org.apache.jackrabbit.util.Text in Jackrabbit JCR Commons</b>
- * This Class provides some text related utilities
- */
-class Text {
-
- /**
- * Hidden constructor.
- */
- private Text() {
- }
-
- /**
- * used for the md5
- */
- public static final char[] hexTable = "0123456789abcdef".toCharArray();
-
- /**
- * Calculate an MD5 hash of the string given.
- *
- * @param data
- * the data to encode
- * @param enc
- * the character encoding to use
- * @return a hex encoded string of the md5 digested input
- */
- public static String md5(String data, String enc) throws UnsupportedEncodingException {
- try {
- return digest("MD5", data.getBytes(enc));
- } catch (NoSuchAlgorithmException e) {
- throw new InternalError("MD5 digest not available???");
- }
- }
-
- /**
- * Calculate an MD5 hash of the string given using 'utf-8' encoding.
- *
- * @param data
- * the data to encode
- * @return a hex encoded string of the md5 digested input
- */
- public static String md5(String data) {
- try {
- return md5(data, "utf-8");
- } catch (UnsupportedEncodingException e) {
- throw new InternalError("UTF8 digest not available???");
- }
- }
-
- /**
- * Digest the plain string using the given algorithm.
- *
- * @param algorithm
- * The alogrithm for the digest. This algorithm must be supported
- * by the MessageDigest class.
- * @param data
- * The plain text String to be digested.
- * @param enc
- * The character encoding to use
- * @return The digested plain text String represented as Hex digits.
- * @throws java.security.NoSuchAlgorithmException
- * if the desired algorithm is not supported by the
- * MessageDigest class.
- * @throws java.io.UnsupportedEncodingException
- * if the encoding is not supported
- */
- public static String digest(String algorithm, String data, String enc)
- throws NoSuchAlgorithmException, UnsupportedEncodingException {
-
- return digest(algorithm, data.getBytes(enc));
- }
-
- /**
- * Digest the plain string using the given algorithm.
- *
- * @param algorithm
- * The algorithm for the digest. This algorithm must be supported
- * by the MessageDigest class.
- * @param data
- * the data to digest with the given algorithm
- * @return The digested plain text String represented as Hex digits.
- * @throws java.security.NoSuchAlgorithmException
- * if the desired algorithm is not supported by the
- * MessageDigest class.
- */
- public static String digest(String algorithm, byte[] data) throws NoSuchAlgorithmException {
-
- MessageDigest md = MessageDigest.getInstance(algorithm);
- byte[] digest = md.digest(data);
- StringBuilder res = new StringBuilder(digest.length * 2);
- for (byte b : digest) {
- res.append(hexTable[(b >> 4) & 15]);
- res.append(hexTable[b & 15]);
- }
- return res.toString();
- }
-
- /**
- * returns an array of strings decomposed of the original string, split at
- * every occurrence of 'ch'. if 2 'ch' follow each other with no
- * intermediate characters, empty "" entries are avoided.
- *
- * @param str
- * the string to decompose
- * @param ch
- * the character to use a split pattern
- * @return an array of strings
- */
- public static String[] explode(String str, int ch) {
- return explode(str, ch, false);
- }
-
- /**
- * returns an array of strings decomposed of the original string, split at
- * every occurrence of 'ch'.
- *
- * @param str
- * the string to decompose
- * @param ch
- * the character to use a split pattern
- * @param respectEmpty
- * if <code>true</code>, empty elements are generated
- * @return an array of strings
- */
- public static String[] explode(String str, int ch, boolean respectEmpty) {
- if (str == null || str.length() == 0) {
- return new String[0];
- }
-
- ArrayList<String> strings = new ArrayList<String>();
- int pos;
- int lastpos = 0;
-
- // add snipples
- while ((pos = str.indexOf(ch, lastpos)) >= 0) {
- if (pos - lastpos > 0 || respectEmpty) {
- strings.add(str.substring(lastpos, pos));
- }
- lastpos = pos + 1;
- }
- // add rest
- if (lastpos < str.length()) {
- strings.add(str.substring(lastpos));
- } else if (respectEmpty && lastpos == str.length()) {
- strings.add("");
- }
-
- // return string array
- return strings.toArray(new String[strings.size()]);
- }
-
- /**
- * Concatenates all strings in the string array using the specified
- * delimiter.
- *
- * @param arr
- * @param delim
- * @return the concatenated string
- */
- public static String implode(String[] arr, String delim) {
- StringBuilder buf = new StringBuilder();
- for (int i = 0; i < arr.length; i++) {
- if (i > 0) {
- buf.append(delim);
- }
- buf.append(arr[i]);
- }
- return buf.toString();
- }
-
- /**
- * Replaces all occurrences of <code>oldString</code> in <code>text</code>
- * with <code>newString</code>.
- *
- * @param text
- * @param oldString
- * old substring to be replaced with <code>newString</code>
- * @param newString
- * new substring to replace occurrences of <code>oldString</code>
- * @return a string
- */
- public static String replace(String text, String oldString, String newString) {
- if (text == null || oldString == null || newString == null) {
- throw new IllegalArgumentException("null argument");
- }
- int pos = text.indexOf(oldString);
- if (pos == -1) {
- return text;
- }
- int lastPos = 0;
- StringBuilder sb = new StringBuilder(text.length());
- while (pos != -1) {
- sb.append(text.substring(lastPos, pos));
- sb.append(newString);
- lastPos = pos + oldString.length();
- pos = text.indexOf(oldString, lastPos);
- }
- if (lastPos < text.length()) {
- sb.append(text.substring(lastPos));
- }
- return sb.toString();
- }
-
- /**
- * Replaces XML characters in the given string that might need escaping as
- * XML text or attribute
- *
- * @param text
- * text to be escaped
- * @return a string
- */
- public static String encodeIllegalXMLCharacters(String text) {
- return encodeMarkupCharacters(text, false);
- }
-
- /**
- * Replaces HTML characters in the given string that might need escaping as
- * HTML text or attribute
- *
- * @param text
- * text to be escaped
- * @return a string
- */
- public static String encodeIllegalHTMLCharacters(String text) {
- return encodeMarkupCharacters(text, true);
- }
-
- private static String encodeMarkupCharacters(String text, boolean isHtml) {
- if (text == null) {
- throw new IllegalArgumentException("null argument");
- }
- StringBuilder buf = null;
- int length = text.length();
- int pos = 0;
- for (int i = 0; i < length; i++) {
- int ch = text.charAt(i);
- switch (ch) {
- case '<':
- case '>':
- case '&':
- case '"':
- case '\'':
- if (buf == null) {
- buf = new StringBuilder();
- }
- if (i > 0) {
- buf.append(text.substring(pos, i));
- }
- pos = i + 1;
- break;
- default:
- continue;
- }
- if (ch == '<') {
- buf.append("<");
- } else if (ch == '>') {
- buf.append(">");
- } else if (ch == '&') {
- buf.append("&");
- } else if (ch == '"') {
- buf.append(""");
- } else if (ch == '\'') {
- buf.append(isHtml ? "'" : "'");
- }
- }
- if (buf == null) {
- return text;
- } else {
- if (pos < length) {
- buf.append(text.substring(pos));
- }
- return buf.toString();
- }
- }
-
- /**
- * The list of characters that are not encoded by the <code>escape()</code>
- * and <code>unescape()</code> METHODS. They contains the characters as
- * defined 'unreserved' in section 2.3 of the RFC 2396 'URI generic syntax':
- * <p>
- *
- * <pre>
- * unreserved = alphanum | mark
- * mark = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")"
- * </pre>
- */
- public static BitSet URISave;
-
- /**
- * Same as {@link #URISave} but also contains the '/'
- */
- public static BitSet URISaveEx;
-
- static {
- URISave = new BitSet(256);
- int i;
- for (i = 'a'; i <= 'z'; i++) {
- URISave.set(i);
- }
- for (i = 'A'; i <= 'Z'; i++) {
- URISave.set(i);
- }
- for (i = '0'; i <= '9'; i++) {
- URISave.set(i);
- }
- URISave.set('-');
- URISave.set('_');
- URISave.set('.');
- URISave.set('!');
- URISave.set('~');
- URISave.set('*');
- URISave.set('\'');
- URISave.set('(');
- URISave.set(')');
-
- URISaveEx = (BitSet) URISave.clone();
- URISaveEx.set('/');
- }
-
- /**
- * Does an URL encoding of the <code>string</code> using the
- * <code>escape</code> character. The characters that don't need encoding
- * are those defined 'unreserved' in section 2.3 of the 'URI generic syntax'
- * RFC 2396, but without the escape character.
- *
- * @param string
- * the string to encode.
- * @param escape
- * the escape character.
- * @return the escaped string
- * @throws NullPointerException
- * if <code>string</code> is <code>null</code>.
- */
- public static String escape(String string, char escape) {
- return escape(string, escape, false);
- }
-
- /**
- * Does an URL encoding of the <code>string</code> using the
- * <code>escape</code> character. The characters that don't need encoding
- * are those defined 'unreserved' in section 2.3 of the 'URI generic syntax'
- * RFC 2396, but without the escape character. If <code>isPath</code> is
- * <code>true</code>, additionally the slash '/' is ignored, too.
- *
- * @param string
- * the string to encode.
- * @param escape
- * the escape character.
- * @param isPath
- * if <code>true</code>, the string is treated as path
- * @return the escaped string
- * @throws NullPointerException
- * if <code>string</code> is <code>null</code>.
- */
- public static String escape(String string, char escape, boolean isPath) {
- try {
- BitSet validChars = isPath ? URISaveEx : URISave;
- byte[] bytes = string.getBytes("utf-8");
- StringBuilder out = new StringBuilder(bytes.length);
- for (byte aByte : bytes) {
- int c = aByte & 0xff;
- if (validChars.get(c) && c != escape) {
- out.append((char) c);
- } else {
- out.append(escape);
- out.append(hexTable[(c >> 4) & 0x0f]);
- out.append(hexTable[(c) & 0x0f]);
- }
- }
- return out.toString();
- } catch (UnsupportedEncodingException e) {
- throw new InternalError(e.toString());
- }
- }
-
- /**
- * Does a URL encoding of the <code>string</code>. The characters that don't
- * need encoding are those defined 'unreserved' in section 2.3 of the 'URI
- * generic syntax' RFC 2396.
- *
- * @param string
- * the string to encode
- * @return the escaped string
- * @throws NullPointerException
- * if <code>string</code> is <code>null</code>.
- */
- public static String escape(String string) {
- return escape(string, '%');
- }
-
- /**
- * Does a URL encoding of the <code>path</code>. The characters that don't
- * need encoding are those defined 'unreserved' in section 2.3 of the 'URI
- * generic syntax' RFC 2396. In contrast to the {@link #escape(String)}
- * method, not the entire path string is escaped, but every individual part
- * (i.e. the slashes are not escaped).
- *
- * @param path
- * the path to encode
- * @return the escaped path
- * @throws NullPointerException
- * if <code>path</code> is <code>null</code>.
- */
- public static String escapePath(String path) {
- return escape(path, '%', true);
- }
-
- /**
- * Does a URL decoding of the <code>string</code> using the
- * <code>escape</code> character. Please note that in opposite to the
- * {@link java.net.URLDecoder} it does not transform the + into spaces.
- *
- * @param string
- * the string to decode
- * @param escape
- * the escape character
- * @return the decoded string
- * @throws NullPointerException
- * if <code>string</code> is <code>null</code>.
- * @throws IllegalArgumentException
- * if the 2 characters following the escape character do not
- * represent a hex-number or if not enough characters follow an
- * escape character
- */
- public static String unescape(String string, char escape) {
- try {
- byte[] utf8 = string.getBytes("utf-8");
-
- // Check whether escape occurs at invalid position
- if ((utf8.length >= 1 && utf8[utf8.length - 1] == escape)
- || (utf8.length >= 2 && utf8[utf8.length - 2] == escape)) {
- throw new IllegalArgumentException("Premature end of escape sequence at end of input");
- }
-
- ByteArrayOutputStream out = new ByteArrayOutputStream(utf8.length);
- for (int k = 0; k < utf8.length; k++) {
- byte b = utf8[k];
- if (b == escape) {
- out.write((decodeDigit(utf8[++k]) << 4) + decodeDigit(utf8[++k]));
- } else {
- out.write(b);
- }
- }
-
- return new String(out.toByteArray(), "utf-8");
- } catch (UnsupportedEncodingException e) {
- throw new InternalError(e.toString());
- }
- }
-
- /**
- * Does a URL decoding of the <code>string</code>. Please note that in
- * opposite to the {@link java.net.URLDecoder} it does not transform the +
- * into spaces.
- *
- * @param string
- * the string to decode
- * @return the decoded string
- * @throws NullPointerException
- * if <code>string</code> is <code>null</code>.
- * @throws ArrayIndexOutOfBoundsException
- * if not enough character follow an escape character
- * @throws IllegalArgumentException
- * if the 2 characters following the escape character do not
- * represent a hex-number.
- */
- public static String unescape(String string) {
- return unescape(string, '%');
- }
-
- /**
- * Escapes all illegal JCR name characters of a string. The encoding is
- * loosely modeled after URI encoding, but only encodes the characters it
- * absolutely needs to in order to make the resulting string a valid JCR
- * name. Use {@link #unescapeIllegalJcrChars(String)} for decoding.
- * <p>
- * QName EBNF:<br>
- * <xmp> simplename ::= onecharsimplename | twocharsimplename |
- * threeormorecharname onecharsimplename ::= (* Any Unicode character
- * except: '.', '/', ':', '[', ']', '*', '|' or any whitespace character *)
- * twocharsimplename ::= '.' onecharsimplename | onecharsimplename '.' |
- * onecharsimplename onecharsimplename threeormorecharname ::= nonspace
- * string nonspace string ::= char | string char char ::= nonspace | ' '
- * nonspace ::= (* Any Unicode character except: '/', ':', '[', ']', '*',
- * '|' or any whitespace character *) </xmp>
- *
- * @param name
- * the name to escape
- * @return the escaped name
- */
- public static String escapeIllegalJcrChars(String name) {
- return escapeIllegalChars(name, "%/:[]*|\t\r\n");
- }
-
- /**
- * Escapes all illegal JCR 1.0 name characters of a string. Use
- * {@link #unescapeIllegalJcrChars(String)} for decoding.
- * <p>
- * QName EBNF:<br>
- * <xmp> simplename ::= onecharsimplename | twocharsimplename |
- * threeormorecharname onecharsimplename ::= (* Any Unicode character
- * except: '.', '/', ':', '[', ']', '*', ''', '"', '|' or any whitespace
- * character *) twocharsimplename ::= '.' onecharsimplename |
- * onecharsimplename '.' | onecharsimplename onecharsimplename
- * threeormorecharname ::= nonspace string nonspace string ::= char | string
- * char char ::= nonspace | ' ' nonspace ::= (* Any Unicode character
- * except: '/', ':', '[', ']', '*', ''', '"', '|' or any whitespace
- * character *) </xmp>
- *
- * @since Apache Jackrabbit 2.3.2 and 2.2.10
- * @see <a href=
- * "https://issues.apache.org/jira/browse/JCR-3128">JCR-3128</a>
- * @param name
- * the name to escape
- * @return the escaped name
- */
- public static String escapeIllegalJcr10Chars(String name) {
- return escapeIllegalChars(name, "%/:[]*'\"|\t\r\n");
- }
-
- private static String escapeIllegalChars(String name, String illegal) {
- StringBuilder buffer = new StringBuilder(name.length() * 2);
- for (int i = 0; i < name.length(); i++) {
- char ch = name.charAt(i);
- if (illegal.indexOf(ch) != -1 || (ch == '.' && name.length() < 3)
- || (ch == ' ' && (i == 0 || i == name.length() - 1))) {
- buffer.append('%');
- buffer.append(Character.toUpperCase(Character.forDigit(ch / 16, 16)));
- buffer.append(Character.toUpperCase(Character.forDigit(ch % 16, 16)));
- } else {
- buffer.append(ch);
- }
- }
- return buffer.toString();
- }
-
- /**
- * Escapes illegal XPath search characters at the end of a string.
- * <p>
- * Example:<br>
- * A search string like 'test?' will run into a ParseException documented in
- * http://issues.apache.org/jira/browse/JCR-1248
- *
- * @param s
- * the string to encode
- * @return the escaped string
- */
- public static String escapeIllegalXpathSearchChars(String s) {
- StringBuilder sb = new StringBuilder();
- sb.append(s.substring(0, (s.length() - 1)));
- char c = s.charAt(s.length() - 1);
- // NOTE: keep this in sync with _ESCAPED_CHAR below!
- if (c == '!' || c == '(' || c == ':' || c == '^' || c == '[' || c == ']' || c == '{' || c == '}' || c == '?') {
- sb.append('\\');
- }
- sb.append(c);
- return sb.toString();
- }
-
- /**
- * Unescapes previously escaped jcr chars.
- * <p>
- * Please note, that this does not exactly the same as the url related
- * {@link #unescape(String)}, since it handles the byte-encoding
- * differently.
- *
- * @param name
- * the name to unescape
- * @return the unescaped name
- */
- public static String unescapeIllegalJcrChars(String name) {
- StringBuilder buffer = new StringBuilder(name.length());
- int i = name.indexOf('%');
- while (i > -1 && i + 2 < name.length()) {
- buffer.append(name.toCharArray(), 0, i);
- int a = Character.digit(name.charAt(i + 1), 16);
- int b = Character.digit(name.charAt(i + 2), 16);
- if (a > -1 && b > -1) {
- buffer.append((char) (a * 16 + b));
- name = name.substring(i + 3);
- } else {
- buffer.append('%');
- name = name.substring(i + 1);
- }
- i = name.indexOf('%');
- }
- buffer.append(name);
- return buffer.toString();
- }
-
- /**
- * Returns the name part of the path. If the given path is already a name
- * (i.e. contains no slashes) it is returned.
- *
- * @param path
- * the path
- * @return the name part or <code>null</code> if <code>path</code> is
- * <code>null</code>.
- */
- public static String getName(String path) {
- return getName(path, '/');
- }
-
- /**
- * Returns the name part of the path, delimited by the given
- * <code>delim</code>. If the given path is already a name (i.e. contains no
- * <code>delim</code> characters) it is returned.
- *
- * @param path
- * the path
- * @param delim
- * the delimiter
- * @return the name part or <code>null</code> if <code>path</code> is
- * <code>null</code>.
- */
- public static String getName(String path, char delim) {
- return path == null ? null : path.substring(path.lastIndexOf(delim) + 1);
- }
-
- /**
- * Same as {@link #getName(String)} but adding the possibility to pass paths
- * that end with a trailing '/'
- *
- * @see #getName(String)
- */
- public static String getName(String path, boolean ignoreTrailingSlash) {
- if (ignoreTrailingSlash && path != null && path.endsWith("/") && path.length() > 1) {
- path = path.substring(0, path.length() - 1);
- }
- return getName(path);
- }
-
- /**
- * Returns the namespace prefix of the given <code>qname</code>. If the
- * prefix is missing, an empty string is returned. Please note, that this
- * method does not validate the name or prefix.
- * </p>
- * the qname has the format: qname := [prefix ':'] local;
- *
- * @param qname
- * a qualified name
- * @return the prefix of the name or "".
- *
- * @see #getLocalName(String)
- *
- * @throws NullPointerException
- * if <code>qname</code> is <code>null</code>
- */
- public static String getNamespacePrefix(String qname) {
- int pos = qname.indexOf(':');
- return pos >= 0 ? qname.substring(0, pos) : "";
- }
-
- /**
- * Returns the local name of the given <code>qname</code>. Please note, that
- * this method does not validate the name.
- * </p>
- * the qname has the format: qname := [prefix ':'] local;
- *
- * @param qname
- * a qualified name
- * @return the localname
- *
- * @see #getNamespacePrefix(String)
- *
- * @throws NullPointerException
- * if <code>qname</code> is <code>null</code>
- */
- public static String getLocalName(String qname) {
- int pos = qname.indexOf(':');
- return pos >= 0 ? qname.substring(pos + 1) : qname;
- }
-
- /**
- * Determines, if two paths denote hierarchical siblins.
- *
- * @param p1
- * first path
- * @param p2
- * second path
- * @return true if on same level, false otherwise
- */
- public static boolean isSibling(String p1, String p2) {
- int pos1 = p1.lastIndexOf('/');
- int pos2 = p2.lastIndexOf('/');
- return (pos1 == pos2 && pos1 >= 0 && p1.regionMatches(0, p2, 0, pos1));
- }
-
- /**
- * Determines if the <code>descendant</code> path is hierarchical a
- * descendant of <code>path</code>.
- *
- * @param path
- * the current path
- * @param descendant
- * the potential descendant
- * @return <code>true</code> if the <code>descendant</code> is a descendant;
- * <code>false</code> otherwise.
- */
- public static boolean isDescendant(String path, String descendant) {
- String pattern = path.endsWith("/") ? path : path + "/";
- return !pattern.equals(descendant) && descendant.startsWith(pattern);
- }
-
- /**
- * Determines if the <code>descendant</code> path is hierarchical a
- * descendant of <code>path</code> or equal to it.
- *
- * @param path
- * the path to check
- * @param descendant
- * the potential descendant
- * @return <code>true</code> if the <code>descendant</code> is a descendant
- * or equal; <code>false</code> otherwise.
- */
- public static boolean isDescendantOrEqual(String path, String descendant) {
- if (path.equals(descendant)) {
- return true;
- } else {
- String pattern = path.endsWith("/") ? path : path + "/";
- return descendant.startsWith(pattern);
- }
- }
-
- /**
- * Returns the n<sup>th</sup> relative parent of the path, where n=level.
- * <p>
- * Example:<br>
- * <code>
- * Text.getRelativeParent("/foo/bar/test", 1) == "/foo/bar"
- * </code>
- *
- * @param path
- * the path of the page
- * @param level
- * the level of the parent
- */
- public static String getRelativeParent(String path, int level) {
- int idx = path.length();
- while (level > 0) {
- idx = path.lastIndexOf('/', idx - 1);
- if (idx < 0) {
- return "";
- }
- level--;
- }
- return (idx == 0) ? "/" : path.substring(0, idx);
- }
-
- /**
- * Same as {@link #getRelativeParent(String, int)} but adding the
- * possibility to pass paths that end with a trailing '/'
- *
- * @see #getRelativeParent(String, int)
- */
- public static String getRelativeParent(String path, int level, boolean ignoreTrailingSlash) {
- if (ignoreTrailingSlash && path.endsWith("/") && path.length() > 1) {
- path = path.substring(0, path.length() - 1);
- }
- return getRelativeParent(path, level);
- }
-
- /**
- * Returns the n<sup>th</sup> absolute parent of the path, where n=level.
- * <p>
- * Example:<br>
- * <code>
- * Text.getAbsoluteParent("/foo/bar/test", 1) == "/foo/bar"
- * </code>
- *
- * @param path
- * the path of the page
- * @param level
- * the level of the parent
- */
- public static String getAbsoluteParent(String path, int level) {
- int idx = 0;
- int len = path.length();
- while (level >= 0 && idx < len) {
- idx = path.indexOf('/', idx + 1);
- if (idx < 0) {
- idx = len;
- }
- level--;
- }
- return level >= 0 ? "" : path.substring(0, idx);
- }
-
- /**
- * Performs variable replacement on the given string value. Each
- * <code>${...}</code> sequence within the given value is replaced with the
- * value of the named parser variable. If a variable is not found in the
- * properties an IllegalArgumentException is thrown unless
- * <code>ignoreMissing</code> is <code>true</code>. In the later case, the
- * missing variable is replaced by the empty string.
- *
- * @param value
- * the original value
- * @param ignoreMissing
- * if <code>true</code>, missing variables are replaced by the
- * empty string.
- * @return value after variable replacements
- * @throws IllegalArgumentException
- * if the replacement of a referenced variable is not found
- */
- public static String replaceVariables(Properties variables, String value, boolean ignoreMissing)
- throws IllegalArgumentException {
- StringBuilder result = new StringBuilder();
-
- // Value:
- // +--+-+--------+-+-----------------+
- // | |p|--> |q|--> |
- // +--+-+--------+-+-----------------+
- int p = 0, q = value.indexOf("${"); // Find first ${
- while (q != -1) {
- result.append(value.substring(p, q)); // Text before ${
- p = q;
- q = value.indexOf("}", q + 2); // Find }
- if (q != -1) {
- String variable = value.substring(p + 2, q);
- String replacement = variables.getProperty(variable);
- if (replacement == null) {
- if (ignoreMissing) {
- replacement = "";
- } else {
- throw new IllegalArgumentException("Replacement not found for ${" + variable + "}.");
- }
- }
- result.append(replacement);
- p = q + 1;
- q = value.indexOf("${", p); // Find next ${
- }
- }
- result.append(value.substring(p, value.length())); // Trailing text
-
- return result.toString();
- }
-
- private static byte decodeDigit(byte b) {
- if (b >= 0x30 && b <= 0x39) {
- return (byte) (b - 0x30);
- } else if (b >= 0x41 && b <= 0x46) {
- return (byte) (b - 0x37);
- } else if (b >= 0x61 && b <= 0x66) {
- return (byte) (b - 0x57);
- } else {
- throw new IllegalArgumentException("Escape sequence is not hexadecimal: " + (char) b);
- }
- }
-
-}
+++ /dev/null
-package org.argeo.jcr.fs;
-
-import java.io.IOException;
-import java.nio.file.FileStore;
-import java.nio.file.attribute.FileAttributeView;
-import java.nio.file.attribute.FileStoreAttributeView;
-import java.util.Arrays;
-
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.Workspace;
-
-import org.argeo.api.acr.fs.AbstractFsStore;
-import org.argeo.jcr.JcrUtils;
-
-/** A {@link FileStore} implementation based on JCR {@link Workspace}. */
-public class WorkspaceFileStore extends AbstractFsStore {
- private final String mountPath;
- private final Workspace workspace;
- private final String workspaceName;
- private final int mountDepth;
-
- public WorkspaceFileStore(String mountPath, Workspace workspace) {
- if ("/".equals(mountPath) || "".equals(mountPath))
- throw new IllegalArgumentException(
- "Mount path '" + mountPath + "' is unsupported, use null for the base file store");
- if (mountPath != null && !mountPath.startsWith(JcrPath.separator))
- throw new IllegalArgumentException("Mount path '" + mountPath + "' cannot end with /");
- if (mountPath != null && mountPath.endsWith(JcrPath.separator))
- throw new IllegalArgumentException("Mount path '" + mountPath + "' cannot end with /");
- this.mountPath = mountPath;
- if (mountPath == null)
- mountDepth = 0;
- else {
- mountDepth = mountPath.split(JcrPath.separator).length - 1;
- }
- this.workspace = workspace;
- this.workspaceName = workspace.getName();
- }
-
- public void close() {
- JcrUtils.logoutQuietly(workspace.getSession());
- }
-
- @Override
- public String name() {
- return workspace.getName();
- }
-
- @Override
- public String type() {
- return "workspace";
- }
-
- @Override
- public boolean isReadOnly() {
- return false;
- }
-
- @Override
- public long getTotalSpace() throws IOException {
- return 0;
- }
-
- @Override
- public long getUsableSpace() throws IOException {
- return 0;
- }
-
- @Override
- public long getUnallocatedSpace() throws IOException {
- return 0;
- }
-
- @Override
- public boolean supportsFileAttributeView(Class<? extends FileAttributeView> type) {
- return false;
- }
-
- @Override
- public boolean supportsFileAttributeView(String name) {
- return false;
- }
-
- @Override
- public <V extends FileStoreAttributeView> V getFileStoreAttributeView(Class<V> type) {
- return null;
- }
-
- @Override
- public Object getAttribute(String attribute) throws IOException {
- return workspace.getSession().getRepository().getDescriptor(attribute);
- }
-
- public Workspace getWorkspace() {
- return workspace;
- }
-
- public String toFsPath(Node node) throws RepositoryException {
- String nodeWorkspaceName = node.getSession().getWorkspace().getName();
- if (!nodeWorkspaceName.equals(workspace.getName()))
- throw new IllegalArgumentException("Icompatible " + node + " from workspace '" + nodeWorkspaceName
- + "' in file store '" + workspace.getName() + "'");
- return mountPath == null ? node.getPath() : mountPath + node.getPath();
- }
-
- public boolean isBase() {
- return mountPath == null;
- }
-
- Node toNode(String[] fullPath) throws RepositoryException {
- String jcrPath = toJcrPath(fullPath);
- Session session = workspace.getSession();
- if (!session.itemExists(jcrPath))
- return null;
- Node node = session.getNode(jcrPath);
- return node;
- }
-
- String toJcrPath(String fsPath) {
- if (fsPath.length() == 1)
- return toJcrPath((String[]) null);// root
- String[] arr = fsPath.substring(1).split("/");
-// if (arr.length == 0 || (arr.length == 1 && arr[0].equals("")))
-// return toJcrPath((String[]) null);// root
-// else
- return toJcrPath(arr);
- }
-
- private String toJcrPath(String[] path) {
- if (path == null)
- return "/";
- if (path.length < mountDepth)
- throw new IllegalArgumentException(
- "Path " + Arrays.asList(path) + " is no compatible with mount " + mountPath);
-
- if (!isBase()) {
- // check mount compatibility
- StringBuilder mount = new StringBuilder();
- mount.append('/');
- for (int i = 0; i < mountDepth; i++) {
- if (i != 0)
- mount.append('/');
- mount.append(Text.escapeIllegalJcrChars(path[i]));
- }
- if (!mountPath.equals(mount.toString()))
- throw new IllegalArgumentException(
- "Path " + Arrays.asList(path) + " is no compatible with mount " + mountPath);
- }
-
- StringBuilder sb = new StringBuilder();
- sb.append('/');
- for (int i = mountDepth; i < path.length; i++) {
- if (i != mountDepth)
- sb.append('/');
- sb.append(Text.escapeIllegalJcrChars(path[i]));
- }
- return sb.toString();
- }
-
- public String getMountPath() {
- return mountPath;
- }
-
- public String getWorkspaceName() {
- return workspaceName;
- }
-
- public int getMountDepth() {
- return mountDepth;
- }
-
- @Override
- public int hashCode() {
- return workspaceName.hashCode();
- }
-
- @Override
- public boolean equals(Object obj) {
- if (!(obj instanceof WorkspaceFileStore))
- return false;
- WorkspaceFileStore other = (WorkspaceFileStore) obj;
- return workspaceName.equals(other.workspaceName);
- }
-
- @Override
- public String toString() {
- return "WorkspaceFileStore " + workspaceName;
- }
-
-}
+++ /dev/null
-/** Java NIO file system implementation based on plain JCR. */
-package org.argeo.jcr.fs;
\ No newline at end of file
+++ /dev/null
-//
-// JCR EXTENSIONS
-//
-<jcrx = "http://www.argeo.org/ns/jcrx">
-
-[jcrx:xmlvalue]
-- *
-+ jcr:xmltext (jcrx:xmltext) = jcrx:xmltext
-
-[jcrx:xmltext]
- - jcr:xmlcharacters (STRING) mandatory
-
-[jcrx:csum]
-mixin
- - jcrx:sum (STRING) *
-
\ No newline at end of file
+++ /dev/null
-/** Generic JCR utilities. */
-package org.argeo.jcr;
\ No newline at end of file
+++ /dev/null
-package org.argeo.jcr.proxy;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.URL;
-
-import javax.jcr.Binary;
-import javax.jcr.Node;
-import javax.jcr.Property;
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.nodetype.NodeType;
-
-import org.argeo.api.cms.CmsLog;
-import org.argeo.jcr.JcrException;
-import org.argeo.jcr.JcrUtils;
-
-/** Base class for URL based proxys. */
-public abstract class AbstractUrlProxy implements ResourceProxy {
- private final static CmsLog log = CmsLog.getLog(AbstractUrlProxy.class);
-
- private Repository jcrRepository;
- private Session jcrAdminSession;
- private String proxyWorkspace = "proxy";
-
- protected abstract Node retrieve(Session session, String path);
-
- void init() {
- try {
- jcrAdminSession = JcrUtils.loginOrCreateWorkspace(jcrRepository, proxyWorkspace);
- beforeInitSessionSave(jcrAdminSession);
- if (jcrAdminSession.hasPendingChanges())
- jcrAdminSession.save();
- } catch (RepositoryException e) {
- JcrUtils.discardQuietly(jcrAdminSession);
- throw new JcrException("Cannot initialize URL proxy", e);
- }
- }
-
- /**
- * Called before the (admin) session is saved at the end of the initialization.
- * Does nothing by default, to be overridden.
- */
- protected void beforeInitSessionSave(Session session) throws RepositoryException {
- }
-
- void destroy() {
- JcrUtils.logoutQuietly(jcrAdminSession);
- }
-
- /**
- * Called before the (admin) session is logged out when resources are released.
- * Does nothing by default, to be overridden.
- */
- protected void beforeDestroySessionLogout() throws RepositoryException {
- }
-
- public Node proxy(String path) {
- // we open a JCR session with client credentials in order not to use the
- // admin session in multiple thread or make it a bottleneck.
- Node nodeAdmin = null;
- Node nodeClient = null;
- Session clientSession = null;
- try {
- clientSession = jcrRepository.login(proxyWorkspace);
- if (!clientSession.itemExists(path) || shouldUpdate(clientSession, path)) {
- nodeAdmin = retrieveAndSave(path);
- if (nodeAdmin != null)
- nodeClient = clientSession.getNode(path);
- } else
- nodeClient = clientSession.getNode(path);
- return nodeClient;
- } catch (RepositoryException e) {
- throw new JcrException("Cannot proxy " + path, e);
- } finally {
- if (nodeClient == null)
- JcrUtils.logoutQuietly(clientSession);
- }
- }
-
- protected synchronized Node retrieveAndSave(String path) {
- try {
- Node node = retrieve(jcrAdminSession, path);
- if (node == null)
- return null;
- jcrAdminSession.save();
- return node;
- } catch (RepositoryException e) {
- JcrUtils.discardQuietly(jcrAdminSession);
- throw new JcrException("Cannot retrieve and save " + path, e);
- } finally {
- notifyAll();
- }
- }
-
- /** Session is not saved */
- protected synchronized Node proxyUrl(Session session, String remoteUrl, String path) throws RepositoryException {
- Node node = null;
- if (session.itemExists(path)) {
- // throw new ArgeoJcrException("Node " + path + " already exists");
- }
- try (InputStream in = new URL(remoteUrl).openStream()) {
- // URL u = new URL(remoteUrl);
- // in = u.openStream();
- node = importFile(session, path, in);
- } catch (IOException e) {
- if (log.isDebugEnabled()) {
- log.debug("Cannot read " + remoteUrl + ", skipping... " + e.getMessage());
- // log.trace("Cannot read because of ", e);
- }
- JcrUtils.discardQuietly(session);
- // } finally {
- // IOUtils.closeQuietly(in);
- }
- return node;
- }
-
- protected synchronized Node importFile(Session session, String path, InputStream in) throws RepositoryException {
- Binary binary = null;
- try {
- Node content = null;
- Node node = null;
- if (!session.itemExists(path)) {
- node = JcrUtils.mkdirs(session, path, NodeType.NT_FILE, NodeType.NT_FOLDER, false);
- content = node.addNode(Node.JCR_CONTENT, NodeType.NT_UNSTRUCTURED);
- } else {
- node = session.getNode(path);
- content = node.getNode(Node.JCR_CONTENT);
- }
- binary = session.getValueFactory().createBinary(in);
- content.setProperty(Property.JCR_DATA, binary);
- JcrUtils.updateLastModifiedAndParents(node, null, true);
- return node;
- } finally {
- JcrUtils.closeQuietly(binary);
- }
- }
-
- /** Whether the file should be updated. */
- protected Boolean shouldUpdate(Session clientSession, String nodePath) {
- return false;
- }
-
- public void setJcrRepository(Repository jcrRepository) {
- this.jcrRepository = jcrRepository;
- }
-
- public void setProxyWorkspace(String localWorkspace) {
- this.proxyWorkspace = localWorkspace;
- }
-
-}
+++ /dev/null
-package org.argeo.jcr.proxy;
-
-import javax.jcr.Node;
-
-/** A proxy which nows how to resolve and synchronize relative URLs */
-public interface ResourceProxy {
- /**
- * Proxy the file referenced by this relative path in the underlying
- * repository. A new session is created by each call, so the underlying
- * session of the returned node must be closed by the caller.
- *
- * @return the proxied Node, <code>null</code> if the resource was not found
- * (e.g. HTTP 404)
- */
- public Node proxy(String relativePath);
-}
+++ /dev/null
-package org.argeo.jcr.proxy;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-import javax.jcr.Node;
-import javax.jcr.Property;
-import javax.jcr.RepositoryException;
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.apache.commons.io.FilenameUtils;
-import org.apache.commons.io.IOUtils;
-import org.argeo.jcr.JcrException;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.jcr.Bin;
-import org.argeo.jcr.JcrUtils;
-
-/** Wraps a proxy via HTTP */
-public class ResourceProxyServlet extends HttpServlet {
- private static final long serialVersionUID = -8886549549223155801L;
-
- private final static CmsLog log = CmsLog
- .getLog(ResourceProxyServlet.class);
-
- private ResourceProxy proxy;
-
- private String contentTypeCharset = "UTF-8";
-
- @Override
- protected void doGet(HttpServletRequest request,
- HttpServletResponse response) throws ServletException, IOException {
- String path = request.getPathInfo();
-
- if (log.isTraceEnabled()) {
- log.trace("path=" + path);
- log.trace("UserPrincipal = " + request.getUserPrincipal().getName());
- log.trace("SessionID = " + request.getSession(false).getId());
- log.trace("ContextPath = " + request.getContextPath());
- log.trace("ServletPath = " + request.getServletPath());
- log.trace("PathInfo = " + request.getPathInfo());
- log.trace("Method = " + request.getMethod());
- log.trace("User-Agent = " + request.getHeader("User-Agent"));
- }
-
- Node node = null;
- try {
- node = proxy.proxy(path);
- if (node == null)
- response.sendError(404);
- else
- processResponse(node, response);
- } finally {
- if (node != null)
- try {
- JcrUtils.logoutQuietly(node.getSession());
- } catch (RepositoryException e) {
- // silent
- }
- }
-
- }
-
- /** Retrieve the content of the node. */
- protected void processResponse(Node node, HttpServletResponse response) {
-// Binary binary = null;
-// InputStream in = null;
- try(Bin binary = new Bin( node.getNode(Property.JCR_CONTENT)
- .getProperty(Property.JCR_DATA));InputStream in = binary.getStream()) {
- String fileName = node.getName();
- String ext = FilenameUtils.getExtension(fileName);
-
- // TODO use a more generic / standard approach
- // see http://svn.apache.org/viewvc/tomcat/trunk/conf/web.xml
- String contentType;
- if ("xml".equals(ext))
- contentType = "text/xml;charset=" + contentTypeCharset;
- else if ("jar".equals(ext))
- contentType = "application/java-archive";
- else if ("zip".equals(ext))
- contentType = "application/zip";
- else if ("gz".equals(ext))
- contentType = "application/x-gzip";
- else if ("bz2".equals(ext))
- contentType = "application/x-bzip2";
- else if ("tar".equals(ext))
- contentType = "application/x-tar";
- else if ("rpm".equals(ext))
- contentType = "application/x-redhat-package-manager";
- else
- contentType = "application/octet-stream";
- contentType = contentType + ";name=\"" + fileName + "\"";
- response.setHeader("Content-Disposition", "attachment; filename=\""
- + fileName + "\"");
- response.setHeader("Expires", "0");
- response.setHeader("Cache-Control", "no-cache, must-revalidate");
- response.setHeader("Pragma", "no-cache");
-
- response.setContentType(contentType);
-
- IOUtils.copy(in, response.getOutputStream());
- } catch (RepositoryException e) {
- throw new JcrException("Cannot download " + node, e);
- } catch (IOException e) {
- throw new RuntimeException("Cannot download " + node, e);
- }
- }
-
- public void setProxy(ResourceProxy resourceProxy) {
- this.proxy = resourceProxy;
- }
-
-}
+++ /dev/null
-/** Components to build proxys based on JCR. */
-package org.argeo.jcr.proxy;
\ No newline at end of file
+++ /dev/null
-package org.argeo.jcr.unit;
-
-import java.io.File;
-import java.security.AccessController;
-import java.security.PrivilegedAction;
-
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.SimpleCredentials;
-import javax.security.auth.Subject;
-import javax.security.auth.login.LoginContext;
-import javax.security.auth.login.LoginException;
-
-import org.apache.commons.io.FileUtils;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.jcr.JcrException;
-
-import junit.framework.TestCase;
-
-/** Base for unit tests with a JCR repository. */
-public abstract class AbstractJcrTestCase extends TestCase {
- private final static CmsLog log = CmsLog.getLog(AbstractJcrTestCase.class);
-
- private Repository repository;
- private Session session = null;
-
- public final static String LOGIN_CONTEXT_TEST_SYSTEM = "TEST_JACKRABBIT_ADMIN";
-
- // protected abstract File getRepositoryFile() throws Exception;
-
- protected abstract Repository createRepository() throws Exception;
-
- protected abstract void clearRepository(Repository repository) throws Exception;
-
- @Override
- protected void setUp() throws Exception {
- File homeDir = getHomeDir();
- FileUtils.deleteDirectory(homeDir);
- repository = createRepository();
- }
-
- @Override
- protected void tearDown() throws Exception {
- if (session != null) {
- session.logout();
- if (log.isTraceEnabled())
- log.trace("Logout session");
- }
- clearRepository(repository);
- }
-
- protected Session session() {
- if (session != null && session.isLive())
- return session;
- Session session;
- if (getLoginContext() != null) {
- LoginContext lc;
- try {
- lc = new LoginContext(getLoginContext());
- lc.login();
- } catch (LoginException e) {
- throw new IllegalStateException("JAAS login failed", e);
- }
- session = Subject.doAs(lc.getSubject(), new PrivilegedAction<Session>() {
-
- @Override
- public Session run() {
- return login();
- }
-
- });
- } else
- session = login();
- this.session = session;
- return this.session;
- }
-
- protected String getLoginContext() {
- return null;
- }
-
- protected Session login() {
- try {
- if (log.isTraceEnabled())
- log.trace("Login session");
- Subject subject = Subject.getSubject(AccessController.getContext());
- if (subject != null)
- return getRepository().login();
- else
- return getRepository().login(new SimpleCredentials("demo", "demo".toCharArray()));
- } catch (RepositoryException e) {
- throw new JcrException("Cannot login to repository", e);
- }
- }
-
- protected Repository getRepository() {
- return repository;
- }
-
- /**
- * enables children class to set an existing repository in case it is not
- * deleted on startup, to test migration by instance
- */
- public void setRepository(Repository repository) {
- this.repository = repository;
- }
-
- protected File getHomeDir() {
- File homeDir = new File(System.getProperty("java.io.tmpdir"),
- AbstractJcrTestCase.class.getSimpleName() + "-" + System.getProperty("user.name"));
- return homeDir;
- }
-
-}
+++ /dev/null
-/** Helpers for unit tests with JCR repositories. */
-package org.argeo.jcr.unit;
\ No newline at end of file
+++ /dev/null
-package org.argeo.jcr.xml;
-
-import java.io.IOException;
-import java.io.Writer;
-import java.util.Map;
-import java.util.TreeMap;
-
-import javax.jcr.Node;
-import javax.jcr.NodeIterator;
-import javax.jcr.Property;
-import javax.jcr.PropertyIterator;
-import javax.jcr.RepositoryException;
-import javax.jcr.Value;
-import javax.jcr.nodetype.NodeType;
-
-import org.argeo.jcr.Jcr;
-
-/** Utilities around JCR and XML. */
-public class JcrXmlUtils {
- /**
- * Convenience method calling {@link #toXmlElements(Writer, Node, boolean)} with
- * <code>false</code>.
- */
- public static void toXmlElements(Writer writer, Node node) throws RepositoryException, IOException {
- toXmlElements(writer, node, null, false, false, false);
- }
-
- /**
- * Write JCR properties as XML elements in a tree structure whose elements are
- * named by node primary type.
- *
- * @param writer the writer to use
- * @param node the subtree
- * @param depth maximal depth, or if <code>null</code> the whole
- * subtree. It must be positive, with depth 0
- * describing just the node without its children.
- * @param withMetadata whether to write the primary type and mixins as
- * elements
- * @param withPrefix whether to keep the namespace prefixes
- * @param propertiesAsElements whether single properties should be written as
- * elements rather than attributes. If
- * <code>false</code>, multiple properties will be
- * skipped.
- */
- public static void toXmlElements(Writer writer, Node node, Integer depth, boolean withMetadata, boolean withPrefix,
- boolean propertiesAsElements) throws RepositoryException, IOException {
- if (depth != null && depth < 0)
- throw new IllegalArgumentException("Depth " + depth + " is negative.");
-
- if (node.getName().equals(Jcr.JCR_XMLTEXT)) {
- writer.write(node.getProperty(Jcr.JCR_XMLCHARACTERS).getString());
- return;
- }
-
- if (!propertiesAsElements) {
- Map<String, String> attrs = new TreeMap<>();
- PropertyIterator pit = node.getProperties();
- properties: while (pit.hasNext()) {
- Property p = pit.nextProperty();
- if (!p.isMultiple()) {
- String pName = p.getName();
- if (!withMetadata && (pName.equals(Jcr.JCR_PRIMARY_TYPE) || pName.equals(Jcr.JCR_UUID)
- || pName.equals(Jcr.JCR_CREATED) || pName.equals(Jcr.JCR_CREATED_BY)
- || pName.equals(Jcr.JCR_LAST_MODIFIED) || pName.equals(Jcr.JCR_LAST_MODIFIED_BY)))
- continue properties;
- attrs.put(withPrefix(p.getName(), withPrefix), p.getString());
- }
- }
- if (withMetadata && node.hasProperty(Property.JCR_UUID))
- attrs.put("id", "urn:uuid:" + node.getProperty(Property.JCR_UUID).getString());
- attrs.put(withPrefix ? Jcr.JCR_NAME : "name", node.getName());
- writeStart(writer, withPrefix(node.getPrimaryNodeType().getName(), withPrefix), attrs, node.hasNodes());
- } else {
- if (withMetadata && node.hasProperty(Property.JCR_UUID)) {
- writeStart(writer, withPrefix(node.getPrimaryNodeType().getName(), withPrefix), "id",
- "urn:uuid:" + node.getProperty(Property.JCR_UUID).getString());
- } else {
- writeStart(writer, withPrefix(node.getPrimaryNodeType().getName(), withPrefix));
- }
- // name
- writeStart(writer, withPrefix ? Jcr.JCR_NAME : "name");
- writer.append(node.getName());
- writeEnd(writer, withPrefix ? Jcr.JCR_NAME : "name");
- }
-
- // mixins
- if (withMetadata) {
- for (NodeType mixin : node.getMixinNodeTypes()) {
- writeStart(writer, withPrefix ? Jcr.JCR_MIXIN_TYPES : "mixinTypes");
- writer.append(mixin.getName());
- writeEnd(writer, withPrefix ? Jcr.JCR_MIXIN_TYPES : "mixinTypes");
- }
- }
-
- // properties as elements
- if (propertiesAsElements) {
- PropertyIterator pit = node.getProperties();
- properties: while (pit.hasNext()) {
- Property p = pit.nextProperty();
- if (p.isMultiple()) {
- for (Value value : p.getValues()) {
- writeStart(writer, withPrefix(p.getName(), withPrefix));
- writer.write(value.getString());
- writeEnd(writer, withPrefix(p.getName(), withPrefix));
- }
- } else {
- Value value = p.getValue();
- String pName = p.getName();
- if (!withMetadata && (pName.equals(Jcr.JCR_PRIMARY_TYPE) || pName.equals(Jcr.JCR_UUID)
- || pName.equals(Jcr.JCR_CREATED) || pName.equals(Jcr.JCR_CREATED_BY)
- || pName.equals(Jcr.JCR_LAST_MODIFIED) || pName.equals(Jcr.JCR_LAST_MODIFIED_BY)))
- continue properties;
- writeStart(writer, withPrefix(p.getName(), withPrefix));
- writer.write(value.getString());
- writeEnd(writer, withPrefix(p.getName(), withPrefix));
- }
- }
- }
-
- // children
- if (node.hasNodes()) {
- if (depth == null || depth > 0) {
- NodeIterator nit = node.getNodes();
- while (nit.hasNext()) {
- toXmlElements(writer, nit.nextNode(), depth == null ? null : depth - 1, withMetadata, withPrefix,
- propertiesAsElements);
- }
- }
- writeEnd(writer, withPrefix(node.getPrimaryNodeType().getName(), withPrefix));
- }
- }
-
- private static String withPrefix(String str, boolean withPrefix) {
- if (withPrefix)
- return str;
- int index = str.indexOf(':');
- if (index < 0)
- return str;
- return str.substring(index + 1);
- }
-
- private static void writeStart(Writer writer, String tagName) throws IOException {
- writer.append('<');
- writer.append(tagName);
- writer.append('>');
- }
-
- private static void writeStart(Writer writer, String tagName, String attr, String value) throws IOException {
- writer.append('<');
- writer.append(tagName);
- writer.append(' ');
- writer.append(attr);
- writer.append("=\"");
- writer.append(value);
- writer.append("\">");
- }
-
- private static void writeStart(Writer writer, String tagName, Map<String, String> attrs, boolean hasChildren)
- throws IOException {
- writer.append('<');
- writer.append(tagName);
- for (String attr : attrs.keySet()) {
- writer.append(' ');
- writer.append(attr);
- writer.append("=\"");
- writer.append(attrs.get(attr));
- writer.append('\"');
- }
- if (hasChildren)
- writer.append('>');
- else
- writer.append("/>");
- }
-
- private static void writeEnd(Writer writer, String tagName) throws IOException {
- writer.append("</");
- writer.append(tagName);
- writer.append('>');
- }
-
- /** Singleton. */
- private JcrXmlUtils() {
-
- }
-
-}
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8" ?>
-<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
- <xsl:output method="xml" indent="yes"/>
- <xsl:template match="/|comment()|processing-instruction()">
- <xsl:copy>
- <xsl:apply-templates/>
- </xsl:copy>
- </xsl:template>
- <xsl:template match="*">
- <xsl:element name="{local-name()}">
- <xsl:apply-templates select="@*|node()"/>
- </xsl:element>
- </xsl:template>
- <xsl:template match="@*">
- <xsl:attribute name="{local-name()}">
- <xsl:value-of select="."/>
- </xsl:attribute>
- </xsl:template>
-</xsl:stylesheet>
\ No newline at end of file
+++ /dev/null
-package org.argeo.maintenance;
-
-import java.io.IOException;
-import java.util.EnumSet;
-import java.util.HashSet;
-import java.util.Set;
-
-import javax.jcr.NoSuchWorkspaceException;
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-
-import org.argeo.api.cms.CmsLog;
-import org.argeo.cms.jcr.CmsJcrUtils;
-import org.argeo.jcr.Jcr;
-import org.argeo.jcr.JcrUtils;
-import org.argeo.osgi.transaction.WorkTransaction;
-import org.argeo.util.naming.Distinguished;
-import org.osgi.service.useradmin.Group;
-import org.osgi.service.useradmin.Role;
-import org.osgi.service.useradmin.UserAdmin;
-
-/** Make sure roles and access rights are properly configured. */
-public abstract class AbstractMaintenanceService {
- private final static CmsLog log = CmsLog.getLog(AbstractMaintenanceService.class);
-
- private Repository repository;
-// private UserAdminService userAdminService;
- private UserAdmin userAdmin;
- private WorkTransaction userTransaction;
-
- public void init() {
- makeSureRolesExists(getRequiredRoles());
- configureStandardRoles();
-
- Set<String> workspaceNames = getWorkspaceNames();
- if (workspaceNames == null || workspaceNames.isEmpty()) {
- configureJcr(repository, null);
- } else {
- for (String workspaceName : workspaceNames)
- configureJcr(repository, workspaceName);
- }
- }
-
- /** Configures a workspace. */
- protected void configureJcr(Repository repository, String workspaceName) {
- Session adminSession;
- try {
- adminSession = CmsJcrUtils.openDataAdminSession(repository, workspaceName);
- } catch (RuntimeException e1) {
- if (e1.getCause() != null && e1.getCause() instanceof NoSuchWorkspaceException) {
- Session defaultAdminSession = CmsJcrUtils.openDataAdminSession(repository, null);
- try {
- defaultAdminSession.getWorkspace().createWorkspace(workspaceName);
- log.info("Created JCR workspace " + workspaceName);
- } catch (RepositoryException e) {
- throw new IllegalStateException("Cannot create workspace " + workspaceName, e);
- } finally {
- Jcr.logout(defaultAdminSession);
- }
- adminSession = CmsJcrUtils.openDataAdminSession(repository, workspaceName);
- } else
- throw e1;
- }
- try {
- if (prepareJcrTree(adminSession)) {
- configurePrivileges(adminSession);
- }
- } catch (RepositoryException | IOException e) {
- throw new IllegalStateException("Cannot initialise JCR data layer.", e);
- } finally {
- JcrUtils.logoutQuietly(adminSession);
- }
- }
-
- /** To be overridden. */
- protected Set<String> getWorkspaceNames() {
- return null;
- }
-
- /**
- * To be overridden in order to programmatically set relationships between
- * roles. Does nothing by default.
- */
- protected void configureStandardRoles() {
- }
-
- /**
- * Creates the base JCR tree structure expected for this app if necessary.
- *
- * Expects a clean session ({@link Session#hasPendingChanges()} should return
- * false) and saves it once the changes have been done. Thus the session can be
- * rolled back if an exception occurs.
- *
- * @return true if something as been updated
- */
- public boolean prepareJcrTree(Session adminSession) throws RepositoryException, IOException {
- return false;
- }
-
- /**
- * Adds app specific default privileges.
- *
- * Expects a clean session ({@link Session#hasPendingChanges()} should return
- * false} and saves it once the changes have been done. Thus the session can be
- * rolled back if an exception occurs.
- *
- * Warning: no check is done and corresponding privileges are always added, so
- * only call this when necessary
- */
- public void configurePrivileges(Session session) throws RepositoryException {
- }
-
- /** The system roles that must be available in the system. */
- protected Set<String> getRequiredRoles() {
- return new HashSet<>();
- }
-
- public void destroy() {
-
- }
-
- /*
- * UTILITIES
- */
-
- /** Create these roles as group if they don't exist. */
- protected void makeSureRolesExists(EnumSet<? extends Distinguished> enumSet) {
- makeSureRolesExists(Distinguished.enumToDns(enumSet));
- }
-
- /** Create these roles as group if they don't exist. */
- protected void makeSureRolesExists(Set<String> requiredRoles) {
- if (requiredRoles == null)
- return;
- if (getUserAdmin() == null) {
- log.warn("No user admin service available, cannot make sure that role exists");
- return;
- }
- for (String role : requiredRoles) {
- Role systemRole = getUserAdmin().getRole(role);
- if (systemRole == null) {
- try {
- getUserTransaction().begin();
- getUserAdmin().createRole(role, Role.GROUP);
- getUserTransaction().commit();
- log.info("Created role " + role);
- } catch (Exception e) {
- try {
- getUserTransaction().rollback();
- } catch (Exception e1) {
- // silent
- }
- throw new IllegalStateException("Cannot create role " + role, e);
- }
- }
- }
- }
-
- /** Add a user or group to a group. */
- protected void addToGroup(String groupToAddDn, String groupDn) {
- if (groupToAddDn.contentEquals(groupDn)) {
- if (log.isTraceEnabled())
- log.trace("Ignore adding group " + groupDn + " to itself");
- return;
- }
-
- if (getUserAdmin() == null) {
- log.warn("No user admin service available, cannot add group " + groupToAddDn + " to " + groupDn);
- return;
- }
- Group groupToAdd = (Group) getUserAdmin().getRole(groupToAddDn);
- if (groupToAdd == null)
- throw new IllegalArgumentException("Group " + groupToAddDn + " not found");
- Group group = (Group) getUserAdmin().getRole(groupDn);
- if (group == null)
- throw new IllegalArgumentException("Group " + groupDn + " not found");
- try {
- getUserTransaction().begin();
- if (group.addMember(groupToAdd))
- log.info("Added " + groupToAddDn + " to " + group);
- getUserTransaction().commit();
- } catch (Exception e) {
- try {
- getUserTransaction().rollback();
- } catch (Exception e1) {
- // silent
- }
- throw new IllegalStateException("Cannot add " + groupToAddDn + " to " + groupDn);
- }
- }
-
- /*
- * DEPENDENCY INJECTION
- */
- public void setRepository(Repository repository) {
- this.repository = repository;
- }
-
-// public void setUserAdminService(UserAdminService userAdminService) {
-// this.userAdminService = userAdminService;
-// }
-
- protected WorkTransaction getUserTransaction() {
- return userTransaction;
- }
-
- protected UserAdmin getUserAdmin() {
- return userAdmin;
- }
-
- public void setUserAdmin(UserAdmin userAdmin) {
- this.userAdmin = userAdmin;
- }
-
- public void setUserTransaction(WorkTransaction userTransaction) {
- this.userTransaction = userTransaction;
- }
-
-}
+++ /dev/null
-package org.argeo.maintenance;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-
-import javax.naming.InvalidNameException;
-import javax.naming.ldap.LdapName;
-
-import org.argeo.api.cms.CmsLog;
-import org.argeo.osgi.transaction.WorkTransaction;
-import org.osgi.service.useradmin.Role;
-import org.osgi.service.useradmin.UserAdmin;
-
-/**
- * Register one or many roles via a user admin service. Does nothing if the role
- * is already registered.
- */
-public class SimpleRoleRegistration implements Runnable {
- private final static CmsLog log = CmsLog.getLog(SimpleRoleRegistration.class);
-
- private String role;
- private List<String> roles = new ArrayList<String>();
- private UserAdmin userAdmin;
- private WorkTransaction userTransaction;
-
- @Override
- public void run() {
- try {
- userTransaction.begin();
- if (role != null && !roleExists(role))
- newRole(toDn(role));
-
- for (String r : roles)
- if (!roleExists(r))
- newRole(toDn(r));
- userTransaction.commit();
- } catch (Exception e) {
- try {
- userTransaction.rollback();
- } catch (Exception e1) {
- log.error("Cannot rollback", e1);
- }
- throw new IllegalArgumentException("Cannot add roles", e);
- }
- }
-
- private boolean roleExists(String role) {
- return userAdmin.getRole(toDn(role).toString()) != null;
- }
-
- protected void newRole(LdapName r) {
- userAdmin.createRole(r.toString(), Role.GROUP);
- log.info("Added role " + r + " required by application.");
- }
-
- public void register(UserAdmin userAdminService, Map<?, ?> properties) {
- this.userAdmin = userAdminService;
- run();
- }
-
- protected LdapName toDn(String name) {
- try {
- return new LdapName("cn=" + name + ",ou=roles,ou=node");
- } catch (InvalidNameException e) {
- throw new IllegalArgumentException("Badly formatted role name " + name, e);
- }
- }
-
- public void setRole(String role) {
- this.role = role;
- }
-
- public void setRoles(List<String> roles) {
- this.roles = roles;
- }
-
- public void setUserAdmin(UserAdmin userAdminService) {
- this.userAdmin = userAdminService;
- }
-
- public void setUserTransaction(WorkTransaction userTransaction) {
- this.userTransaction = userTransaction;
- }
-
-}
+++ /dev/null
-package org.argeo.maintenance.backup;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.Writer;
-import java.util.Arrays;
-import java.util.Base64;
-import java.util.Set;
-import java.util.TreeSet;
-
-import javax.jcr.Binary;
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-
-import org.apache.commons.io.IOUtils;
-import org.argeo.jcr.Jcr;
-import org.argeo.jcr.JcrException;
-import org.xml.sax.Attributes;
-import org.xml.sax.SAXException;
-import org.xml.sax.helpers.DefaultHandler;
-
-/** XML handler serialising a JCR system view. */
-public class BackupContentHandler extends DefaultHandler {
- final static int MAX_DEPTH = 1024;
- final static String SV_NAMESPACE_URI = "http://www.jcp.org/jcr/sv/1.0";
- final static String SV_PREFIX = "sv";
- // elements
- final static String NODE = "node";
- final static String PROPERTY = "property";
- final static String VALUE = "value";
- // attributes
- final static String NAME = "name";
- final static String MULTIPLE = "multiple";
- final static String TYPE = "type";
-
- // values
- final static String BINARY = "Binary";
- final static String JCR_CONTENT = "jcr:content";
-
- private Writer out;
- private Session session;
- private Set<String> contentPaths = new TreeSet<>();
-
- boolean prettyPrint = true;
-
- private final String parentPath;
-
-// private boolean inSystem = false;
-
- public BackupContentHandler(Writer out, Node node) {
- super();
- this.out = out;
- this.session = Jcr.getSession(node);
- parentPath = Jcr.getParentPath(node);
- }
-
- private int currentDepth = -1;
- private String[] currentPath = new String[MAX_DEPTH];
-
- private boolean currentPropertyIsMultiple = false;
- private String currentEncoded = null;
- private Base64.Encoder base64encore = Base64.getEncoder();
-
- @Override
- public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
- boolean isNode;
- boolean isProperty;
- switch (localName) {
- case NODE:
- isNode = true;
- isProperty = false;
- break;
- case PROPERTY:
- isNode = false;
- isProperty = true;
- break;
- default:
- isNode = false;
- isProperty = false;
- }
-
- if (isNode) {
- String nodeName = attributes.getValue(SV_NAMESPACE_URI, NAME);
- currentDepth = currentDepth + 1;
-// if (currentDepth >= 0)
- currentPath[currentDepth] = nodeName;
-// System.out.println(getCurrentPath() + " , depth=" + currentDepth);
-// if ("jcr:system".equals(nodeName)) {
-// inSystem = true;
-// }
- }
-// if (inSystem)
-// return;
-
- if (SV_NAMESPACE_URI.equals(uri))
- try {
- if (prettyPrint) {
- if (isNode) {
- out.write(spaces());
- out.write("<!-- ");
- out.write(getCurrentJcrPath());
- out.write(" -->\n");
- out.write(spaces());
- } else if (isProperty)
- out.write(spaces());
- else if (currentPropertyIsMultiple)
- out.write(spaces());
- }
-
- out.write("<");
- out.write(SV_PREFIX + ":" + localName);
- if (isProperty)
- currentPropertyIsMultiple = false; // always reset
- for (int i = 0; i < attributes.getLength(); i++) {
- String ns = attributes.getURI(i);
- if (SV_NAMESPACE_URI.equals(ns)) {
- String attrName = attributes.getLocalName(i);
- String attrValue = attributes.getValue(i);
- out.write(" ");
- out.write(SV_PREFIX + ":" + attrName);
- out.write("=");
- out.write("\"");
- out.write(attrValue);
- out.write("\"");
- if (isProperty) {
- if (MULTIPLE.equals(attrName))
- currentPropertyIsMultiple = Boolean.parseBoolean(attrValue);
- else if (TYPE.equals(attrName)) {
- if (BINARY.equals(attrValue)) {
- if (JCR_CONTENT.equals(getCurrentName())) {
- contentPaths.add(getCurrentJcrPath());
- } else {
- Binary binary = session.getNode(getCurrentJcrPath()).getProperty(attrName)
- .getBinary();
- try (InputStream in = binary.getStream()) {
- currentEncoded = base64encore.encodeToString(IOUtils.toByteArray(in));
- } finally {
-
- }
- }
- }
- }
- }
- }
- }
- if (isNode && currentDepth == 0) {
- // out.write(" xmlns=\"" + SV_NAMESPACE_URI + "\"");
- out.write(" xmlns:" + SV_PREFIX + "=\"" + SV_NAMESPACE_URI + "\"");
- }
- out.write(">");
-
- if (prettyPrint)
- if (isNode)
- out.write("\n");
- else if (isProperty && currentPropertyIsMultiple)
- out.write("\n");
- } catch (IOException e) {
- throw new RuntimeException(e);
- } catch (RepositoryException e) {
- throw new JcrException(e);
- }
- }
-
- @Override
- public void endElement(String uri, String localName, String qName) throws SAXException {
- boolean isNode = localName.equals(NODE);
- boolean isValue = localName.equals(VALUE);
- if (prettyPrint)
- if (!isValue)
- try {
- if (isNode || currentPropertyIsMultiple)
- out.write(spaces());
- } catch (IOException e1) {
- throw new RuntimeException(e1);
- }
- if (isNode) {
-// System.out.println("endElement " + getCurrentPath() + " , depth=" + currentDepth);
-// if (currentDepth > 0)
- currentPath[currentDepth] = null;
- currentDepth = currentDepth - 1;
-// if (inSystem) {
-// // System.out.println("Skip " + getCurrentPath()+" ,
-// // currentDepth="+currentDepth);
-// if (currentDepth == 0) {
-// inSystem = false;
-// return;
-// }
-// }
- }
-// if (inSystem)
-// return;
- if (SV_NAMESPACE_URI.equals(uri))
- try {
- if (isValue && currentEncoded != null) {
- out.write(currentEncoded);
- }
- currentEncoded = null;
- out.write("</");
- out.write(SV_PREFIX + ":" + localName);
- out.write(">");
- if (prettyPrint)
- if (!isValue)
- out.write("\n");
- else {
- if (currentPropertyIsMultiple)
- out.write("\n");
- }
- if (currentDepth == 0)
- out.flush();
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
-
- }
-
- private char[] spaces() {
- char[] arr = new char[currentDepth];
- Arrays.fill(arr, ' ');
- return arr;
- }
-
- @Override
- public void characters(char[] ch, int start, int length) throws SAXException {
-// if (inSystem)
-// return;
- try {
- out.write(ch, start, length);
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
-
- protected String getCurrentName() {
- assert currentDepth >= 0;
-// if (currentDepth == 0)
-// return "jcr:root";
- return currentPath[currentDepth];
- }
-
- protected String getCurrentJcrPath() {
-// if (currentDepth == 0)
-// return "/";
- StringBuilder sb = new StringBuilder(parentPath.equals("/") ? "" : parentPath);
- for (int i = 0; i <= currentDepth; i++) {
-// if (i != 0)
- sb.append('/');
- sb.append(currentPath[i]);
- }
- return sb.toString();
- }
-
- public Set<String> getContentPaths() {
- return contentPaths;
- }
-
-}
+++ /dev/null
-package org.argeo.maintenance.backup;
-
-import java.io.BufferedWriter;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.OutputStreamWriter;
-import java.io.Writer;
-import java.net.URI;
-import java.net.URL;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.Dictionary;
-import java.util.Enumeration;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Set;
-import java.util.TreeMap;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.Future;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-import java.util.jar.JarOutputStream;
-import java.util.jar.Manifest;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipException;
-import java.util.zip.ZipOutputStream;
-
-import javax.jcr.Binary;
-import javax.jcr.Node;
-import javax.jcr.NodeIterator;
-import javax.jcr.Property;
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.RepositoryFactory;
-import javax.jcr.Session;
-
-import org.apache.commons.io.IOUtils;
-import org.apache.jackrabbit.api.JackrabbitSession;
-import org.apache.jackrabbit.api.JackrabbitValue;
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.cms.jcr.CmsJcrUtils;
-import org.argeo.jackrabbit.client.ClientDavexRepositoryFactory;
-import org.argeo.jcr.Jcr;
-import org.argeo.jcr.JcrException;
-import org.argeo.jcr.JcrUtils;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.BundleContext;
-
-/**
- * Performs a backup of the data based only on programmatic interfaces. Useful
- * for migration or live backup. Physical backups of the underlying file
- * systems, databases, LDAP servers, etc. should be performed for disaster
- * recovery.
- */
-public class LogicalBackup implements Runnable {
- private final static CmsLog log = CmsLog.getLog(LogicalBackup.class);
-
- public final static String WORKSPACES_BASE = "workspaces/";
- public final static String FILES_BASE = "files/";
- public final static String OSGI_BASE = "share/osgi/";
-
- public final static String JCR_SYSTEM = "jcr:system";
- public final static String JCR_VERSION_STORAGE_PATH = "/jcr:system/jcr:versionStorage";
-
- private final Repository repository;
- private String defaultWorkspace;
- private final BundleContext bundleContext;
-
- private final ZipOutputStream zout;
- private final Path basePath;
-
- private ExecutorService executorService;
-
- private boolean performSoftwareBackup = false;
-
- private Map<String, String> checksums = new TreeMap<>();
-
- private int threadCount = 5;
-
- private boolean backupFailed = false;
-
- public LogicalBackup(BundleContext bundleContext, Repository repository, Path basePath) {
- this.repository = repository;
- this.zout = null;
- this.basePath = basePath;
- this.bundleContext = bundleContext;
- }
-
- @Override
- public void run() {
- try {
- log.info("Start logical backup to " + basePath);
- perform();
- } catch (Exception e) {
- log.error("Unexpected exception when performing logical backup", e);
- throw new IllegalStateException("Logical backup failed", e);
- }
-
- }
-
- public void perform() throws RepositoryException, IOException {
- if (executorService != null && !executorService.isTerminated())
- throw new IllegalStateException("Another backup is running");
- executorService = Executors.newFixedThreadPool(threadCount);
- long begin = System.currentTimeMillis();
- // software backup
- if (bundleContext != null && performSoftwareBackup)
- executorService.submit(() -> performSoftwareBackup(bundleContext));
-
- // data backup
- Session defaultSession = login(null);
- defaultWorkspace = defaultSession.getWorkspace().getName();
- try {
- String[] workspaceNames = defaultSession.getWorkspace().getAccessibleWorkspaceNames();
- workspaces: for (String workspaceName : workspaceNames) {
- if ("security".equals(workspaceName))
- continue workspaces;
- performDataBackup(workspaceName);
- }
- } finally {
- JcrUtils.logoutQuietly(defaultSession);
- executorService.shutdown();
- try {
- executorService.awaitTermination(24, TimeUnit.HOURS);
- } catch (InterruptedException e) {
- // silent
- throw new IllegalStateException("Backup was interrupted before completion", e);
- }
- }
- // versions
- executorService = Executors.newFixedThreadPool(threadCount);
- try {
- performVersionsBackup();
- } finally {
- executorService.shutdown();
- try {
- executorService.awaitTermination(24, TimeUnit.HOURS);
- } catch (InterruptedException e) {
- // silent
- throw new IllegalStateException("Backup was interrupted before completion", e);
- }
- }
- long duration = System.currentTimeMillis() - begin;
- if (isBackupFailed())
- log.info("System logical backup failed after " + (duration / 60000) + "min " + (duration / 1000) + "s");
- else
- log.info("System logical backup completed in " + (duration / 60000) + "min " + (duration / 1000) + "s");
- }
-
- protected void performDataBackup(String workspaceName) throws RepositoryException, IOException {
- Session session = login(workspaceName);
- try {
- nodes: for (NodeIterator nit = session.getRootNode().getNodes(); nit.hasNext();) {
- if (isBackupFailed())
- return;
- Node nodeToExport = nit.nextNode();
- if (JCR_SYSTEM.equals(nodeToExport.getName()))
- continue nodes;
- String nodePath = nodeToExport.getPath();
- Future<Set<String>> contentPathsFuture = executorService
- .submit(() -> performNodeBackup(workspaceName, nodePath));
- executorService.submit(() -> performFilesBackup(workspaceName, contentPathsFuture));
- }
- } finally {
- Jcr.logout(session);
- }
- }
-
- protected void performVersionsBackup() throws RepositoryException, IOException {
- Session session = login(defaultWorkspace);
- Node versionStorageNode = session.getNode(JCR_VERSION_STORAGE_PATH);
- try {
- for (NodeIterator nit = versionStorageNode.getNodes(); nit.hasNext();) {
- Node nodeToExport = nit.nextNode();
- String nodePath = nodeToExport.getPath();
- if (isBackupFailed())
- return;
- Future<Set<String>> contentPathsFuture = executorService
- .submit(() -> performNodeBackup(defaultWorkspace, nodePath));
- executorService.submit(() -> performFilesBackup(defaultWorkspace, contentPathsFuture));
- }
- } finally {
- Jcr.logout(session);
- }
-
- }
-
- protected Set<String> performNodeBackup(String workspaceName, String nodePath) {
- Session session = login(workspaceName);
- try {
- Node nodeToExport = session.getNode(nodePath);
-// String nodeName = nodeToExport.getName();
-// if (nodeName.startsWith("jcr:") || nodeName.startsWith("rep:"))
-// continue nodes;
-// // TODO make it more robust / configurable
-// if (nodeName.equals("user"))
-// continue nodes;
- String relativePath = WORKSPACES_BASE + workspaceName + nodePath + ".xml";
- OutputStream xmlOut = openOutputStream(relativePath);
- BackupContentHandler contentHandler;
- try (Writer writer = new BufferedWriter(new OutputStreamWriter(xmlOut, StandardCharsets.UTF_8))) {
- contentHandler = new BackupContentHandler(writer, nodeToExport);
- session.exportSystemView(nodeToExport.getPath(), contentHandler, true, false);
- if (log.isDebugEnabled())
- log.debug(workspaceName + ":" + nodePath + " metadata exported to " + relativePath);
- }
-
- // Files
- Set<String> contentPaths = contentHandler.getContentPaths();
- return contentPaths;
- } catch (Exception e) {
- markBackupFailed("Cannot backup node " + workspaceName + ":" + nodePath, e);
- throw new ThreadDeath();
- } finally {
- Jcr.logout(session);
- }
- }
-
- protected void performFilesBackup(String workspaceName, Future<Set<String>> contentPathsFuture) {
- Set<String> contentPaths;
- try {
- contentPaths = contentPathsFuture.get(24, TimeUnit.HOURS);
- } catch (InterruptedException | ExecutionException | TimeoutException e1) {
- markBackupFailed("Cannot retrieve content paths for workspace " + workspaceName, e1);
- return;
- }
- if (contentPaths == null || contentPaths.size() == 0)
- return;
- Session session = login(workspaceName);
- try {
- String workspacesFilesBasePath = FILES_BASE + workspaceName;
- for (String path : contentPaths) {
- if (isBackupFailed())
- return;
- Node contentNode = session.getNode(path);
- Binary binary = null;
- try {
- binary = contentNode.getProperty(Property.JCR_DATA).getBinary();
- String fileRelativePath = workspacesFilesBasePath + contentNode.getParent().getPath();
-
- // checksum
- boolean skip = false;
- String checksum = null;
- if (session instanceof JackrabbitSession) {
- JackrabbitValue value = (JackrabbitValue) contentNode.getProperty(Property.JCR_DATA).getValue();
-// ReferenceBinary referenceBinary = (ReferenceBinary) binary;
- checksum = value.getContentIdentity();
- }
- if (checksum != null) {
- if (!checksums.containsKey(checksum)) {
- checksums.put(checksum, fileRelativePath);
- } else {
- skip = true;
- String sourcePath = checksums.get(checksum);
- if (log.isTraceEnabled())
- log.trace(fileRelativePath + " : already " + sourcePath + " with checksum " + checksum);
- createLink(sourcePath, fileRelativePath);
- try (Writer writerSum = new OutputStreamWriter(
- openOutputStream(fileRelativePath + ".sha256"), StandardCharsets.UTF_8)) {
- writerSum.write(checksum);
- }
- }
- }
-
- // copy file
- if (!skip)
- try (InputStream in = binary.getStream();
- OutputStream out = openOutputStream(fileRelativePath)) {
- IOUtils.copy(in, out);
- if (log.isTraceEnabled())
- log.trace("Workspace " + workspaceName + ": file content exported to "
- + fileRelativePath);
- }
- } finally {
- JcrUtils.closeQuietly(binary);
- }
- }
- if (log.isDebugEnabled())
- log.debug(workspaceName + ":" + contentPaths.size() + " files exported to " + workspacesFilesBasePath);
- } catch (Exception e) {
- markBackupFailed("Cannot backup files from " + workspaceName + ":", e);
- } finally {
- Jcr.logout(session);
- }
- }
-
- protected OutputStream openOutputStream(String relativePath) throws IOException {
- if (zout != null) {
- ZipEntry entry = new ZipEntry(relativePath);
- zout.putNextEntry(entry);
- return zout;
- } else if (basePath != null) {
- Path targetPath = basePath.resolve(Paths.get(relativePath));
- Files.createDirectories(targetPath.getParent());
- return Files.newOutputStream(targetPath);
- } else {
- throw new UnsupportedOperationException();
- }
- }
-
- protected void createLink(String source, String target) throws IOException {
- if (zout != null) {
- // TODO implement for zip
- throw new UnsupportedOperationException();
- } else if (basePath != null) {
- Path sourcePath = basePath.resolve(Paths.get(source));
- Path targetPath = basePath.resolve(Paths.get(target));
- Path relativeSource = targetPath.getParent().relativize(sourcePath);
- Files.createDirectories(targetPath.getParent());
- Files.createSymbolicLink(targetPath, relativeSource);
- } else {
- throw new UnsupportedOperationException();
- }
- }
-
- protected void closeOutputStream(String relativePath, OutputStream out) throws IOException {
- if (zout != null) {
- zout.closeEntry();
- } else if (basePath != null) {
- out.close();
- } else {
- throw new UnsupportedOperationException();
- }
- }
-
- protected Session login(String workspaceName) {
- if (bundleContext != null) {// local
- return CmsJcrUtils.openDataAdminSession(repository, workspaceName);
- } else {// remote
- try {
- return repository.login(workspaceName);
- } catch (RepositoryException e) {
- throw new JcrException(e);
- }
- }
- }
-
- public final static void main(String[] args) throws Exception {
- if (args.length == 0) {
- printUsage("No argument");
- System.exit(1);
- }
- URI uri = new URI(args[0]);
- Repository repository = createRemoteRepository(uri);
- Path basePath = args.length > 1 ? Paths.get(args[1]) : Paths.get(System.getProperty("user.dir"));
- if (!Files.exists(basePath))
- Files.createDirectories(basePath);
- LogicalBackup backup = new LogicalBackup(null, repository, basePath);
- backup.run();
- }
-
- private static void printUsage(String errorMessage) {
- if (errorMessage != null)
- System.err.println(errorMessage);
- System.out.println("Usage: LogicalBackup <remote URL> [<target directory>]");
-
- }
-
- protected static Repository createRemoteRepository(URI uri) throws RepositoryException {
- RepositoryFactory repositoryFactory = new ClientDavexRepositoryFactory();
- Map<String, String> params = new HashMap<String, String>();
- params.put(ClientDavexRepositoryFactory.JACKRABBIT_DAVEX_URI, uri.toString());
- // TODO make it configurable
- params.put(ClientDavexRepositoryFactory.JACKRABBIT_REMOTE_DEFAULT_WORKSPACE, CmsConstants.SYS_WORKSPACE);
- return repositoryFactory.getRepository(params);
- }
-
- public void performSoftwareBackup(BundleContext bundleContext) {
- String bootBasePath = OSGI_BASE + "boot";
- Bundle[] bundles = bundleContext.getBundles();
- for (Bundle bundle : bundles) {
- String relativePath = bootBasePath + "/" + bundle.getSymbolicName() + ".jar";
- Dictionary<String, String> headers = bundle.getHeaders();
- Manifest manifest = new Manifest();
- Enumeration<String> headerKeys = headers.keys();
- while (headerKeys.hasMoreElements()) {
- String headerKey = headerKeys.nextElement();
- String headerValue = headers.get(headerKey);
- manifest.getMainAttributes().putValue(headerKey, headerValue);
- }
- try (JarOutputStream jarOut = new JarOutputStream(openOutputStream(relativePath), manifest)) {
- Enumeration<URL> resourcePaths = bundle.findEntries("/", "*", true);
- resources: while (resourcePaths.hasMoreElements()) {
- URL entryUrl = resourcePaths.nextElement();
- String entryPath = entryUrl.getPath();
- if (entryPath.equals(""))
- continue resources;
- if (entryPath.endsWith("/"))
- continue resources;
- String entryName = entryPath.substring(1);// remove first '/'
- if (entryUrl.getPath().equals("/META-INF/"))
- continue resources;
- if (entryUrl.getPath().equals("/META-INF/MANIFEST.MF"))
- continue resources;
- // dev
- if (entryUrl.getPath().startsWith("/target"))
- continue resources;
- if (entryUrl.getPath().startsWith("/src"))
- continue resources;
- if (entryUrl.getPath().startsWith("/ext"))
- continue resources;
-
- if (entryName.startsWith("bin/")) {// dev
- entryName = entryName.substring("bin/".length());
- }
-
- ZipEntry entry = new ZipEntry(entryName);
- try (InputStream in = entryUrl.openStream()) {
- try {
- jarOut.putNextEntry(entry);
- } catch (ZipException e) {// duplicate
- continue resources;
- }
- IOUtils.copy(in, jarOut);
- jarOut.closeEntry();
-// log.info(entryUrl);
- } catch (FileNotFoundException e) {
- log.warn(entryUrl + ": " + e.getMessage());
- }
- }
- } catch (IOException e1) {
- throw new RuntimeException("Cannot export bundle " + bundle, e1);
- }
- }
- if (log.isDebugEnabled())
- log.debug(bundles.length + " OSGi bundles exported to " + bootBasePath);
-
- }
-
- protected synchronized void markBackupFailed(Object message, Exception e) {
- log.error(message, e);
- backupFailed = true;
- notifyAll();
- if (executorService != null)
- executorService.shutdownNow();
- }
-
- protected boolean isBackupFailed() {
- return backupFailed;
- }
-}
+++ /dev/null
-package org.argeo.maintenance.backup;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.file.DirectoryStream;
-import java.nio.file.Files;
-import java.nio.file.Path;
-
-import javax.jcr.ImportUUIDBehavior;
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.cms.jcr.CmsJcrUtils;
-import org.argeo.jcr.Jcr;
-import org.argeo.jcr.JcrException;
-import org.argeo.jcr.JcrUtils;
-import org.osgi.framework.BundleContext;
-
-/** Restores a backup in the format defined by {@link LogicalBackup}. */
-public class LogicalRestore implements Runnable {
- private final static CmsLog log = CmsLog.getLog(LogicalRestore.class);
-
- private final Repository repository;
- private final BundleContext bundleContext;
- private final Path basePath;
-
- public LogicalRestore(BundleContext bundleContext, Repository repository, Path basePath) {
- this.repository = repository;
- this.basePath = basePath;
- this.bundleContext = bundleContext;
- }
-
- @Override
- public void run() {
- Path workspaces = basePath.resolve(LogicalBackup.WORKSPACES_BASE);
- try {
- // import jcr:system first
-// Session defaultSession = NodeUtils.openDataAdminSession(repository, null);
-// try (DirectoryStream<Path> xmls = Files.newDirectoryStream(
-// workspaces.resolve(NodeConstants.SYS_WORKSPACE + LogicalBackup.JCR_VERSION_STORAGE_PATH),
-// "*.xml")) {
-// for (Path xml : xmls) {
-// try (InputStream in = Files.newInputStream(xml)) {
-// defaultSession.getWorkspace().importXML(LogicalBackup.JCR_VERSION_STORAGE_PATH, in,
-// ImportUUIDBehavior.IMPORT_UUID_COLLISION_REPLACE_EXISTING);
-// if (log.isDebugEnabled())
-// log.debug("Restored " + xml + " to " + defaultSession.getWorkspace().getName() + ":");
-// }
-// }
-// } finally {
-// Jcr.logout(defaultSession);
-// }
-
- // non-system content
- try (DirectoryStream<Path> workspaceDirs = Files.newDirectoryStream(workspaces)) {
- for (Path workspacePath : workspaceDirs) {
- String workspaceName = workspacePath.getFileName().toString();
- Session session = JcrUtils.loginOrCreateWorkspace(repository, workspaceName);
- try (DirectoryStream<Path> xmls = Files.newDirectoryStream(workspacePath, "*.xml")) {
- xmls: for (Path xml : xmls) {
- if (xml.getFileName().toString().startsWith("rep:"))
- continue xmls;
- try (InputStream in = Files.newInputStream(xml)) {
- session.getWorkspace().importXML("/", in,
- ImportUUIDBehavior.IMPORT_UUID_COLLISION_REPLACE_EXISTING);
- if (log.isDebugEnabled())
- log.debug("Restored " + xml + " to workspace " + workspaceName);
- }
- }
- } finally {
- Jcr.logout(session);
- }
- }
- }
- } catch (IOException e) {
- throw new RuntimeException("Cannot restore backup from " + basePath, e);
- } catch (RepositoryException e) {
- throw new JcrException("Cannot restore backup from " + basePath, e);
- }
- }
-
-}
+++ /dev/null
-/** Argeo Node backup utilities. */
-package org.argeo.maintenance.backup;
\ No newline at end of file
+++ /dev/null
-package org.argeo.maintenance.internal;
-
-import java.nio.file.Path;
-import java.nio.file.Paths;
-
-import javax.jcr.Repository;
-
-import org.argeo.maintenance.backup.LogicalBackup;
-import org.osgi.framework.BundleActivator;
-import org.osgi.framework.BundleContext;
-
-public class Activator implements BundleActivator {
-
- @Override
- public void start(BundleContext context) throws Exception {
- // Start backup
- Repository repository = context.getService(context.getServiceReference(Repository.class));
- Path basePath = Paths.get(System.getProperty("user.dir"), "backup");
- LogicalBackup backup = new LogicalBackup(context, repository, basePath);
- backup.run();
- }
-
- @Override
- public void stop(BundleContext context) throws Exception {
- }
-
-}
+++ /dev/null
-/** Utilities for the maintenance of an Argeo Node. */
-package org.argeo.maintenance;
\ No newline at end of file
+++ /dev/null
-package org.argeo.security.jackrabbit;
-
-import java.security.Principal;
-import java.util.Map;
-import java.util.Set;
-
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-
-import org.apache.jackrabbit.core.security.authorization.acl.ACLProvider;
-
-/** Argeo specific access control provider */
-public class ArgeoAccessControlProvider extends ACLProvider {
-
- @SuppressWarnings({ "rawtypes", "unchecked" })
- @Override
- public void init(Session systemSession, Map configuration) throws RepositoryException {
- if (!configuration.containsKey(PARAM_ALLOW_UNKNOWN_PRINCIPALS))
- configuration.put(PARAM_ALLOW_UNKNOWN_PRINCIPALS, "true");
- if (!configuration.containsKey(PARAM_OMIT_DEFAULT_PERMISSIONS))
- configuration.put(PARAM_OMIT_DEFAULT_PERMISSIONS, "true");
- super.init(systemSession, configuration);
- }
-
- @Override
- public boolean canAccessRoot(Set<Principal> principals) throws RepositoryException {
- return super.canAccessRoot(principals);
- }
-
-}
+++ /dev/null
-package org.argeo.security.jackrabbit;
-
-import javax.jcr.PathNotFoundException;
-import javax.jcr.RepositoryException;
-import javax.jcr.security.Privilege;
-
-import org.apache.jackrabbit.core.id.ItemId;
-import org.apache.jackrabbit.core.security.DefaultAccessManager;
-import org.apache.jackrabbit.spi.Path;
-
-/**
- * Intermediary class in order to have a consistent naming in config files. Does
- * nothing for the time being, but may in the future.
- */
-public class ArgeoAccessManager extends DefaultAccessManager {
-
- @Override
- public boolean canRead(Path itemPath, ItemId itemId)
- throws RepositoryException {
- return super.canRead(itemPath, itemId);
- }
-
- @Override
- public Privilege[] getPrivileges(String absPath)
- throws PathNotFoundException, RepositoryException {
- return super.getPrivileges(absPath);
- }
-
- @Override
- public boolean hasPrivileges(String absPath, Privilege[] privileges)
- throws PathNotFoundException, RepositoryException {
- return super.hasPrivileges(absPath, privileges);
- }
-
-}
+++ /dev/null
-package org.argeo.security.jackrabbit;
-
-import javax.security.auth.Subject;
-import javax.security.auth.callback.CallbackHandler;
-import javax.security.auth.login.LoginContext;
-import javax.security.auth.login.LoginException;
-
-import org.apache.jackrabbit.core.security.authentication.AuthContext;
-
-/** Wraps a regular {@link LoginContext}, using the proper class loader. */
-class ArgeoAuthContext implements AuthContext {
- private LoginContext lc;
-
- public ArgeoAuthContext(String appName, Subject subject, CallbackHandler callbackHandler) {
- try {
- lc = new LoginContext(appName, subject, callbackHandler);
- } catch (LoginException e) {
- throw new IllegalStateException("Cannot configure Jackrabbit login context", e);
- }
- }
-
- @Override
- public void login() throws LoginException {
- lc.login();
- }
-
- @Override
- public Subject getSubject() {
- return lc.getSubject();
- }
-
- @Override
- public void logout() throws LoginException {
- lc.logout();
- }
-
-}
+++ /dev/null
-package org.argeo.security.jackrabbit;
-
-import java.security.Principal;
-import java.util.HashSet;
-import java.util.Properties;
-import java.util.Set;
-
-import javax.jcr.Credentials;
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.security.auth.Subject;
-import javax.security.auth.callback.CallbackHandler;
-import javax.security.auth.x500.X500Principal;
-
-import org.apache.jackrabbit.api.security.user.UserManager;
-import org.apache.jackrabbit.core.DefaultSecurityManager;
-import org.apache.jackrabbit.core.security.AMContext;
-import org.apache.jackrabbit.core.security.AccessManager;
-import org.apache.jackrabbit.core.security.SecurityConstants;
-import org.apache.jackrabbit.core.security.SystemPrincipal;
-import org.apache.jackrabbit.core.security.authentication.AuthContext;
-import org.apache.jackrabbit.core.security.authentication.CallbackHandlerImpl;
-import org.apache.jackrabbit.core.security.authorization.WorkspaceAccessManager;
-import org.apache.jackrabbit.core.security.principal.AdminPrincipal;
-import org.apache.jackrabbit.core.security.principal.PrincipalProvider;
-import org.argeo.api.cms.CmsSession;
-import org.argeo.api.cms.DataAdminPrincipal;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.api.cms.AnonymousPrincipal;
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.cms.osgi.CmsOsgiUtils;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.FrameworkUtil;
-
-/** Customises Jackrabbit security. */
-public class ArgeoSecurityManager extends DefaultSecurityManager {
- private final static CmsLog log = CmsLog.getLog(ArgeoSecurityManager.class);
-
- private BundleContext cmsBundleContext = null;
-
- public ArgeoSecurityManager() {
- if (FrameworkUtil.getBundle(CmsSession.class) != null) {
- cmsBundleContext = FrameworkUtil.getBundle(CmsSession.class).getBundleContext();
- }
- }
-
- public AuthContext getAuthContext(Credentials creds, Subject subject, String workspaceName)
- throws RepositoryException {
- checkInitialized();
-
- CallbackHandler cbHandler = new CallbackHandlerImpl(creds, getSystemSession(), getPrincipalProviderRegistry(),
- adminId, anonymousId);
- String appName = "Jackrabbit";
- return new ArgeoAuthContext(appName, subject, cbHandler);
- }
-
- @Override
- public AccessManager getAccessManager(Session session, AMContext amContext) throws RepositoryException {
- synchronized (getSystemSession()) {
- return super.getAccessManager(session, amContext);
- }
- }
-
- @Override
- public UserManager getUserManager(Session session) throws RepositoryException {
- synchronized (getSystemSession()) {
- return super.getUserManager(session);
- }
- }
-
- @Override
- protected PrincipalProvider createDefaultPrincipalProvider(Properties[] moduleConfig) throws RepositoryException {
- return super.createDefaultPrincipalProvider(moduleConfig);
- }
-
- /** Called once when the session is created */
- @Override
- public String getUserID(Subject subject, String workspaceName) throws RepositoryException {
- boolean isAnonymous = !subject.getPrincipals(AnonymousPrincipal.class).isEmpty();
- boolean isDataAdmin = !subject.getPrincipals(DataAdminPrincipal.class).isEmpty();
- boolean isJackrabbitSystem = !subject.getPrincipals(SystemPrincipal.class).isEmpty();
- Set<X500Principal> userPrincipal = subject.getPrincipals(X500Principal.class);
- boolean isRegularUser = !userPrincipal.isEmpty();
- CmsSession cmsSession = null;
- if (cmsBundleContext != null) {
- cmsSession = CmsOsgiUtils.getCmsSession(cmsBundleContext, subject);
- if (log.isTraceEnabled())
- log.trace("Opening JCR session for CMS session " + cmsSession);
- }
-
- if (isAnonymous) {
- if (isDataAdmin || isJackrabbitSystem || isRegularUser)
- throw new IllegalStateException("Inconsistent " + subject);
- else
- return CmsConstants.ROLE_ANONYMOUS;
- } else if (isRegularUser) {// must be before DataAdmin
- if (isAnonymous || isJackrabbitSystem)
- throw new IllegalStateException("Inconsistent " + subject);
- else {
- if (userPrincipal.size() > 1) {
- StringBuilder buf = new StringBuilder();
- for (X500Principal principal : userPrincipal)
- buf.append(' ').append('\"').append(principal).append('\"');
- throw new RuntimeException("Multiple user principals:" + buf);
- }
- return userPrincipal.iterator().next().getName();
- }
- } else if (isDataAdmin) {
- if (isAnonymous || isJackrabbitSystem || isRegularUser)
- throw new IllegalStateException("Inconsistent " + subject);
- else {
- assert !subject.getPrincipals(AdminPrincipal.class).isEmpty();
- return CmsConstants.ROLE_DATA_ADMIN;
- }
- } else if (isJackrabbitSystem) {
- if (isAnonymous || isDataAdmin || isRegularUser)
- throw new IllegalStateException("Inconsistent " + subject);
- else
- return super.getUserID(subject, workspaceName);
- } else {
- throw new IllegalStateException("Unrecognized subject type: " + subject);
- }
- }
-
- @Override
- protected WorkspaceAccessManager createDefaultWorkspaceAccessManager() {
- WorkspaceAccessManager wam = super.createDefaultWorkspaceAccessManager();
- ArgeoWorkspaceAccessManagerImpl workspaceAccessManager = new ArgeoWorkspaceAccessManagerImpl(wam);
- if (log.isTraceEnabled())
- log.trace("Created workspace access manager");
- return workspaceAccessManager;
- }
-
- private class ArgeoWorkspaceAccessManagerImpl implements SecurityConstants, WorkspaceAccessManager {
- private final WorkspaceAccessManager wam;
-
- public ArgeoWorkspaceAccessManagerImpl(WorkspaceAccessManager wam) {
- super();
- this.wam = wam;
- }
-
- public void init(Session systemSession) throws RepositoryException {
- wam.init(systemSession);
- Repository repository = systemSession.getRepository();
- if (log.isTraceEnabled())
- log.trace("Initialised workspace access manager on repository " + repository
- + ", systemSession workspace: " + systemSession.getWorkspace().getName());
- }
-
- public void close() throws RepositoryException {
- }
-
- public boolean grants(Set<Principal> principals, String workspaceName) throws RepositoryException {
- // TODO: implements finer access to workspaces
- if (log.isTraceEnabled())
- log.trace("Grants " + new HashSet<>(principals) + " access to workspace '" + workspaceName + "'");
- return true;
- // return wam.grants(principals, workspaceName);
- }
- }
-
-}
+++ /dev/null
-package org.argeo.security.jackrabbit;
-
-import java.util.Map;
-import java.util.Set;
-
-import javax.security.auth.Subject;
-import javax.security.auth.callback.CallbackHandler;
-import javax.security.auth.login.LoginException;
-import javax.security.auth.spi.LoginModule;
-import javax.security.auth.x500.X500Principal;
-
-import org.apache.jackrabbit.core.security.AnonymousPrincipal;
-import org.apache.jackrabbit.core.security.SecurityConstants;
-import org.apache.jackrabbit.core.security.principal.AdminPrincipal;
-import org.argeo.api.cms.DataAdminPrincipal;
-
-/** JAAS login module used when initiating a new Jackrabbit session. */
-public class SystemJackrabbitLoginModule implements LoginModule {
- private Subject subject;
-
- @Override
- public void initialize(Subject subject, CallbackHandler callbackHandler, Map<String, ?> sharedState,
- Map<String, ?> options) {
- this.subject = subject;
- }
-
- @Override
- public boolean login() throws LoginException {
- return true;
- }
-
- @Override
- public boolean commit() throws LoginException {
- Set<org.argeo.api.cms.AnonymousPrincipal> anonPrincipal = subject
- .getPrincipals(org.argeo.api.cms.AnonymousPrincipal.class);
- if (!anonPrincipal.isEmpty()) {
- subject.getPrincipals().add(new AnonymousPrincipal());
- return true;
- }
-
- Set<DataAdminPrincipal> initPrincipal = subject.getPrincipals(DataAdminPrincipal.class);
- if (!initPrincipal.isEmpty()) {
- subject.getPrincipals().add(new AdminPrincipal(SecurityConstants.ADMIN_ID));
- return true;
- }
-
- Set<X500Principal> userPrincipal = subject.getPrincipals(X500Principal.class);
- if (userPrincipal.isEmpty())
- throw new LoginException("Subject must be pre-authenticated");
- if (userPrincipal.size() > 1)
- throw new LoginException("Multiple user principals " + userPrincipal);
-
- return true;
- }
-
- @Override
- public boolean abort() throws LoginException {
- return true;
- }
-
- @Override
- public boolean logout() throws LoginException {
- subject.getPrincipals().removeAll(subject.getPrincipals(AnonymousPrincipal.class));
- subject.getPrincipals().removeAll(subject.getPrincipals(AdminPrincipal.class));
- return true;
- }
-}
+++ /dev/null
-/** Integration of Jackrabbit with Argeo security model. */
-package org.argeo.security.jackrabbit;
\ 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-11"/>
- <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.cms.servlet</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" activate="start" deactivate="stop" name="Jetty Service Factory">
- <implementation class="org.argeo.cms.servlet.internal.jetty.JettyServiceFactory"/>
- <service>
- <provide interface="org.osgi.service.cm.ManagedServiceFactory"/>
- </service>
- <property name="service.pid" type="String" value="org.argeo.equinox.jetty.config"/>
-</scr:component>
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8"?>
-<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="org.argeo.cms.pkgServlet">
- <implementation class="org.argeo.cms.servlet.internal.PkgServlet"/>
- <service>
- <provide interface="javax.servlet.Servlet"/>
- </service>
- <property name="osgi.http.whiteboard.servlet.pattern" type="String" value="/*"/>
- <property name="osgi.http.whiteboard.context.select" type="String" value="(osgi.http.whiteboard.context.name=pkgServletContext)"/>
-</scr:component>
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8"?>
-<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="init" deactivate="destroy" name="org.argeo.cms.pkgServletContext">
- <implementation class="org.argeo.cms.servlet.CmsServletContext"/>
- <service>
- <provide interface="org.osgi.service.http.context.ServletContextHelper"/>
- </service>
- <property name="osgi.http.whiteboard.context.name" type="String" value="pkgServletContext"/>
- <property name="osgi.http.whiteboard.context.path" type="String" value="/pkg"/>
-</scr:component>
+++ /dev/null
-Import-Package:\
-org.osgi.service.http;version=0.0.0,\
-org.osgi.service.http.whiteboard;version=0.0.0,\
-org.osgi.framework.namespace;version=0.0.0,\
-org.argeo.cms.osgi,\
-*
-
-Service-Component:\
-OSGI-INF/jettyServiceFactory.xml,\
-OSGI-INF/pkgServletContext.xml,\
-OSGI-INF/pkgServlet.xml
+++ /dev/null
-output.. = bin/
-bin.includes = META-INF/,\
- .,\
- OSGI-INF/jettyServiceFactory.xml
-source.. = src/
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<project xmlns="http://maven.apache.org/POM/4.0.0"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
- <modelVersion>4.0.0</modelVersion>
- <parent>
- <groupId>org.argeo.commons</groupId>
- <version>2.3-SNAPSHOT</version>
- <artifactId>argeo-commons</artifactId>
- <relativePath>..</relativePath>
- </parent>
- <artifactId>org.argeo.cms.servlet</artifactId>
- <packaging>jar</packaging>
- <name>CMS Servlet</name>
- <description>CMS components depending on the Servlet APIs</description>
- <dependencies>
- <dependency>
- <groupId>org.argeo.commons</groupId>
- <artifactId>org.argeo.cms</artifactId>
- <version>2.3-SNAPSHOT</version>
- </dependency>
- </dependencies>
-</project>
\ No newline at end of file
+++ /dev/null
-package org.argeo.cms.servlet;
-
-import java.io.IOException;
-import java.net.URL;
-import java.security.PrivilegedAction;
-import java.util.Map;
-
-import javax.security.auth.Subject;
-import javax.security.auth.login.LoginContext;
-import javax.security.auth.login.LoginException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.argeo.api.cms.CmsAuth;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.cms.auth.RemoteAuthCallbackHandler;
-import org.argeo.cms.auth.RemoteAuthUtils;
-import org.argeo.cms.servlet.internal.HttpUtils;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.FrameworkUtil;
-import org.osgi.service.http.context.ServletContextHelper;
-
-/**
- * Default servlet context degrading to anonymous if the the session is not
- * pre-authenticated.
- */
-public class CmsServletContext extends ServletContextHelper {
- private final static CmsLog log = CmsLog.getLog(CmsServletContext.class);
- // use CMS bundle for resources
- private Bundle bundle = FrameworkUtil.getBundle(getClass());
-
- public void init(Map<String, String> properties) {
-
- }
-
- public void destroy() {
-
- }
-
- @Override
- public boolean handleSecurity(HttpServletRequest request, HttpServletResponse response) throws IOException {
- if (log.isTraceEnabled())
- HttpUtils.logRequestHeaders(log, request);
- LoginContext lc;
- try {
- lc = CmsAuth.USER.newLoginContext(
- new RemoteAuthCallbackHandler(new ServletHttpRequest(request), new ServletHttpResponse(response)));
- lc.login();
- } catch (LoginException e) {
- lc = processUnauthorized(request, response);
- if (log.isTraceEnabled())
- HttpUtils.logResponseHeaders(log, response);
- if (lc == null)
- return false;
- }
-
- Subject subject = lc.getSubject();
- // log.debug("SERVLET CONTEXT: "+subject);
- Subject.doAs(subject, new PrivilegedAction<Void>() {
-
- @Override
- public Void run() {
- // TODO also set login context in order to log out ?
- RemoteAuthUtils.configureRequestSecurity(new ServletHttpRequest(request));
- return null;
- }
-
- });
- return true;
- }
-
- @Override
- public void finishSecurity(HttpServletRequest request, HttpServletResponse response) {
- RemoteAuthUtils.clearRequestSecurity(new ServletHttpRequest(request));
- }
-
- protected LoginContext processUnauthorized(HttpServletRequest request, HttpServletResponse response) {
- // anonymous
- try {
- LoginContext lc = new LoginContext(CmsAuth.LOGIN_CONTEXT_ANONYMOUS,
- new RemoteAuthCallbackHandler(new ServletHttpRequest(request), new ServletHttpResponse(response)));
- lc.login();
- return lc;
- } catch (LoginException e1) {
- if (log.isDebugEnabled())
- log.error("Cannot log in as anonymous", e1);
- return null;
- }
- }
-
- @Override
- public URL getResource(String name) {
- // TODO make it more robust and versatile
- // if used directly it can only load from within this bundle
- return bundle.getResource(name);
- }
-
-}
+++ /dev/null
-package org.argeo.cms.servlet;
-
-import javax.security.auth.login.LoginContext;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.argeo.cms.auth.SpnegoLoginModule;
-import org.argeo.cms.servlet.internal.HttpUtils;
-
-/** Servlet context forcing authentication. */
-public class PrivateWwwAuthServletContext extends CmsServletContext {
- // TODO make it configurable
- private final String httpAuthRealm = "Argeo";
- private final boolean forceBasic = false;
-
- @Override
- protected LoginContext processUnauthorized(HttpServletRequest request, HttpServletResponse response) {
- askForWwwAuth(request, response);
- return null;
- }
-
- protected void askForWwwAuth(HttpServletRequest request, HttpServletResponse response) {
- // response.setHeader(HttpUtils.HEADER_WWW_AUTHENTICATE, "basic
- // realm=\"" + httpAuthRealm + "\"");
- if (SpnegoLoginModule.hasAcceptorCredentials() && !forceBasic)// SPNEGO
- response.setHeader(HttpUtils.HEADER_WWW_AUTHENTICATE, "Negotiate");
- else
- response.setHeader(HttpUtils.HEADER_WWW_AUTHENTICATE, "Basic realm=\"" + httpAuthRealm + "\"");
-
- // response.setDateHeader("Date", System.currentTimeMillis());
- // response.setDateHeader("Expires", System.currentTimeMillis() + (24 *
- // 60 * 60 * 1000));
- // response.setHeader("Accept-Ranges", "bytes");
- // response.setHeader("Connection", "Keep-Alive");
- // response.setHeader("Keep-Alive", "timeout=5, max=97");
- // response.setContentType("text/html; charset=UTF-8");
- response.setStatus(401);
- }
-}
+++ /dev/null
-package org.argeo.cms.servlet;
-
-import java.util.Locale;
-import java.util.Objects;
-
-import javax.servlet.http.HttpServletRequest;
-
-import org.argeo.cms.auth.RemoteAuthRequest;
-import org.argeo.cms.auth.RemoteAuthSession;
-
-public class ServletHttpRequest implements RemoteAuthRequest {
- private final HttpServletRequest request;
-
- public ServletHttpRequest(HttpServletRequest request) {
- Objects.requireNonNull(request);
- this.request = request;
- }
-
- @Override
- public RemoteAuthSession getSession() {
- return new ServletHttpSession(request.getSession(false));
- }
-
- @Override
- public RemoteAuthSession createSession() {
- return new ServletHttpSession(request.getSession(true));
- }
-
- @Override
- public Locale getLocale() {
- return request.getLocale();
- }
-
- @Override
- public Object getAttribute(String key) {
- return request.getAttribute(key);
- }
-
- @Override
- public void setAttribute(String key, Object object) {
- request.setAttribute(key, object);
- }
-
- @Override
- public String getHeader(String key) {
- return request.getHeader(key);
- }
-
- @Override
- public String getRemoteAddr() {
- return request.getRemoteAddr();
- }
-
- @Override
- public int getLocalPort() {
- return request.getLocalPort();
- }
-
- @Override
- public int getRemotePort() {
- return request.getRemotePort();
- }
-}
+++ /dev/null
-package org.argeo.cms.servlet;
-
-import java.util.Objects;
-
-import javax.servlet.http.HttpServletResponse;
-
-import org.argeo.cms.auth.RemoteAuthResponse;
-
-public class ServletHttpResponse implements RemoteAuthResponse {
- private final HttpServletResponse response;
-
- public ServletHttpResponse(HttpServletResponse response) {
- Objects.requireNonNull(response);
- this.response = response;
- }
-
- @Override
- public void setHeader(String keys, String value) {
- response.setHeader(keys, value);
- }
-
-}
+++ /dev/null
-package org.argeo.cms.servlet;
-
-import org.argeo.cms.auth.RemoteAuthSession;
-
-public class ServletHttpSession implements RemoteAuthSession {
- private javax.servlet.http.HttpSession session;
-
- public ServletHttpSession(javax.servlet.http.HttpSession session) {
- super();
- this.session = session;
- }
-
- @Override
- public boolean isValid() {
- try {// test http session
- session.getCreationTime();
- return true;
- } catch (IllegalStateException ise) {
- return false;
- }
- }
-
- @Override
- public String getId() {
- return session.getId();
- }
-
-}
+++ /dev/null
-package org.argeo.cms.servlet.internal;
-
-import java.util.Enumeration;
-
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.argeo.api.cms.CmsLog;
-
-public class HttpUtils {
- public final static String HEADER_AUTHORIZATION = "Authorization";
- public final static String HEADER_WWW_AUTHENTICATE = "WWW-Authenticate";
-
- static boolean isBrowser(String userAgent) {
- return userAgent.contains("webkit") || userAgent.contains("gecko") || userAgent.contains("firefox")
- || userAgent.contains("msie") || userAgent.contains("chrome") || userAgent.contains("chromium")
- || userAgent.contains("opera") || userAgent.contains("browser");
- }
-
- public static void logResponseHeaders(CmsLog log, HttpServletResponse response) {
- if (!log.isDebugEnabled())
- return;
- for (String headerName : response.getHeaderNames()) {
- Object headerValue = response.getHeader(headerName);
- log.debug(headerName + ": " + headerValue);
- }
- }
-
- public static void logRequestHeaders(CmsLog log, HttpServletRequest request) {
- if (!log.isDebugEnabled())
- return;
- for (Enumeration<String> headerNames = request.getHeaderNames(); headerNames.hasMoreElements();) {
- String headerName = headerNames.nextElement();
- Object headerValue = request.getHeader(headerName);
- log.debug(headerName + ": " + headerValue);
- }
- log.debug(request.getRequestURI() + "\n");
- }
-
- public static void logRequest(CmsLog log, HttpServletRequest request) {
- log.debug("contextPath=" + request.getContextPath());
- log.debug("servletPath=" + request.getServletPath());
- log.debug("requestURI=" + request.getRequestURI());
- log.debug("queryString=" + request.getQueryString());
- StringBuilder buf = new StringBuilder();
- // headers
- Enumeration<String> en = request.getHeaderNames();
- while (en.hasMoreElements()) {
- String header = en.nextElement();
- Enumeration<String> values = request.getHeaders(header);
- while (values.hasMoreElements())
- buf.append(" " + header + ": " + values.nextElement());
- buf.append('\n');
- }
-
- // attributed
- Enumeration<String> an = request.getAttributeNames();
- while (an.hasMoreElements()) {
- String attr = an.nextElement();
- Object value = request.getAttribute(attr);
- buf.append(" " + attr + ": " + value);
- buf.append('\n');
- }
- log.debug("\n" + buf);
- }
-
- private HttpUtils() {
-
- }
-}
+++ /dev/null
-package org.argeo.cms.servlet.internal;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.URL;
-import java.util.Collection;
-import java.util.SortedMap;
-import java.util.TreeMap;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.apache.commons.io.IOUtils;
-import org.argeo.cms.osgi.PublishNamespace;
-import org.argeo.osgi.util.FilterRequirement;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.FrameworkUtil;
-import org.osgi.framework.Version;
-import org.osgi.framework.VersionRange;
-import org.osgi.framework.namespace.PackageNamespace;
-import org.osgi.framework.wiring.BundleCapability;
-import org.osgi.framework.wiring.BundleWiring;
-import org.osgi.framework.wiring.FrameworkWiring;
-import org.osgi.resource.Requirement;
-
-public class PkgServlet extends HttpServlet {
- private static final long serialVersionUID = 7660824185145214324L;
-
- private BundleContext bundleContext = FrameworkUtil.getBundle(PkgServlet.class).getBundleContext();
-
- @Override
- protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
- String pathInfo = req.getPathInfo();
-
- String pkg, versionStr, file;
- String[] parts = pathInfo.split("/");
- // first is always empty
- if (parts.length == 4) {
- pkg = parts[1];
- versionStr = parts[2];
- file = parts[3];
- } else if (parts.length == 3) {
- pkg = parts[1];
- versionStr = null;
- file = parts[2];
- } else {
- throw new IllegalArgumentException("Unsupported path length " + pathInfo);
- }
-
- FrameworkWiring frameworkWiring = bundleContext.getBundle(0).adapt(FrameworkWiring.class);
- String filter;
- if (versionStr == null) {
- filter = "(" + PackageNamespace.PACKAGE_NAMESPACE + "=" + pkg + ")";
- } else {
- if (versionStr.startsWith("[") || versionStr.startsWith("(")) {// range
- VersionRange versionRange = new VersionRange(versionStr);
- filter = "(&(" + PackageNamespace.PACKAGE_NAMESPACE + "=" + pkg + ")"
- + versionRange.toFilterString(PackageNamespace.CAPABILITY_VERSION_ATTRIBUTE) + ")";
-
- } else {
- Version version = new Version(versionStr);
- filter = "(&(" + PackageNamespace.PACKAGE_NAMESPACE + "=" + pkg + ")("
- + PackageNamespace.CAPABILITY_VERSION_ATTRIBUTE + "=" + version + "))";
- }
- }
- Requirement requirement = new FilterRequirement(PackageNamespace.PACKAGE_NAMESPACE, filter);
- Collection<BundleCapability> packages = frameworkWiring.findProviders(requirement);
- if (packages.isEmpty()) {
- resp.sendError(404);
- return;
- }
-
- // TODO verify that it works with multiple versions
- SortedMap<Version, BundleCapability> sorted = new TreeMap<>();
- for (BundleCapability capability : packages) {
- sorted.put(capability.getRevision().getVersion(), capability);
- }
-
- Bundle bundle = sorted.get(sorted.firstKey()).getRevision().getBundle();
- String entryPath = '/' + pkg.replace('.', '/') + '/' + file;
- URL internalURL = bundle.getResource(entryPath);
- if (internalURL == null) {
- resp.sendError(404);
- return;
- }
-
- // Resource found, we now check whether it can be published
- boolean publish = false;
- BundleWiring bundleWiring = bundle.adapt(BundleWiring.class);
- capabilities: for (BundleCapability bundleCapability : bundleWiring
- .getCapabilities(PublishNamespace.CMS_PUBLISH_NAMESPACE)) {
- Object publishedPkg = bundleCapability.getAttributes().get(PublishNamespace.PKG);
- if (publishedPkg != null) {
- if (publishedPkg.equals("*") || publishedPkg.equals(pkg)) {
- Object publishedFile = bundleCapability.getAttributes().get(PublishNamespace.FILE);
- if (publishedFile == null) {
- publish = true;
- break capabilities;
- } else {
- String[] publishedFiles = publishedFile.toString().split(",");
- for (String pattern : publishedFiles) {
- if (pattern.startsWith("*.")) {
- String ext = pattern.substring(1);
- if (file.endsWith(ext)) {
- publish = true;
- break capabilities;
- }
- } else {
- if (publishedFile.equals(file)) {
- publish = true;
- break capabilities;
- }
- }
- }
- }
- }
- }
- }
-
- if (!publish) {
- resp.sendError(404);
- return;
- }
-
- try (InputStream in = internalURL.openStream()) {
- IOUtils.copy(in, resp.getOutputStream());
- }
- }
-
-}
+++ /dev/null
-package org.argeo.cms.servlet.internal;
-
-import java.io.IOException;
-import java.io.PrintWriter;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-public class RobotServlet extends HttpServlet {
- private static final long serialVersionUID = 7935661175336419089L;
-
- @Override
- protected void service(HttpServletRequest request, HttpServletResponse response)
- throws ServletException, IOException {
- PrintWriter writer = response.getWriter();
- writer.append("User-agent: *\n");
- writer.append("Disallow:\n");
- response.setHeader("Content-Type", "text/plain");
- writer.flush();
- }
-
-}
+++ /dev/null
-package org.argeo.cms.servlet.internal.jetty;
-
-import java.util.Dictionary;
-
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.api.cms.CmsLog;
-import org.eclipse.equinox.http.jetty.JettyConfigurator;
-import org.osgi.framework.FrameworkUtil;
-import org.osgi.service.cm.ConfigurationException;
-import org.osgi.service.cm.ManagedServiceFactory;
-
-public class JettyServiceFactory implements ManagedServiceFactory {
- private final CmsLog log = CmsLog.getLog(JettyServiceFactory.class);
-
- public void start() {
-
- }
-
- @Override
- public String getName() {
- return "Jetty Service Factory";
- }
-
- @Override
- public void updated(String pid, Dictionary<String, ?> properties) throws ConfigurationException {
- // Explicitly configures Jetty so that the default server is not started by the
- // activator of the Equinox Jetty bundle.
-
-// if (!webServerConfig.isEmpty()) {
-// webServerConfig.put("customizer.class", KernelConstants.CMS_JETTY_CUSTOMIZER_CLASS);
-//
-// // TODO centralise with Jetty extender
-// Object webSocketEnabled = webServerConfig.get(InternalHttpConstants.WEBSOCKET_ENABLED);
-// if (webSocketEnabled != null && webSocketEnabled.toString().equals("true")) {
-// bc.registerService(ServerEndpointConfig.Configurator.class, new CmsWebSocketConfigurator(), null);
-// webServerConfig.put(InternalHttpConstants.WEBSOCKET_ENABLED, "true");
-// }
-// }
-
- int tryCount = 60;
- try {
- tryGettyJetty: while (tryCount > 0) {
- try {
- // FIXME deal with multiple ids
- JettyConfigurator.startServer(CmsConstants.DEFAULT, properties);
- // Explicitly starts Jetty OSGi HTTP bundle, so that it gets triggered if OSGi
- // configuration is not cleaned
- FrameworkUtil.getBundle(JettyConfigurator.class).start();
- break tryGettyJetty;
- } catch (IllegalStateException e) {
- // Jetty may not be ready
- try {
- Thread.sleep(1000);
- } catch (Exception e1) {
- // silent
- }
- tryCount--;
- }
- }
- } catch (Exception e) {
- log.error("Cannot start default Jetty server with config " + properties, e);
- }
-
- }
-
- @Override
- public void deleted(String pid) {
- }
-
- public void stop() {
- try {
- JettyConfigurator.stopServer(CmsConstants.DEFAULT);
- } catch (Exception e) {
- log.error("Cannot stop default Jetty server.", e);
- }
-
- }
-
-}
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8"?>
-<classpath>
- <classpathentry kind="src" path="src" />
- <classpathentry kind="con"
- path="org.eclipse.pde.core.requiredPlugins" />
- <classpathentry kind="con"
- path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11" />
- <classpathentry kind="output" path="bin" />
-</classpath>
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
- <name>org.argeo.cms.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
-/MANIFEST.MF
+++ /dev/null
-Import-Package: org.eclipse.swt,\
- org.eclipse.jface.window,\
- org.eclipse.core.commands.common,\
- *
-
-Bundle-ActivationPolicy: lazy
-
\ No newline at end of file
+++ /dev/null
-source.. = src/
-output.. = bin/
-bin.includes = META-INF/,\
- .
-
\ No newline at end of file
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
- <modelVersion>4.0.0</modelVersion>
- <parent>
- <groupId>org.argeo.commons</groupId>
- <version>2.3-SNAPSHOT</version>
- <artifactId>argeo-commons</artifactId>
- <relativePath>..</relativePath>
- </parent>
- <artifactId>org.argeo.cms.swt</artifactId>
- <name>CMS SWT</name>
- <dependencies>
-<!-- <dependency> -->
-<!-- <groupId>org.argeo.commons</groupId> -->
-<!-- <artifactId>org.argeo.util</artifactId> -->
-<!-- <version>2.1.89-SNAPSHOT</version> -->
-<!-- </dependency> -->
- <dependency>
- <groupId>org.argeo.commons</groupId>
- <artifactId>org.argeo.cms</artifactId>
- <version>2.3-SNAPSHOT</version>
- </dependency>
- <dependency>
- <groupId>org.argeo.commons</groupId>
- <artifactId>org.argeo.cms.servlet</artifactId>
- <version>2.3-SNAPSHOT</version>
- </dependency>
-
- <!-- Specific -->
- <dependency>
- <groupId>org.argeo.commons.rap</groupId>
- <artifactId>org.argeo.swt.specific.rap</artifactId>
- <version>2.3-SNAPSHOT</version>
- <scope>provided</scope>
- </dependency>
- <!-- UI -->
- <dependency>
- <groupId>org.argeo.tp.rap.e4</groupId>
- <artifactId>org.eclipse.rap.rwt</artifactId>
- <scope>provided</scope>
- </dependency>
- <dependency>
- <groupId>org.argeo.tp.rap.e4</groupId>
- <artifactId>org.eclipse.core.commands</artifactId>
- <scope>provided</scope>
- </dependency>
- <dependency>
- <groupId>org.argeo.tp.rap.e4</groupId>
- <artifactId>org.eclipse.rap.jface</artifactId>
- <scope>provided</scope>
- </dependency>
- </dependencies>
-</project>
\ No newline at end of file
+++ /dev/null
-package org.argeo.cms.swt;
-
-import org.argeo.api.cms.CmsTheme;
-import org.eclipse.swt.graphics.Image;
-
-/** Can be applied to {@link Enum}s in order to generated {@link Image}s. */
-public interface CmsIcon {
- String name();
-
- default Image getSmallIcon(CmsTheme theme) {
- return ((CmsSwtTheme) theme).getIcon(name(), getSmallIconSize());
- }
-
- default Image getBigIcon(CmsTheme theme) {
- return ((CmsSwtTheme) theme).getIcon(name(), getBigIconSize());
- }
-
- default Integer getSmallIconSize() {
- return 16;
- }
-
- default Integer getBigIconSize() {
- return 32;
- }
-}
+++ /dev/null
-package org.argeo.cms.swt;
-
-/** Styles references in the CSS. */
-@Deprecated
-public interface CmsStyles {
- // General
- public final static String CMS_SHELL = "cms_shell";
- public final static String CMS_MENU_LINK = "cms_menu_link";
-
- // Header
- public final static String CMS_HEADER = "cms_header";
- public final static String CMS_HEADER_LEAD = "cms_header-lead";
- public final static String CMS_HEADER_CENTER = "cms_header-center";
- public final static String CMS_HEADER_END = "cms_header-end";
-
- public final static String CMS_LEAD = "cms_lead";
- public final static String CMS_END = "cms_end";
- public final static String CMS_FOOTER = "cms_footer";
-
- public final static String CMS_USER_MENU = "cms_user_menu";
- public final static String CMS_USER_MENU_LINK = "cms_user_menu-link";
- public final static String CMS_USER_MENU_ITEM = "cms_user_menu-item";
- public final static String CMS_LOGIN_DIALOG = "cms_login_dialog";
- public final static String CMS_LOGIN_DIALOG_USERNAME = "cms_login_dialog-username";
- public final static String CMS_LOGIN_DIALOG_PASSWORD = "cms_login_dialog-password";
-
- // Body
- public final static String CMS_SCROLLED_AREA = "cms_scrolled_area";
- public final static String CMS_BODY = "cms_body";
- public final static String CMS_STATIC_TEXT = "cms_static-text";
- public final static String CMS_LINK = "cms_link";
-}
+++ /dev/null
-package org.argeo.cms.swt;
-
-import org.argeo.api.cms.CmsTheme;
-import org.eclipse.swt.graphics.Image;
-
-/** SWT specific {@link CmsTheme}. */
-public interface CmsSwtTheme extends CmsTheme {
- /** The image registered at this path, or <code>null</code> if not found. */
- Image getImage(String path);
-
- /**
- * And icon with this file name (without the extension), with a best effort to
- * find the appropriate size, or <code>null</code> if not found.
- *
- * @param name An icon file name without path and extension.
- * @param preferredSize the preferred size, if <code>null</code>,
- * {@link #getDefaultIconSize()} will be tried.
- */
- Image getIcon(String name, Integer preferredSize);
-
-}
+++ /dev/null
-package org.argeo.cms.swt;
-
-import java.util.HashMap;
-import java.util.Map;
-
-import org.argeo.api.cms.CmsStyle;
-import org.argeo.api.cms.CmsTheme;
-import org.argeo.api.cms.CmsView;
-import org.argeo.eclipse.ui.specific.EclipseUiSpecificUtils;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.events.SelectionListener;
-import org.eclipse.swt.layout.FormAttachment;
-import org.eclipse.swt.layout.FormData;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.layout.RowData;
-import org.eclipse.swt.layout.RowLayout;
-import org.eclipse.swt.widgets.Button;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Label;
-import org.eclipse.swt.widgets.Shell;
-import org.eclipse.swt.widgets.Text;
-import org.eclipse.swt.widgets.Widget;
-
-/** SWT utilities. */
-public class CmsSwtUtils {
-
- /** Singleton. */
- private CmsSwtUtils() {
- }
-
- public static CmsTheme getCmsTheme(Composite parent) {
- CmsTheme theme = (CmsTheme) parent.getData(CmsTheme.class.getName());
- if (theme == null) {
- // find parent shell
- Shell topShell = parent.getShell();
- while (topShell.getParent() != null)
- topShell = (Shell) topShell.getParent();
- theme = (CmsTheme) topShell.getData(CmsTheme.class.getName());
- parent.setData(CmsTheme.class.getName(), theme);
- }
- return theme;
- }
-
- public static void registerCmsTheme(Shell shell, CmsTheme theme) {
- // find parent shell
- Shell topShell = shell;
- while (topShell.getParent() != null)
- topShell = (Shell) topShell.getParent();
- // check if already set
- if (topShell.getData(CmsTheme.class.getName()) != null) {
- CmsTheme registeredTheme = (CmsTheme) topShell.getData(CmsTheme.class.getName());
- throw new IllegalArgumentException(
- "Theme " + registeredTheme.getThemeId() + " already registered in this shell");
- }
- topShell.setData(CmsTheme.class.getName(), theme);
- }
-
- public static CmsView getCmsView(Control parent) {
- // find parent shell
- Shell topShell = parent.getShell();
- while (topShell.getParent() != null)
- topShell = (Shell) topShell.getParent();
- return (CmsView) topShell.getData(CmsView.class.getName());
- }
-
- public static void registerCmsView(Shell shell, CmsView view) {
- // find parent shell
- Shell topShell = shell;
- while (topShell.getParent() != null)
- topShell = (Shell) topShell.getParent();
- // check if already set
- if (topShell.getData(CmsView.class.getName()) != null) {
- CmsView registeredView = (CmsView) topShell.getData(CmsView.class.getName());
- throw new IllegalArgumentException("Cms view " + registeredView + " already registered in this shell");
- }
- shell.setData(CmsView.class.getName(), view);
- }
-
- /** Sends an event via {@link CmsView#sendEvent(String, Map)}. */
- public static void sendEventOnSelect(Control control, String topic, Map<String, Object> properties) {
- SelectionListener listener = (Selected) (e) -> {
- getCmsView(control.getParent()).sendEvent(topic, properties);
- };
- if (control instanceof Button) {
- ((Button) control).addSelectionListener(listener);
- } else
- throw new UnsupportedOperationException("Control type " + control.getClass() + " is not supported.");
- }
-
- /**
- * Convenience method to sends an event via
- * {@link CmsView#sendEvent(String, Map)}.
- */
- public static void sendEventOnSelect(Control control, String topic, String key, Object value) {
- Map<String, Object> properties = new HashMap<>();
- properties.put(key, value);
- sendEventOnSelect(control, topic, properties);
- }
-
- /*
- * GRID LAYOUT
- */
- public static GridLayout noSpaceGridLayout() {
- return noSpaceGridLayout(new GridLayout());
- }
-
- public static GridLayout noSpaceGridLayout(int columns) {
- return noSpaceGridLayout(new GridLayout(columns, false));
- }
-
- /** @return the same layout, with spaces removed. */
- public static GridLayout noSpaceGridLayout(GridLayout layout) {
- layout.horizontalSpacing = 0;
- layout.verticalSpacing = 0;
- layout.marginWidth = 0;
- layout.marginHeight = 0;
- return layout;
- }
-
- public static GridData fillAll() {
- return new GridData(SWT.FILL, SWT.FILL, true, true);
- }
-
- public static GridData fillWidth() {
- return grabWidth(SWT.FILL, SWT.FILL);
- }
-
- public static GridData grabWidth(int horizontalAlignment, int verticalAlignment) {
- return new GridData(horizontalAlignment, horizontalAlignment, true, false);
- }
-
- public static GridData fillHeight() {
- return grabHeight(SWT.FILL, SWT.FILL);
- }
-
- public static GridData grabHeight(int horizontalAlignment, int verticalAlignment) {
- return new GridData(horizontalAlignment, horizontalAlignment, false, true);
- }
-
- /*
- * ROW LAYOUT
- */
- /** @return the same layout, with margins removed. */
- public static RowLayout noMarginsRowLayout(RowLayout rowLayout) {
- rowLayout.marginTop = 0;
- rowLayout.marginBottom = 0;
- rowLayout.marginLeft = 0;
- rowLayout.marginRight = 0;
- return rowLayout;
- }
-
- public static RowLayout noMarginsRowLayout(int type) {
- return noMarginsRowLayout(new RowLayout(type));
- }
-
- public static RowData rowData16px() {
- return new RowData(16, 16);
- }
-
- /*
- * FORM LAYOUT
- */
- public static FormData coverAll() {
- FormData fdLabel = new FormData();
- fdLabel.top = new FormAttachment(0, 0);
- fdLabel.left = new FormAttachment(0, 0);
- fdLabel.right = new FormAttachment(100, 0);
- fdLabel.bottom = new FormAttachment(100, 0);
- return fdLabel;
- }
-
- /*
- * STYLING
- */
-
- /** Style widget */
- public static <T extends Widget> T style(T widget, String style) {
- if (style == null)
- return widget;// does nothing
- EclipseUiSpecificUtils.setStyleData(widget, style);
- if (widget instanceof Control) {
- CmsView cmsView = getCmsView((Control) widget);
- if (cmsView != null)
- cmsView.applyStyles(widget);
- }
- return widget;
- }
-
- /** Style widget */
- public static <T extends Widget> T style(T widget, CmsStyle style) {
- return style(widget, style.style());
- }
-
- /** Enable markups on widget */
- public static <T extends Widget> T markup(T widget) {
- EclipseUiSpecificUtils.setMarkupData(widget);
- return widget;
- }
-
- /** Disable markup validation. */
- public static <T extends Widget> T disableMarkupValidation(T widget) {
- EclipseUiSpecificUtils.setMarkupValidationDisabledData(widget);
- return widget;
- }
-
- /**
- * Apply markup and set text on {@link Label}, {@link Button}, {@link Text}.
- *
- * @param widget the widget to style and to use in order to display text
- * @param txt the object to display via its <code>toString()</code> method.
- * This argument should not be null, but if it is null and
- * assertions are disabled "<null>" is displayed instead; if
- * assertions are enabled the call will fail.
- *
- * @see markup
- */
- public static <T extends Widget> T text(T widget, Object txt) {
- assert txt != null;
- String str = txt != null ? txt.toString() : "<null>";
- markup(widget);
- if (widget instanceof Label)
- ((Label) widget).setText(str);
- else if (widget instanceof Button)
- ((Button) widget).setText(str);
- else if (widget instanceof Text)
- ((Text) widget).setText(str);
- else
- throw new IllegalArgumentException("Unsupported widget type " + widget.getClass());
- return widget;
- }
-
- /** A {@link Label} with markup activated. */
- public static Label lbl(Composite parent, Object txt) {
- return text(new Label(parent, SWT.NONE), txt);
- }
-
- /** A read-only {@link Text} whose content can be copy/pasted. */
- public static Text txt(Composite parent, Object txt) {
- return text(new Text(parent, SWT.NONE), txt);
- }
-
- /** Dispose all children of a Composite */
- public static void clear(Composite composite) {
- if (composite.isDisposed())
- return;
- for (Control child : composite.getChildren())
- child.dispose();
- }
-}
+++ /dev/null
-package org.argeo.cms.swt;
-
-import org.eclipse.swt.events.MouseEvent;
-import org.eclipse.swt.events.MouseListener;
-
-/**
- * {@link MouseListener#mouseDoubleClick(MouseEvent)} as a functional interface
- * in order to use as a short lambda expression in UI code.
- * {@link MouseListener#mouseDownouseEvent)} and
- * {@link MouseListener#mouseUp(MouseEvent)} do nothing by default.
- */
-@FunctionalInterface
-public interface MouseDoubleClick extends MouseListener {
- @Override
- void mouseDoubleClick(MouseEvent e);
-
- @Override
- default void mouseDown(MouseEvent e) {
- // does nothing
- }
-
- @Override
- default void mouseUp(MouseEvent e) {
- // does nothing
- }
-}
+++ /dev/null
-package org.argeo.cms.swt;
-
-import org.eclipse.swt.events.MouseEvent;
-import org.eclipse.swt.events.MouseListener;
-
-/**
- * {@link MouseListener#mouseDown(MouseEvent)} as a functional interface in
- * order to use as a short lambda expression in UI code.
- * {@link MouseListener#mouseDoubleClick(MouseEvent)} and
- * {@link MouseListener#mouseUp(MouseEvent)} do nothing by default.
- */
-@FunctionalInterface
-public interface MouseDown extends MouseListener {
- @Override
- void mouseDown(MouseEvent e);
-
- @Override
- default void mouseDoubleClick(MouseEvent e) {
- // does nothing
- }
-
- @Override
- default void mouseUp(MouseEvent e) {
- // does nothing
- }
-}
+++ /dev/null
-package org.argeo.cms.swt;
-
-import org.eclipse.swt.events.SelectionEvent;
-import org.eclipse.swt.events.SelectionListener;
-
-/**
- * {@link SelectionListener} as a functional interface in order to use as a
- * short lambda expression in UI code.
- * {@link SelectionListener#widgetDefaultSelected(SelectionEvent)} does nothing
- * by default.
- */
-@FunctionalInterface
-public interface Selected extends SelectionListener {
- @Override
- public void widgetSelected(SelectionEvent e);
-
- default public void widgetDefaultSelected(SelectionEvent e) {
- // does nothing
- }
-
-}
+++ /dev/null
-package org.argeo.cms.swt;
-
-import org.argeo.api.cms.UxContext;
-import org.eclipse.swt.graphics.Point;
-import org.eclipse.swt.graphics.Rectangle;
-import org.eclipse.swt.widgets.Display;
-
-public class SimpleSwtUxContext implements UxContext {
- private Point size;
- private Point small = new Point(400, 400);
-
- public SimpleSwtUxContext() {
- this(Display.getCurrent().getBounds());
- }
-
- public SimpleSwtUxContext(Rectangle rect) {
- this.size = new Point(rect.width, rect.height);
- }
-
- public SimpleSwtUxContext(Point size) {
- this.size = size;
- }
-
- @Override
- public boolean isPortrait() {
- return size.x >= size.y;
- }
-
- @Override
- public boolean isLandscape() {
- return size.x < size.y;
- }
-
- @Override
- public boolean isSquare() {
- return size.x == size.y;
- }
-
- @Override
- public boolean isSmall() {
- return size.x <= small.x || size.y <= small.y;
- }
-
- @Override
- public boolean isMasterData() {
- // TODO make it configurable
- return true;
- }
-
-}
+++ /dev/null
-package org.argeo.cms.swt.auth;
-
-import static org.argeo.cms.CmsMsg.password;
-import static org.argeo.cms.CmsMsg.username;
-
-import java.io.IOException;
-import java.util.List;
-import java.util.Locale;
-
-import javax.security.auth.Subject;
-import javax.security.auth.callback.Callback;
-import javax.security.auth.callback.CallbackHandler;
-import javax.security.auth.callback.LanguageCallback;
-import javax.security.auth.callback.NameCallback;
-import javax.security.auth.callback.PasswordCallback;
-import javax.security.auth.callback.UnsupportedCallbackException;
-import javax.security.auth.login.LoginContext;
-import javax.security.auth.login.LoginException;
-
-import org.argeo.api.cms.CmsAuth;
-import org.argeo.api.cms.CmsContext;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.api.cms.CmsView;
-import org.argeo.cms.CmsMsg;
-import org.argeo.cms.LocaleUtils;
-import org.argeo.cms.auth.RemoteAuthCallback;
-import org.argeo.cms.servlet.ServletHttpRequest;
-import org.argeo.cms.servlet.ServletHttpResponse;
-import org.argeo.cms.swt.CmsStyles;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.eclipse.ui.specific.UiContext;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.events.MouseAdapter;
-import org.eclipse.swt.events.MouseEvent;
-import org.eclipse.swt.events.SelectionAdapter;
-import org.eclipse.swt.events.SelectionEvent;
-import org.eclipse.swt.events.SelectionListener;
-import org.eclipse.swt.events.TraverseEvent;
-import org.eclipse.swt.events.TraverseListener;
-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.Label;
-import org.eclipse.swt.widgets.Shell;
-import org.eclipse.swt.widgets.Text;
-
-public class CmsLogin implements CmsStyles, CallbackHandler {
- private final static CmsLog log = CmsLog.getLog(CmsLogin.class);
-
- private Composite parent;
- private Text usernameT, passwordT;
- private Composite credentialsBlock;
- private final SelectionListener loginSelectionListener;
-
- private final Locale defaultLocale;
- private LocaleChoice localeChoice = null;
-
- private final CmsView cmsView;
-
- // optional subject to be set explicitly
- private Subject subject = null;
-
- public CmsLogin(CmsView cmsView) {
- this.cmsView = cmsView;
- CmsContext nodeState = null;// = Activator.getNodeState();
- // FIXME reactivate locales
- if (nodeState != null) {
- defaultLocale = nodeState.getDefaultLocale();
- List<Locale> locales = nodeState.getLocales();
- if (locales != null)
- localeChoice = new LocaleChoice(locales, defaultLocale);
- } else {
- defaultLocale = Locale.getDefault();
- }
- loginSelectionListener = new SelectionListener() {
- private static final long serialVersionUID = -8832133363830973578L;
-
- @Override
- public void widgetSelected(SelectionEvent e) {
- login();
- }
-
- @Override
- public void widgetDefaultSelected(SelectionEvent e) {
- }
- };
- }
-
- protected boolean isAnonymous() {
- return cmsView.isAnonymous();
- }
-
- public final void createUi(Composite parent) {
- this.parent = parent;
- createContents(parent);
- }
-
- protected void createContents(Composite parent) {
- defaultCreateContents(parent);
- }
-
- public final void defaultCreateContents(Composite parent) {
- parent.setLayout(CmsSwtUtils.noSpaceGridLayout());
- Composite credentialsBlock = createCredentialsBlock(parent);
- if (parent instanceof Shell) {
- credentialsBlock.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, true, true));
- }
- }
-
- public final Composite createCredentialsBlock(Composite parent) {
- if (isAnonymous()) {
- return anonymousUi(parent);
- } else {
- return userUi(parent);
- }
- }
-
- public Composite getCredentialsBlock() {
- return credentialsBlock;
- }
-
- protected Composite userUi(Composite parent) {
- Locale locale = localeChoice == null ? this.defaultLocale : localeChoice.getSelectedLocale();
- credentialsBlock = new Composite(parent, SWT.NONE);
- credentialsBlock.setLayout(new GridLayout());
- // credentialsBlock.setLayoutData(CmsUiUtils.fillAll());
-
- specificUserUi(credentialsBlock);
-
- Label l = new Label(credentialsBlock, SWT.NONE);
- CmsSwtUtils.style(l, CMS_USER_MENU_ITEM);
- l.setText(CmsMsg.logout.lead(locale));
- GridData lData = CmsSwtUtils.fillWidth();
- lData.widthHint = 120;
- l.setLayoutData(lData);
-
- l.addMouseListener(new MouseAdapter() {
- private static final long serialVersionUID = 6444395812777413116L;
-
- public void mouseDown(MouseEvent e) {
- logout();
- }
- });
- return credentialsBlock;
- }
-
- /** To be overridden */
- protected void specificUserUi(Composite parent) {
-
- }
-
- protected Composite anonymousUi(Composite parent) {
- Locale locale = localeChoice == null ? this.defaultLocale : localeChoice.getSelectedLocale();
- // We need a composite for the traversal
- credentialsBlock = new Composite(parent, SWT.NONE);
- credentialsBlock.setLayout(new GridLayout());
- // credentialsBlock.setLayoutData(CmsUiUtils.fillAll());
- CmsSwtUtils.style(credentialsBlock, CMS_LOGIN_DIALOG);
-
- Integer textWidth = 120;
- if (parent instanceof Shell)
- CmsSwtUtils.style(parent, CMS_USER_MENU);
- // new Label(this, SWT.NONE).setText(CmsMsg.username.lead());
- usernameT = new Text(credentialsBlock, SWT.BORDER);
- usernameT.setMessage(username.lead(locale));
- CmsSwtUtils.style(usernameT, CMS_LOGIN_DIALOG_USERNAME);
- GridData gd = CmsSwtUtils.fillWidth();
- gd.widthHint = textWidth;
- usernameT.setLayoutData(gd);
-
- // new Label(this, SWT.NONE).setText(CmsMsg.password.lead());
- passwordT = new Text(credentialsBlock, SWT.BORDER | SWT.PASSWORD);
- passwordT.setMessage(password.lead(locale));
- CmsSwtUtils.style(passwordT, CMS_LOGIN_DIALOG_PASSWORD);
- gd = CmsSwtUtils.fillWidth();
- gd.widthHint = textWidth;
- passwordT.setLayoutData(gd);
-
- TraverseListener tl = new TraverseListener() {
- private static final long serialVersionUID = -1158892811534971856L;
-
- public void keyTraversed(TraverseEvent e) {
- if (e.detail == SWT.TRAVERSE_RETURN)
- login();
- }
- };
- credentialsBlock.addTraverseListener(tl);
- usernameT.addTraverseListener(tl);
- passwordT.addTraverseListener(tl);
- parent.setTabList(new Control[] { credentialsBlock });
- credentialsBlock.setTabList(new Control[] { usernameT, passwordT });
-
- // Button
- Button loginButton = new Button(credentialsBlock, SWT.PUSH);
- loginButton.setText(CmsMsg.login.lead(locale));
- loginButton.setLayoutData(CmsSwtUtils.fillWidth());
- loginButton.addSelectionListener(loginSelectionListener);
-
- extendsCredentialsBlock(credentialsBlock, locale, loginSelectionListener);
- if (localeChoice != null)
- createLocalesBlock(credentialsBlock);
- return credentialsBlock;
- }
-
- /**
- * To be overridden in order to provide custom login button and other links.
- */
- protected void extendsCredentialsBlock(Composite credentialsBlock, Locale selectedLocale,
- SelectionListener loginSelectionListener) {
-
- }
-
- protected void updateLocale(Locale selectedLocale) {
- // save already entered values
- String usernameStr = usernameT.getText();
- char[] pwd = passwordT.getTextChars();
-
- for (Control child : parent.getChildren())
- child.dispose();
- createContents(parent);
- if (parent.getParent() != null)
- parent.getParent().layout(true, true);
- else
- parent.layout();
- usernameT.setText(usernameStr);
- passwordT.setTextChars(pwd);
- }
-
- protected Composite createLocalesBlock(final Composite parent) {
- Composite c = new Composite(parent, SWT.NONE);
- CmsSwtUtils.style(c, CMS_USER_MENU_ITEM);
- c.setLayout(CmsSwtUtils.noSpaceGridLayout());
- c.setLayoutData(CmsSwtUtils.fillAll());
-
- SelectionListener selectionListener = new SelectionAdapter() {
- private static final long serialVersionUID = 4891637813567806762L;
-
- public void widgetSelected(SelectionEvent event) {
- Button button = (Button) event.widget;
- if (button.getSelection()) {
- localeChoice.setSelectedIndex((Integer) event.widget.getData());
- updateLocale(localeChoice.getSelectedLocale());
- }
- };
- };
-
- List<Locale> locales = localeChoice.getLocales();
- for (Integer i = 0; i < locales.size(); i++) {
- Locale locale = locales.get(i);
- Button button = new Button(c, SWT.RADIO);
- CmsSwtUtils.style(button, CMS_USER_MENU_ITEM);
- button.setData(i);
- button.setText(LocaleUtils.toLead(locale.getDisplayName(locale), locale) + " (" + locale + ")");
- // button.addListener(SWT.Selection, listener);
- button.addSelectionListener(selectionListener);
- if (i == localeChoice.getSelectedIndex())
- button.setSelection(true);
- }
- return c;
- }
-
- protected boolean login() {
- // TODO use CmsVie in order to retrieve subject?
- // Subject subject = cmsView.getLoginContext().getSubject();
- // LoginContext loginContext = cmsView.getLoginContext();
- try {
- //
- // LOGIN
- //
- // loginContext.logout();
- LoginContext loginContext;
- if (subject == null)
- loginContext = new LoginContext(CmsAuth.LOGIN_CONTEXT_USER, this);
- else
- loginContext = new LoginContext(CmsAuth.LOGIN_CONTEXT_USER, subject, this);
- loginContext.login();
- cmsView.authChange(loginContext);
- return true;
- } catch (LoginException e) {
- if (log.isTraceEnabled())
- log.warn("Login failed: " + e.getMessage(), e);
- else
- log.warn("Login failed: " + e.getMessage());
-
- try {
- Thread.sleep(3000);
- } catch (InterruptedException e2) {
- // silent
- }
- // ErrorFeedback.show("Login failed", e);
- return false;
- }
- // catch (LoginException e) {
- // log.error("Cannot login", e);
- // return false;
- // }
- }
-
- protected void logout() {
- cmsView.logout();
- cmsView.navigateTo("~");
- }
-
- @Override
- public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
- for (Callback callback : callbacks) {
- if (callback instanceof NameCallback && usernameT != null)
- ((NameCallback) callback).setName(usernameT.getText());
- else if (callback instanceof PasswordCallback && passwordT != null)
- ((PasswordCallback) callback).setPassword(passwordT.getTextChars());
- else if (callback instanceof RemoteAuthCallback) {
- ((RemoteAuthCallback) callback).setRequest(new ServletHttpRequest(UiContext.getHttpRequest()));
- ((RemoteAuthCallback) callback).setResponse(new ServletHttpResponse(UiContext.getHttpResponse()));
- } else if (callback instanceof LanguageCallback) {
- Locale toUse = null;
- if (localeChoice != null)
- toUse = localeChoice.getSelectedLocale();
- else if (defaultLocale != null)
- toUse = defaultLocale;
-
- if (toUse != null) {
- ((LanguageCallback) callback).setLocale(toUse);
- UiContext.setLocale(toUse);
- }
-
- }
- }
- }
-
- public void setSubject(Subject subject) {
- this.subject = subject;
- }
-
-}
+++ /dev/null
-package org.argeo.cms.swt.auth;
-
-import org.argeo.api.cms.CmsView;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Display;
-import org.eclipse.swt.widgets.Shell;
-
-/** The site-related user menu */
-public class CmsLoginShell extends CmsLogin {
- private final Shell shell;
-
- public CmsLoginShell(CmsView cmsView) {
- super(cmsView);
- shell = createShell();
-// createUi(shell);
- }
-
- /** To be overridden. */
- protected Shell createShell() {
- Shell shell = new Shell(Display.getCurrent(), SWT.NO_TRIM);
- shell.setMaximized(true);
- return shell;
- }
-
- /** To be overridden. */
- public void open() {
- CmsSwtUtils.style(shell, CMS_USER_MENU);
- shell.open();
- }
-
- @Override
- protected boolean login() {
- boolean success = false;
- try {
- success = super.login();
- return success;
- } finally {
- if (success)
- closeShell();
- else {
- for (Control child : shell.getChildren())
- child.dispose();
- createUi(shell);
- shell.layout();
- // TODO error message
- }
- }
- }
-
- @Override
- protected void logout() {
- closeShell();
- super.logout();
- }
-
- protected void closeShell() {
- if (!shell.isDisposed()) {
- shell.close();
- shell.dispose();
- }
- }
-
- public Shell getShell() {
- return shell;
- }
-
- public void createUi(){
- createUi(shell);
- }
-}
+++ /dev/null
-package org.argeo.cms.swt.auth;
-
-import java.io.IOException;
-import java.util.Arrays;
-
-import javax.security.auth.callback.Callback;
-import javax.security.auth.callback.CallbackHandler;
-import javax.security.auth.callback.NameCallback;
-import javax.security.auth.callback.PasswordCallback;
-import javax.security.auth.callback.TextOutputCallback;
-import javax.security.auth.callback.UnsupportedCallbackException;
-
-import org.eclipse.swt.SWT;
-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.events.SelectionEvent;
-import org.eclipse.swt.events.SelectionListener;
-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.Text;
-
-/**
- * A composite that can populate itself based on {@link Callback}s. It can be
- * used directly as a {@link CallbackHandler} or be used by one by calling the
- * {@link #createCallbackHandlers(Callback[])}. Supported standard
- * {@link Callback}s are:<br>
- * <ul>
- * <li>{@link PasswordCallback}</li>
- * <li>{@link NameCallback}</li>
- * <li>{@link TextOutputCallback}</li>
- * </ul>
- * Supported Argeo {@link Callback}s are:<br>
- * <ul>
- * <li>{@link LocaleChoice}</li>
- * </ul>
- */
-public class CompositeCallbackHandler extends Composite implements CallbackHandler {
- private static final long serialVersionUID = -928223893722723777L;
-
- private boolean wasUsedAlready = false;
- private boolean isSubmitted = false;
- private boolean isCanceled = false;
-
- public CompositeCallbackHandler(Composite parent, int style) {
- super(parent, style);
- }
-
- @Override
- public synchronized void handle(final Callback[] callbacks) throws IOException, UnsupportedCallbackException {
- // reset
- if (wasUsedAlready && !isSubmitted() && !isCanceled()) {
- cancel();
- for (Control control : getChildren())
- control.dispose();
- isSubmitted = false;
- isCanceled = false;
- }
-
- for (Callback callback : callbacks)
- checkCallbackSupported(callback);
- // create controls synchronously in the UI thread
- getDisplay().syncExec(new Runnable() {
-
- @Override
- public void run() {
- createCallbackHandlers(callbacks);
- }
- });
-
- if (!wasUsedAlready)
- wasUsedAlready = true;
-
- // while (!isSubmitted() && !isCanceled()) {
- // try {
- // wait(1000l);
- // } catch (InterruptedException e) {
- // // silent
- // }
- // }
-
- // cleanCallbacksAfterCancel(callbacks);
- }
-
- public void checkCallbackSupported(Callback callback) throws UnsupportedCallbackException {
- if (callback instanceof TextOutputCallback || callback instanceof NameCallback
- || callback instanceof PasswordCallback || callback instanceof LocaleChoice) {
- return;
- } else {
- throw new UnsupportedCallbackException(callback);
- }
- }
-
- /**
- * Set writable callbacks to null if the handle is canceled (check is done
- * by the method)
- */
- public void cleanCallbacksAfterCancel(Callback[] callbacks) {
- if (isCanceled()) {
- for (Callback callback : callbacks) {
- if (callback instanceof NameCallback) {
- ((NameCallback) callback).setName(null);
- } else if (callback instanceof PasswordCallback) {
- PasswordCallback pCallback = (PasswordCallback) callback;
- char[] arr = pCallback.getPassword();
- if (arr != null) {
- Arrays.fill(arr, '*');
- pCallback.setPassword(null);
- }
- }
- }
- }
- }
-
- public void createCallbackHandlers(Callback[] callbacks) {
- Composite composite = this;
- for (int i = 0; i < callbacks.length; i++) {
- Callback callback = callbacks[i];
- if (callback instanceof TextOutputCallback) {
- createLabelTextoutputHandler(composite, (TextOutputCallback) callback);
- } else if (callback instanceof NameCallback) {
- createNameHandler(composite, (NameCallback) callback);
- } else if (callback instanceof PasswordCallback) {
- createPasswordHandler(composite, (PasswordCallback) callback);
- } else if (callback instanceof LocaleChoice) {
- createLocaleHandler(composite, (LocaleChoice) callback);
- }
- }
- }
-
- protected Text createNameHandler(Composite composite, final NameCallback callback) {
- Label label = new Label(composite, SWT.NONE);
- label.setText(callback.getPrompt());
- final Text text = new Text(composite, SWT.SINGLE | SWT.LEAD | SWT.BORDER);
- if (callback.getDefaultName() != null) {
- // set default value, if provided
- text.setText(callback.getDefaultName());
- callback.setName(callback.getDefaultName());
- }
- text.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
- text.addModifyListener(new ModifyListener() {
- private static final long serialVersionUID = 7300032545287292973L;
-
- public void modifyText(ModifyEvent event) {
- callback.setName(text.getText());
- }
- });
- text.addSelectionListener(new SelectionListener() {
- private static final long serialVersionUID = 1820530045857665111L;
-
- @Override
- public void widgetSelected(SelectionEvent e) {
- }
-
- @Override
- public void widgetDefaultSelected(SelectionEvent e) {
- submit();
- }
- });
-
- text.addKeyListener(new KeyListener() {
- private static final long serialVersionUID = -8698107785092095713L;
-
- @Override
- public void keyReleased(KeyEvent e) {
- }
-
- @Override
- public void keyPressed(KeyEvent e) {
- }
- });
- return text;
- }
-
- protected Text createPasswordHandler(Composite composite, final PasswordCallback callback) {
- Label label = new Label(composite, SWT.NONE);
- label.setText(callback.getPrompt());
- final Text passwordText = new Text(composite, SWT.SINGLE | SWT.LEAD | SWT.PASSWORD | SWT.BORDER);
- passwordText.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
- passwordText.addModifyListener(new ModifyListener() {
- private static final long serialVersionUID = -7099363995047686732L;
-
- public void modifyText(ModifyEvent event) {
- callback.setPassword(passwordText.getTextChars());
- }
- });
- passwordText.addSelectionListener(new SelectionListener() {
- private static final long serialVersionUID = 1820530045857665111L;
-
- @Override
- public void widgetSelected(SelectionEvent e) {
- }
-
- @Override
- public void widgetDefaultSelected(SelectionEvent e) {
- submit();
- }
- });
- return passwordText;
- }
-
- protected Combo createLocaleHandler(Composite composite, final LocaleChoice callback) {
- String[] labels = callback.getSupportedLocalesLabels();
- if (labels.length == 0)
- return null;
- Label label = new Label(composite, SWT.NONE);
- label.setText("Language");
-
- final Combo combo = new Combo(composite, SWT.READ_ONLY);
- combo.setItems(labels);
- combo.select(callback.getDefaultIndex());
- combo.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
- combo.addSelectionListener(new SelectionListener() {
- private static final long serialVersionUID = 38678989091946277L;
-
- @Override
- public void widgetSelected(SelectionEvent e) {
- callback.setSelectedIndex(combo.getSelectionIndex());
- }
-
- @Override
- public void widgetDefaultSelected(SelectionEvent e) {
- }
- });
- return combo;
- }
-
- protected Label createLabelTextoutputHandler(Composite composite, final TextOutputCallback callback) {
- Label label = new Label(composite, SWT.NONE);
- label.setText(callback.getMessage());
- GridData data = new GridData(SWT.FILL, SWT.FILL, true, true);
- data.horizontalSpan = 2;
- label.setLayoutData(data);
- return label;
- // TODO: find a way to pass this information
- // int messageType = callback.getMessageType();
- // int dialogMessageType = IMessageProvider.NONE;
- // switch (messageType) {
- // case TextOutputCallback.INFORMATION:
- // dialogMessageType = IMessageProvider.INFORMATION;
- // break;
- // case TextOutputCallback.WARNING:
- // dialogMessageType = IMessageProvider.WARNING;
- // break;
- // case TextOutputCallback.ERROR:
- // dialogMessageType = IMessageProvider.ERROR;
- // break;
- // }
- // setMessage(callback.getMessage(), dialogMessageType);
- }
-
- synchronized boolean isSubmitted() {
- return isSubmitted;
- }
-
- synchronized boolean isCanceled() {
- return isCanceled;
- }
-
- protected synchronized void submit() {
- isSubmitted = true;
- notifyAll();
- }
-
- protected synchronized void cancel() {
- isCanceled = true;
- notifyAll();
- }
-}
+++ /dev/null
-package org.argeo.cms.swt.auth;
-
-import java.io.IOException;
-
-import javax.security.auth.callback.Callback;
-import javax.security.auth.callback.CallbackHandler;
-import javax.security.auth.callback.UnsupportedCallbackException;
-
-import org.argeo.eclipse.ui.dialogs.LightweightDialog;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Display;
-import org.eclipse.swt.widgets.Shell;
-
-public class DynamicCallbackHandler implements CallbackHandler {
-
- @Override
- public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
- Shell activeShell = Display.getCurrent().getActiveShell();
- LightweightDialog dialog = new LightweightDialog(activeShell) {
-
- @Override
- protected Control createDialogArea(Composite parent) {
- CompositeCallbackHandler cch = new CompositeCallbackHandler(parent, SWT.NONE);
- cch.createCallbackHandlers(callbacks);
- return cch;
- }
- };
- dialog.setBlockOnOpen(true);
- dialog.open();
- }
-
-}
+++ /dev/null
-package org.argeo.cms.swt.auth;
-
-import java.util.Collections;
-import java.util.List;
-import java.util.Locale;
-
-import javax.security.auth.callback.LanguageCallback;
-
-import org.argeo.cms.CmsException;
-import org.argeo.cms.LocaleUtils;
-
-/** Choose in a list of locales. TODO: replace with {@link LanguageCallback} */
-public class LocaleChoice {
- private final List<Locale> locales;
-
- private Integer selectedIndex = null;
- private final Integer defaultIndex;
-
- public LocaleChoice(List<Locale> locales, Locale defaultLocale) {
- Integer defaultIndex = null;
- this.locales = Collections.unmodifiableList(locales);
- for (int i = 0; i < locales.size(); i++)
- if (locales.get(i).equals(defaultLocale))
- defaultIndex = i;
-
- // based on language only
- if (defaultIndex == null)
- for (int i = 0; i < locales.size(); i++)
- if (locales.get(i).getLanguage().equals(defaultLocale.getLanguage()))
- defaultIndex = i;
-
- if (defaultIndex == null)
- throw new CmsException("Default locale " + defaultLocale + " is not in available locales " + locales);
- this.defaultIndex = defaultIndex;
-
- this.selectedIndex = defaultIndex;
- }
-
- /**
- * Convenience constructor based on a comma separated list of iso codes (en,
- * en_US, fr_CA, etc.). Default selection is default locale.
- */
- public LocaleChoice(String locales, Locale defaultLocale) {
- this(LocaleUtils.asLocaleList(locales), defaultLocale);
- }
-
- public String[] getSupportedLocalesLabels() {
- String[] labels = new String[locales.size()];
- for (int i = 0; i < locales.size(); i++) {
- Locale locale = locales.get(i);
- if (locale.getCountry().equals(""))
- labels[i] = locale.getDisplayLanguage(locale) + " [" + locale.getLanguage() + "]";
- else
- labels[i] = locale.getDisplayLanguage(locale) + " (" + locale.getDisplayCountry(locale) + ") ["
- + locale.getLanguage() + "_" + locale.getCountry() + "]";
-
- }
- return labels;
- }
-
- public Locale getSelectedLocale() {
- if (selectedIndex == null)
- return null;
- return locales.get(selectedIndex);
- }
-
- public void setSelectedIndex(Integer selectedIndex) {
- this.selectedIndex = selectedIndex;
- }
-
- public Integer getSelectedIndex() {
- return selectedIndex;
- }
-
- public Integer getDefaultIndex() {
- return defaultIndex;
- }
-
- public List<Locale> getLocales() {
- return locales;
- }
-
- public Locale getDefaultLocale() {
- return locales.get(getDefaultIndex());
- }
-}
+++ /dev/null
-/** Argeo CMS authentication widgets, based on SWT. */
-package org.argeo.cms.swt.auth;
\ No newline at end of file
+++ /dev/null
-package org.argeo.cms.swt.dialogs;
-
-import java.security.PrivilegedAction;
-import java.util.Arrays;
-
-import org.argeo.api.cms.CmsView;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.cms.CmsMsg;
-import org.argeo.cms.CmsUserManager;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Label;
-import org.eclipse.swt.widgets.Shell;
-import org.eclipse.swt.widgets.Text;
-
-/** Dialog to change a password. */
-public class ChangePasswordDialog extends CmsMessageDialog {
- private final static CmsLog log = CmsLog.getLog(ChangePasswordDialog.class);
-
- private CmsUserManager cmsUserManager;
- private CmsView cmsView;
-
- private PrivilegedAction<Integer> doIt;
-
- public ChangePasswordDialog(Shell parentShell, String message, int kind, CmsUserManager cmsUserManager) {
- super(parentShell, message, kind);
- this.cmsUserManager = cmsUserManager;
- cmsView = CmsSwtUtils.getCmsView(parentShell);
- }
-
- @Override
- protected Control createInputArea(Composite userSection) {
- addFormLabel(userSection, CmsMsg.currentPassword.lead());
- Text previousPassword = new Text(userSection, SWT.BORDER | SWT.PASSWORD);
- previousPassword.setLayoutData(CmsSwtUtils.fillWidth());
- addFormLabel(userSection, CmsMsg.newPassword.lead());
- Text newPassword = new Text(userSection, SWT.BORDER | SWT.PASSWORD);
- newPassword.setLayoutData(CmsSwtUtils.fillWidth());
- addFormLabel(userSection, CmsMsg.repeatNewPassword.lead());
- Text confirmPassword = new Text(userSection, SWT.BORDER | SWT.PASSWORD);
- confirmPassword.setLayoutData(CmsSwtUtils.fillWidth());
-
- doIt = () -> {
- if (Arrays.equals(newPassword.getTextChars(), confirmPassword.getTextChars())) {
- try {
- cmsUserManager.changeOwnPassword(previousPassword.getTextChars(), newPassword.getTextChars());
- return OK;
- } catch (Exception e1) {
- log.error("Could not change password", e1);
- cancel();
- CmsMessageDialog.openError(CmsMsg.invalidPassword.lead());
- return CANCEL;
- }
- } else {
- cancel();
- CmsMessageDialog.openError(CmsMsg.repeatNewPassword.lead());
- return CANCEL;
- }
- };
-
- pack();
- return previousPassword;
- }
-
- @Override
- protected void okPressed() {
- Integer returnCode = cmsView.doAs(doIt);
- if (returnCode.equals(OK)) {
- super.okPressed();
- CmsMessageDialog.openInformation(CmsMsg.passwordChanged.lead());
- }
- }
-
- private static Label addFormLabel(Composite parent, String label) {
- Label lbl = new Label(parent, SWT.WRAP);
- lbl.setText(label);
-// CmsUiUtils.style(lbl, SuiteStyle.simpleLabel);
- return lbl;
- }
-
-}
+++ /dev/null
-package org.argeo.cms.swt.dialogs;
-
-import java.io.PrintWriter;
-import java.io.StringWriter;
-
-import org.argeo.api.cms.CmsLog;
-import org.argeo.cms.CmsMsg;
-import org.argeo.cms.swt.Selected;
-import org.eclipse.swt.SWT;
-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.Label;
-import org.eclipse.swt.widgets.Shell;
-import org.eclipse.swt.widgets.Text;
-
-/** A dialog feedback based on a {@link LightweightDialog}. */
-public class CmsFeedback extends LightweightDialog {
- private final static CmsLog log = CmsLog.getLog(CmsFeedback.class);
-
- private String message;
- private Throwable exception;
-
- public CmsFeedback(Shell parentShell, String message, Throwable e) {
- super(parentShell);
- this.message = message;
- this.exception = e;
- log.error(message, e);
- }
-
- public static CmsFeedback show(String message, Throwable e) {
- // rethrow ThreaDeath in order to make sure that RAP will properly clean
- // up the UI thread
- if (e instanceof ThreadDeath)
- throw (ThreadDeath) e;
-
- try {
- CmsFeedback cmsFeedback = new CmsFeedback(null, message, e);
- cmsFeedback.setBlockOnOpen(false);
- cmsFeedback.open();
- return cmsFeedback;
- } catch (Throwable e1) {
- log.error("Cannot open error feedback (" + e.getMessage() + "), original error below", e);
- return null;
- }
- }
-
- public static CmsFeedback show(String message) {
- CmsFeedback cmsFeedback = new CmsFeedback(null, message, null);
- cmsFeedback.open();
- return cmsFeedback;
- }
-
- /** Tries to find a display */
- // private static Display getDisplay() {
- // try {
- // Display display = Display.getCurrent();
- // if (display != null)
- // return display;
- // else
- // return Display.getDefault();
- // } catch (Exception e) {
- // return Display.getCurrent();
- // }
- // }
-
- protected Control createDialogArea(Composite parent) {
- parent.setLayout(new GridLayout(2, false));
-
- Label messageLbl = new Label(parent, SWT.WRAP);
- if (message != null)
- messageLbl.setText(message);
- else if (exception != null)
- messageLbl.setText(exception.getLocalizedMessage());
-
- Button close = new Button(parent, SWT.FLAT);
- close.setText(CmsMsg.close.lead());
- close.setLayoutData(new GridData(SWT.END, SWT.TOP, false, false));
- close.addSelectionListener((Selected) (e) -> closeShell(OK));
-
- // Composite composite = new Composite(dialogarea, SWT.NONE);
- // composite.setLayout(new GridLayout(2, false));
- // composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
-
- if (exception != null) {
- Text stack = new Text(parent, SWT.MULTI | SWT.LEAD | SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL);
- stack.setEditable(false);
- stack.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 2, 1));
- StringWriter sw = new StringWriter();
- exception.printStackTrace(new PrintWriter(sw));
- stack.setText(sw.toString());
- }
-
- // parent.pack();
- return messageLbl;
- }
-
-}
+++ /dev/null
-package org.argeo.cms.swt.dialogs;
-
-import org.argeo.cms.CmsMsg;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.cms.swt.Selected;
-import org.argeo.eclipse.ui.EclipseUiUtils;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.events.TraverseEvent;
-import org.eclipse.swt.events.TraverseListener;
-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;
-
-/** Base class for dialogs displaying messages or small forms. */
-public class CmsMessageDialog extends LightweightDialog {
- public final static int NONE = 0;
- public final static int ERROR = 1;
- public final static int INFORMATION = 2;
- public final static int QUESTION = 3;
- public final static int WARNING = 4;
- public final static int CONFIRM = 5;
- public final static int QUESTION_WITH_CANCEL = 6;
-
- private int kind;
- private String message;
-
- public CmsMessageDialog(Shell parentShell, String message, int kind) {
- super(parentShell);
- this.kind = kind;
- this.message = message;
- }
-
- protected Control createDialogArea(Composite parent) {
- parent.setLayout(new GridLayout());
-
- TraverseListener traverseListener = new TraverseListener() {
- private static final long serialVersionUID = -1158892811534971856L;
-
- public void keyTraversed(TraverseEvent e) {
- if (e.detail == SWT.TRAVERSE_RETURN)
- okPressed();
- else if (e.detail == SWT.TRAVERSE_ESCAPE)
- cancelPressed();
- }
- };
-
- // message
- Composite body = new Composite(parent, SWT.NONE);
- body.addTraverseListener(traverseListener);
- GridLayout bodyGridLayout = new GridLayout();
- bodyGridLayout.marginHeight = 20;
- bodyGridLayout.marginWidth = 20;
- body.setLayout(bodyGridLayout);
- body.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
-
- if (message != null) {
- Label messageLbl = new Label(body, SWT.WRAP);
- CmsSwtUtils.markup(messageLbl);
- messageLbl.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
- messageLbl.setFont(EclipseUiUtils.getBoldFont(parent));
- messageLbl.setText(message);
- }
-
- // buttons
- Composite buttons = new Composite(parent, SWT.NONE);
- buttons.addTraverseListener(traverseListener);
- buttons.setLayoutData(new GridData(SWT.END, SWT.FILL, true, false));
- if (kind == INFORMATION || kind == WARNING || kind == ERROR || kind == ERROR) {
- GridLayout layout = new GridLayout(1, true);
- layout.marginWidth = 0;
- layout.marginHeight = 0;
- buttons.setLayout(layout);
-
- Button close = new Button(buttons, SWT.FLAT);
- close.setText(CmsMsg.close.lead());
- close.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
- close.addSelectionListener((Selected) (e) -> closeShell(OK));
- close.setFocus();
- close.addTraverseListener(traverseListener);
-
- buttons.setTabList(new Control[] { close });
- } else if (kind == CONFIRM || kind == QUESTION || kind == QUESTION_WITH_CANCEL) {
- Control input = createInputArea(body);
- if (input != null) {
- input.addTraverseListener(traverseListener);
- body.setTabList(new Control[] { input });
- }
- GridLayout layout = new GridLayout(2, true);
- layout.marginWidth = 0;
- layout.marginHeight = 0;
- buttons.setLayout(layout);
-
- Button cancel = new Button(buttons, SWT.FLAT);
- cancel.setText(CmsMsg.cancel.lead());
- cancel.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
- cancel.addSelectionListener((Selected) (e) -> cancelPressed());
- cancel.addTraverseListener(traverseListener);
-
- Button ok = new Button(buttons, SWT.FLAT);
- ok.setText(CmsMsg.ok.lead());
- ok.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
- ok.addSelectionListener((Selected) (e) -> okPressed());
- ok.addTraverseListener(traverseListener);
- if (input == null)
- ok.setFocus();
- else
- input.setFocus();
-
- buttons.setTabList(new Control[] { ok, cancel });
- }
- // pack();
- parent.setTabList(new Control[] { body, buttons });
- return body;
- }
-
- protected Control createInputArea(Composite parent) {
- return null;
- }
-
- protected void okPressed() {
- closeShell(OK);
- }
-
- protected void cancelPressed() {
- closeShell(CANCEL);
- }
-
- protected void cancel() {
- closeShell(CANCEL);
- }
-
- protected Point getInitialSize() {
- return new Point(400, 200);
- }
-
- public static boolean open(int kind, Shell parent, String message) {
- CmsMessageDialog dialog = new CmsMessageDialog(parent, message, kind);
- return dialog.open() == 0;
- }
-
- public static boolean openConfirm(String message) {
- return open(CONFIRM, Display.getCurrent().getActiveShell(), message);
- }
-
- public static void openInformation(String message) {
- open(INFORMATION, Display.getCurrent().getActiveShell(), message);
- }
-
- public static boolean openQuestion(String message) {
- return open(QUESTION, Display.getCurrent().getActiveShell(), message);
- }
-
- public static void openWarning(String message) {
- open(WARNING, Display.getCurrent().getActiveShell(), message);
- }
-
- public static void openError(String message) {
- open(ERROR, Display.getCurrent().getActiveShell(), message);
- }
-
-}
+++ /dev/null
-package org.argeo.cms.swt.dialogs;
-
-import java.lang.reflect.InvocationTargetException;
-
-import org.argeo.cms.CmsMsg;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.cms.swt.Selected;
-import org.argeo.eclipse.ui.EclipseUiUtils;
-import org.eclipse.jface.operation.IRunnableWithProgress;
-import org.eclipse.jface.wizard.IWizard;
-import org.eclipse.jface.wizard.IWizardContainer2;
-import org.eclipse.jface.wizard.IWizardPage;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.layout.FormAttachment;
-import org.eclipse.swt.layout.FormData;
-import org.eclipse.swt.layout.FormLayout;
-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.Label;
-import org.eclipse.swt.widgets.Shell;
-
-/** A wizard dialog based on {@link LightweightDialog}. */
-public class CmsWizardDialog extends LightweightDialog implements IWizardContainer2 {
- private static final long serialVersionUID = -2123153353654812154L;
-
- private IWizard wizard;
- private IWizardPage currentPage;
- private int currentPageIndex;
-
- private Label titleBar;
- private Label message;
- private Composite[] pageBodies;
- private Composite buttons;
- private Button back;
- private Button next;
- private Button finish;
-
- public CmsWizardDialog(Shell parentShell, IWizard wizard) {
- super(parentShell);
- this.wizard = wizard;
- wizard.setContainer(this);
- // create the pages
- wizard.addPages();
- currentPage = wizard.getStartingPage();
- if (currentPage == null)
- throw new IllegalArgumentException("At least one wizard page is required");
- }
-
- @Override
- protected Control createDialogArea(Composite parent) {
- updateWindowTitle();
-
- Composite messageArea = new Composite(parent, SWT.NONE);
- messageArea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
- {
- messageArea.setLayout(CmsSwtUtils.noSpaceGridLayout(new GridLayout(2, false)));
- titleBar = new Label(messageArea, SWT.WRAP);
- titleBar.setFont(EclipseUiUtils.getBoldFont(parent));
- titleBar.setLayoutData(new GridData(SWT.BEGINNING, SWT.FILL, true, false));
- updateTitleBar();
- Button cancelButton = new Button(messageArea, SWT.FLAT);
- cancelButton.setText(CmsMsg.cancel.lead());
- cancelButton.setLayoutData(new GridData(SWT.END, SWT.TOP, false, false, 1, 3));
- cancelButton.addSelectionListener((Selected) (e) -> closeShell(CANCEL));
- message = new Label(messageArea, SWT.WRAP);
- message.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 1, 2));
- updateMessage();
- }
-
- Composite body = new Composite(parent, SWT.BORDER);
- body.setLayout(new FormLayout());
- body.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
- pageBodies = new Composite[wizard.getPageCount()];
- IWizardPage[] pages = wizard.getPages();
- for (int i = 0; i < pages.length; i++) {
- pageBodies[i] = new Composite(body, SWT.NONE);
- pageBodies[i].setLayout(CmsSwtUtils.noSpaceGridLayout());
- setSwitchingFormData(pageBodies[i]);
- pages[i].createControl(pageBodies[i]);
- }
- showPage(currentPage);
-
- buttons = new Composite(parent, SWT.NONE);
- buttons.setLayoutData(new GridData(SWT.END, SWT.FILL, true, false));
- {
- boolean singlePage = wizard.getPageCount() == 1;
- // singlePage = false;// dev
- GridLayout layout = new GridLayout(singlePage ? 1 : 3, true);
- layout.marginWidth = 0;
- layout.marginHeight = 0;
- buttons.setLayout(layout);
- // TODO revert order for right-to-left languages
-
- if (!singlePage) {
- back = new Button(buttons, SWT.PUSH);
- back.setText(CmsMsg.wizardBack.lead());
- back.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
- back.addSelectionListener((Selected) (e) -> backPressed());
-
- next = new Button(buttons, SWT.PUSH);
- next.setText(CmsMsg.wizardNext.lead());
- next.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
- next.addSelectionListener((Selected) (e) -> nextPressed());
- }
- finish = new Button(buttons, SWT.PUSH);
- finish.setText(CmsMsg.wizardFinish.lead());
- finish.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
- finish.addSelectionListener((Selected) (e) -> finishPressed());
-
- updateButtons();
- }
- return body;
- }
-
- @Override
- public IWizardPage getCurrentPage() {
- return currentPage;
- }
-
- @Override
- public Shell getShell() {
- return getForegoundShell();
- }
-
- @Override
- public void showPage(IWizardPage page) {
- IWizardPage[] pages = wizard.getPages();
- int index = -1;
- for (int i = 0; i < pages.length; i++) {
- if (page == pages[i]) {
- index = i;
- break;
- }
- }
- if (index < 0)
- throw new IllegalArgumentException("Cannot find index of wizard page " + page);
- pageBodies[index].moveAbove(pageBodies[currentPageIndex]);
-
- // // clear
- // for (Control c : body.getChildren())
- // c.dispose();
- // page.createControl(body);
- // body.layout(true, true);
- currentPageIndex = index;
- currentPage = page;
- }
-
- @Override
- public void updateButtons() {
- if (back != null)
- back.setEnabled(wizard.getPreviousPage(currentPage) != null);
- if (next != null)
- next.setEnabled(wizard.getNextPage(currentPage) != null && currentPage.canFlipToNextPage());
- if (finish != null) {
- finish.setEnabled(wizard.canFinish());
- }
- }
-
- @Override
- public void updateMessage() {
- if (currentPage.getMessage() != null)
- message.setText(currentPage.getMessage());
- }
-
- @Override
- public void updateTitleBar() {
- if (currentPage.getTitle() != null)
- titleBar.setText(currentPage.getTitle());
- }
-
- @Override
- public void updateWindowTitle() {
- setTitle(wizard.getWindowTitle());
- }
-
- @Override
- public void run(boolean fork, boolean cancelable, IRunnableWithProgress runnable)
- throws InvocationTargetException, InterruptedException {
- runnable.run(null);
- }
-
- @Override
- public void updateSize() {
- // TODO pack?
- }
-
- protected boolean onCancel() {
- return wizard.performCancel();
- }
-
- protected void nextPressed() {
- IWizardPage page = wizard.getNextPage(currentPage);
- showPage(page);
- updateButtons();
- }
-
- protected void backPressed() {
- IWizardPage page = wizard.getPreviousPage(currentPage);
- showPage(page);
- updateButtons();
- }
-
- protected void finishPressed() {
- if (wizard.performFinish())
- closeShell(OK);
- }
-
- private static void setSwitchingFormData(Composite composite) {
- FormData fdLabel = new FormData();
- fdLabel.top = new FormAttachment(0, 0);
- fdLabel.left = new FormAttachment(0, 0);
- fdLabel.right = new FormAttachment(100, 0);
- fdLabel.bottom = new FormAttachment(100, 0);
- composite.setLayoutData(fdLabel);
- }
-
-}
+++ /dev/null
-package org.argeo.cms.swt.dialogs;
-
-import org.argeo.api.cms.CmsLog;
-import org.argeo.eclipse.ui.EclipseUiException;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.events.FocusEvent;
-import org.eclipse.swt.events.FocusListener;
-import org.eclipse.swt.events.ShellAdapter;
-import org.eclipse.swt.events.ShellEvent;
-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.Display;
-import org.eclipse.swt.widgets.Shell;
-
-/** Generic lightweight dialog, not based on JFace. */
-public class LightweightDialog {
- private final static CmsLog log = CmsLog.getLog(LightweightDialog.class);
-
- // must be the same value as org.eclipse.jface.window.Window#OK
- public final static int OK = 0;
- // must be the same value as org.eclipse.jface.window.Window#CANCEL
- public final static int CANCEL = 1;
-
- private Shell parentShell;
- private Shell backgroundShell;
- private Shell foregoundShell;
-
- private Integer returnCode = null;
- private boolean block = true;
-
- private String title;
-
- /** Tries to find a display */
- private static Display getDisplay() {
- try {
- Display display = Display.getCurrent();
- if (display != null)
- return display;
- else
- return Display.getDefault();
- } catch (Exception e) {
- return Display.getCurrent();
- }
- }
-
- public LightweightDialog(Shell parentShell) {
- this.parentShell = parentShell;
- }
-
- public int open() {
- if (foregoundShell != null)
- throw new EclipseUiException("There is already a shell");
- backgroundShell = new Shell(parentShell, SWT.ON_TOP);
- backgroundShell.setFullScreen(true);
- // if (parentShell != null) {
- // backgroundShell.setBounds(parentShell.getBounds());
- // } else
- // backgroundShell.setMaximized(true);
- backgroundShell.setAlpha(128);
- backgroundShell.setBackground(getDisplay().getSystemColor(SWT.COLOR_BLACK));
- foregoundShell = new Shell(backgroundShell, SWT.NO_TRIM | SWT.ON_TOP);
- if (title != null)
- setTitle(title);
- foregoundShell.setLayout(new GridLayout());
- foregoundShell.setSize(getInitialSize());
- createDialogArea(foregoundShell);
- // shell.pack();
- // shell.layout();
-
- Rectangle shellBounds = parentShell != null ? parentShell.getBounds() : Display.getCurrent().getBounds();// RAP
- Point dialogSize = foregoundShell.getSize();
- int x = shellBounds.x + (shellBounds.width - dialogSize.x) / 2;
- int y = shellBounds.y + (shellBounds.height - dialogSize.y) / 2;
- foregoundShell.setLocation(x, y);
-
- foregoundShell.addShellListener(new ShellAdapter() {
- private static final long serialVersionUID = -2701270481953688763L;
-
- @Override
- public void shellDeactivated(ShellEvent e) {
- if (hasChildShells())
- return;
- if (returnCode == null)// not yet closed
- closeShell(CANCEL);
- }
-
- @Override
- public void shellClosed(ShellEvent e) {
- notifyClose();
- }
-
- });
-
- backgroundShell.open();
- foregoundShell.open();
- // after the foreground shell has been opened
- backgroundShell.addFocusListener(new FocusListener() {
- private static final long serialVersionUID = 3137408447474661070L;
-
- @Override
- public void focusLost(FocusEvent event) {
- }
-
- @Override
- public void focusGained(FocusEvent event) {
- if (hasChildShells())
- return;
- if (returnCode == null)// not yet closed
- closeShell(CANCEL);
- }
- });
-
- if (block) {
- block();
- }
- if (returnCode == null)
- returnCode = OK;
- return returnCode;
- }
-
- public void block() {
- try {
- runEventLoop(foregoundShell);
- } catch (ThreadDeath t) {
- returnCode = CANCEL;
- if (log.isTraceEnabled())
- log.error("Thread death, canceling dialog", t);
- } catch (Throwable t) {
- returnCode = CANCEL;
- log.error("Cannot open blocking lightweight dialog", t);
- }
- }
-
- private boolean hasChildShells() {
- if (foregoundShell == null)
- return false;
- return foregoundShell.getShells().length != 0;
- }
-
- // public synchronized int openAndWait() {
- // open();
- // while (returnCode == null)
- // try {
- // wait(100);
- // } catch (InterruptedException e) {
- // // silent
- // }
- // return returnCode;
- // }
-
- private synchronized void notifyClose() {
- if (returnCode == null)
- returnCode = CANCEL;
- notifyAll();
- }
-
- protected void closeShell(int returnCode) {
- this.returnCode = returnCode;
- if (CANCEL == returnCode)
- onCancel();
- if (foregoundShell != null && !foregoundShell.isDisposed()) {
- foregoundShell.close();
- foregoundShell.dispose();
- foregoundShell = null;
- }
-
- if (backgroundShell != null && !backgroundShell.isDisposed()) {
- backgroundShell.close();
- backgroundShell.dispose();
- }
- }
-
- protected Point getInitialSize() {
- // if (exception != null)
- // return new Point(800, 600);
- // else
- return new Point(600, 400);
- }
-
- protected Control createDialogArea(Composite parent) {
- Composite dialogarea = new Composite(parent, SWT.NONE);
- dialogarea.setLayout(new GridLayout());
- dialogarea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
- return dialogarea;
- }
-
- protected Shell getBackgroundShell() {
- return backgroundShell;
- }
-
- protected Shell getForegoundShell() {
- return foregoundShell;
- }
-
- public void setBlockOnOpen(boolean shouldBlock) {
- block = shouldBlock;
- }
-
- public void pack() {
- foregoundShell.pack();
- }
-
- private void runEventLoop(Shell loopShell) {
- Display display;
- if (foregoundShell == null) {
- display = Display.getCurrent();
- } else {
- display = loopShell.getDisplay();
- }
-
- while (loopShell != null && !loopShell.isDisposed()) {
- try {
- if (!display.readAndDispatch()) {
- display.sleep();
- }
- } catch (UnsupportedOperationException e) {
- throw e;
- } catch (Throwable e) {
- handleException(e);
- }
- }
- if (!display.isDisposed())
- display.update();
- }
-
- protected void handleException(Throwable t) {
- if (t instanceof ThreadDeath) {
- // Don't catch ThreadDeath as this is a normal occurrence when
- // the thread dies
- throw (ThreadDeath) t;
- }
- // Try to keep running.
- t.printStackTrace();
- }
-
- /** @return false, if the dialog should not be closed. */
- protected boolean onCancel() {
- return true;
- }
-
- public void setTitle(String title) {
- this.title = title;
- if (title != null && getForegoundShell() != null)
- getForegoundShell().setText(title);
- }
-
- public Integer getReturnCode() {
- return returnCode;
- }
-
-}
\ No newline at end of file
+++ /dev/null
-package org.argeo.cms.swt.dialogs;
-
-import org.eclipse.jface.window.Window;
-import org.eclipse.swt.SWT;
-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.Shell;
-import org.eclipse.swt.widgets.Text;
-
-/** A dialog asking a for a single value. */
-public class SingleValueDialog extends CmsMessageDialog {
- private Text valueT;
- private String value;
- private String defaultValue;
-
- public SingleValueDialog(Shell parentShell, String message) {
- super(parentShell, message, QUESTION);
- }
-
- public SingleValueDialog(Shell parentShell, String message, String defaultValue) {
- super(parentShell, message, QUESTION);
- this.defaultValue = defaultValue;
- }
-
- @Override
- protected Control createInputArea(Composite parent) {
- valueT = new Text(parent, SWT.LEAD | SWT.BORDER | SWT.SINGLE);
- valueT.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, true));
- if (defaultValue != null)
- valueT.setText(defaultValue);
- return valueT;
- }
-
- @Override
- protected void okPressed() {
- value = valueT.getText();
- super.okPressed();
- }
-
- public String getString() {
- return value;
- }
-
- public Long getLong() {
- return Long.valueOf(getString());
- }
-
- public Double getDouble() {
- return Double.valueOf(getString());
- }
-
- public static String ask(String message) {
- return ask(message, null);
- }
-
- public static String ask(String message, String defaultValue) {
- SingleValueDialog svd = new SingleValueDialog(Display.getCurrent().getActiveShell(), message, defaultValue);
- if (svd.open() == Window.OK)
- return svd.getString();
- else
- return null;
- }
-
- public static Long askLong(String message) {
- SingleValueDialog svd = new SingleValueDialog(Display.getCurrent().getActiveShell(), message);
- if (svd.open() == Window.OK)
- return svd.getLong();
- else
- return null;
- }
-
- public static Double askDouble(String message) {
- SingleValueDialog svd = new SingleValueDialog(Display.getCurrent().getActiveShell(), message);
- if (svd.open() == Window.OK)
- return svd.getDouble();
- else
- return null;
- }
-
-}
+++ /dev/null
-/** SWT/JFace dialogs. */
-package org.argeo.cms.swt.dialogs;
\ No newline at end of file
+++ /dev/null
-package org.argeo.cms.swt.gcr;
-
-import java.nio.file.Path;
-import java.nio.file.Paths;
-
-import javax.xml.namespace.QName;
-
-import org.argeo.api.acr.Content;
-import org.argeo.cms.acr.fs.FsContentProvider;
-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.Display;
-import org.eclipse.swt.widgets.Shell;
-import org.eclipse.swt.widgets.Table;
-import org.eclipse.swt.widgets.TableColumn;
-import org.eclipse.swt.widgets.TableItem;
-import org.eclipse.swt.widgets.Tree;
-import org.eclipse.swt.widgets.TreeItem;
-
-public class GcrContentTreeView extends Composite {
- private Tree tree;
- private Table table;
- private Content rootContent;
-
- private Content selected;
-
- public GcrContentTreeView(Composite parent, int style, Content content) {
- super(parent, style);
- this.rootContent = content;
- this.selected = rootContent;
- setLayout(new GridLayout(2, false));
- initTree();
- GridData treeGd = CmsSwtUtils.fillHeight();
- treeGd.widthHint = 300;
- tree.setLayoutData(treeGd);
- initTable();
-
- table.setLayoutData(CmsSwtUtils.fillAll());
- }
-
- protected void initTree() {
- tree = new Tree(this, 0);
- for (Content c : rootContent) {
- TreeItem root = new TreeItem(tree, 0);
- root.setText(c.getName().toString());
- root.setData(c);
- new TreeItem(root, 0);
- }
- tree.addListener(SWT.Expand, event -> {
- final TreeItem root = (TreeItem) event.item;
- TreeItem[] items = root.getItems();
- for (TreeItem item : items) {
- if (item.getData() != null)
- return;
- item.dispose();
- }
- Content content = (Content) root.getData();
- for (Content c : content) {
- TreeItem item = new TreeItem(root, 0);
- item.setText(c.getName().toString());
- item.setData(c);
- boolean hasChildren = true;
- if (hasChildren) {
- new TreeItem(item, 0);
- }
- }
- });
- tree.addListener(SWT.Selection, event -> {
- TreeItem item = (TreeItem) event.item;
- selected = (Content) item.getData();
- refreshTable();
- });
- }
-
- protected void initTable() {
- table = new Table(this, 0);
- table.setLinesVisible(true);
- table.setHeaderVisible(true);
- TableColumn keyCol = new TableColumn(table, SWT.NONE);
- keyCol.setText("Attribute");
- keyCol.setWidth(200);
- TableColumn valueCol = new TableColumn(table, SWT.NONE);
- valueCol.setText("Value");
- keyCol.setWidth(300);
- refreshTable();
- }
-
- protected void refreshTable() {
- for (TableItem item : table.getItems()) {
- item.dispose();
- }
- for (QName key : selected.keySet()) {
- TableItem item = new TableItem(table, 0);
- item.setText(0, key.toString());
- Object value = selected.get(key);
- item.setText(1, value.toString());
- }
- table.getColumn(0).pack();
- table.getColumn(1).pack();
- }
-
- public static void main(String[] args) {
- Path basePath;
- if (args.length > 0) {
- basePath = Paths.get(args[0]);
- } else {
- basePath = Paths.get(System.getProperty("user.home"));
- }
-
- final Display display = new Display();
- final Shell shell = new Shell(display);
- shell.setText(basePath.toString());
- shell.setLayout(new FillLayout());
-
- FsContentProvider contentSession = new FsContentProvider(basePath);
-// GcrContentTreeView treeView = new GcrContentTreeView(shell, 0, contentSession.get("/"));
-
- shell.setSize(shell.computeSize(800, 600));
- shell.open();
- while (!shell.isDisposed()) {
- if (!display.readAndDispatch())
- display.sleep();
- }
- display.dispose();
- }
-}
+++ /dev/null
-package org.argeo.cms.swt.gcr;
-
-import org.argeo.api.acr.Content;
-import org.argeo.api.cms.MvcProvider;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-
-@FunctionalInterface
-public interface SwtUiProvider extends MvcProvider<Composite, Content, Control> {
-
-}
+++ /dev/null
-package org.argeo.cms.swt.osgi;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.HashMap;
-import java.util.Map;
-
-import org.argeo.cms.osgi.BundleCmsTheme;
-import org.argeo.cms.swt.CmsSwtTheme;
-import org.eclipse.swt.graphics.Image;
-import org.eclipse.swt.graphics.ImageData;
-import org.eclipse.swt.widgets.Display;
-
-/** Centralises some generic {@link CmsSwtTheme} patterns. */
-public class BundleCmsSwtTheme extends BundleCmsTheme implements CmsSwtTheme {
- private Map<String, ImageData> imageCache = new HashMap<>();
-
- private Map<String, Map<Integer, String>> iconPaths = new HashMap<>();
-
- public Image getImage(String path) {
- if (!imageCache.containsKey(path)) {
- try (InputStream in = getResourceAsStream(path)) {
- if (in == null)
- return null;
- ImageData imageData = new ImageData(in);
- imageCache.put(path, imageData);
- } catch (IOException e) {
- throw new IllegalStateException(e);
- }
- }
- ImageData imageData = imageCache.get(path);
- Image image = new Image(Display.getCurrent(), imageData);
- return image;
- }
-
- /**
- * And icon with this file name (without the extension), with a best effort to
- * find the appropriate size, or <code>null</code> if not found.
- *
- * @param name An icon file name without path and extension.
- * @param preferredSize the preferred size, if <code>null</code>,
- * {@link #getDefaultIconSize()} will be tried.
- */
- public Image getIcon(String name, Integer preferredSize) {
- if (preferredSize == null)
- preferredSize = getDefaultIconSize();
- Map<Integer, String> subCache;
- if (!iconPaths.containsKey(name))
- subCache = new HashMap<>();
- else
- subCache = iconPaths.get(name);
- Image image = null;
- if (!subCache.containsKey(preferredSize)) {
- Image bestMatchSoFar = null;
- paths: for (String p : getImagesPaths()) {
- int lastSlash = p.lastIndexOf('/');
- String fileName = p;
- if (lastSlash >= 0)
- fileName = p.substring(lastSlash + 1);
- int lastDot = fileName.lastIndexOf('.');
- if (lastDot >= 0)
- fileName = fileName.substring(0, lastDot);
- if (fileName.equals(name)) {// matched
- Image img = getImage(p);
- int width = img.getBounds().width;
- if (width == preferredSize) {// perfect match
- subCache.put(preferredSize, p);
- image = img;
- break paths;
- }
- if (bestMatchSoFar == null) {
- bestMatchSoFar = img;
- } else {
- if (Math.abs(width - preferredSize) < Math
- .abs(bestMatchSoFar.getBounds().width - preferredSize))
- bestMatchSoFar = img;
- }
- }
- }
-
- if (image == null)
- image = bestMatchSoFar;
- } else {
- image = getImage(subCache.get(preferredSize));
- }
-
- if (image != null && !iconPaths.containsKey(name))
- iconPaths.put(name, subCache);
-
- return image;
- }
-
-}
+++ /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.eclipse.ui.parts.LdifUsersTable;
-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.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.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
-/** SWT/JFace users management components. */
-package org.argeo.cms.swt.useradmin;
\ No newline at end of file
+++ /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
+++ /dev/null
-package org.argeo.eclipse.ui;
-
-import org.eclipse.jface.viewers.ITreeContentProvider;
-import org.eclipse.jface.viewers.Viewer;
-
-/**
- * Tree content provider dealing with tree objects and providing reasonable
- * defaults.
- */
-public abstract class AbstractTreeContentProvider implements
- ITreeContentProvider {
- private static final long serialVersionUID = 8246126401957763868L;
-
- /** Does nothing */
- public void dispose() {
- }
-
- /** Does nothing */
- public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
- }
-
- public Object[] getChildren(Object element) {
- if (element instanceof TreeParent) {
- return ((TreeParent) element).getChildren();
- }
- return new Object[0];
- }
-
- public Object getParent(Object element) {
- if (element instanceof TreeParent) {
- return ((TreeParent) element).getParent();
- }
- return null;
- }
-
- public boolean hasChildren(Object element) {
- if (element instanceof TreeParent) {
- return ((TreeParent) element).hasChildren();
- }
- return false;
- }
-}
\ No newline at end of file
+++ /dev/null
-package org.argeo.eclipse.ui;
-
-import org.eclipse.jface.viewers.ColumnLabelProvider;
-
-/**
- * Wraps the definition of a column to be used in the various JFace viewers
- * (typically tree and table). It enables definition of generic viewers which
- * column can be then defined externally. Also used to generate export.
- */
-public class ColumnDefinition {
- private ColumnLabelProvider labelProvider;
- private String label;
- private int weight = 0;
- private int minWidth = 120;
-
- public ColumnDefinition(ColumnLabelProvider labelProvider, String label) {
- this.labelProvider = labelProvider;
- this.label = label;
- }
-
- public ColumnDefinition(ColumnLabelProvider labelProvider, String label,
- int weight) {
- this.labelProvider = labelProvider;
- this.label = label;
- this.weight = weight;
- this.minWidth = weight;
- }
-
- public ColumnDefinition(ColumnLabelProvider labelProvider, String label,
- int weight, int minimumWidth) {
- this.labelProvider = labelProvider;
- this.label = label;
- this.weight = weight;
- this.minWidth = minimumWidth;
- }
-
- public ColumnLabelProvider getLabelProvider() {
- return labelProvider;
- }
-
- public void setLabelProvider(ColumnLabelProvider labelProvider) {
- this.labelProvider = labelProvider;
- }
-
- public String getLabel() {
- return label;
- }
-
- public void setLabel(String label) {
- this.label = label;
- }
-
- public int getWeight() {
- return weight;
- }
-
- public void setWeight(int weight) {
- this.weight = weight;
- }
-
- public int getMinWidth() {
- return minWidth;
- }
-
- public void setMinWidth(int minWidth) {
- this.minWidth = minWidth;
- }
-}
\ No newline at end of file
+++ /dev/null
-package org.argeo.eclipse.ui;
-
-import org.eclipse.jface.viewers.ColumnViewer;
-import org.eclipse.jface.viewers.TableViewerColumn;
-import org.eclipse.jface.viewers.Viewer;
-import org.eclipse.jface.viewers.ViewerComparator;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.events.SelectionAdapter;
-import org.eclipse.swt.events.SelectionEvent;
-
-/** Generic column viewer sorter */
-public class ColumnViewerComparator extends ViewerComparator {
- private static final long serialVersionUID = -2266218906355859909L;
-
- public static final int ASC = 1;
-
- public static final int NONE = 0;
-
- public static final int DESC = -1;
-
- private int direction = 0;
-
- private TableViewerColumn column;
-
- private ColumnViewer viewer;
-
- public ColumnViewerComparator(TableViewerColumn column) {
- super(null);
- this.column = column;
- this.viewer = column.getViewer();
- this.column.getColumn().addSelectionListener(new SelectionAdapter() {
- private static final long serialVersionUID = 7586796298965472189L;
-
- public void widgetSelected(SelectionEvent e) {
- if (ColumnViewerComparator.this.viewer.getComparator() != null) {
- if (ColumnViewerComparator.this.viewer.getComparator() == ColumnViewerComparator.this) {
- int tdirection = ColumnViewerComparator.this.direction;
-
- if (tdirection == ASC) {
- setSortDirection(DESC);
- } else if (tdirection == DESC) {
- setSortDirection(NONE);
- }
- } else {
- setSortDirection(ASC);
- }
- } else {
- setSortDirection(ASC);
- }
- }
- });
- }
-
- private void setSortDirection(int direction) {
- if (direction == NONE) {
- column.getColumn().getParent().setSortColumn(null);
- column.getColumn().getParent().setSortDirection(SWT.NONE);
- viewer.setComparator(null);
- } else {
- column.getColumn().getParent().setSortColumn(column.getColumn());
- this.direction = direction;
-
- if (direction == ASC) {
- column.getColumn().getParent().setSortDirection(SWT.DOWN);
- } else {
- column.getColumn().getParent().setSortDirection(SWT.UP);
- }
-
- if (viewer.getComparator() == this) {
- viewer.refresh();
- } else {
- viewer.setComparator(this);
- }
-
- }
- }
-
- public int compare(Viewer viewer, Object e1, Object e2) {
- return direction * super.compare(viewer, e1, e2);
- }
-}
+++ /dev/null
-package org.argeo.eclipse.ui;
-
-/** CMS specific exceptions. */
-public class EclipseUiException extends RuntimeException {
- private static final long serialVersionUID = -5341764743356771313L;
-
- public EclipseUiException(String message) {
- super(message);
- }
-
- public EclipseUiException(String message, Throwable e) {
- super(message, e);
- }
-
-}
+++ /dev/null
-package org.argeo.eclipse.ui;
-
-import org.eclipse.jface.resource.JFaceResources;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.events.ModifyListener;
-import org.eclipse.swt.graphics.Font;
-import org.eclipse.swt.layout.FormAttachment;
-import org.eclipse.swt.layout.FormData;
-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.Text;
-
-/** Utilities to simplify UI development. */
-public class EclipseUiUtils {
-
- /** Dispose all children of a Composite */
- public static void clear(Composite composite) {
- for (Control child : composite.getChildren())
- child.dispose();
- }
-
- /**
- * Enables efficient call to the layout method of a composite, refreshing only
- * some of the children controls.
- */
- public static void layout(Composite parent, Control... toUpdateControls) {
- parent.layout(toUpdateControls);
- }
-
- //
- // FONTS
- //
- /** Shortcut to retrieve default italic font from display */
- public static Font getItalicFont(Composite parent) {
- return JFaceResources.getFontRegistry().defaultFontDescriptor().setStyle(SWT.ITALIC)
- .createFont(parent.getDisplay());
- }
-
- /** Shortcut to retrieve default bold font from display */
- public static Font getBoldFont(Composite parent) {
- return JFaceResources.getFontRegistry().defaultFontDescriptor().setStyle(SWT.BOLD)
- .createFont(parent.getDisplay());
- }
-
- /** Shortcut to retrieve default bold italic font from display */
- public static Font getBoldItalicFont(Composite parent) {
- return JFaceResources.getFontRegistry().defaultFontDescriptor().setStyle(SWT.BOLD | SWT.ITALIC)
- .createFont(parent.getDisplay());
- }
-
- //
- // Simplify grid layouts management
- //
- public static GridLayout noSpaceGridLayout() {
- return noSpaceGridLayout(new GridLayout());
- }
-
- public static GridLayout noSpaceGridLayout(int columns) {
- return noSpaceGridLayout(new GridLayout(columns, false));
- }
-
- public static GridLayout noSpaceGridLayout(GridLayout layout) {
- layout.horizontalSpacing = 0;
- layout.verticalSpacing = 0;
- layout.marginWidth = 0;
- layout.marginHeight = 0;
- return layout;
- }
-
- public static GridData fillWidth() {
- return grabWidth(SWT.FILL, SWT.FILL);
- }
-
- public static GridData fillWidth(int colSpan) {
- GridData gd = grabWidth(SWT.FILL, SWT.FILL);
- gd.horizontalSpan = colSpan;
- return gd;
- }
-
- public static GridData fillAll() {
- return new GridData(SWT.FILL, SWT.FILL, true, true);
- }
-
- public static GridData fillAll(int colSpan, int rowSpan) {
- return new GridData(SWT.FILL, SWT.FILL, true, true, colSpan, rowSpan);
- }
-
- public static GridData grabWidth(int horizontalAlignment, int verticalAlignment) {
- return new GridData(horizontalAlignment, horizontalAlignment, true, false);
- }
-
- //
- // Simplify Form layout management
- //
-
- /**
- * Creates a basic form data that is attached to the 4 corners of the parent
- * composite
- */
- public static FormData fillFormData() {
- FormData formData = new FormData();
- formData.top = new FormAttachment(0, 0);
- formData.left = new FormAttachment(0, 0);
- formData.right = new FormAttachment(100, 0);
- formData.bottom = new FormAttachment(100, 0);
- return formData;
- }
-
- /**
- * Create a label and a text field for a grid layout, the text field grabbing
- * excess horizontal
- *
- * @param parent
- * the parent composite
- * @param label
- * the label to display
- * @param modifyListener
- * a {@link ModifyListener} to listen on events on the text, can be
- * null
- * @return the created text
- *
- */
- // FIXME why was this deprecated.
- // * @ deprecated use { @ link #createGridLT(Composite, String)} instead
- // @ Deprecated
- public static Text createGridLT(Composite parent, String label, ModifyListener modifyListener) {
- Label lbl = new Label(parent, SWT.LEAD);
- lbl.setText(label);
- lbl.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false));
- Text txt = new Text(parent, SWT.LEAD | SWT.BORDER);
- txt.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
- if (modifyListener != null)
- txt.addModifyListener(modifyListener);
- return txt;
- }
-
- /**
- * Create a label and a text field for a grid layout, the text field grabbing
- * excess horizontal
- */
- public static Text createGridLT(Composite parent, String label) {
- return createGridLT(parent, label, null);
- }
-
- /**
- * Creates one label and a text field not editable with background colour of the
- * parent (like a label but with selectable text)
- */
- public static Text createGridLL(Composite parent, String label, String text) {
- Text txt = createGridLT(parent, label);
- txt.setText(text);
- txt.setEditable(false);
- txt.setBackground(parent.getBackground());
- return txt;
- }
-
- /**
- * Create a label and a text field with password display for a grid layout, the
- * text field grabbing excess horizontal
- */
- public static Text createGridLP(Composite parent, String label) {
- return createGridLP(parent, label, null);
- }
-
- /**
- * Create a label and a text field with password display for a grid layout, the
- * text field grabbing excess horizontal. The given modify listener will be
- * added to the newly created text field if not null.
- */
- public static Text createGridLP(Composite parent, String label, ModifyListener modifyListener) {
- Label lbl = new Label(parent, SWT.LEAD);
- lbl.setText(label);
- lbl.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false));
- Text txt = new Text(parent, SWT.LEAD | SWT.BORDER | SWT.PASSWORD);
- txt.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
- if (modifyListener != null)
- txt.addModifyListener(modifyListener);
- return txt;
- }
-
- // MISCELLANEOUS
-
- /** Simply checks if a string is not null nor empty */
- public static boolean notEmpty(String stringToTest) {
- return !(stringToTest == null || "".equals(stringToTest.trim()));
- }
-
- /** Simply checks if a string is null or empty */
- public static boolean isEmpty(String stringToTest) {
- return stringToTest == null || "".equals(stringToTest.trim());
- }
-}
\ No newline at end of file
+++ /dev/null
-package org.argeo.eclipse.ui;
-
-import java.io.InputStream;
-
-/**
- * Used for file download : subclasses must implement model specific methods to
- * get a byte array representing a file given is ID.
- */
-@Deprecated
-public interface FileProvider {
-
- public byte[] getByteArrayFileFromId(String fileId);
-
- public InputStream getInputStreamFromFileId(String fileId);
-
-}
+++ /dev/null
-package org.argeo.eclipse.ui;
-
-import org.eclipse.jface.viewers.Viewer;
-import org.eclipse.jface.viewers.ViewerComparator;
-
-public abstract class GenericTableComparator extends ViewerComparator {
- private static final long serialVersionUID = -1175894935075325810L;
- protected int propertyIndex;
- public static final int ASCENDING = 0, DESCENDING = 1;
- protected int direction = DESCENDING;
-
- /**
- * Creates an instance of a sorter for TableViewer.
- *
- * @param defaultColumnIndex
- * the default sorter column
- */
-
- public GenericTableComparator(int defaultColumnIndex, int direction) {
- propertyIndex = defaultColumnIndex;
- this.direction = direction;
- }
-
- public void setColumn(int column) {
- if (column == this.propertyIndex) {
- // Same column as last sort; toggle the direction
- direction = 1 - direction;
- } else {
- // New column; do a descending sort
- this.propertyIndex = column;
- direction = DESCENDING;
- }
- }
-
- /**
- * Must be Overriden in each view.
- */
- public abstract int compare(Viewer viewer, Object e1, Object e2);
-}
+++ /dev/null
-package org.argeo.eclipse.ui;
-
-import java.util.List;
-
-/**
- * Views and editors can implement this interface so that one of the list that
- * is displayed in the part (For instance in a Table or a Tree Viewer) can be
- * rebuilt externally. Typically to generate csv or calc extract.
- */
-public interface IListProvider {
- /**
- * Returns an array of current and relevant elements
- */
- public Object[] getElements(String extractId);
-
- /**
- * Returns the column definition for passed ID
- */
- public List<ColumnDefinition> getColumnDefinition(String extractId);
-}
+++ /dev/null
-package org.argeo.eclipse.ui;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/** Parent / children semantic to be used for simple UI Tree structure */
-public class TreeParent {
- private String name;
- private TreeParent parent;
-
- private List<Object> children;
-
- /**
- * Unique id within the context of a tree display. If set, equals() and
- * hashCode() methods will be based on it
- */
- private String path = null;
-
- /** False until at least one child has been added, then true until cleared */
- private boolean loaded = false;
-
- public TreeParent(String name) {
- this.name = name;
- children = new ArrayList<Object>();
- }
-
- public synchronized void addChild(Object child) {
- loaded = true;
- children.add(child);
- if (child instanceof TreeParent)
- ((TreeParent) child).setParent(this);
- }
-
- /**
- * Remove this child. The child is disposed.
- */
- public synchronized void removeChild(Object child) {
- children.remove(child);
- if (child instanceof TreeParent) {
- ((TreeParent) child).dispose();
- }
- }
-
- public synchronized void clearChildren() {
- for (Object obj : children) {
- if (obj instanceof TreeParent)
- ((TreeParent) obj).dispose();
- }
- loaded = false;
- children.clear();
- }
-
- /**
- * If overridden, <code>super.dispose()</code> must be called, typically
- * after custom cleaning.
- */
- public synchronized void dispose() {
- clearChildren();
- parent = null;
- children = null;
- }
-
- public synchronized Object[] getChildren() {
- return children.toArray(new Object[children.size()]);
- }
-
- @SuppressWarnings("unchecked")
- public synchronized <T> List<T> getChildrenOfType(Class<T> clss) {
- List<T> lst = new ArrayList<T>();
- for (Object obj : children) {
- if (clss.isAssignableFrom(obj.getClass()))
- lst.add((T) obj);
- }
- return lst;
- }
-
- public synchronized boolean hasChildren() {
- return children.size() > 0;
- }
-
- public Object getChildByName(String name) {
- for (Object child : children) {
- if (child.toString().equals(name))
- return child;
- }
- return null;
- }
-
- public synchronized Boolean isLoaded() {
- return loaded;
- }
-
- public String getName() {
- return name;
- }
-
- public void setParent(TreeParent parent) {
- this.parent = parent;
- if (parent != null && parent.path != null)
- this.path = parent.path + '/' + name;
- else
- this.path = '/' + name;
- }
-
- public TreeParent getParent() {
- return parent;
- }
-
- public String toString() {
- return getName();
- }
-
- public int compareTo(TreeParent o) {
- return name.compareTo(o.name);
- }
-
- @Override
- public int hashCode() {
- if (path != null)
- return path.hashCode();
- else
- return name.hashCode();
- }
-
- @Override
- public boolean equals(Object obj) {
- if (path != null && obj instanceof TreeParent)
- return path.equals(((TreeParent) obj).path);
- else
- return name.equals(obj.toString());
- }
-
-}
+++ /dev/null
-package org.argeo.eclipse.ui.dialogs;
-
-import java.io.PrintWriter;
-import java.io.StringWriter;
-
-import org.argeo.api.cms.CmsLog;
-import org.eclipse.jface.dialogs.IMessageProvider;
-import org.eclipse.jface.dialogs.TitleAreaDialog;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.graphics.Point;
-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.Display;
-import org.eclipse.swt.widgets.Shell;
-import org.eclipse.swt.widgets.Text;
-
-/**
- * Generic error dialog to be used in try/catch blocks.
- *
- * @deprecated Use CMS dialogs instead.
- */
-@Deprecated
-public class ErrorFeedback extends TitleAreaDialog {
- private static final long serialVersionUID = -8918084784628179044L;
-
- private final static CmsLog log = CmsLog.getLog(ErrorFeedback.class);
-
- private final String message;
- private final Throwable exception;
-
- public static void show(String message, Throwable e) {
- // rethrow ThreaDeath in order to make sure that RAP will properly clean
- // up the UI thread
- if (e instanceof ThreadDeath)
- throw (ThreadDeath) e;
-
- new ErrorFeedback(newShell(), message, e).open();
- }
-
- public static void show(String message) {
- new ErrorFeedback(newShell(), message, null).open();
- }
-
- private static Shell newShell() {
- return new Shell(getDisplay(), SWT.NO_TRIM);
- }
-
- /** Tries to find a display */
- private static Display getDisplay() {
- try {
- Display display = Display.getCurrent();
- if (display != null)
- return display;
- else
- return Display.getDefault();
- } catch (Exception e) {
- return Display.getCurrent();
- }
- }
-
- public ErrorFeedback(Shell parentShell, String message, Throwable e) {
- super(parentShell);
- setShellStyle(SWT.NO_TRIM);
- this.message = message;
- this.exception = e;
- log.error(message, e);
- }
-
- protected Point getInitialSize() {
- if (exception != null)
- return new Point(800, 600);
- else
- return new Point(400, 300);
- }
-
- @Override
- 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, true));
-
- setMessage(message != null ? message + (exception != null ? ": " + exception.getMessage() : "")
- : exception != null ? exception.getMessage() : "Unkown Error", IMessageProvider.ERROR);
-
- if (exception != null) {
- Text stack = new Text(composite, SWT.MULTI | SWT.LEAD | SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL);
- stack.setEditable(false);
- stack.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
- StringWriter sw = new StringWriter();
- exception.printStackTrace(new PrintWriter(sw));
- stack.setText(sw.toString());
- }
-
- parent.pack();
- return composite;
- }
-
- protected void configureShell(Shell shell) {
- super.configureShell(shell);
- shell.setText("Error");
- }
-}
\ No newline at end of file
+++ /dev/null
-package org.argeo.eclipse.ui.dialogs;
-
-import java.io.PrintWriter;
-import java.io.StringWriter;
-
-import org.argeo.api.cms.CmsLog;
-import org.argeo.eclipse.ui.EclipseUiException;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.events.ShellAdapter;
-import org.eclipse.swt.events.ShellEvent;
-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.Display;
-import org.eclipse.swt.widgets.Label;
-import org.eclipse.swt.widgets.Shell;
-import org.eclipse.swt.widgets.Text;
-
-/**
- * Generic lightweight dialog, not based on JFace.
- *
- * @deprecated Use CMS dialogs instead.
- */
-@Deprecated
-public class FeedbackDialog extends LightweightDialog {
- private final static CmsLog log = CmsLog.getLog(FeedbackDialog.class);
-
- private String message;
- private Throwable exception;
-
-// private Shell parentShell;
- private Shell shell;
-
- public static void show(String message, Throwable e) {
- // rethrow ThreaDeath in order to make sure that RAP will properly clean
- // up the UI thread
- if (e instanceof ThreadDeath)
- throw (ThreadDeath) e;
-
- new FeedbackDialog(getDisplay().getActiveShell(), message, e).open();
- }
-
- public static void show(String message) {
- new FeedbackDialog(getDisplay().getActiveShell(), message, null).open();
- }
-
- /** Tries to find a display */
- private static Display getDisplay() {
- try {
- Display display = Display.getCurrent();
- if (display != null)
- return display;
- else
- return Display.getDefault();
- } catch (Exception e) {
- return Display.getCurrent();
- }
- }
-
- public FeedbackDialog(Shell parentShell, String message, Throwable e) {
- super(parentShell);
- this.message = message;
- this.exception = e;
- log.error(message, e);
- }
-
- public int open() {
- if (shell != null)
- throw new EclipseUiException("There is already a shell");
- shell = new Shell(getDisplay(), SWT.NO_TRIM | SWT.BORDER | SWT.ON_TOP);
- shell.setLayout(new GridLayout());
- // shell.setText("Error");
- shell.setSize(getInitialSize());
- createDialogArea(shell);
- // shell.pack();
- // shell.layout();
-
- Rectangle shellBounds = Display.getCurrent().getBounds();// RAP
- Point dialogSize = shell.getSize();
- int x = shellBounds.x + (shellBounds.width - dialogSize.x) / 2;
- int y = shellBounds.y + (shellBounds.height - dialogSize.y) / 2;
- shell.setLocation(x, y);
-
- shell.addShellListener(new ShellAdapter() {
- private static final long serialVersionUID = -2701270481953688763L;
-
- @Override
- public void shellDeactivated(ShellEvent e) {
- closeShell();
- }
- });
-
- shell.open();
- return OK;
- }
-
- protected void closeShell() {
- shell.close();
- shell.dispose();
- shell = null;
- }
-
- protected Point getInitialSize() {
- // if (exception != null)
- // return new Point(800, 600);
- // else
- return new Point(400, 300);
- }
-
- protected Control createDialogArea(Composite parent) {
- Composite dialogarea = new Composite(parent, SWT.NONE);
- dialogarea.setLayout(new GridLayout());
- // Composite dialogarea = (Composite) super.createDialogArea(parent);
- dialogarea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
-
- Label messageLbl = new Label(dialogarea, SWT.NONE);
- if (message != null)
- messageLbl.setText(message);
- else if (exception != null)
- messageLbl.setText(exception.getLocalizedMessage());
-
- Composite composite = new Composite(dialogarea, SWT.NONE);
- composite.setLayout(new GridLayout(2, false));
- composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
-
- if (exception != null) {
- Text stack = new Text(composite, SWT.MULTI | SWT.LEAD | SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL);
- stack.setEditable(false);
- stack.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
- StringWriter sw = new StringWriter();
- exception.printStackTrace(new PrintWriter(sw));
- stack.setText(sw.toString());
- }
-
- // parent.pack();
- return composite;
- }
-}
\ No newline at end of file
+++ /dev/null
-package org.argeo.eclipse.ui.dialogs;
-
-import org.argeo.api.cms.CmsLog;
-import org.argeo.eclipse.ui.EclipseUiException;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.events.FocusEvent;
-import org.eclipse.swt.events.FocusListener;
-import org.eclipse.swt.events.ShellAdapter;
-import org.eclipse.swt.events.ShellEvent;
-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.Display;
-import org.eclipse.swt.widgets.Shell;
-
-/** Generic lightweight dialog, not based on JFace. */
-@Deprecated
-public class LightweightDialog {
- private final static CmsLog log = CmsLog.getLog(LightweightDialog.class);
-
- // must be the same value as org.eclipse.jface.window.Window#OK
- public final static int OK = 0;
- // must be the same value as org.eclipse.jface.window.Window#CANCEL
- public final static int CANCEL = 1;
-
- private Shell parentShell;
- private Shell backgroundShell;
- private Shell foregoundShell;
-
- private Integer returnCode = null;
- private boolean block = true;
-
- private String title;
-
- /** Tries to find a display */
- private static Display getDisplay() {
- try {
- Display display = Display.getCurrent();
- if (display != null)
- return display;
- else
- return Display.getDefault();
- } catch (Exception e) {
- return Display.getCurrent();
- }
- }
-
- public LightweightDialog(Shell parentShell) {
- this.parentShell = parentShell;
- }
-
- public int open() {
- if (foregoundShell != null)
- throw new EclipseUiException("There is already a shell");
- backgroundShell = new Shell(parentShell, SWT.ON_TOP);
- backgroundShell.setFullScreen(true);
- // if (parentShell != null) {
- // backgroundShell.setBounds(parentShell.getBounds());
- // } else
- // backgroundShell.setMaximized(true);
- backgroundShell.setAlpha(128);
- backgroundShell.setBackground(getDisplay().getSystemColor(SWT.COLOR_BLACK));
- foregoundShell = new Shell(backgroundShell, SWT.NO_TRIM | SWT.ON_TOP);
- if (title != null)
- setTitle(title);
- foregoundShell.setLayout(new GridLayout());
- foregoundShell.setSize(getInitialSize());
- createDialogArea(foregoundShell);
- // shell.pack();
- // shell.layout();
-
- Rectangle shellBounds = parentShell != null ? parentShell.getBounds() : Display.getCurrent().getBounds();// RAP
- Point dialogSize = foregoundShell.getSize();
- int x = shellBounds.x + (shellBounds.width - dialogSize.x) / 2;
- int y = shellBounds.y + (shellBounds.height - dialogSize.y) / 2;
- foregoundShell.setLocation(x, y);
-
- foregoundShell.addShellListener(new ShellAdapter() {
- private static final long serialVersionUID = -2701270481953688763L;
-
- @Override
- public void shellDeactivated(ShellEvent e) {
- if (hasChildShells())
- return;
- if (returnCode == null)// not yet closed
- closeShell(CANCEL);
- }
-
- @Override
- public void shellClosed(ShellEvent e) {
- notifyClose();
- }
-
- });
-
- backgroundShell.open();
- foregoundShell.open();
- // after the foreground shell has been opened
- backgroundShell.addFocusListener(new FocusListener() {
- private static final long serialVersionUID = 3137408447474661070L;
-
- @Override
- public void focusLost(FocusEvent event) {
- }
-
- @Override
- public void focusGained(FocusEvent event) {
- if (hasChildShells())
- return;
- if (returnCode == null)// not yet closed
- closeShell(CANCEL);
- }
- });
-
- if (block) {
- block();
- }
- if (returnCode == null)
- returnCode = OK;
- return returnCode;
- }
-
- public void block() {
- try {
- runEventLoop(foregoundShell);
- } catch (ThreadDeath t) {
- returnCode = CANCEL;
- if (log.isTraceEnabled())
- log.error("Thread death, canceling dialog", t);
- } catch (Throwable t) {
- returnCode = CANCEL;
- log.error("Cannot open blocking lightweight dialog", t);
- }
- }
-
- private boolean hasChildShells() {
- if (foregoundShell == null)
- return false;
- return foregoundShell.getShells().length != 0;
- }
-
- // public synchronized int openAndWait() {
- // open();
- // while (returnCode == null)
- // try {
- // wait(100);
- // } catch (InterruptedException e) {
- // // silent
- // }
- // return returnCode;
- // }
-
- private synchronized void notifyClose() {
- if (returnCode == null)
- returnCode = CANCEL;
- notifyAll();
- }
-
- protected void closeShell(int returnCode) {
- this.returnCode = returnCode;
- if (CANCEL == returnCode)
- onCancel();
- if (foregoundShell != null && !foregoundShell.isDisposed()) {
- foregoundShell.close();
- foregoundShell.dispose();
- foregoundShell = null;
- }
-
- if (backgroundShell != null && !backgroundShell.isDisposed()) {
- backgroundShell.close();
- backgroundShell.dispose();
- }
- }
-
- protected Point getInitialSize() {
- // if (exception != null)
- // return new Point(800, 600);
- // else
- return new Point(600, 400);
- }
-
- protected Control createDialogArea(Composite parent) {
- Composite dialogarea = new Composite(parent, SWT.NONE);
- dialogarea.setLayout(new GridLayout());
- dialogarea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
- return dialogarea;
- }
-
- protected Shell getBackgroundShell() {
- return backgroundShell;
- }
-
- protected Shell getForegoundShell() {
- return foregoundShell;
- }
-
- public void setBlockOnOpen(boolean shouldBlock) {
- block = shouldBlock;
- }
-
- public void pack() {
- foregoundShell.pack();
- }
-
- private void runEventLoop(Shell loopShell) {
- Display display;
- if (foregoundShell == null) {
- display = Display.getCurrent();
- } else {
- display = loopShell.getDisplay();
- }
-
- while (loopShell != null && !loopShell.isDisposed()) {
- try {
- if (!display.readAndDispatch()) {
- display.sleep();
- }
- } catch (UnsupportedOperationException e) {
- throw e;
- } catch (Throwable e) {
- handleException(e);
- }
- }
- if (!display.isDisposed())
- display.update();
- }
-
- protected void handleException(Throwable t) {
- if (t instanceof ThreadDeath) {
- // Don't catch ThreadDeath as this is a normal occurrence when
- // the thread dies
- throw (ThreadDeath) t;
- }
- // Try to keep running.
- t.printStackTrace();
- }
-
- /** @return false, if the dialog should not be closed. */
- protected boolean onCancel() {
- return true;
- }
-
- public void setTitle(String title) {
- this.title = title;
- if (title != null && getForegoundShell() != null)
- getForegoundShell().setText(title);
- }
-
- public Integer getReturnCode() {
- return returnCode;
- }
-
-}
\ No newline at end of file
+++ /dev/null
-package org.argeo.eclipse.ui.dialogs;
-
-import org.argeo.eclipse.ui.EclipseUiUtils;
-import org.eclipse.jface.dialogs.IMessageProvider;
-import org.eclipse.jface.dialogs.TitleAreaDialog;
-import org.eclipse.jface.window.Window;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.graphics.Point;
-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.Display;
-import org.eclipse.swt.widgets.Label;
-import org.eclipse.swt.widgets.Shell;
-import org.eclipse.swt.widgets.Text;
-
-/**
- * Dialog to retrieve a single value.
- *
- * @deprecated Use CMS dialogs instead.
- */
-@Deprecated
-public class SingleValue extends TitleAreaDialog {
- private static final long serialVersionUID = 2843538207460082349L;
-
- private Text valueT;
- private String value;
- private final String title, message, label;
- private final Boolean multiline;
-
- public static String ask(String label, String message) {
- SingleValue svd = new SingleValue(label, message);
- if (svd.open() == Window.OK)
- return svd.getString();
- else
- return null;
- }
-
- public static Long askLong(String label, String message) {
- SingleValue svd = new SingleValue(label, message);
- if (svd.open() == Window.OK)
- return svd.getLong();
- else
- return null;
- }
-
- public static Double askDouble(String label, String message) {
- SingleValue svd = new SingleValue(label, message);
- if (svd.open() == Window.OK)
- return svd.getDouble();
- else
- return null;
- }
-
- public SingleValue(String label, String message) {
- this(Display.getDefault().getActiveShell(), label, message, label, false);
- }
-
- public SingleValue(Shell parentShell, String title, String message, String label, Boolean multiline) {
- super(parentShell);
- this.title = title;
- this.message = message;
- this.label = label;
- this.multiline = multiline;
- }
-
- protected Point getInitialSize() {
- if (multiline)
- return new Point(450, 350);
-
- else
- return new Point(400, 270);
- }
-
- 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.setLayoutData(EclipseUiUtils.fillAll());
- GridLayout layout = new GridLayout(2, false);
- layout.marginWidth = layout.marginHeight = 20;
- composite.setLayout(layout);
-
- valueT = createLT(composite, label);
-
- setMessage(message, IMessageProvider.NONE);
-
- parent.pack();
- valueT.setFocus();
- return composite;
- }
-
- @Override
- protected void okPressed() {
- value = valueT.getText();
- super.okPressed();
- }
-
- /** Creates label and text. */
- protected Text createLT(Composite parent, String label) {
- new Label(parent, SWT.NONE).setText(label);
- Text text;
- if (multiline) {
- text = new Text(parent, SWT.LEAD | SWT.BORDER | SWT.MULTI);
- text.setLayoutData(EclipseUiUtils.fillAll());
- } else {
- text = new Text(parent, SWT.LEAD | SWT.BORDER | SWT.SINGLE);
- text.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, true));
- }
- return text;
- }
-
- protected void configureShell(Shell shell) {
- super.configureShell(shell);
- shell.setText(title);
- }
-
- public String getString() {
- return value;
- }
-
- public Long getLong() {
- return Long.valueOf(getString());
- }
-
- public Double getDouble() {
- return Double.valueOf(getString());
- }
-}
+++ /dev/null
-/** Generic SWT/JFace dialogs. */
-package org.argeo.eclipse.ui.dialogs;
\ No newline at end of file
+++ /dev/null
-package org.argeo.eclipse.ui.fs;
-
-import java.io.IOException;
-import java.nio.file.DirectoryStream;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.LinkedHashMap;
-
-import org.argeo.api.cms.CmsLog;
-import org.argeo.eclipse.ui.EclipseUiUtils;
-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.swt.SWT;
-import org.eclipse.swt.custom.SashForm;
-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.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.Text;
-
-/** Simple UI provider that populates a composite parent given a NIO path */
-public class AdvancedFsBrowser {
- private final static CmsLog log = CmsLog.getLog(AdvancedFsBrowser.class);
-
- // Some local constants to experiment. should be cleaned
- // private final static int THUMBNAIL_WIDTH = 400;
- // private Point imageWidth = new Point(250, 0);
- private final static int COLUMN_WIDTH = 160;
-
- private Path initialPath;
- private Path currEdited;
- // Filter
- private Composite displayBoxCmp;
- private Text parentPathTxt;
- private Text filterTxt;
- // Browser columns
- private ScrolledComposite scrolledCmp;
- // Keep a cache of the opened directories
- private LinkedHashMap<Path, FilterEntitiesVirtualTable> browserCols = new LinkedHashMap<>();
- private Composite scrolledCmpBody;
-
- public Control createUi(Composite parent, Path basePath) {
- if (basePath == null)
- throw new IllegalArgumentException("Context cannot be null");
- parent.setLayout(new GridLayout());
-
- // top filter
- Composite filterCmp = new Composite(parent, SWT.NO_FOCUS);
- filterCmp.setLayoutData(EclipseUiUtils.fillWidth());
- addFilterPanel(filterCmp);
-
- // Bottom part a sash with browser on the left
- SashForm form = new SashForm(parent, SWT.HORIZONTAL);
- // form.setLayout(new FillLayout());
- form.setLayoutData(EclipseUiUtils.fillAll());
- Composite leftCmp = new Composite(form, SWT.NO_FOCUS);
- displayBoxCmp = new Composite(form, SWT.NONE);
- form.setWeights(new int[] { 3, 1 });
-
- createBrowserPart(leftCmp, basePath);
- // leftCmp.addControlListener(new ControlAdapter() {
- // @Override
- // public void controlResized(ControlEvent e) {
- // Rectangle r = leftCmp.getClientArea();
- // log.warn("Browser resized: " + r.toString());
- // scrolledCmp.setMinSize(browserCols.size() * (COLUMN_WIDTH + 2),
- // SWT.DEFAULT);
- // // scrolledCmp.setMinSize(scrolledCmpBody.computeSize(SWT.DEFAULT,
- // // r.height));
- // }
- // });
-
- populateCurrEditedDisplay(displayBoxCmp, basePath);
-
- // INIT
- setEdited(basePath);
- initialPath = basePath;
- // form.layout(true, true);
- return parent;
- }
-
- private void createBrowserPart(Composite parent, Path context) {
- parent.setLayout(EclipseUiUtils.noSpaceGridLayout());
-
- // scrolled composite
- scrolledCmp = new ScrolledComposite(parent, SWT.H_SCROLL | SWT.BORDER | SWT.NO_FOCUS);
- scrolledCmp.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
- scrolledCmp.setExpandVertical(true);
- scrolledCmp.setExpandHorizontal(true);
- scrolledCmp.setShowFocusedControl(true);
-
- scrolledCmpBody = new Composite(scrolledCmp, SWT.NO_FOCUS);
- scrolledCmp.setContent(scrolledCmpBody);
- scrolledCmpBody.addControlListener(new ControlAdapter() {
- private static final long serialVersionUID = 183238447102854553L;
-
- @Override
- public void controlResized(ControlEvent e) {
- Rectangle r = scrolledCmp.getClientArea();
- scrolledCmp.setMinSize(scrolledCmpBody.computeSize(SWT.DEFAULT, r.height));
- }
- });
- initExplorer(scrolledCmpBody, context);
- scrolledCmpBody.layout(true, true);
- scrolledCmp.layout();
-
- }
-
- private Control initExplorer(Composite parent, Path context) {
- parent.setLayout(EclipseUiUtils.noSpaceGridLayout());
- return createBrowserColumn(parent, context);
- }
-
- private Control createBrowserColumn(Composite parent, Path context) {
- // TODO style is not correctly managed.
- FilterEntitiesVirtualTable table = new FilterEntitiesVirtualTable(parent, SWT.BORDER | SWT.NO_FOCUS, context);
- // CmsUtils.style(table, ArgeoOrgStyle.browserColumn.style());
- table.filterList("*");
- table.setLayoutData(new GridData(SWT.LEFT, SWT.FILL, false, true));
- browserCols.put(context, table);
- parent.layout(true, true);
- return table;
- }
-
- public void addFilterPanel(Composite parent) {
- parent.setLayout(EclipseUiUtils.noSpaceGridLayout(new GridLayout(2, false)));
-
- 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(EclipseUiUtils.fillWidth());
- filterTxt.addModifyListener(new ModifyListener() {
- private static final long serialVersionUID = 1L;
-
- public void modifyText(ModifyEvent event) {
- modifyFilter(false);
- }
- });
- filterTxt.addKeyListener(new KeyListener() {
- private static final long serialVersionUID = 2533535233583035527L;
-
- @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(currEdited);
- if (table != null && !table.isDisposed())
- currTable = table;
- }
-
- if (e.keyCode == SWT.ARROW_DOWN)
- currTable.setFocus();
- else if (e.keyCode == SWT.BS) {
- if (filterTxt.getText().equals("")
- && !(currEdited.getNameCount() == 1 || currEdited.equals(initialPath))) {
- Path oldEdited = currEdited;
- Path parentPath = currEdited.getParent();
- setEdited(parentPath);
- if (browserCols.containsKey(parentPath))
- browserCols.get(parentPath).setSelected(oldEdited);
- filterTxt.setFocus();
- e.doit = false;
- }
- } else if (e.keyCode == SWT.TAB && !shiftPressed) {
- Path uniqueChild = getOnlyChild(currEdited, filterTxt.getText());
- if (uniqueChild != null) {
- // Highlight the unique chosen child
- currTable.setSelected(uniqueChild);
- setEdited(uniqueChild);
- }
- filterTxt.setFocus();
- e.doit = false;
- }
- }
- });
- }
-
- private Path getOnlyChild(Path parent, String filter) {
- try (DirectoryStream<Path> stream = Files.newDirectoryStream(currEdited, filter + "*")) {
- Path uniqueChild = null;
- boolean moreThanOne = false;
- loop: for (Path entry : stream) {
- if (uniqueChild == null) {
- uniqueChild = entry;
- } else {
- moreThanOne = true;
- break loop;
- }
- }
- if (!moreThanOne)
- return uniqueChild;
- return null;
- } catch (IOException ioe) {
- throw new FsUiException(
- "Unable to determine unique child existence and get it under " + parent + " with filter " + filter,
- ioe);
- }
- }
-
- private void setEdited(Path path) {
- currEdited = path;
- EclipseUiUtils.clear(displayBoxCmp);
- populateCurrEditedDisplay(displayBoxCmp, currEdited);
- refreshFilters(path);
- refreshBrowser(path);
- }
-
- private void refreshFilters(Path path) {
- parentPathTxt.setText(path.toUri().toString());
- filterTxt.setText("");
- filterTxt.getParent().layout();
- }
-
- private void refreshBrowser(Path currPath) {
- Path currParPath = currPath.getParent();
- Object[][] colMatrix = new Object[browserCols.size()][2];
-
- int i = 0, currPathIndex = -1, lastLeftOpenedIndex = -1;
- for (Path path : browserCols.keySet()) {
- colMatrix[i][0] = path;
- colMatrix[i][1] = browserCols.get(path);
- if (currPathIndex >= 0 && lastLeftOpenedIndex < 0 && currParPath != null) {
- boolean leaveOpened = path.startsWith(currPath);
- if (!leaveOpened)
- lastLeftOpenedIndex = i;
- }
- if (currParPath.equals(path))
- currPathIndex = i;
- i++;
- }
-
- if (currPathIndex >= 0 && lastLeftOpenedIndex >= 0) {
- // dispose and remove useless cols
- for (int l = i - 1; l >= lastLeftOpenedIndex; l--) {
- ((FilterEntitiesVirtualTable) colMatrix[l][1]).dispose();
- browserCols.remove(colMatrix[l][0]);
- }
- }
-
- if (browserCols.containsKey(currPath)) {
- FilterEntitiesVirtualTable currCol = browserCols.get(currPath);
- if (currCol.isDisposed()) {
- // Does it still happen ?
- log.warn(currPath + " browser column was disposed and still listed");
- browserCols.remove(currPath);
- }
- }
-
- if (!browserCols.containsKey(currPath) && Files.isDirectory(currPath))
- createBrowserColumn(scrolledCmpBody, currPath);
-
- scrolledCmpBody.setLayout(EclipseUiUtils.noSpaceGridLayout(new GridLayout(browserCols.size(), false)));
- scrolledCmpBody.layout(true, true);
- // also resize the scrolled composite
- scrolledCmp.layout();
- }
-
- private void modifyFilter(boolean fromOutside) {
- if (!fromOutside)
- if (currEdited != null) {
- String filter = filterTxt.getText() + "*";
- FilterEntitiesVirtualTable table = browserCols.get(currEdited);
- if (table != null && !table.isDisposed())
- table.filterList(filter);
- }
- }
-
- /**
- * Recreates the content of the box that displays information about the current
- * selected node.
- */
- private void populateCurrEditedDisplay(Composite parent, Path context) {
- parent.setLayout(new GridLayout());
-
- // if (isImg(context)) {
- // EditableImage image = new Img(parent, RIGHT, context, imageWidth);
- // image.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, true, false,
- // 2, 1));
- // }
-
- try {
- Label contextL = new Label(parent, SWT.NONE);
- contextL.setText(context.getFileName().toString());
- contextL.setFont(EclipseUiUtils.getBoldFont(parent));
- addProperty(parent, "Last modified", Files.getLastModifiedTime(context).toString());
- addProperty(parent, "Owner", Files.getOwner(context).getName());
- if (Files.isDirectory(context)) {
- addProperty(parent, "Type", "Folder");
- } else {
- String mimeType = Files.probeContentType(context);
- if (EclipseUiUtils.isEmpty(mimeType))
- mimeType = "<i>Unknown</i>";
- addProperty(parent, "Type", mimeType);
- addProperty(parent, "Size", FsUiUtils.humanReadableByteCount(Files.size(context), false));
- }
- parent.layout(true, true);
- } catch (IOException e) {
- throw new FsUiException("Cannot display details for " + context, e);
- }
- }
-
- private void addProperty(Composite parent, String propName, String value) {
- Label contextL = new Label(parent, SWT.NONE);
- contextL.setText(propName + ": " + value);
- }
-
- /**
- * Almost canonical implementation of a table that displays the content of a
- * directory
- */
- private class FilterEntitiesVirtualTable extends Composite {
- private static final long serialVersionUID = 2223410043691844875L;
-
- // Context
- private Path context;
- private Path currSelected = null;
-
- // UI Objects
- private FsTableViewer viewer;
-
- @Override
- public boolean setFocus() {
- if (viewer.getTable().isDisposed())
- return false;
- if (currSelected != null)
- viewer.setSelection(new StructuredSelection(currSelected), true);
- else if (viewer.getSelection().isEmpty()) {
- Object first = viewer.getElementAt(0);
- if (first != null)
- viewer.setSelection(new StructuredSelection(first), true);
- }
- return viewer.getTable().setFocus();
- }
-
- /**
- * Enable highlighting the correct element in the table when externally browsing
- * (typically via the command-line-like Text field)
- */
- void setSelected(Path selected) {
- // to prevent change selection event to be thrown
- currSelected = selected;
- viewer.setSelection(new StructuredSelection(currSelected), true);
- }
-
- void filterList(String filter) {
- viewer.setInput(context, filter);
- }
-
- public FilterEntitiesVirtualTable(Composite parent, int style, Path context) {
- super(parent, SWT.NO_FOCUS);
- this.context = context;
- createTableViewer(this);
- }
-
- private void createTableViewer(final Composite parent) {
- parent.setLayout(EclipseUiUtils.noSpaceGridLayout());
-
- // 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(EclipseUiUtils.noSpaceGridLayout());
- // viewer = new TableViewer(listCmp, SWT.VIRTUAL | SWT.MULTI |
- // SWT.V_SCROLL);
- // Table table = viewer.getTable();
- // table.setLayoutData(EclipseUiUtils.fillAll());
-
- viewer = new FsTableViewer(parent, SWT.MULTI);
- Table table = viewer.configureDefaultSingleColumnTable(COLUMN_WIDTH);
-
- viewer.addSelectionChangedListener(new ISelectionChangedListener() {
-
- @Override
- public void selectionChanged(SelectionChangedEvent event) {
- IStructuredSelection selection = (IStructuredSelection) viewer.getSelection();
- if (selection.isEmpty())
- return;
- Object obj = selection.getFirstElement();
- Path newSelected;
- if (obj instanceof Path)
- newSelected = (Path) obj;
- else if (obj instanceof ParentDir)
- newSelected = ((ParentDir) obj).getPath();
- else
- return;
- if (newSelected.equals(currSelected))
- return;
- currSelected = newSelected;
- setEdited(newSelected);
-
- }
- });
-
- table.addKeyListener(new KeyListener() {
- private static final long serialVersionUID = -8083424284436715709L;
-
- @Override
- public void keyReleased(KeyEvent e) {
- }
-
- @Override
- public void keyPressed(KeyEvent e) {
- IStructuredSelection selection = (IStructuredSelection) viewer.getSelection();
- Path selected = null;
- if (!selection.isEmpty())
- selected = ((Path) selection.getFirstElement());
- if (e.keyCode == SWT.ARROW_RIGHT) {
- if (!Files.isDirectory(selected))
- return;
- if (selected != null) {
- setEdited(selected);
- browserCols.get(selected).setFocus();
- }
- } else if (e.keyCode == SWT.ARROW_LEFT) {
- if (context.equals(initialPath))
- return;
- Path parent = context.getParent();
- if (parent == null)
- return;
-
- setEdited(parent);
- browserCols.get(parent).setFocus();
- }
- }
- });
- }
- }
-}
+++ /dev/null
-package org.argeo.eclipse.ui.fs;
-
-import java.nio.file.Files;
-import java.nio.file.Path;
-
-import org.eclipse.jface.resource.ImageDescriptor;
-import org.eclipse.jface.viewers.ColumnLabelProvider;
-import org.eclipse.swt.graphics.Image;
-
-/** Basic label provider with icon for NIO file viewers */
-public class FileIconNameLabelProvider extends ColumnLabelProvider {
- private static final long serialVersionUID = 8187902187946523148L;
-
- private Image folderIcon;
- private Image fileIcon;
-
- public FileIconNameLabelProvider() {
- // if (!PlatformUI.isWorkbenchRunning()) {
- folderIcon = ImageDescriptor.createFromFile(getClass(), "folder.png").createImage();
- fileIcon = ImageDescriptor.createFromFile(getClass(), "file.png").createImage();
- // }
- }
-
- @Override
- public void dispose() {
- if (folderIcon != null)
- folderIcon.dispose();
- if (fileIcon != null)
- fileIcon.dispose();
- super.dispose();
- }
-
- @Override
- public String getText(Object element) {
- if (element instanceof Path) {
- Path curr = ((Path) element);
- Path name = curr.getFileName();
- if (name == null)
- return "[No name]";
- else
- return name.toString();
- } else if (element instanceof ParentDir) {
- return "..";
- }
- return null;
- }
-
- @Override
- public Image getImage(Object element) {
- if (element instanceof Path) {
- Path curr = ((Path) element);
- if (Files.isDirectory(curr))
- // if (folderIcon != null)
- return folderIcon;
- // else
- // return
- // PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_OBJ_FOLDER);
- // else if (fileIcon != null)
- return fileIcon;
- // else
- // return
- // PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_OBJ_FILE);
- } else if (element instanceof ParentDir) {
- return folderIcon;
- }
- return null;
- }
-
- @Override
- public String getToolTipText(Object element) {
- if (element instanceof Path) {
- Path curr = ((Path) element);
- Path name = curr.getFileName();
- if (name == null)
- return "[No name]";
- else
- return name.toAbsolutePath().toString();
- } else if (element instanceof ParentDir) {
- return ((ParentDir) element).getPath().toAbsolutePath().toString();
- }
- return null;
- }
-
-}
\ No newline at end of file
+++ /dev/null
-package org.argeo.eclipse.ui.fs;
-
-import java.nio.file.Path;
-import java.util.List;
-
-import org.argeo.eclipse.ui.ColumnDefinition;
-import org.eclipse.jface.viewers.CellLabelProvider;
-import org.eclipse.jface.viewers.ILazyContentProvider;
-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.layout.GridData;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Table;
-import org.eclipse.swt.widgets.TableColumn;
-
-/**
- * Canonical implementation of a JFace table viewer to display the content of a
- * file folder
- */
-public class FsTableViewer extends TableViewer {
- private static final long serialVersionUID = -5632407542678477234L;
-
- private boolean showHiddenItems = false;
- private boolean folderFirst = true;
- private boolean reverseOrder = false;
- private String orderProperty = FsUiConstants.PROPERTY_NAME;
-
- private Path initialPath = null;
-
- public FsTableViewer(Composite parent, int style) {
- super(parent, style | SWT.VIRTUAL);
- }
-
- public Table configureDefaultSingleColumnTable(int tableWidthHint) {
-
- return configureDefaultSingleColumnTable(tableWidthHint, new FileIconNameLabelProvider());
- }
-
- public Table configureDefaultSingleColumnTable(int tableWidthHint, CellLabelProvider labelProvider) {
- Table table = this.getTable();
- table.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
- table.setLinesVisible(false);
- table.setHeaderVisible(false);
- // CmsUtils.markup(table);
- // CmsUtils.style(table, MaintenanceStyles.BROWSER_COLUMN);
-
- TableViewerColumn column = new TableViewerColumn(this, SWT.NONE);
- TableColumn tcol = column.getColumn();
- tcol.setWidth(tableWidthHint);
- column.setLabelProvider(labelProvider);
- this.setContentProvider(new MyLazyCP());
- return table;
- }
-
- public Table configureDefaultTable(List<ColumnDefinition> columns) {
- this.setContentProvider(new MyLazyCP());
- Table table = this.getTable();
- table.setLinesVisible(true);
- table.setHeaderVisible(true);
- // CmsUtils.markup(table);
- // CmsUtils.style(table, MaintenanceStyles.BROWSER_COLUMN);
- for (ColumnDefinition colDef : columns) {
- TableViewerColumn column = new TableViewerColumn(this, SWT.NONE);
- column.setLabelProvider(colDef.getLabelProvider());
- TableColumn tcol = column.getColumn();
- tcol.setResizable(true);
- tcol.setText(colDef.getLabel());
- tcol.setWidth(colDef.getMinWidth());
- }
- return table;
- }
-
- public void setInput(Path dir, String filter) {
- Path[] rows = FsUiUtils.getChildren(dir, filter, showHiddenItems, folderFirst, orderProperty, reverseOrder);
- if (rows == null) {
- this.setInput(null);
- this.setItemCount(0);
- return;
- }
- boolean isRoot;
- try {
- isRoot = dir.getRoot().equals(dir);
- } catch (Exception e) {
- // FIXME Workaround for JCR root node access
- isRoot = dir.toString().equals("/");
- }
- final Object[] res;
- if (isRoot)
- res = rows;
- else if (initialPath != null && initialPath.equals(dir))
- res = rows;
- else {
- res = new Object[rows.length + 1];
- res[0] = new ParentDir(dir.getParent());
- for (int i = 1; i < res.length; i++) {
- res[i] = rows[i - 1];
- }
- }
- this.setInput(res);
- int length = res.length;
- this.setItemCount(length);
- this.refresh();
- }
-
- /** Directly displays bookmarks **/
- public void setPathsInput(Path... paths) {
- this.setInput((Object[]) paths);
- this.setItemCount(paths.length);
- this.refresh();
- }
-
- /**
- * A path which is to be considered as root (and thus provide no link to a
- * parent directory)
- */
- public void setInitialPath(Path initialPath) {
- this.initialPath = initialPath;
- }
-
- private class MyLazyCP implements ILazyContentProvider {
- private static final long serialVersionUID = 9096550041395433128L;
- private Object[] elements;
-
- 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) {
- if (index < elements.length)
- FsTableViewer.this.replace(elements[index], index);
- }
- }
-}
+++ /dev/null
-package org.argeo.eclipse.ui.fs;
-
-import java.io.IOException;
-import java.nio.file.DirectoryIteratorException;
-import java.nio.file.DirectoryStream;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.ArrayList;
-import java.util.List;
-
-import org.argeo.eclipse.ui.ColumnDefinition;
-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.layout.GridData;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Tree;
-import org.eclipse.swt.widgets.TreeColumn;
-
-/**
- * Canonical implementation of a JFace TreeViewer to display the content of a
- * repository
- */
-public class FsTreeViewer extends TreeViewer {
- private static final long serialVersionUID = -5632407542678477234L;
-
- private boolean showHiddenItems = false;
- private boolean showDirectoryFirst = true;
- private String orderingProperty = FsUiConstants.PROPERTY_NAME;
-
- public FsTreeViewer(Composite parent, int style) {
- super(parent, style | SWT.VIRTUAL);
- }
-
- public Tree configureDefaultSingleColumnTable(int tableWidthHint) {
- Tree tree = this.getTree();
- tree.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
- tree.setLinesVisible(true);
- tree.setHeaderVisible(false);
-// CmsUtils.markup(tree);
-
- TreeViewerColumn column = new TreeViewerColumn(this, SWT.NONE);
- TreeColumn tcol = column.getColumn();
- tcol.setWidth(tableWidthHint);
- column.setLabelProvider(new FileIconNameLabelProvider());
-
- this.setContentProvider(new MyCP());
- return tree;
- }
-
- public Tree configureDefaultTable(List<ColumnDefinition> columns) {
- this.setContentProvider(new MyCP());
- Tree tree = this.getTree();
- tree.setLinesVisible(true);
- tree.setHeaderVisible(true);
-// CmsUtils.markup(tree);
-// CmsUtils.style(tree, MaintenanceStyles.BROWSER_COLUMN);
- for (ColumnDefinition colDef : columns) {
- TreeViewerColumn column = new TreeViewerColumn(this, SWT.NONE);
- column.setLabelProvider(colDef.getLabelProvider());
- TreeColumn tcol = column.getColumn();
- tcol.setResizable(true);
- tcol.setText(colDef.getLabel());
- tcol.setWidth(colDef.getMinWidth());
- }
- return tree;
- }
-
- public void setInput(Path dir, String filter) {
- try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir, filter)) {
- // TODO make this lazy
- List<Path> paths = new ArrayList<>();
- for (Path entry : stream) {
- paths.add(entry);
- }
- Object[] rows = paths.toArray(new Object[0]);
- this.setInput(rows);
- // this.setItemCount(rows.length);
- this.refresh();
- } catch (IOException | DirectoryIteratorException e) {
- throw new FsUiException("Unable to filter " + dir + " children with filter " + filter, e);
- }
- }
-
- /** Directly displays bookmarks **/
- public void setPathsInput(Path... paths) {
- this.setInput((Object[]) paths);
- // this.setItemCount(paths.length);
- this.refresh();
- }
-
- private class MyCP implements ITreeContentProvider {
- private static final long serialVersionUID = 9096550041395433128L;
- private Object[] elements;
-
- 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;
- }
-
- @Override
- public Object[] getElements(Object inputElement) {
- return elements;
- }
-
- @Override
- public Object[] getChildren(Object parentElement) {
- Path path = (Path) parentElement;
- if (!Files.isDirectory(path))
- return null;
- else
- return FsUiUtils.getChildren(path, "*", showHiddenItems, showDirectoryFirst, orderingProperty, false);
- }
-
- @Override
- public Object getParent(Object element) {
- Path path = (Path) element;
- return path.getParent();
- }
-
- @Override
- public boolean hasChildren(Object element) {
- Path path = (Path) element;
- try {
- if (!Files.isDirectory(path))
- return false;
- else
- try (DirectoryStream<Path> children = Files.newDirectoryStream(path, "*")) {
- return children.iterator().hasNext();
- }
- } catch (IOException e) {
- throw new FsUiException("Unable to check child existence on " + path, e);
- }
- }
-
- }
-}
+++ /dev/null
-package org.argeo.eclipse.ui.fs;
-
-/** Centralize constants used by the Nio FS UI parts */
-public interface FsUiConstants {
-
- // TODO use standard properties
- String PROPERTY_NAME = "name";
- String PROPERTY_SIZE = "size";
- String PROPERTY_LAST_MODIFIED = "last-modified";
- String PROPERTY_TYPE = "type";
-}
+++ /dev/null
-package org.argeo.eclipse.ui.fs;
-
-/** Files specific exception */
-public class FsUiException extends RuntimeException {
- private static final long serialVersionUID = 1L;
-
- public FsUiException(String message) {
- super(message);
- }
-
- public FsUiException(String message, Throwable e) {
- super(message, e);
- }
-}
+++ /dev/null
-package org.argeo.eclipse.ui.fs;
-
-import java.io.IOException;
-import java.nio.file.DirectoryIteratorException;
-import java.nio.file.DirectoryStream;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-/** Centralise additional utilitary methods to manage Java7 NIO files */
-public class FsUiUtils {
-
- /**
- * thanks to
- * http://programming.guide/java/formatting-byte-size-to-human-readable-format.html
- */
- public static String humanReadableByteCount(long bytes, boolean si) {
- int unit = si ? 1000 : 1024;
- if (bytes < unit)
- return bytes + " B";
- int exp = (int) (Math.log(bytes) / Math.log(unit));
- String pre = (si ? "kMGTPE" : "KMGTPE").charAt(exp - 1) + (si ? "" : "i");
- return String.format("%.1f %sB", bytes / Math.pow(unit, exp), pre);
- }
-
- public static Path[] getChildren(Path parent, String filter, boolean showHiddenItems, boolean folderFirst,
- String orderProperty, boolean reverseOrder) {
- if (!Files.isDirectory(parent))
- return null;
- List<Pair> pairs = new ArrayList<>();
- try (DirectoryStream<Path> stream = Files.newDirectoryStream(parent, filter)) {
- loop: for (Path entry : stream) {
- if (!showHiddenItems)
- if (Files.isHidden(entry))
- continue loop;
- switch (orderProperty) {
- case FsUiConstants.PROPERTY_SIZE:
- if (folderFirst)
- pairs.add(new LPair(entry, Files.size(entry), Files.isDirectory(entry)));
- else
- pairs.add(new LPair(entry, Files.size(entry)));
- break;
- case FsUiConstants.PROPERTY_LAST_MODIFIED:
- if (folderFirst)
- pairs.add(new LPair(entry, Files.getLastModifiedTime(entry).toMillis(),
- Files.isDirectory(entry)));
- else
- pairs.add(new LPair(entry, Files.getLastModifiedTime(entry).toMillis()));
- break;
- case FsUiConstants.PROPERTY_NAME:
- if (folderFirst)
- pairs.add(new SPair(entry, entry.getFileName().toString(), Files.isDirectory(entry)));
- else
- pairs.add(new SPair(entry, entry.getFileName().toString()));
- break;
- default:
- throw new FsUiException("Unable to prepare sort for property " + orderProperty);
- }
- }
- Pair[] rows = pairs.toArray(new Pair[0]);
- Arrays.sort(rows);
- Path[] results = new Path[rows.length];
- if (reverseOrder) {
- int j = rows.length - 1;
- for (int i = 0; i < rows.length; i++)
- results[i] = rows[j - i].p;
- } else
- for (int i = 0; i < rows.length; i++)
- results[i] = rows[i].p;
- return results;
- } catch (IOException | DirectoryIteratorException e) {
- throw new FsUiException("Unable to filter " + parent + " children with filter " + filter, e);
- }
- }
-
- static abstract class Pair implements Comparable<Object> {
- Path p;
- Boolean i;
- };
-
- static class LPair extends Pair {
- long v;
-
- public LPair(Path path, long propValue) {
- p = path;
- v = propValue;
- }
-
- public LPair(Path path, long propValue, boolean isDir) {
- p = path;
- v = propValue;
- i = isDir;
- }
-
- public int compareTo(Object o) {
- if (i != null) {
- Boolean j = ((LPair) o).i;
- if (i.booleanValue() != j.booleanValue())
- return i.booleanValue() ? -1 : 1;
- }
- long u = ((LPair) o).v;
- return v < u ? -1 : v == u ? 0 : 1;
- }
- };
-
- static class SPair extends Pair {
- String v;
-
- public SPair(Path path, String propValue) {
- p = path;
- v = propValue;
- }
-
- public SPair(Path path, String propValue, boolean isDir) {
- p = path;
- v = propValue;
- i = isDir;
- }
-
- public int compareTo(Object o) {
- if (i != null) {
- Boolean j = ((SPair) o).i;
- if (i.booleanValue() != j.booleanValue())
- return i.booleanValue() ? -1 : 1;
- }
- String u = ((SPair) o).v;
- return v.compareTo(u);
- }
- };
-}
+++ /dev/null
-package org.argeo.eclipse.ui.fs;
-
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.attribute.FileTime;
-import java.text.DateFormat;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-
-import org.argeo.eclipse.ui.EclipseUiUtils;
-import org.eclipse.jface.viewers.ColumnLabelProvider;
-
-/** Expect a {@link Path} as input element */
-public class NioFileLabelProvider extends ColumnLabelProvider {
- private final static FileTime EPOCH = FileTime.fromMillis(0);
- private static final long serialVersionUID = 2160026425187796930L;
- private final String propName;
- private DateFormat dateFormat = new SimpleDateFormat("YYYY-MM-dd HH:mm");
-
- // TODO use new formatting
- // DateTimeFormatter formatter =
- // DateTimeFormatter.ofLocalizedDateTime( FormatStyle.SHORT )
- // .withLocale( Locale.UK )
- // .withZone( ZoneId.systemDefault() );
- public NioFileLabelProvider(String propName) {
- this.propName = propName;
- }
-
- @Override
- public String getText(Object element) {
- try {
- Path path;
- if (element instanceof ParentDir) {
-// switch (propName) {
-// case FsUiConstants.PROPERTY_SIZE:
-// return "-";
-// case FsUiConstants.PROPERTY_LAST_MODIFIED:
-// return "-";
-// // return Files.getLastModifiedTime(((ParentDir) element).getPath()).toString();
-// case FsUiConstants.PROPERTY_TYPE:
-// return "Folder";
-// }
- path = ((ParentDir) element).getPath();
- } else
- path = (Path) element;
- switch (propName) {
- case FsUiConstants.PROPERTY_SIZE:
- if (Files.isDirectory(path))
- return "-";
- else
- return FsUiUtils.humanReadableByteCount(Files.size(path), false);
- case FsUiConstants.PROPERTY_LAST_MODIFIED:
- if (Files.isDirectory(path))
- return "-";
- FileTime time = Files.getLastModifiedTime(path);
- if (time.equals(EPOCH))
- return "-";
- else
- return dateFormat.format(new Date(time.toMillis()));
- case FsUiConstants.PROPERTY_TYPE:
- if (Files.isDirectory(path))
- return "Folder";
- else {
- String mimeType = Files.probeContentType(path);
- if (EclipseUiUtils.isEmpty(mimeType))
- return "Unknown";
- else
- return mimeType;
- }
- default:
- throw new IllegalArgumentException("Unsupported property " + propName);
- }
- } catch (IOException ioe) {
- throw new FsUiException("Cannot get property " + propName + " on " + element);
- }
- }
-}
+++ /dev/null
-package org.argeo.eclipse.ui.fs;
-
-import java.nio.file.Path;
-
-/** A parent directory (..) reference. */
-public class ParentDir {
- Path path;
-
- public ParentDir(Path path) {
- super();
- this.path = path;
- }
-
- public Path getPath() {
- return path;
- }
-
- @Override
- public int hashCode() {
- return path.hashCode();
- }
-
- @Override
- public String toString() {
- return "Parent dir " + path;
- }
-
-}
+++ /dev/null
-package org.argeo.eclipse.ui.fs;
-
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.ArrayList;
-import java.util.List;
-
-import org.argeo.api.cms.CmsLog;
-import org.argeo.eclipse.ui.ColumnDefinition;
-import org.argeo.eclipse.ui.EclipseUiUtils;
-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.Viewer;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.custom.SashForm;
-import org.eclipse.swt.events.KeyEvent;
-import org.eclipse.swt.events.KeyListener;
-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.Table;
-
-/**
- * Experimental UI upon Java 7 nio files api: SashForm layout with bookmarks on
- * the left hand side and a simple table on the right hand side.
- */
-public class SimpleFsBrowser extends Composite {
- private final static CmsLog log = CmsLog.getLog(SimpleFsBrowser.class);
- private static final long serialVersionUID = -40347919096946585L;
-
- private Path currSelected;
- private FsTableViewer bookmarksViewer;
- private FsTableViewer directoryDisplayViewer;
-
- public SimpleFsBrowser(Composite parent, int style) {
- super(parent, style);
- createContent(this);
- // parent.layout(true, true);
- }
-
- public Viewer getViewer() {
- return directoryDisplayViewer;
- }
-
- private void createContent(Composite parent) {
- parent.setLayout(EclipseUiUtils.noSpaceGridLayout());
-
- SashForm form = new SashForm(parent, SWT.HORIZONTAL);
- Composite leftCmp = new Composite(form, SWT.NONE);
- populateBookmarks(leftCmp);
-
- Composite rightCmp = new Composite(form, SWT.BORDER);
- populateDisplay(rightCmp);
- form.setLayoutData(EclipseUiUtils.fillAll());
- form.setWeights(new int[] { 1, 3 });
- }
-
- public void setInput(Path... paths) {
- bookmarksViewer.setPathsInput(paths);
- bookmarksViewer.getTable().getParent().layout(true, true);
- }
-
- private void populateBookmarks(final Composite parent) {
- // GridLayout layout = EclipseUiUtils.noSpaceGridLayout();
- // layout.verticalSpacing = 5;
- parent.setLayout(new GridLayout());
-
- ISelectionChangedListener selList = new MySelectionChangedListener();
-
- appendTitle(parent, "My bookmarks");
- bookmarksViewer = new FsTableViewer(parent, SWT.MULTI | SWT.NO_SCROLL);
- Table table = bookmarksViewer.configureDefaultSingleColumnTable(500);
- GridData gd = EclipseUiUtils.fillWidth();
- gd.horizontalIndent = 10;
- table.setLayoutData(gd);
- bookmarksViewer.addSelectionChangedListener(selList);
-
- appendTitle(parent, "Jcr + File");
-
- FsTableViewer jcrFilesViewers = new FsTableViewer(parent, SWT.MULTI | SWT.NO_SCROLL);
- table = jcrFilesViewers.configureDefaultSingleColumnTable(500);
- gd = EclipseUiUtils.fillWidth();
- gd.horizontalIndent = 10;
- table.setLayoutData(gd);
- jcrFilesViewers.addSelectionChangedListener(selList);
-
- // FileSystemProvider fsProvider = new JackrabbitMemoryFsProvider();
- // try {
- // Path testPath = fsProvider.getPath(new URI("jcr+memory:/"));
- // jcrFilesViewers.setPathsInput(testPath);
- // } catch (URISyntaxException e) {
- // // TODO Auto-generated catch block
- // e.printStackTrace();
- // }
- }
-
- private Label appendTitle(Composite parent, String value) {
- Label titleLbl = new Label(parent, SWT.NONE);
- titleLbl.setText(value);
- titleLbl.setFont(EclipseUiUtils.getBoldFont(parent));
- GridData gd = EclipseUiUtils.fillWidth();
- gd.horizontalIndent = 5;
- gd.verticalIndent = 5;
- titleLbl.setLayoutData(gd);
- return titleLbl;
- }
-
- private class MySelectionChangedListener implements ISelectionChangedListener {
- @Override
- public void selectionChanged(SelectionChangedEvent event) {
- IStructuredSelection selection = (IStructuredSelection) bookmarksViewer.getSelection();
- if (selection.isEmpty())
- return;
- else {
- Path newSelected = (Path) selection.getFirstElement();
- if (newSelected.equals(currSelected))
- return;
- currSelected = newSelected;
- directoryDisplayViewer.setInput(currSelected, "*");
- }
- }
- }
-
- private void populateDisplay(final Composite parent) {
- parent.setLayout(EclipseUiUtils.noSpaceGridLayout());
- directoryDisplayViewer = new FsTableViewer(parent, SWT.MULTI);
- List<ColumnDefinition> colDefs = new ArrayList<>();
- colDefs.add(new ColumnDefinition(new FileIconNameLabelProvider(), "Name", 200));
- colDefs.add(new ColumnDefinition(new NioFileLabelProvider(FsUiConstants.PROPERTY_SIZE), "Size", 100));
- colDefs.add(new ColumnDefinition(new NioFileLabelProvider(FsUiConstants.PROPERTY_TYPE), "Type", 250));
- colDefs.add(new ColumnDefinition(new NioFileLabelProvider(FsUiConstants.PROPERTY_LAST_MODIFIED),
- "Last modified", 200));
- Table table = directoryDisplayViewer.configureDefaultTable(colDefs);
- table.setLayoutData(EclipseUiUtils.fillAll());
-
- table.addKeyListener(new KeyListener() {
- private static final long serialVersionUID = -8083424284436715709L;
-
- @Override
- public void keyReleased(KeyEvent e) {
- }
-
- @Override
- public void keyPressed(KeyEvent e) {
- log.debug("Key event received: " + e.keyCode);
- IStructuredSelection selection = (IStructuredSelection) directoryDisplayViewer.getSelection();
- Path selected = null;
- if (!selection.isEmpty())
- selected = ((Path) selection.getFirstElement());
- if (e.keyCode == SWT.CR) {
- if (!Files.isDirectory(selected))
- return;
- if (selected != null) {
- currSelected = selected;
- directoryDisplayViewer.setInput(currSelected, "*");
- }
- } else if (e.keyCode == SWT.BS) {
- currSelected = currSelected.getParent();
- directoryDisplayViewer.setInput(currSelected, "*");
- directoryDisplayViewer.getTable().setFocus();
- }
- }
- });
-
-// directoryDisplayViewer.addDoubleClickListener(new IDoubleClickListener() {
-// @Override
-// public void doubleClick(DoubleClickEvent event) {
-// IStructuredSelection selection = (IStructuredSelection) directoryDisplayViewer.getSelection();
-// Path selected = null;
-// if (!selection.isEmpty()) {
-// Object obj = selection.getFirstElement();
-// if (obj instanceof Path)
-// selected = (Path) obj;
-// else if (obj instanceof ParentDir)
-// selected = ((ParentDir) obj).getPath();
-// }
-// if (selected != null) {
-// if (!Files.isDirectory(selected))
-// return;
-// currSelected = selected;
-// directoryDisplayViewer.setInput(currSelected, "*");
-// }
-// }
-// });
-
- directoryDisplayViewer.addDoubleClickListener(new IDoubleClickListener() {
- @Override
- public void doubleClick(DoubleClickEvent event) {
- IStructuredSelection selection = (IStructuredSelection) directoryDisplayViewer.getSelection();
- Path selected = null;
- if (!selection.isEmpty()) {
- Object obj = selection.getFirstElement();
- if (obj instanceof Path)
- selected = (Path) obj;
- else if (obj instanceof ParentDir)
- selected = ((ParentDir) obj).getPath();
- }
- if (selected != null) {
- if (!Files.isDirectory(selected))
- return;
- currSelected = selected;
- directoryDisplayViewer.setInput(currSelected, "*");
- }
- }
- });
- }
-}
+++ /dev/null
-package org.argeo.eclipse.ui.fs;
-
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.ArrayList;
-import java.util.List;
-
-import org.argeo.api.cms.CmsLog;
-import org.argeo.eclipse.ui.ColumnDefinition;
-import org.argeo.eclipse.ui.EclipseUiUtils;
-import org.eclipse.jface.viewers.ISelectionChangedListener;
-import org.eclipse.jface.viewers.IStructuredSelection;
-import org.eclipse.jface.viewers.SelectionChangedEvent;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.custom.SashForm;
-import org.eclipse.swt.events.KeyEvent;
-import org.eclipse.swt.events.KeyListener;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Table;
-import org.eclipse.swt.widgets.Tree;
-
-/** A simple Java 7 nio files browser with a tree */
-public class SimpleFsTreeBrowser extends Composite {
- private final static CmsLog log = CmsLog.getLog(SimpleFsTreeBrowser.class);
- private static final long serialVersionUID = -40347919096946585L;
-
- private Path currSelected;
- private FsTreeViewer treeViewer;
- private FsTableViewer directoryDisplayViewer;
-
- public SimpleFsTreeBrowser(Composite parent, int style) {
- super(parent, style);
- createContent(this);
- // parent.layout(true, true);
- }
-
- private void createContent(Composite parent) {
- parent.setLayout(EclipseUiUtils.noSpaceGridLayout());
- SashForm form = new SashForm(parent, SWT.HORIZONTAL);
- Composite child1 = new Composite(form, SWT.NONE);
- populateTree(child1);
- Composite child2 = new Composite(form, SWT.BORDER);
- populateDisplay(child2);
- form.setLayoutData(EclipseUiUtils.fillAll());
- form.setWeights(new int[] { 1, 3 });
- }
-
- public void setInput(Path... paths) {
- treeViewer.setPathsInput(paths);
- treeViewer.getControl().getParent().layout(true, true);
- }
-
- private void populateTree(final Composite parent) {
- // GridLayout layout = EclipseUiUtils.noSpaceGridLayout();
- // layout.verticalSpacing = 5;
- parent.setLayout(new GridLayout());
-
- ISelectionChangedListener selList = new MySelectionChangedListener();
-
- treeViewer = new FsTreeViewer(parent, SWT.MULTI);
- Tree tree = treeViewer.configureDefaultSingleColumnTable(500);
- GridData gd = EclipseUiUtils.fillAll();
- // gd.horizontalIndent = 10;
- tree.setLayoutData(gd);
- treeViewer.addSelectionChangedListener(selList);
- }
-
- private class MySelectionChangedListener implements ISelectionChangedListener {
- @Override
- public void selectionChanged(SelectionChangedEvent event) {
- IStructuredSelection selection = (IStructuredSelection) treeViewer.getSelection();
- if (selection.isEmpty())
- return;
- else {
- Path newSelected = (Path) selection.getFirstElement();
- if (newSelected.equals(currSelected))
- return;
- currSelected = newSelected;
- if (Files.isDirectory(currSelected))
- directoryDisplayViewer.setInput(currSelected, "*");
- }
- }
- }
-
- private void populateDisplay(final Composite parent) {
- parent.setLayout(EclipseUiUtils.noSpaceGridLayout());
- directoryDisplayViewer = new FsTableViewer(parent, SWT.MULTI);
- List<ColumnDefinition> colDefs = new ArrayList<>();
- colDefs.add(new ColumnDefinition(new FileIconNameLabelProvider(), "Name", 200, 200));
- colDefs.add(new ColumnDefinition(new NioFileLabelProvider(FsUiConstants.PROPERTY_SIZE), "Size", 100, 100));
- colDefs.add(new ColumnDefinition(new NioFileLabelProvider(FsUiConstants.PROPERTY_TYPE), "Type", 300, 300));
- colDefs.add(new ColumnDefinition(new NioFileLabelProvider(FsUiConstants.PROPERTY_LAST_MODIFIED),
- "Last modified", 100, 100));
- Table table = directoryDisplayViewer.configureDefaultTable(colDefs);
- table.setLayoutData(EclipseUiUtils.fillAll());
-
- table.addKeyListener(new KeyListener() {
- private static final long serialVersionUID = -8083424284436715709L;
-
- @Override
- public void keyReleased(KeyEvent e) {
- }
-
- @Override
- public void keyPressed(KeyEvent e) {
- log.debug("Key event received: " + e.keyCode);
- IStructuredSelection selection = (IStructuredSelection) directoryDisplayViewer.getSelection();
- Path selected = null;
- if (!selection.isEmpty())
- selected = ((Path) selection.getFirstElement());
- if (e.keyCode == SWT.CR) {
- if (!Files.isDirectory(selected))
- return;
- if (selected != null) {
- currSelected = selected;
- directoryDisplayViewer.setInput(currSelected, "*");
- }
- } else if (e.keyCode == SWT.BS) {
- currSelected = currSelected.getParent();
- directoryDisplayViewer.setInput(currSelected, "*");
- directoryDisplayViewer.getTable().setFocus();
- }
- }
- });
- }
-}
+++ /dev/null
-/** Generic SWT/JFace file system utilities. */
-package org.argeo.eclipse.ui.fs;
\ No newline at end of file
+++ /dev/null
-/** Generic SWT/JFace utilities. */
-package org.argeo.eclipse.ui;
\ No newline at end of file
+++ /dev/null
-package org.argeo.eclipse.ui.parts;
-
-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.argeo.eclipse.ui.util.ViewerUtils;
-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
-/** Generic SWT/JFace composites. */
-package org.argeo.eclipse.ui.parts;
\ No newline at end of file
+++ /dev/null
-package org.argeo.eclipse.ui.util;
-
-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
-/** Generic SWT/JFace JCR helpers. */
-package org.argeo.eclipse.ui.util;
\ 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-11"/>
- <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.cms.ui</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
-/MANIFEST.MF
+++ /dev/null
-Bundle-Activator: org.argeo.cms.ui.internal.Activator
-Bundle-ActivationPolicy: lazy
-
-Import-Package: org.eclipse.swt,\
-org.eclipse.jface.window,\
-org.eclipse.core.commands,\
-javax.jcr.security,\
-org.eclipse.rap.fileupload;version="[2.1,4)",\
-org.eclipse.rap.rwt;version="[2.1,4)",\
-org.eclipse.rap.rwt.application;version="[2.1,4)",\
-org.eclipse.rap.rwt.client;version="[2.1,4)",\
-org.eclipse.rap.rwt.client.service;version="[2.1,4)",\
-org.eclipse.rap.rwt.service;version="[2.1,4)",\
-org.eclipse.rap.rwt.widgets;version="[2.1,4)",\
-org.osgi.*;version=0.0.0,\
-*
-
-## TODO: in order to enable single sourcing, we have introduced dummy RAP packages
-# in the RCP specific bundle org.argeo.eclipse.ui.rap.
-# this was working with RAP 2.x but since we upgrade Rap to 3.1.x,
-# there is a version range issue cause org.argeo.eclipse.ui.rap bundle is still in 2.x
-# We enable large RAP version range as a workaround that must be fixed
\ No newline at end of file
+++ /dev/null
-source.. = src/
-output.. = bin/
-bin.includes = META-INF/,\
- .,\
- icons/
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
- <modelVersion>4.0.0</modelVersion>
- <parent>
- <groupId>org.argeo.commons</groupId>
- <artifactId>argeo-commons</artifactId>
- <version>2.3-SNAPSHOT</version>
- <relativePath>..</relativePath>
- </parent>
- <artifactId>org.argeo.cms.ui</artifactId>
- <name>CMS UI</name>
- <packaging>jar</packaging>
- <dependencies>
- <dependency>
- <groupId>org.argeo.commons</groupId>
- <artifactId>org.argeo.cms</artifactId>
- <version>2.3-SNAPSHOT</version>
- </dependency>
- <dependency>
- <groupId>org.argeo.commons</groupId>
- <artifactId>org.argeo.cms.swt</artifactId>
- <version>2.3-SNAPSHOT</version>
- </dependency>
- <dependency>
- <groupId>org.argeo.commons</groupId>
- <artifactId>org.argeo.cms.jcr</artifactId>
- <version>2.3-SNAPSHOT</version>
- </dependency>
-
- <!-- Specific -->
- <dependency>
- <groupId>org.argeo.commons.rap</groupId>
- <artifactId>org.argeo.swt.specific.rap</artifactId>
- <version>2.3-SNAPSHOT</version>
- <scope>provided</scope>
- </dependency>
-
- <!-- UI -->
- <dependency>
- <groupId>org.argeo.tp.rap.e4</groupId>
- <artifactId>org.eclipse.rap.rwt</artifactId>
- <scope>provided</scope>
- </dependency>
- <dependency>
- <groupId>org.argeo.tp.rap.e4</groupId>
- <artifactId>org.eclipse.core.commands</artifactId>
- <scope>provided</scope>
- </dependency>
- <dependency>
- <groupId>org.argeo.tp.rap.e4</groupId>
- <artifactId>org.eclipse.rap.jface</artifactId>
- <scope>provided</scope>
- </dependency>
-
- <!-- TODO move it to specific -->
- <dependency>
- <groupId>org.argeo.tp.rap.e4</groupId>
- <artifactId>org.eclipse.rap.filedialog</artifactId>
- <scope>provided</scope>
- </dependency>
- <dependency>
- <groupId>org.argeo.tp.rap.e4</groupId>
- <artifactId>org.eclipse.rap.fileupload</artifactId>
- <scope>provided</scope>
- </dependency>
-
- </dependencies>
-</project>
\ No newline at end of file
+++ /dev/null
-package org.argeo.cms.ui;
-
-import java.util.EventObject;
-
-/** Notify of the edition lifecycle */
-public class CmsEditionEvent extends EventObject {
- private static final long serialVersionUID = 950914736016693110L;
-
- public final static Integer START_EDITING = 0;
- public final static Integer STOP_EDITING = 1;
-
- private final Integer type;
-
- public CmsEditionEvent(Object source, Integer type) {
- super(source);
- this.type = type;
- }
-
- public Integer getType() {
- return type;
- }
-
-}
+++ /dev/null
-package org.argeo.cms.ui;
-
-import org.argeo.api.cms.Cms2DSize;
-
-/** Commons constants */
-@Deprecated
-public interface CmsUiConstants {
- // DATAKEYS
-// public final static String STYLE = EclipseUiConstants.CSS_CLASS;
-// public final static String MARKUP = EclipseUiConstants.MARKUP_SUPPORT;
- @Deprecated
- /* RWT.CUSTOM_ITEM_HEIGHT */
- public final static String ITEM_HEIGHT = "org.eclipse.rap.rwt.customItemHeight";
-
- // EVENT DETAILS
- @Deprecated
- /* RWT.HYPERLINK */
- public final static int HYPERLINK = 1 << 26;
-
- // STANDARD RESOURCES
- public final static String LOADING_IMAGE = "icons/loading.gif";
-
- public final static String NO_IMAGE = "icons/noPic-square-640px.png";
- public final static Cms2DSize NO_IMAGE_SIZE = new Cms2DSize(320, 320);
- public final static Float NO_IMAGE_RATIO = 1f;
- // MISCEALLENEOUS
- String DATE_TIME_FORMAT = "dd/MM/yyyy, HH:mm";
-}
+++ /dev/null
-package org.argeo.cms.ui;
-
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-
-import org.argeo.api.cms.MvcProvider;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-
-/** Stateless factory building an SWT user interface given a JCR context. */
-@FunctionalInterface
-public interface CmsUiProvider extends MvcProvider<Composite, Node, Control> {
- /**
- * Initialises a user interface.
- *
- * @param parent the parent composite
- * @param context a context node (holding the JCR underlying session), or null
- */
- Control createUi(Composite parent, Node context) throws RepositoryException;
-
- @Override
- default Control createUiPart(Composite parent, Node context) {
- try {
- return createUi(parent, context);
- } catch (RepositoryException e) {
- throw new IllegalStateException("Cannot create UI for context " + context, e);
- }
- }
-
-}
+++ /dev/null
-package org.argeo.cms.ui;
-
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-
-/** CmsUiProvider notified of initialisation with a system session. */
-public interface LifeCycleUiProvider extends CmsUiProvider {
- public void init(Session adminSession) throws RepositoryException;
-
- public void destroy();
-}
+++ /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.core.runtime.ListenerList;
-import org.eclipse.jface.dialogs.IPageChangeProvider;
-import org.eclipse.jface.dialogs.IPageChangedListener;
-import org.eclipse.jface.dialogs.PageChangedEvent;
-import org.eclipse.jface.util.SafeRunnable;
-
-/**
- * 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.core.runtime.IProgressMonitor;
-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.forms;
-
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-
-import org.argeo.cms.ui.viewers.EditablePart;
-import org.argeo.eclipse.ui.EclipseUiUtils;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Label;
-import org.eclipse.swt.widgets.Text;
-
-/** Editable String that displays a browsable link when read-only */
-public class EditableLink extends EditablePropertyString implements
- EditablePart {
- private static final long serialVersionUID = 5055000749992803591L;
-
- private String type;
- private String message;
- private boolean readOnly;
-
- public EditableLink(Composite parent, int style, Node node,
- String propertyName, String type, String message)
- throws RepositoryException {
- super(parent, style, node, propertyName, message);
- this.message = message;
- this.type = type;
-
- readOnly = SWT.READ_ONLY == (style & SWT.READ_ONLY);
- if (node.hasProperty(propertyName)) {
- this.setStyle(FormStyle.propertyText.style());
- this.setText(node.getProperty(propertyName).getString());
- } else {
- this.setStyle(FormStyle.propertyMessage.style());
- this.setText("");
- }
- }
-
- public void setText(String text) {
- Control child = getControl();
- if (child instanceof Label) {
- Label lbl = (Label) child;
- if (EclipseUiUtils.isEmpty(text))
- lbl.setText(message);
- else if (readOnly)
- setLinkValue(lbl, text);
- else
- // if canEdit() we put only the value with no link
- // to avoid glitches of the edition life cycle
- lbl.setText(text);
- } else if (child instanceof Text) {
- Text txt = (Text) child;
- if (EclipseUiUtils.isEmpty(text)) {
- txt.setText("");
- txt.setMessage(message);
- } else
- txt.setText(text);
- }
- }
-
- private void setLinkValue(Label lbl, String text) {
- if (FormStyle.email.style().equals(type))
- lbl.setText(FormUtils.getMailLink(text));
- else if (FormStyle.phone.style().equals(type))
- lbl.setText(FormUtils.getPhoneLink(text));
- else if (FormStyle.website.style().equals(type))
- lbl.setText(FormUtils.getUrlLink(text));
- else if (FormStyle.facebook.style().equals(type)
- || FormStyle.instagram.style().equals(type)
- || FormStyle.linkedIn.style().equals(type)
- || FormStyle.twitter.style().equals(type))
- lbl.setText(FormUtils.getUrlLink(text));
- }
-}
\ No newline at end of file
+++ /dev/null
-package org.argeo.cms.ui.forms;
-
-import java.util.List;
-
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.cms.ui.viewers.EditablePart;
-import org.argeo.cms.ui.widgets.StyledControl;
-import org.argeo.eclipse.ui.EclipseUiUtils;
-import org.eclipse.jface.dialogs.MessageDialog;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.events.SelectionListener;
-import org.eclipse.swt.events.TraverseEvent;
-import org.eclipse.swt.events.TraverseListener;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.layout.RowLayout;
-import org.eclipse.swt.widgets.Button;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Label;
-import org.eclipse.swt.widgets.Text;
-
-/** Display, add or remove values from a list in a CMS context */
-public class EditableMultiStringProperty extends StyledControl implements EditablePart {
- private static final long serialVersionUID = -7044614381252178595L;
-
- private String propertyName;
- private String message;
- // TODO implement the ability to provide a list of possible values
-// private String[] possibleValues;
- private boolean canEdit;
- private SelectionListener removeValueSL;
- private List<String> values;
-
- // TODO manage within the CSS
- private int rowSpacing = 5;
- private int rowMarging = 0;
- private int oneValueMargingRight = 5;
- private int btnWidth = 16;
- private int btnHeight = 16;
- private int btnHorizontalIndent = 3;
-
- public EditableMultiStringProperty(Composite parent, int style, Node node, String propertyName, List<String> values,
- String[] possibleValues, String addValueMsg, SelectionListener removeValueSelectionListener)
- throws RepositoryException {
- super(parent, style, node, true);
-
- this.propertyName = propertyName;
- this.values = values;
-// this.possibleValues = new String[] { "Un", "Deux", "Trois" };
- this.message = addValueMsg;
- this.canEdit = removeValueSelectionListener != null;
- this.removeValueSL = removeValueSelectionListener;
- }
-
- public List<String> getValues() {
- return values;
- }
-
- public void setValues(List<String> values) {
- this.values = values;
- }
-
- // Row layout items do not need explicit layout data
- protected void setControlLayoutData(Control control) {
- }
-
- /** To be overridden */
- protected void setContainerLayoutData(Composite composite) {
- composite.setLayoutData(CmsSwtUtils.fillWidth());
- }
-
- @Override
- public Control getControl() {
- return super.getControl();
- }
-
- @Override
- protected Control createControl(Composite box, String style) {
- Composite row = new Composite(box, SWT.NO_FOCUS);
- row.setLayoutData(EclipseUiUtils.fillAll());
-
- RowLayout rl = new RowLayout(SWT.HORIZONTAL);
- rl.wrap = true;
- rl.spacing = rowSpacing;
- rl.marginRight = rl.marginLeft = rl.marginBottom = rl.marginTop = rowMarging;
- row.setLayout(rl);
-
- if (values != null) {
- for (final String value : values) {
- if (canEdit)
- createRemovableValue(row, SWT.SINGLE, value);
- else
- createValueLabel(row, SWT.SINGLE, value);
- }
- }
-
- if (!canEdit)
- return row;
- else if (isEditing())
- return createText(row, style);
- else
- return createLabel(row, style);
- }
-
- /**
- * Override to provide specific layout for the existing values, typically adding
- * a pound (#) char for tags or anchor info for browsable links. We assume the
- * parent composite already has a layout and it is the caller responsibility to
- * apply corresponding layout data
- */
- protected Label createValueLabel(Composite parent, int style, String value) {
- Label label = new Label(parent, style);
- label.setText("#" + value);
- CmsSwtUtils.markup(label);
- CmsSwtUtils.style(label, FormStyle.propertyText.style());
- return label;
- }
-
- private Composite createRemovableValue(Composite parent, int style, String value) {
- Composite valCmp = new Composite(parent, SWT.NO_FOCUS);
- GridLayout gl = EclipseUiUtils.noSpaceGridLayout(new GridLayout(2, false));
- gl.marginRight = oneValueMargingRight;
- valCmp.setLayout(gl);
-
- createValueLabel(valCmp, SWT.WRAP, value);
-
- Button deleteBtn = new Button(valCmp, SWT.FLAT);
- deleteBtn.setData(FormConstants.LINKED_VALUE, value);
- deleteBtn.addSelectionListener(removeValueSL);
- CmsSwtUtils.style(deleteBtn, FormStyle.delete.style() + FormStyle.BUTTON_SUFFIX);
- GridData gd = new GridData();
- gd.heightHint = btnHeight;
- gd.widthHint = btnWidth;
- gd.horizontalIndent = btnHorizontalIndent;
- deleteBtn.setLayoutData(gd);
-
- return valCmp;
- }
-
- protected Text createText(Composite box, String style) {
- final Text text = new Text(box, getStyle());
- // The "add new value" text is not meant to change, so we can set it on
- // creation
- text.setMessage(message);
- CmsSwtUtils.style(text, style);
- text.setFocus();
-
- text.addTraverseListener(new TraverseListener() {
- private static final long serialVersionUID = 1L;
-
- public void keyTraversed(TraverseEvent e) {
- if (e.keyCode == SWT.CR) {
- addValue(text);
- e.doit = false;
- }
- }
- });
-
- // The OK button does not work with the focusOut listener
- // because focus out is called before the OK button is pressed
-
- // // we must call layout() now so that the row data can compute the
- // height
- // // of the other controls.
- // text.getParent().layout();
- // int height = text.getSize().y;
- //
- // Button okBtn = new Button(box, SWT.BORDER | SWT.PUSH | SWT.BOTTOM);
- // okBtn.setText("OK");
- // RowData rd = new RowData(SWT.DEFAULT, height - 2);
- // okBtn.setLayoutData(rd);
- //
- // okBtn.addSelectionListener(new SelectionAdapter() {
- // private static final long serialVersionUID = 2780819012423622369L;
- //
- // @Override
- // public void widgetSelected(SelectionEvent e) {
- // addValue(text);
- // }
- // });
-
- return text;
- }
-
- /** Performs the real addition, overwrite to make further sanity checks */
- protected void addValue(Text text) {
- String value = text.getText();
- String errMsg = null;
-
- if (EclipseUiUtils.isEmpty(value))
- return;
-
- if (values.contains(value))
- errMsg = "Dupplicated value: " + value + ", please correct and try again";
- if (errMsg != null)
- MessageDialog.openError(this.getShell(), "Addition not allowed", errMsg);
- else {
- values.add(value);
- Composite newCmp = createRemovableValue(text.getParent(), SWT.SINGLE, value);
- newCmp.moveAbove(text);
- text.setText("");
- newCmp.getParent().layout();
- }
- }
-
- protected Label createLabel(Composite box, String style) {
- if (canEdit) {
- Label lbl = new Label(box, getStyle());
- lbl.setText(message);
- CmsSwtUtils.style(lbl, style);
- CmsSwtUtils.markup(lbl);
- if (mouseListener != null)
- lbl.addMouseListener(mouseListener);
- return lbl;
- }
- return null;
- }
-
- protected void clear(boolean deep) {
- Control child = getControl();
- if (deep)
- super.clear(deep);
- else {
- child.getParent().dispose();
- }
- }
-
- public void setText(String text) {
- Control child = getControl();
- if (child instanceof Label) {
- Label lbl = (Label) child;
- if (canEdit)
- lbl.setText(text);
- else
- lbl.setText("");
- } else if (child instanceof Text) {
- Text txt = (Text) child;
- txt.setText(text);
- }
- }
-
- public synchronized void startEditing() {
- CmsSwtUtils.style(getControl(), FormStyle.propertyText.style());
-// getControl().setData(STYLE, FormStyle.propertyText.style());
- super.startEditing();
- }
-
- public synchronized void stopEditing() {
- CmsSwtUtils.style(getControl(), FormStyle.propertyMessage.style());
-// getControl().setData(STYLE, FormStyle.propertyMessage.style());
- super.stopEditing();
- }
-
- public String getPropertyName() {
- return propertyName;
- }
-}
\ No newline at end of file
+++ /dev/null
-package org.argeo.cms.ui.forms;
-
-import java.text.DateFormat;
-import java.util.Calendar;
-import java.util.GregorianCalendar;
-
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.cms.ui.viewers.EditablePart;
-import org.argeo.cms.ui.widgets.StyledControl;
-import org.argeo.eclipse.ui.EclipseUiUtils;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.events.MouseEvent;
-import org.eclipse.swt.events.MouseListener;
-import org.eclipse.swt.events.SelectionAdapter;
-import org.eclipse.swt.events.SelectionEvent;
-import org.eclipse.swt.events.ShellAdapter;
-import org.eclipse.swt.events.ShellEvent;
-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.DateTime;
-import org.eclipse.swt.widgets.Label;
-import org.eclipse.swt.widgets.Shell;
-import org.eclipse.swt.widgets.Text;
-
-/** CMS form part to display and edit a date */
-public class EditablePropertyDate extends StyledControl implements EditablePart {
- private static final long serialVersionUID = 2500215515778162468L;
-
- // Context
- private String propertyName;
- private String message;
- private DateFormat dateFormat;
-
- // UI Objects
- private Text dateTxt;
- private Button openCalBtn;
-
- // TODO manage within the CSS
- private int fieldBtnSpacing = 5;
-
- /**
- *
- * @param parent
- * @param style
- * @param node
- * @param propertyName
- * @param message
- * @param dateFormat provide a {@link DateFormat} as contract to be able to
- * read/write dates as strings
- * @throws RepositoryException
- */
- public EditablePropertyDate(Composite parent, int style, Node node, String propertyName, String message,
- DateFormat dateFormat) throws RepositoryException {
- super(parent, style, node, false);
-
- this.propertyName = propertyName;
- this.message = message;
- this.dateFormat = dateFormat;
-
- if (node.hasProperty(propertyName)) {
- this.setStyle(FormStyle.propertyText.style());
- this.setText(dateFormat.format(node.getProperty(propertyName).getDate().getTime()));
- } else {
- this.setStyle(FormStyle.propertyMessage.style());
- this.setText(message);
- }
- }
-
- public void setText(String text) {
- Control child = getControl();
- if (child instanceof Label) {
- Label lbl = (Label) child;
- if (EclipseUiUtils.isEmpty(text))
- lbl.setText(message);
- else
- lbl.setText(text);
- } else if (child instanceof Text) {
- Text txt = (Text) child;
- if (EclipseUiUtils.isEmpty(text)) {
- txt.setText("");
- } else
- txt.setText(text);
- }
- }
-
- public synchronized void startEditing() {
- // if (dateTxt != null && !dateTxt.isDisposed())
- CmsSwtUtils.style(getControl(), FormStyle.propertyText);
-// getControl().setData(STYLE, FormStyle.propertyText.style());
- super.startEditing();
- }
-
- public synchronized void stopEditing() {
- if (EclipseUiUtils.isEmpty(dateTxt.getText()))
- CmsSwtUtils.style(getControl(), FormStyle.propertyMessage);
-// getControl().setData(STYLE, FormStyle.propertyMessage.style());
- else
- CmsSwtUtils.style(getControl(), FormStyle.propertyText);
-// getControl().setData(STYLE, FormStyle.propertyText.style());
- super.stopEditing();
- }
-
- public String getPropertyName() {
- return propertyName;
- }
-
- @Override
- protected Control createControl(Composite box, String style) {
- if (isEditing()) {
- return createCustomEditableControl(box, style);
- } else
- return createLabel(box, style);
- }
-
- protected Label createLabel(Composite box, String style) {
- Label lbl = new Label(box, getStyle() | SWT.WRAP);
- lbl.setLayoutData(CmsSwtUtils.fillWidth());
- CmsSwtUtils.style(lbl, style);
- CmsSwtUtils.markup(lbl);
- if (mouseListener != null)
- lbl.addMouseListener(mouseListener);
- return lbl;
- }
-
- private Control createCustomEditableControl(Composite box, String style) {
- box.setLayoutData(CmsSwtUtils.fillWidth());
- Composite dateComposite = new Composite(box, SWT.NONE);
- GridLayout gl = EclipseUiUtils.noSpaceGridLayout(new GridLayout(2, false));
- gl.horizontalSpacing = fieldBtnSpacing;
- dateComposite.setLayout(gl);
- dateTxt = new Text(dateComposite, SWT.BORDER);
- CmsSwtUtils.style(dateTxt, style);
- dateTxt.setLayoutData(new GridData(120, SWT.DEFAULT));
- dateTxt.setToolTipText(
- "Enter a date with form \"" + FormUtils.DEFAULT_SHORT_DATE_FORMAT + "\" or use the calendar");
- openCalBtn = new Button(dateComposite, SWT.FLAT);
- CmsSwtUtils.style(openCalBtn, FormStyle.calendar.style() + FormStyle.BUTTON_SUFFIX);
- GridData gd = new GridData(SWT.CENTER, SWT.CENTER, false, false);
- gd.heightHint = 17;
- openCalBtn.setLayoutData(gd);
- // openCalBtn.setImage(PeopleRapImages.CALENDAR_BTN);
-
- openCalBtn.addSelectionListener(new SelectionAdapter() {
- private static final long serialVersionUID = 1L;
-
- public void widgetSelected(SelectionEvent event) {
- CalendarPopup popup = new CalendarPopup(dateTxt);
- popup.open();
- }
- });
-
- // dateTxt.addFocusListener(new FocusListener() {
- // private static final long serialVersionUID = 1L;
- //
- // @Override
- // public void focusLost(FocusEvent event) {
- // String newVal = dateTxt.getText();
- // // Enable reset of the field
- // if (FormUtils.notNull(newVal))
- // calendar = null;
- // else {
- // try {
- // Calendar newCal = parseDate(newVal);
- // // DateText.this.setText(newCal);
- // calendar = newCal;
- // } catch (ParseException pe) {
- // // Silent. Manage error popup?
- // if (calendar != null)
- // EditablePropertyDate.this.setText(calendar);
- // }
- // }
- // }
- //
- // @Override
- // public void focusGained(FocusEvent event) {
- // }
- // });
- return dateTxt;
- }
-
- protected void clear(boolean deep) {
- Control child = getControl();
- if (deep || child instanceof Label)
- super.clear(deep);
- else {
- child.getParent().dispose();
- }
- }
-
- /** Enable setting a custom tooltip on the underlying text */
- @Deprecated
- public void setToolTipText(String toolTipText) {
- dateTxt.setToolTipText(toolTipText);
- }
-
- @Deprecated
- /** Enable setting a custom message on the underlying text */
- public void setMessage(String message) {
- dateTxt.setMessage(message);
- }
-
- @Deprecated
- public void setText(Calendar cal) {
- String newValueStr = "";
- if (cal != null)
- newValueStr = dateFormat.format(cal.getTime());
- if (!newValueStr.equals(dateTxt.getText()))
- dateTxt.setText(newValueStr);
- }
-
- // UTILITIES TO MANAGE THE CALENDAR POPUP
- // TODO manage the popup shell in a cleaner way
- private class CalendarPopup extends Shell {
- private static final long serialVersionUID = 1L;
- private DateTime dateTimeCtl;
-
- public CalendarPopup(Control source) {
- super(source.getDisplay(), SWT.NO_TRIM | SWT.BORDER | SWT.ON_TOP);
- populate();
- // Add border and shadow style
- CmsSwtUtils.markup(CalendarPopup.this);
- CmsSwtUtils.style(CalendarPopup.this, FormStyle.popupCalendar.style());
- pack();
- layout();
- setLocation(source.toDisplay((source.getLocation().x - 2), (source.getSize().y) + 3));
-
- addShellListener(new ShellAdapter() {
- private static final long serialVersionUID = 5178980294808435833L;
-
- @Override
- public void shellDeactivated(ShellEvent e) {
- close();
- dispose();
- }
- });
- open();
- }
-
- private void setProperty() {
- // Direct set does not seems to work. investigate
- // cal.set(dateTimeCtl.getYear(), dateTimeCtl.getMonth(),
- // dateTimeCtl.getDay(), 12, 0);
- Calendar cal = new GregorianCalendar();
- cal.set(Calendar.YEAR, dateTimeCtl.getYear());
- cal.set(Calendar.MONTH, dateTimeCtl.getMonth());
- cal.set(Calendar.DAY_OF_MONTH, dateTimeCtl.getDay());
- String dateStr = dateFormat.format(cal.getTime());
- dateTxt.setText(dateStr);
- }
-
- protected void populate() {
- setLayout(EclipseUiUtils.noSpaceGridLayout());
-
- dateTimeCtl = new DateTime(this, SWT.CALENDAR);
- dateTimeCtl.setLayoutData(EclipseUiUtils.fillAll());
-
- Calendar calendar = FormUtils.parseDate(dateFormat, dateTxt.getText());
-
- if (calendar != null)
- dateTimeCtl.setDate(calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH),
- calendar.get(Calendar.DAY_OF_MONTH));
-
- dateTimeCtl.addSelectionListener(new SelectionAdapter() {
- private static final long serialVersionUID = -8414377364434281112L;
-
- @Override
- public void widgetSelected(SelectionEvent e) {
- setProperty();
- }
- });
-
- dateTimeCtl.addMouseListener(new MouseListener() {
- private static final long serialVersionUID = 1L;
-
- @Override
- public void mouseUp(MouseEvent e) {
- }
-
- @Override
- public void mouseDown(MouseEvent e) {
- }
-
- @Override
- public void mouseDoubleClick(MouseEvent e) {
- setProperty();
- close();
- dispose();
- }
- });
- }
- }
-}
\ No newline at end of file
+++ /dev/null
-package org.argeo.cms.ui.forms;
-
-import static org.argeo.cms.ui.forms.FormStyle.propertyMessage;
-import static org.argeo.cms.ui.forms.FormStyle.propertyText;
-
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.cms.ui.viewers.EditablePart;
-import org.argeo.cms.ui.widgets.EditableText;
-import org.argeo.eclipse.ui.EclipseUiUtils;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Label;
-import org.eclipse.swt.widgets.Text;
-
-/** Editable String in a CMS context */
-public class EditablePropertyString extends EditableText implements EditablePart {
- private static final long serialVersionUID = 5055000749992803591L;
-
- private String propertyName;
- private String message;
-
- // encode the '&' character in rap
- private final static String AMPERSAND = "&";
- private final static String AMPERSAND_REGEX = "&(?![#a-zA-Z0-9]+;)";
-
- public EditablePropertyString(Composite parent, int style, Node node, String propertyName, String message)
- throws RepositoryException {
- super(parent, style, node, true);
- //setUseTextAsLabel(true);
- this.propertyName = propertyName;
- this.message = message;
-
- if (node.hasProperty(propertyName)) {
- this.setStyle(propertyText.style());
- this.setText(node.getProperty(propertyName).getString());
- } else {
- this.setStyle(propertyMessage.style());
- this.setText(message + " ");
- }
- }
-
- public void setText(String text) {
- Control child = getControl();
- if (child instanceof Label) {
- Label lbl = (Label) child;
- if (EclipseUiUtils.isEmpty(text))
- lbl.setText(message + " ");
- else
- // TODO enhance this
- lbl.setText(text.replaceAll(AMPERSAND_REGEX, AMPERSAND));
- } else if (child instanceof Text) {
- Text txt = (Text) child;
- if (EclipseUiUtils.isEmpty(text)) {
- txt.setText("");
- txt.setMessage(message + " ");
- } else
- txt.setText(text.replaceAll("<br/>", "\n"));
- }
- }
-
- public synchronized void startEditing() {
- CmsSwtUtils.style(getControl(), FormStyle.propertyText);
- super.startEditing();
- }
-
- public synchronized void stopEditing() {
- if (EclipseUiUtils.isEmpty(((Text) getControl()).getText()))
- CmsSwtUtils.style(getControl(), FormStyle.propertyMessage);
- else
- CmsSwtUtils.style(getControl(), FormStyle.propertyText);
- super.stopEditing();
- }
-
- public String getPropertyName() {
- return propertyName;
- }
-}
\ No newline at end of file
+++ /dev/null
-package org.argeo.cms.ui.forms;
-
-/** Constants used in the various CMS Forms */
-public interface FormConstants {
- // DATAKEYS
- public final static String LINKED_VALUE = "LinkedValue";
-}
+++ /dev/null
-package org.argeo.cms.ui.forms;
-
-import java.util.Observable;
-import java.util.Observer;
-
-import javax.jcr.Node;
-
-import org.argeo.api.cms.CmsEditable;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.events.SelectionEvent;
-import org.eclipse.swt.events.SelectionListener;
-import org.eclipse.swt.widgets.Button;
-import org.eclipse.swt.widgets.Composite;
-
-/** Add life cycle management abilities to an editable form page */
-public class FormEditorHeader implements SelectionListener, Observer {
- private static final long serialVersionUID = 7392898696542484282L;
-
- // private final Node context;
- private final CmsEditable cmsEditable;
- private Button publishBtn;
-
- // Should we provide here the ability to switch from read only to edition
- // mode?
- // private Button editBtn;
- // private boolean readOnly;
-
- // TODO add information about the current node status, typically if it is
- // dirty or not
-
- private Composite parent;
- private Composite display;
- private Object layoutData;
-
- public FormEditorHeader(Composite parent, int style, Node context,
- CmsEditable cmsEditable) {
- this.cmsEditable = cmsEditable;
- this.parent = parent;
- // readOnly = SWT.READ_ONLY == (style & SWT.READ_ONLY);
- // this.context = context;
- if (this.cmsEditable instanceof Observable)
- ((Observable) this.cmsEditable).addObserver(this);
- refresh();
- }
-
- public void setLayoutData(Object layoutData) {
- this.layoutData = layoutData;
- if (display != null && !display.isDisposed())
- display.setLayoutData(layoutData);
- }
-
- protected void refresh() {
- if (display != null && !display.isDisposed())
- display.dispose();
-
- display = new Composite(parent, SWT.NONE);
- display.setLayoutData(layoutData);
-
- CmsSwtUtils.style(display, FormStyle.header.style());
- display.setBackgroundMode(SWT.INHERIT_FORCE);
-
- display.setLayout(CmsSwtUtils.noSpaceGridLayout());
-
- publishBtn = createSimpleBtn(display, getPublishButtonLabel());
- display.moveAbove(null);
- parent.layout();
- }
-
- private Button createSimpleBtn(Composite parent, String label) {
- Button button = new Button(parent, SWT.FLAT | SWT.PUSH);
- button.setText(label);
- CmsSwtUtils.style(button, FormStyle.header.style());
- button.addSelectionListener(this);
- return button;
- }
-
- private String getPublishButtonLabel() {
- // Rather check if the current node differs from what has been
- // previously committed
- // For the time being, we always reach here, the underlying CmsEditable
- // is always editing.
- if (cmsEditable.isEditing())
- return " Publish ";
- else
- return " Edit ";
- }
-
- @Override
- public void widgetSelected(SelectionEvent e) {
- if (e.getSource() == publishBtn) {
- // For the time being, the underlying CmsEditable
- // is always editing when we reach this point
- if (cmsEditable.isEditing()) {
- // we always leave the node in a check outed state
- cmsEditable.stopEditing();
- cmsEditable.startEditing();
- } else {
- cmsEditable.startEditing();
- }
- }
- }
-
- @Override
- public void widgetDefaultSelected(SelectionEvent e) {
- }
-
- @Override
- public void update(Observable o, Object arg) {
- if (o == cmsEditable) {
- refresh();
- }
- }
-}
\ No newline at end of file
+++ /dev/null
-package org.argeo.cms.ui.forms;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.text.DateFormat;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Calendar;
-import java.util.List;
-
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.Value;
-import javax.jcr.ValueFormatException;
-
-import org.argeo.api.cms.Cms2DSize;
-import org.argeo.api.cms.CmsEditable;
-import org.argeo.api.cms.CmsImageManager;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.cms.ui.viewers.AbstractPageViewer;
-import org.argeo.cms.ui.viewers.EditablePart;
-import org.argeo.cms.ui.viewers.Section;
-import org.argeo.cms.ui.viewers.SectionPart;
-import org.argeo.cms.ui.widgets.EditableImage;
-import org.argeo.cms.ui.widgets.Img;
-import org.argeo.cms.ui.widgets.StyledControl;
-import org.argeo.eclipse.ui.EclipseUiUtils;
-import org.argeo.jcr.JcrException;
-import org.argeo.jcr.JcrUtils;
-import org.eclipse.jface.dialogs.MessageDialog;
-import org.eclipse.rap.fileupload.FileDetails;
-import org.eclipse.rap.fileupload.FileUploadEvent;
-import org.eclipse.rap.fileupload.FileUploadHandler;
-import org.eclipse.rap.fileupload.FileUploadListener;
-import org.eclipse.rap.fileupload.FileUploadReceiver;
-import org.eclipse.rap.rwt.service.ServerPushSession;
-import org.eclipse.rap.rwt.widgets.FileUpload;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.events.FocusEvent;
-import org.eclipse.swt.events.FocusListener;
-import org.eclipse.swt.events.MouseAdapter;
-import org.eclipse.swt.events.MouseEvent;
-import org.eclipse.swt.events.MouseListener;
-import org.eclipse.swt.events.SelectionAdapter;
-import org.eclipse.swt.events.SelectionEvent;
-import org.eclipse.swt.events.SelectionListener;
-import org.eclipse.swt.graphics.Point;
-import org.eclipse.swt.layout.FormAttachment;
-import org.eclipse.swt.layout.FormData;
-import org.eclipse.swt.layout.FormLayout;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.layout.RowLayout;
-import org.eclipse.swt.widgets.Button;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Label;
-import org.eclipse.swt.widgets.Text;
-
-/** Manage life cycle of a form page that is linked to a given node */
-public class FormPageViewer extends AbstractPageViewer {
- private final static CmsLog log = CmsLog.getLog(FormPageViewer.class);
- private static final long serialVersionUID = 5277789504209413500L;
-
- private final Section mainSection;
-
- // TODO manage within the CSS
- private Integer labelColWidth = null;
- private int rowLayoutHSpacing = 8;
-
- // Context cached in the viewer
- // The reference to translate from text to calendar and reverse
- private DateFormat dateFormat = new SimpleDateFormat(FormUtils.DEFAULT_SHORT_DATE_FORMAT);
- private CmsImageManager<Control, Node> imageManager;
- private FileUploadListener fileUploadListener;
-
- public FormPageViewer(Section mainSection, int style, CmsEditable cmsEditable) throws RepositoryException {
- super(mainSection, style, cmsEditable);
- this.mainSection = mainSection;
-
- if (getCmsEditable().canEdit()) {
- fileUploadListener = new FUL();
- }
- }
-
- @Override
- protected void prepare(EditablePart part, Object caretPosition) {
- if (part instanceof Img) {
- ((Img) part).setFileUploadListener(fileUploadListener);
- }
- }
-
- /** To be overridden.Save the edited part. */
- protected void save(EditablePart part) throws RepositoryException {
- Node node = null;
- if (part instanceof EditableMultiStringProperty) {
- EditableMultiStringProperty ept = (EditableMultiStringProperty) part;
- // SWT : View
- List<String> values = ept.getValues();
- // JCR : Model
- node = ept.getNode();
- String propName = ept.getPropertyName();
- if (values.isEmpty()) {
- if (node.hasProperty(propName))
- node.getProperty(propName).remove();
- } else {
- node.setProperty(propName, values.toArray(new String[0]));
- }
- // => Viewer : Controller
- } else if (part instanceof EditablePropertyString) {
- EditablePropertyString ept = (EditablePropertyString) part;
- // SWT : View
- String txt = ((Text) ept.getControl()).getText();
- // JCR : Model
- node = ept.getNode();
- String propName = ept.getPropertyName();
- if (EclipseUiUtils.isEmpty(txt)) {
- if (node.hasProperty(propName))
- node.getProperty(propName).remove();
- } else {
- setPropertySilently(node, propName, txt);
- // node.setProperty(propName, txt);
- }
- // node.getSession().save();
- // => Viewer : Controller
- } else if (part instanceof EditablePropertyDate) {
- EditablePropertyDate ept = (EditablePropertyDate) part;
- Calendar cal = FormUtils.parseDate(dateFormat, ((Text) ept.getControl()).getText());
- node = ept.getNode();
- String propName = ept.getPropertyName();
- if (cal == null) {
- if (node.hasProperty(propName))
- node.getProperty(propName).remove();
- } else {
- node.setProperty(propName, cal);
- }
- // node.getSession().save();
- // => Viewer : Controller
- }
- // TODO: make this configurable, sometimes we do not want to save the
- // current session at this stage
- if (node != null && node.getSession().hasPendingChanges()) {
- JcrUtils.updateLastModified(node, true);
- node.getSession().save();
- }
- }
-
- @Override
- protected void updateContent(EditablePart part) throws RepositoryException {
- if (part instanceof EditableMultiStringProperty) {
- EditableMultiStringProperty ept = (EditableMultiStringProperty) part;
- // SWT : View
- Node node = ept.getNode();
- String propName = ept.getPropertyName();
- List<String> valStrings = new ArrayList<String>();
- if (node.hasProperty(propName)) {
- Value[] values = node.getProperty(propName).getValues();
- for (Value val : values)
- valStrings.add(val.getString());
- }
- ept.setValues(valStrings);
- } else if (part instanceof EditablePropertyString) {
- // || part instanceof EditableLink
- EditablePropertyString ept = (EditablePropertyString) part;
- // JCR : Model
- Node node = ept.getNode();
- String propName = ept.getPropertyName();
- if (node.hasProperty(propName)) {
- String value = node.getProperty(propName).getString();
- ept.setText(value);
- } else
- ept.setText("");
- // => Viewer : Controller
- } else if (part instanceof EditablePropertyDate) {
- EditablePropertyDate ept = (EditablePropertyDate) part;
- // JCR : Model
- Node node = ept.getNode();
- String propName = ept.getPropertyName();
- if (node.hasProperty(propName))
- ept.setText(dateFormat.format(node.getProperty(propName).getDate().getTime()));
- else
- ept.setText("");
- } else if (part instanceof SectionPart) {
- SectionPart sectionPart = (SectionPart) part;
- Node partNode = sectionPart.getNode();
- // use control AFTER setting style, since it may have been reset
- if (part instanceof EditableImage) {
- EditableImage editableImage = (EditableImage) part;
- imageManager().load(partNode, part.getControl(), editableImage.getPreferredImageSize());
- }
- }
- }
-
- // FILE UPLOAD LISTENER
- protected class FUL implements FileUploadListener {
-
- public FUL() {
- }
-
- public void uploadProgress(FileUploadEvent event) {
- // TODO Monitor upload progress
- }
-
- public void uploadFailed(FileUploadEvent event) {
- throw new IllegalStateException("Upload failed " + event, event.getException());
- }
-
- public void uploadFinished(FileUploadEvent event) {
- for (FileDetails file : event.getFileDetails()) {
- if (log.isDebugEnabled())
- log.debug("Received: " + file.getFileName());
- }
- mainSection.getDisplay().syncExec(new Runnable() {
- @Override
- public void run() {
- saveEdit();
- }
- });
- FileUploadHandler uploadHandler = (FileUploadHandler) event.getSource();
- uploadHandler.dispose();
- }
- }
-
- // FOCUS OUT LISTENER
- protected FocusListener createFocusListener() {
- return new FocusOutListener();
- }
-
- private class FocusOutListener implements FocusListener {
- private static final long serialVersionUID = -6069205786732354186L;
-
- @Override
- public void focusLost(FocusEvent event) {
- saveEdit();
- }
-
- @Override
- public void focusGained(FocusEvent event) {
- // does nothing;
- }
- }
-
- // MOUSE LISTENER
- @Override
- protected MouseListener createMouseListener() {
- return new ML();
- }
-
- private class ML extends MouseAdapter {
- private static final long serialVersionUID = 8526890859876770905L;
-
- @Override
- public void mouseDoubleClick(MouseEvent e) {
- if (e.button == 1) {
- Control source = (Control) e.getSource();
- if (getCmsEditable().canEdit()) {
- if (getCmsEditable().isEditing() && !(getEdited() instanceof Img)) {
- if (source == mainSection)
- return;
- EditablePart part = findDataParent(source);
- upload(part);
- } else {
- getCmsEditable().startEditing();
- }
- }
- }
- }
-
- @Override
- public void mouseDown(MouseEvent e) {
- if (getCmsEditable().isEditing()) {
- if (e.button == 1) {
- Control source = (Control) e.getSource();
- EditablePart composite = findDataParent(source);
- Point point = new Point(e.x, e.y);
- if (!(composite instanceof Img))
- edit(composite, source.toDisplay(point));
- } else if (e.button == 3) {
- // EditablePart composite = findDataParent((Control) e
- // .getSource());
- // if (styledTools != null)
- // styledTools.show(composite, new Point(e.x, e.y));
- }
- }
- }
-
- protected synchronized void upload(EditablePart part) {
- if (part instanceof SectionPart) {
- if (part instanceof Img) {
- if (getEdited() == part)
- return;
- edit(part, null);
- layout(part.getControl());
- }
- }
- }
- }
-
- @Override
- public Control getControl() {
- return mainSection;
- }
-
- protected CmsImageManager<Control, Node> imageManager() {
- if (imageManager == null)
- imageManager = (CmsImageManager<Control, Node>) CmsSwtUtils.getCmsView(mainSection).getImageManager();
- return imageManager;
- }
-
- // LOCAL UI HELPERS
- protected Section createSectionIfNeeded(Composite body, Node node) throws RepositoryException {
- Section section = null;
- if (node != null) {
- section = new Section(body, SWT.NO_FOCUS, node);
- section.setLayoutData(CmsSwtUtils.fillWidth());
- section.setLayout(CmsSwtUtils.noSpaceGridLayout());
- }
- return section;
- }
-
- protected void createSimpleLT(Composite bodyRow, Node node, String propName, String label, String msg)
- throws RepositoryException {
- if (getCmsEditable().canEdit() || node.hasProperty(propName)) {
- createPropertyLbl(bodyRow, label);
- EditablePropertyString eps = new EditablePropertyString(bodyRow, SWT.WRAP | SWT.LEFT, node, propName, msg);
- eps.setMouseListener(getMouseListener());
- eps.setFocusListener(getFocusListener());
- eps.setLayoutData(CmsSwtUtils.fillWidth());
- }
- }
-
- protected void createMultiStringLT(Composite bodyRow, Node node, String propName, String label, String msg)
- throws RepositoryException {
- boolean canEdit = getCmsEditable().canEdit();
- if (canEdit || node.hasProperty(propName)) {
- createPropertyLbl(bodyRow, label);
-
- List<String> valueStrings = new ArrayList<String>();
-
- if (node.hasProperty(propName)) {
- Value[] values = node.getProperty(propName).getValues();
- for (Value value : values)
- valueStrings.add(value.getString());
- }
-
- // TODO use a drop down to display possible values to the end user
- EditableMultiStringProperty emsp = new EditableMultiStringProperty(bodyRow, SWT.SINGLE | SWT.LEAD, node,
- propName, valueStrings, new String[] { "Implement this" }, msg,
- canEdit ? getRemoveValueSelListener() : null);
- addListeners(emsp);
- // emsp.setMouseListener(getMouseListener());
- emsp.setStyle(FormStyle.propertyMessage.style());
- emsp.setLayoutData(CmsSwtUtils.fillWidth());
- }
- }
-
- protected Label createPropertyLbl(Composite parent, String value) {
- return createPropertyLbl(parent, value, SWT.NONE);
- }
-
- protected Label createPropertyLbl(Composite parent, String value, int vAlign) {
- // boolean isSmall = CmsView.getCmsView(parent).getUxContext().isSmall();
- Label label = new Label(parent, SWT.LEAD | SWT.WRAP);
- label.setText(value + " ");
- CmsSwtUtils.style(label, FormStyle.propertyLabel.style());
- GridData gd = new GridData(SWT.LEAD, vAlign, false, false);
- if (labelColWidth != null)
- gd.widthHint = labelColWidth;
- label.setLayoutData(gd);
- return label;
- }
-
- protected Label newStyledLabel(Composite parent, String style, String value) {
- Label label = new Label(parent, SWT.NONE);
- label.setText(value);
- CmsSwtUtils.style(label, style);
- return label;
- }
-
- protected Composite createRowLayoutComposite(Composite parent) throws RepositoryException {
- Composite bodyRow = new Composite(parent, SWT.NO_FOCUS);
- bodyRow.setLayoutData(CmsSwtUtils.fillWidth());
- RowLayout rl = new RowLayout(SWT.WRAP);
- rl.type = SWT.HORIZONTAL;
- rl.spacing = rowLayoutHSpacing;
- rl.marginHeight = rl.marginWidth = 0;
- rl.marginTop = rl.marginBottom = rl.marginLeft = rl.marginRight = 0;
- bodyRow.setLayout(rl);
- return bodyRow;
- }
-
- protected Composite createAddImgComposite(final Section section, Composite parent, final Node parentNode)
- throws RepositoryException {
-
- Composite body = new Composite(parent, SWT.NO_FOCUS);
- body.setLayout(new GridLayout());
-
- FormFileUploadReceiver receiver = new FormFileUploadReceiver(section, parentNode, null);
- final FileUploadHandler currentUploadHandler = new FileUploadHandler(receiver);
- if (fileUploadListener != null)
- currentUploadHandler.addUploadListener(fileUploadListener);
-
- // Button creation
- final FileUpload fileUpload = new FileUpload(body, SWT.BORDER);
- fileUpload.setText("Import an image");
- fileUpload.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, true, true));
- fileUpload.addSelectionListener(new SelectionAdapter() {
- private static final long serialVersionUID = 4869523412991968759L;
-
- @Override
- public void widgetSelected(SelectionEvent e) {
- ServerPushSession pushSession = new ServerPushSession();
- pushSession.start();
- String uploadURL = currentUploadHandler.getUploadUrl();
- fileUpload.submit(uploadURL);
- }
- });
-
- return body;
- }
-
- protected class FormFileUploadReceiver extends FileUploadReceiver {
-
- private Node context;
- private Section section;
- private String name;
-
- public FormFileUploadReceiver(Section section, Node context, String name) {
- this.context = context;
- this.section = section;
- this.name = name;
- }
-
- @Override
- public void receive(InputStream stream, FileDetails details) throws IOException {
-
- if (name == null)
- name = details.getFileName();
-
- // TODO clean image name more carefully
- String cleanedName = name.replaceAll("[^a-zA-Z0-9-.]", "");
- // We add a unique prefix to workaround the cache issue: when
- // deleting and re-adding a new image with same name, the end user
- // browser will use the cache and the image will remain unchanged
- // for a while
- cleanedName = System.currentTimeMillis() % 100000 + "_" + cleanedName;
-
- imageManager().uploadImage(context, context, cleanedName, stream, details.getContentType());
- // TODO clean refresh strategy
- section.getDisplay().asyncExec(new Runnable() {
- @Override
- public void run() {
- try {
- FormPageViewer.this.refresh(section);
- section.layout();
- section.getParent().layout();
- } catch (RepositoryException re) {
- throw new JcrException("Unable to refresh " + "image section for " + context, re);
- }
- }
- });
- }
- }
-
- protected void addListeners(StyledControl control) {
- control.setMouseListener(getMouseListener());
- control.setFocusListener(getFocusListener());
- }
-
- protected Img createImgComposite(Composite parent, Node node, Point preferredSize) throws RepositoryException {
- Img img = new Img(parent, SWT.NONE, node, new Cms2DSize(preferredSize.x, preferredSize.y)) {
- private static final long serialVersionUID = 1297900641952417540L;
-
- @Override
- protected void setContainerLayoutData(Composite composite) {
- composite.setLayoutData(CmsSwtUtils.grabWidth(SWT.CENTER, SWT.DEFAULT));
- }
-
- @Override
- protected void setControlLayoutData(Control control) {
- control.setLayoutData(CmsSwtUtils.grabWidth(SWT.CENTER, SWT.DEFAULT));
- }
- };
- img.setLayoutData(CmsSwtUtils.grabWidth(SWT.CENTER, SWT.DEFAULT));
- updateContent(img);
- addListeners(img);
- return img;
- }
-
- protected Composite addDeleteAbility(final Section section, final Node sessionNode, int topWeight,
- int rightWeight) {
- Composite comp = new Composite(section, SWT.NONE);
- comp.setLayoutData(CmsSwtUtils.fillAll());
- comp.setLayout(new FormLayout());
-
- // The body to be populated
- Composite body = new Composite(comp, SWT.NO_FOCUS);
- body.setLayoutData(EclipseUiUtils.fillFormData());
-
- if (getCmsEditable().canEdit()) {
- // the delete button
- Button deleteBtn = new Button(comp, SWT.FLAT);
- CmsSwtUtils.style(deleteBtn, FormStyle.deleteOverlay.style());
- FormData formData = new FormData();
- formData.right = new FormAttachment(rightWeight, 0);
- formData.top = new FormAttachment(topWeight, 0);
- deleteBtn.setLayoutData(formData);
- deleteBtn.moveAbove(body);
-
- deleteBtn.addSelectionListener(new SelectionAdapter() {
- private static final long serialVersionUID = 4304223543657238462L;
-
- @Override
- public void widgetSelected(SelectionEvent e) {
- super.widgetSelected(e);
- if (MessageDialog.openConfirm(section.getShell(), "Confirm deletion",
- "Are you really you want to remove this?")) {
- Session session;
- try {
- session = sessionNode.getSession();
- Section parSection = section.getParentSection();
- sessionNode.remove();
- session.save();
- refresh(parSection);
- layout(parSection);
- } catch (RepositoryException re) {
- throw new JcrException("Unable to delete " + sessionNode, re);
- }
-
- }
-
- }
- });
- }
- return body;
- }
-
-// // LOCAL HELPERS FOR NODE MANAGEMENT
-// private Node getOrCreateNode(Node parent, String nodeName, String nodeType) throws RepositoryException {
-// Node node = null;
-// if (getCmsEditable().canEdit() && !parent.hasNode(nodeName)) {
-// node = JcrUtils.mkdirs(parent, nodeName, nodeType);
-// parent.getSession().save();
-// }
-//
-// if (getCmsEditable().canEdit() || parent.hasNode(nodeName))
-// node = parent.getNode(nodeName);
-//
-// return node;
-// }
-
- private SelectionListener getRemoveValueSelListener() {
- return new SelectionAdapter() {
- private static final long serialVersionUID = 9022259089907445195L;
-
- @Override
- public void widgetSelected(SelectionEvent e) {
- Object source = e.getSource();
- if (source instanceof Button) {
- Button btn = (Button) source;
- Object obj = btn.getData(FormConstants.LINKED_VALUE);
- EditablePart ep = findDataParent(btn);
- if (ep != null && ep instanceof EditableMultiStringProperty) {
- EditableMultiStringProperty emsp = (EditableMultiStringProperty) ep;
- List<String> values = emsp.getValues();
- if (values.contains(obj)) {
- values.remove(values.indexOf(obj));
- emsp.setValues(values);
- try {
- save(emsp);
- // TODO workaround to force refresh
- edit(emsp, 0);
- cancelEdit();
- } catch (RepositoryException e1) {
- throw new JcrException("Unable to remove value " + obj, e1);
- }
- layout(emsp);
- }
- }
- }
- }
- };
- }
-
- protected void setPropertySilently(Node node, String propName, String value) throws RepositoryException {
- try {
- // TODO Clean this:
- // Format strings to replace \n
- value = value.replaceAll("\n", "<br/>");
- // Do not make the update if validation fails
- try {
- MarkupValidatorCopy.getInstance().validate(value);
- } catch (Exception e) {
- log.warn("Cannot set [" + value + "] on prop " + propName + "of " + node
- + ", String cannot be validated - " + e.getMessage());
- return;
- }
- // TODO check if the newly created property is of the correct type,
- // otherwise the property will be silently created with a STRING
- // property type.
- node.setProperty(propName, value);
- } catch (ValueFormatException vfe) {
- log.warn("Cannot set [" + value + "] on prop " + propName + "of " + node + " - " + vfe.getMessage());
- }
- }
-}
\ No newline at end of file
+++ /dev/null
-package org.argeo.cms.ui.forms;
-
-import org.argeo.api.cms.CmsStyle;
-
-/** Syles used */
-public enum FormStyle implements CmsStyle {
- // Main
- form, title,
- // main part
- header, headerBtn, headerCombo, section, sectionHeader,
- // Property fields
- propertyLabel, propertyText, propertyMessage, errorMessage,
- // Date
- popupCalendar,
- // Buttons
- starred, unstarred, starOverlay, editOverlay, deleteOverlay, updateOverlay, deleteOverlaySmall, calendar, delete,
- // Contacts
- email, address, phone, website,
- // Social Media
- facebook, twitter, linkedIn, instagram;
-
- @Override
- public String getClassPrefix() {
- return "argeo-form";
- }
-
- // TODO clean button style management
- public final static String BUTTON_SUFFIX = "_btn";
-}
+++ /dev/null
-package org.argeo.cms.ui.forms;
-
-import java.text.DateFormat;
-import java.text.ParseException;
-import java.util.Calendar;
-import java.util.Date;
-import java.util.GregorianCalendar;
-
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-
-import org.argeo.api.cms.CmsView;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.cms.CmsException;
-import org.argeo.cms.ui.util.CmsUiUtils;
-import org.argeo.eclipse.ui.EclipseUiUtils;
-import org.eclipse.jface.fieldassist.ControlDecoration;
-import org.eclipse.jface.fieldassist.FieldDecorationRegistry;
-import org.eclipse.jface.viewers.DoubleClickEvent;
-import org.eclipse.jface.viewers.IDoubleClickListener;
-import org.eclipse.jface.viewers.IStructuredSelection;
-import org.eclipse.jface.viewers.TableViewer;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.graphics.Color;
-import org.eclipse.swt.graphics.Image;
-import org.eclipse.swt.widgets.Label;
-import org.eclipse.swt.widgets.Text;
-
-/** Utilitary methods to ease implementation of CMS forms */
-public class FormUtils {
- private final static CmsLog log = CmsLog.getLog(FormUtils.class);
-
- public final static String DEFAULT_SHORT_DATE_FORMAT = "dd/MM/yyyy";
-
- /** Best effort to convert a String to a calendar. Fails silently */
- public static Calendar parseDate(DateFormat dateFormat, String calStr) {
- Calendar cal = null;
- if (EclipseUiUtils.notEmpty(calStr)) {
- try {
- Date date = dateFormat.parse(calStr);
- cal = new GregorianCalendar();
- cal.setTime(date);
- } catch (ParseException pe) {
- // Silent
- log.warn("Unable to parse date: " + calStr + " - msg: "
- + pe.getMessage());
- }
- }
- return cal;
- }
-
- /** Add a double click listener on tables that display a JCR node list */
- public static void addCanonicalDoubleClickListener(final TableViewer v) {
- v.addDoubleClickListener(new IDoubleClickListener() {
-
- @Override
- public void doubleClick(DoubleClickEvent event) {
- CmsView cmsView = CmsUiUtils.getCmsView();
- Node node = (Node) ((IStructuredSelection) event.getSelection())
- .getFirstElement();
- try {
- cmsView.navigateTo(node.getPath());
- } catch (RepositoryException e) {
- throw new CmsException("Unable to get path for node "
- + node + " before calling navigateTo(path)", e);
- }
- }
- });
- }
-
- // MANAGE ERROR DECORATION
-
- public static ControlDecoration addDecoration(final Text text) {
- final ControlDecoration dynDecoration = new ControlDecoration(text,
- SWT.LEFT);
- Image icon = getDecorationImage(FieldDecorationRegistry.DEC_ERROR);
- dynDecoration.setImage(icon);
- dynDecoration.setMarginWidth(3);
- dynDecoration.hide();
- return dynDecoration;
- }
-
- public static void refreshDecoration(Text text, ControlDecoration deco,
- boolean isValid, boolean clean) {
- if (isValid || clean) {
- text.setBackground(null);
- deco.hide();
- } else {
- text.setBackground(new Color(text.getDisplay(), 250, 200, 150));
- deco.show();
- }
- }
-
- public static Image getDecorationImage(String image) {
- FieldDecorationRegistry registry = FieldDecorationRegistry.getDefault();
- return registry.getFieldDecoration(image).getImage();
- }
-
- public static void addCompulsoryDecoration(Label label) {
- final ControlDecoration dynDecoration = new ControlDecoration(label,
- SWT.RIGHT | SWT.TOP);
- Image icon = getDecorationImage(FieldDecorationRegistry.DEC_REQUIRED);
- dynDecoration.setImage(icon);
- dynDecoration.setMarginWidth(3);
- }
-
- // TODO the read only generation of read only links for various contact type
- // should be factorised in the cms Utils.
- /**
- * Creates the read-only HTML snippet to display in a label with styling
- * enabled in order to provide a click-able phone number
- */
- public static String getPhoneLink(String value) {
- return getPhoneLink(value, value);
- }
-
- /**
- * Creates the read-only HTML snippet to display in a label with styling
- * enabled in order to provide a click-able phone number
- *
- * @param value
- * @param label
- * a potentially distinct label
- * @return
- */
- public static String getPhoneLink(String value, String label) {
- StringBuilder builder = new StringBuilder();
- builder.append("<a href=\"tel:");
- builder.append(value).append("\" target=\"_blank\" >").append(label)
- .append("</a>");
- return builder.toString();
- }
-
- /**
- * Creates the read-only HTML snippet to display in a label with styling
- * enabled in order to provide a click-able mail
- */
- public static String getMailLink(String value) {
- return getMailLink(value, value);
- }
-
- /**
- * Creates the read-only HTML snippet to display in a label with styling
- * enabled in order to provide a click-able mail
- *
- * @param value
- * @param label
- * a potentially distinct label
- * @return
- */
- public static String getMailLink(String value, String label) {
- StringBuilder builder = new StringBuilder();
- value = replaceAmpersand(value);
- builder.append("<a href=\"mailto:");
- builder.append(value).append("\" >").append(label).append("</a>");
- return builder.toString();
- }
-
- /**
- * Creates the read-only HTML snippet to display in a label with styling
- * enabled in order to provide a click-able link
- */
- public static String getUrlLink(String value) {
- return getUrlLink(value, value);
- }
-
- /**
- * Creates the read-only HTML snippet to display in a label with styling
- * enabled in order to provide a click-able link
- */
- public static String getUrlLink(String value, String label) {
- StringBuilder builder = new StringBuilder();
- value = replaceAmpersand(value);
- label = replaceAmpersand(label);
- if (!(value.startsWith("http://") || value.startsWith("https://")))
- value = "http://" + value;
- builder.append("<a href=\"");
- builder.append(value + "\" target=\"_blank\" >" + label + "</a>");
- return builder.toString();
- }
-
- private static String AMPERSAND = "&";
-
- /**
- * Cleans a String by replacing any '&' by its HTML encoding '&#38;' to
- * avoid <code>SAXParseException</code> while rendering HTML with RWT
- */
- public static String replaceAmpersand(String value) {
- value = value.replaceAll("&(?![#a-zA-Z0-9]+;)", AMPERSAND);
- return value;
- }
-
- // Prevents instantiation
- private FormUtils() {
- }
-}
+++ /dev/null
-package org.argeo.cms.ui.forms;
-
-import java.io.StringReader;
-import java.text.MessageFormat;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import javax.xml.parsers.SAXParser;
-import javax.xml.parsers.SAXParserFactory;
-
-import org.eclipse.rap.rwt.SingletonUtil;
-import org.eclipse.swt.widgets.Widget;
-import org.xml.sax.Attributes;
-import org.xml.sax.InputSource;
-import org.xml.sax.helpers.DefaultHandler;
-
-/**
- * Copy of RAP v2.3 since it is in an internal package.
- */
-class MarkupValidatorCopy {
-
- // Used by Eclipse Scout project
- public static final String MARKUP_VALIDATION_DISABLED = "org.eclipse.rap.rwt.markupValidationDisabled";
-
- private static final String DTD = createDTD();
- private static final Map<String, String[]> SUPPORTED_ELEMENTS = createSupportedElementsMap();
- private final SAXParser saxParser;
-
- public static MarkupValidatorCopy getInstance() {
- return SingletonUtil.getSessionInstance(MarkupValidatorCopy.class);
- }
-
- public MarkupValidatorCopy() {
- saxParser = createSAXParser();
- }
-
- public void validate(String text) {
- StringBuilder markup = new StringBuilder();
- markup.append(DTD);
- markup.append("<html>");
- markup.append(text);
- markup.append("</html>");
- InputSource inputSource = new InputSource(new StringReader(markup.toString()));
- try {
- saxParser.parse(inputSource, new MarkupHandler());
- } catch (RuntimeException exception) {
- throw exception;
- } catch (Exception exception) {
- throw new IllegalArgumentException("Failed to parse markup text", exception);
- }
- }
-
- public static boolean isValidationDisabledFor(Widget widget) {
- return Boolean.TRUE.equals(widget.getData(MARKUP_VALIDATION_DISABLED));
- }
-
- private static SAXParser createSAXParser() {
- SAXParser result = null;
- SAXParserFactory parserFactory = SAXParserFactory.newInstance();
- try {
- result = parserFactory.newSAXParser();
- } catch (Exception exception) {
- throw new RuntimeException("Failed to create SAX parser", exception);
- }
- return result;
- }
-
- private static String createDTD() {
- StringBuilder result = new StringBuilder();
- result.append("<!DOCTYPE html [");
- result.append("<!ENTITY quot \""\">");
- result.append("<!ENTITY amp \"&\">");
- result.append("<!ENTITY apos \"'\">");
- result.append("<!ENTITY lt \"<\">");
- result.append("<!ENTITY gt \">\">");
- result.append("<!ENTITY nbsp \" \">");
- result.append("<!ENTITY ensp \" \">");
- result.append("<!ENTITY emsp \" \">");
- result.append("<!ENTITY ndash \"–\">");
- result.append("<!ENTITY mdash \"—\">");
- result.append("]>");
- return result.toString();
- }
-
- private static Map<String, String[]> createSupportedElementsMap() {
- Map<String, String[]> result = new HashMap<String, String[]>();
- result.put("html", new String[0]);
- result.put("br", new String[0]);
- result.put("b", new String[] { "style" });
- result.put("strong", new String[] { "style" });
- result.put("i", new String[] { "style" });
- result.put("em", new String[] { "style" });
- result.put("sub", new String[] { "style" });
- result.put("sup", new String[] { "style" });
- result.put("big", new String[] { "style" });
- result.put("small", new String[] { "style" });
- result.put("del", new String[] { "style" });
- result.put("ins", new String[] { "style" });
- result.put("code", new String[] { "style" });
- result.put("samp", new String[] { "style" });
- result.put("kbd", new String[] { "style" });
- result.put("var", new String[] { "style" });
- result.put("cite", new String[] { "style" });
- result.put("dfn", new String[] { "style" });
- result.put("q", new String[] { "style" });
- result.put("abbr", new String[] { "style", "title" });
- result.put("span", new String[] { "style" });
- result.put("img", new String[] { "style", "src", "width", "height", "title", "alt" });
- result.put("a", new String[] { "style", "href", "target", "title" });
- return result;
- }
-
- private static class MarkupHandler extends DefaultHandler {
-
- @Override
- public void startElement(String uri, String localName, String name, Attributes attributes) {
- checkSupportedElements(name, attributes);
- checkSupportedAttributes(name, attributes);
- checkMandatoryAttributes(name, attributes);
- }
-
- private static void checkSupportedElements(String elementName, Attributes attributes) {
- if (!SUPPORTED_ELEMENTS.containsKey(elementName)) {
- throw new IllegalArgumentException("Unsupported element in markup text: " + elementName);
- }
- }
-
- private static void checkSupportedAttributes(String elementName, Attributes attributes) {
- if (attributes.getLength() > 0) {
- List<String> supportedAttributes = Arrays.asList(SUPPORTED_ELEMENTS.get(elementName));
- int index = 0;
- String attributeName = attributes.getQName(index);
- while (attributeName != null) {
- if (!supportedAttributes.contains(attributeName)) {
- String message = "Unsupported attribute \"{0}\" for element \"{1}\" in markup text";
- message = MessageFormat.format(message, new Object[] { attributeName, elementName });
- throw new IllegalArgumentException(message);
- }
- index++;
- attributeName = attributes.getQName(index);
- }
- }
- }
-
- private static void checkMandatoryAttributes(String elementName, Attributes attributes) {
- checkIntAttribute(elementName, attributes, "img", "width");
- checkIntAttribute(elementName, attributes, "img", "height");
- }
-
- private static void checkIntAttribute(String elementName, Attributes attributes, String checkedElementName,
- String checkedAttributeName) {
- if (checkedElementName.equals(elementName)) {
- String attribute = attributes.getValue(checkedAttributeName);
- try {
- Integer.parseInt(attribute);
- } catch (NumberFormatException exception) {
- String message = "Mandatory attribute \"{0}\" for element \"{1}\" is missing or not a valid integer";
- Object[] arguments = new Object[] { checkedAttributeName, checkedElementName };
- message = MessageFormat.format(message, arguments);
- throw new IllegalArgumentException(message);
- }
- }
- }
-
- }
-
-}
+++ /dev/null
-/** Argeo CMS forms, based on SWT/JFace. */
-package org.argeo.cms.ui.forms;
\ No newline at end of file
+++ /dev/null
-package org.argeo.cms.ui.fs;
-
-import java.io.IOException;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.nio.file.DirectoryStream;
-import java.nio.file.FileSystem;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.nio.file.spi.FileSystemProvider;
-import java.security.PrivilegedActionException;
-import java.security.PrivilegedExceptionAction;
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.jcr.Node;
-import javax.jcr.Repository;
-import javax.jcr.Session;
-
-import org.argeo.cms.CmsException;
-import org.argeo.cms.auth.CurrentUser;
-import org.argeo.cms.jcr.CmsJcrUtils;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.eclipse.ui.ColumnDefinition;
-import org.argeo.eclipse.ui.EclipseUiUtils;
-import org.argeo.eclipse.ui.fs.FileIconNameLabelProvider;
-import org.argeo.eclipse.ui.fs.FsTableViewer;
-import org.argeo.eclipse.ui.fs.FsUiConstants;
-import org.argeo.eclipse.ui.fs.FsUiUtils;
-import org.argeo.eclipse.ui.fs.NioFileLabelProvider;
-import org.argeo.jcr.JcrUtils;
-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.Viewer;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.custom.SashForm;
-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.events.MouseAdapter;
-import org.eclipse.swt.events.MouseEvent;
-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.layout.RowData;
-import org.eclipse.swt.layout.RowLayout;
-import org.eclipse.swt.widgets.Button;
-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.Text;
-
-/**
- * Default CMS browser composite: a sashForm layout with bookmarks at the left
- * hand side, a simple table in the middle and an overview at right hand side.
- */
-public class CmsFsBrowser extends Composite {
- // private final static Log log = LogFactory.getLog(CmsFsBrowser.class);
- private static final long serialVersionUID = -40347919096946585L;
-
- private final FileSystemProvider nodeFileSystemProvider;
- private final Node currentBaseContext;
-
- // UI Parts for the browser
- private Composite leftPannelCmp;
- private Composite filterCmp;
- private Text filterTxt;
- private FsTableViewer directoryDisplayViewer;
- private Composite rightPannelCmp;
-
- private FsContextMenu contextMenu;
-
- // Local context (this composite is state full)
- private Path initialPath;
- private Path currDisplayedFolder;
- private Path currSelected;
-
- // local variables (to be cleaned)
- private int bookmarkColWith = 500;
-
- /*
- * WARNING: unfinalised implementation of the mechanism to retrieve base
- * paths
- */
-
- private final static String NODE_PREFIX = "node://";
-
- private String getCurrentHomePath() {
- Session session = null;
- try {
- Repository repo = currentBaseContext.getSession().getRepository();
- session = CurrentUser.tryAs(() -> repo.login());
- String homepath = CmsJcrUtils.getUserHome(session).getPath();
- return homepath;
- } catch (Exception e) {
- throw new CmsException("Cannot retrieve Current User Home Path", e);
- } finally {
- JcrUtils.logoutQuietly(session);
- }
- }
-
- protected Path[] getMyFilesPath() {
- // return Paths.get(System.getProperty("user.dir"));
- String currHomeUriStr = NODE_PREFIX + getCurrentHomePath();
- try {
- URI uri = new URI(currHomeUriStr);
- FileSystem fileSystem = nodeFileSystemProvider.getFileSystem(uri);
- if (fileSystem == null) {
- PrivilegedExceptionAction<FileSystem> pea = new PrivilegedExceptionAction<FileSystem>() {
- @Override
- public FileSystem run() throws Exception {
- return nodeFileSystemProvider.newFileSystem(uri, null);
- }
-
- };
- fileSystem = CurrentUser.tryAs(pea);
- }
- Path[] paths = { fileSystem.getPath(getCurrentHomePath()), fileSystem.getPath("/") };
- return paths;
- } catch (URISyntaxException | PrivilegedActionException e) {
- throw new RuntimeException("unable to initialise home file system for " + currHomeUriStr, e);
- }
- }
-
- private Path[] getMyGroupsFilesPath() {
- // TODO
- Path[] paths = { Paths.get(System.getProperty("user.dir")), Paths.get("/tmp") };
- return paths;
- }
-
- private Path[] getMyBookmarks() {
- // TODO
- Path[] paths = { Paths.get(System.getProperty("user.dir")), Paths.get("/tmp"), Paths.get("/opt") };
- return paths;
- }
-
- /* End of warning */
-
- public CmsFsBrowser(Composite parent, int style, Node context, FileSystemProvider fileSystemProvider) {
- super(parent, style);
- this.nodeFileSystemProvider = fileSystemProvider;
- this.currentBaseContext = context;
-
- this.setLayout(EclipseUiUtils.noSpaceGridLayout());
-
- SashForm form = new SashForm(this, SWT.HORIZONTAL);
-
- leftPannelCmp = new Composite(form, SWT.NO_FOCUS);
- // Bookmarks are still static
- populateBookmarks(leftPannelCmp);
-
- Composite centerCmp = new Composite(form, SWT.BORDER | SWT.NO_FOCUS);
- createDisplay(centerCmp);
-
- rightPannelCmp = new Composite(form, SWT.NO_FOCUS);
-
- form.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
- form.setWeights(new int[] { 15, 40, 20 });
- }
-
- void refresh() {
- modifyFilter(false);
- // also refresh bookmarks and groups
- }
-
- private void createDisplay(final Composite parent) {
- parent.setLayout(EclipseUiUtils.noSpaceGridLayout());
-
- // top filter
- filterCmp = new Composite(parent, SWT.NO_FOCUS);
- filterCmp.setLayoutData(EclipseUiUtils.fillWidth());
- addFilterPanel(filterCmp);
-
- // Main display
- directoryDisplayViewer = new FsTableViewer(parent, SWT.MULTI);
- List<ColumnDefinition> colDefs = new ArrayList<>();
- colDefs.add(new ColumnDefinition(new FileIconNameLabelProvider(), "Name", 250));
- colDefs.add(new ColumnDefinition(new NioFileLabelProvider(FsUiConstants.PROPERTY_SIZE), "Size", 100));
- colDefs.add(new ColumnDefinition(new NioFileLabelProvider(FsUiConstants.PROPERTY_TYPE), "Type", 150));
- colDefs.add(new ColumnDefinition(new NioFileLabelProvider(FsUiConstants.PROPERTY_LAST_MODIFIED),
- "Last modified", 400));
- final Table table = directoryDisplayViewer.configureDefaultTable(colDefs);
- table.setLayoutData(EclipseUiUtils.fillAll());
-
- // table.addKeyListener(new KeyListener() {
- // private static final long serialVersionUID = -8083424284436715709L;
- //
- // @Override
- // public void keyReleased(KeyEvent e) {
- // }
- //
- // @Override
- // public void keyPressed(KeyEvent e) {
- // if (log.isDebugEnabled())
- // log.debug("Key event received: " + e.keyCode);
- // IStructuredSelection selection = (IStructuredSelection)
- // directoryDisplayViewer.getSelection();
- // Path selected = null;
- // if (!selection.isEmpty())
- // selected = ((Path) selection.getFirstElement());
- // if (e.keyCode == SWT.CR) {
- // if (!Files.isDirectory(selected))
- // return;
- // if (selected != null) {
- // currDisplayedFolder = selected;
- // directoryDisplayViewer.setInput(currDisplayedFolder, "*");
- // }
- // } else if (e.keyCode == SWT.BS) {
- // currDisplayedFolder = currDisplayedFolder.getParent();
- // directoryDisplayViewer.setInput(currDisplayedFolder, "*");
- // directoryDisplayViewer.getTable().setFocus();
- // }
- // }
- // });
-
- directoryDisplayViewer.addSelectionChangedListener(new ISelectionChangedListener() {
-
- @Override
- public void selectionChanged(SelectionChangedEvent event) {
- IStructuredSelection selection = (IStructuredSelection) directoryDisplayViewer.getSelection();
- Path selected = null;
- if (selection.isEmpty())
- setSelected(null);
- else
- selected = ((Path) selection.getFirstElement());
- if (selected != null) {
- // TODO manage multiple selection
- setSelected(selected);
- }
- }
- });
-
- directoryDisplayViewer.addDoubleClickListener(new IDoubleClickListener() {
- @Override
- public void doubleClick(DoubleClickEvent event) {
- IStructuredSelection selection = (IStructuredSelection) directoryDisplayViewer.getSelection();
- Path selected = null;
- if (!selection.isEmpty())
- selected = ((Path) selection.getFirstElement());
- if (selected != null) {
- if (!Files.isDirectory(selected))
- return;
- setInput(selected);
- }
- }
- });
-
- // The context menu
- contextMenu = new FsContextMenu(this);
-
- table.addMouseListener(new MouseAdapter() {
- private static final long serialVersionUID = 6737579410648595940L;
-
- @Override
- public void mouseDown(MouseEvent e) {
- if (e.button == 3) {
- // contextMenu.setCurrFolderPath(currDisplayedFolder);
- contextMenu.show(table, new Point(e.x, e.y), currDisplayedFolder);
- }
- }
- });
- }
-
- private void addPathElementBtn(Path path) {
- Button elemBtn = new Button(filterCmp, SWT.PUSH);
- String nameStr;
- if (path.toString().equals("/"))
- nameStr = "[jcr:root]";
- else
- nameStr = path.getFileName().toString();
- elemBtn.setText(nameStr + " >> ");
- CmsSwtUtils.style(elemBtn, FsStyles.BREAD_CRUMB_BTN);
- elemBtn.addSelectionListener(new SelectionAdapter() {
- private static final long serialVersionUID = -4103695476023480651L;
-
- @Override
- public void widgetSelected(SelectionEvent e) {
- setInput(path);
- }
- });
- }
-
- public void setInput(Path path) {
- if (path.equals(currDisplayedFolder))
- return;
- currDisplayedFolder = path;
-
- Path diff = initialPath.relativize(currDisplayedFolder);
-
- for (Control child : filterCmp.getChildren())
- if (!child.equals(filterTxt))
- child.dispose();
-
- addPathElementBtn(initialPath);
- Path currTarget = initialPath;
- if (!diff.toString().equals(""))
- for (Path pathElem : diff) {
- currTarget = currTarget.resolve(pathElem);
- addPathElementBtn(currTarget);
- }
-
- filterTxt.setText("");
- filterTxt.moveBelow(null);
- setSelected(null);
- filterCmp.getParent().layout(true, true);
- }
-
- private void setSelected(Path path) {
- currSelected = path;
- setOverviewInput(path);
- }
-
- public Viewer getViewer() {
- return directoryDisplayViewer;
- }
-
- private void populateBookmarks(Composite parent) {
- CmsSwtUtils.clear(parent);
- parent.setLayout(new GridLayout());
- ISelectionChangedListener selList = new BookmarksSelChangeListener();
-
- FsTableViewer homeViewer = new FsTableViewer(parent, SWT.SINGLE | SWT.NO_SCROLL);
- Table table = homeViewer.configureDefaultSingleColumnTable(bookmarkColWith);
- GridData gd = EclipseUiUtils.fillWidth();
- gd.horizontalIndent = 10;
- table.setLayoutData(gd);
- homeViewer.addSelectionChangedListener(selList);
- homeViewer.setPathsInput(getMyFilesPath());
-
- appendTitle(parent, "Shared files");
- FsTableViewer groupsViewer = new FsTableViewer(parent, SWT.SINGLE | SWT.NO_SCROLL);
- table = groupsViewer.configureDefaultSingleColumnTable(bookmarkColWith);
- gd = EclipseUiUtils.fillWidth();
- gd.horizontalIndent = 10;
- table.setLayoutData(gd);
- groupsViewer.addSelectionChangedListener(selList);
- groupsViewer.setPathsInput(getMyGroupsFilesPath());
-
- appendTitle(parent, "My bookmarks");
- FsTableViewer bookmarksViewer = new FsTableViewer(parent, SWT.SINGLE | SWT.NO_SCROLL);
- table = bookmarksViewer.configureDefaultSingleColumnTable(bookmarkColWith);
- gd = EclipseUiUtils.fillWidth();
- gd.horizontalIndent = 10;
- table.setLayoutData(gd);
- bookmarksViewer.addSelectionChangedListener(selList);
- bookmarksViewer.setPathsInput(getMyBookmarks());
- }
-
- /**
- * Recreates the content of the box that displays information about the
- * current selected Path.
- */
- private void setOverviewInput(Path path) {
- try {
- EclipseUiUtils.clear(rightPannelCmp);
- rightPannelCmp.setLayout(new GridLayout());
- if (path != null) {
- // if (isImg(context)) {
- // EditableImage image = new Img(parent, RIGHT, context,
- // imageWidth);
- // image.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER,
- // true, false,
- // 2, 1));
- // }
-
- Label contextL = new Label(rightPannelCmp, SWT.NONE);
- contextL.setText(path.getFileName().toString());
- contextL.setFont(EclipseUiUtils.getBoldFont(rightPannelCmp));
- addProperty(rightPannelCmp, "Last modified", Files.getLastModifiedTime(path).toString());
- // addProperty(rightPannelCmp, "Owner",
- // Files.getOwner(path).getName());
- if (Files.isDirectory(path)) {
- addProperty(rightPannelCmp, "Type", "Folder");
- } else {
- String mimeType = Files.probeContentType(path);
- if (EclipseUiUtils.isEmpty(mimeType))
- mimeType = "<i>Unknown</i>";
- addProperty(rightPannelCmp, "Type", mimeType);
- addProperty(rightPannelCmp, "Size", FsUiUtils.humanReadableByteCount(Files.size(path), false));
- }
- }
- rightPannelCmp.layout(true, true);
- } catch (IOException e) {
- throw new CmsException("Cannot display details for " + path.toString(), e);
- }
- }
-
- private void addFilterPanel(Composite parent) {
- RowLayout rl = new RowLayout(SWT.HORIZONTAL);
- rl.wrap = true;
- parent.setLayout(rl);
- // parent.setLayout(EclipseUiUtils.noSpaceGridLayout(new GridLayout(2,
- // false)));
-
- filterTxt = new Text(parent, SWT.SEARCH | SWT.ICON_CANCEL);
- filterTxt.setMessage("Search current folder");
- filterTxt.setLayoutData(new RowData(250, SWT.DEFAULT));
- filterTxt.addModifyListener(new ModifyListener() {
- private static final long serialVersionUID = 1L;
-
- public void modifyText(ModifyEvent event) {
- modifyFilter(false);
- }
- });
- filterTxt.addKeyListener(new KeyListener() {
- private static final long serialVersionUID = 2533535233583035527L;
-
- @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(currEdited);
- // if (table != null && !table.isDisposed())
- // currTable = table;
- // }
- //
- // if (e.keyCode == SWT.ARROW_DOWN)
- // currTable.setFocus();
- // else if (e.keyCode == SWT.BS) {
- // if (filterTxt.getText().equals("")
- // && !(currEdited.getNameCount() == 1 ||
- // currEdited.equals(initialPath))) {
- // Path oldEdited = currEdited;
- // Path parentPath = currEdited.getParent();
- // setEdited(parentPath);
- // if (browserCols.containsKey(parentPath))
- // browserCols.get(parentPath).setSelected(oldEdited);
- // filterTxt.setFocus();
- // e.doit = false;
- // }
- // } else if (e.keyCode == SWT.TAB && !shiftPressed) {
- // Path uniqueChild = getOnlyChild(currEdited,
- // filterTxt.getText());
- // if (uniqueChild != null) {
- // // Highlight the unique chosen child
- // currTable.setSelected(uniqueChild);
- // setEdited(uniqueChild);
- // }
- // filterTxt.setFocus();
- // e.doit = false;
- // }
- }
- });
- }
-
- private Path getOnlyChild(Path parent, String filter) {
- try (DirectoryStream<Path> stream = Files.newDirectoryStream(currDisplayedFolder, filter + "*")) {
- Path uniqueChild = null;
- boolean moreThanOne = false;
- loop: for (Path entry : stream) {
- if (uniqueChild == null) {
- uniqueChild = entry;
- } else {
- moreThanOne = true;
- break loop;
- }
- }
- if (!moreThanOne)
- return uniqueChild;
- return null;
- } catch (IOException ioe) {
- throw new CmsException(
- "Unable to determine unique child existence and get it under " + parent + " with filter " + filter,
- ioe);
- }
- }
-
- private void modifyFilter(boolean fromOutside) {
- if (!fromOutside)
- if (currDisplayedFolder != null) {
- String filter = filterTxt.getText() + "*";
- directoryDisplayViewer.setInput(currDisplayedFolder, filter);
- }
- }
-
- private class BookmarksSelChangeListener implements ISelectionChangedListener {
-
- @Override
- public void selectionChanged(SelectionChangedEvent event) {
- IStructuredSelection selection = (IStructuredSelection) event.getSelection();
- if (selection.isEmpty())
- return;
- else {
- Path newSelected = (Path) selection.getFirstElement();
- if (newSelected.equals(currDisplayedFolder) && newSelected.equals(initialPath))
- return;
- initialPath = newSelected;
- setInput(newSelected);
- }
- }
- }
-
- // Simplify UI implementation
- private void addProperty(Composite parent, String propName, String value) {
- Label contextL = new Label(parent, SWT.NONE);
- contextL.setText(propName + ": " + value);
- }
-
- private Label appendTitle(Composite parent, String value) {
- Label titleLbl = new Label(parent, SWT.NONE);
- titleLbl.setText(value);
- titleLbl.setFont(EclipseUiUtils.getBoldFont(parent));
- GridData gd = EclipseUiUtils.fillWidth();
- gd.horizontalIndent = 5;
- gd.verticalIndent = 5;
- titleLbl.setLayoutData(gd);
- return titleLbl;
- }
-}
+++ /dev/null
-package org.argeo.cms.ui.fs;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-import org.argeo.api.cms.CmsLog;
-import org.argeo.eclipse.ui.specific.FileDropAdapter;
-import org.eclipse.swt.dnd.DND;
-import org.eclipse.swt.dnd.DropTarget;
-import org.eclipse.swt.dnd.DropTargetEvent;
-import org.eclipse.swt.widgets.Control;
-
-/** Allows a control to receive file drops. */
-public class FileDrop {
- private final static CmsLog log = CmsLog.getLog(FileDrop.class);
-
- public void createDropTarget(Control control) {
- FileDropAdapter fileDropAdapter = new FileDropAdapter() {
- @Override
- protected void processUpload(InputStream in, String fileName, String contentType) throws IOException {
- if (log.isDebugEnabled())
- log.debug("Process upload of " + fileName + " (" + contentType + ")");
- processFileUpload(in, fileName, contentType);
- }
- };
- DropTarget dropTarget = new DropTarget(control, DND.DROP_MOVE | DND.DROP_COPY);
- fileDropAdapter.prepareDropTarget(control, dropTarget);
- }
-
- public void handleFileDrop(Control control, DropTargetEvent event) {
- }
-
- /** Executed in UI thread */
- protected void processFileUpload(InputStream in, String fileName, String contentType) throws IOException {
-
- }
-}
+++ /dev/null
-package org.argeo.cms.ui.fs;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.lang.reflect.Method;
-import java.net.URI;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-
-import org.apache.commons.io.IOUtils;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.cms.CmsException;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.eclipse.ui.EclipseUiUtils;
-import org.argeo.eclipse.ui.dialogs.SingleValue;
-import org.eclipse.jface.dialogs.MessageDialog;
-import org.eclipse.jface.viewers.IStructuredSelection;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.events.SelectionAdapter;
-import org.eclipse.swt.events.SelectionEvent;
-import org.eclipse.swt.events.ShellEvent;
-import org.eclipse.swt.graphics.Point;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.widgets.Button;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.FileDialog;
-import org.eclipse.swt.widgets.Label;
-import org.eclipse.swt.widgets.Shell;
-
-/** Generic popup context menu to manage NIO Path in a Viewer. */
-public class FsContextMenu extends Shell {
- private static final long serialVersionUID = -9120261153509855795L;
-
- private final static CmsLog log = CmsLog.getLog(FsContextMenu.class);
-
- // Default known actions
- public final static String ACTION_ID_CREATE_FOLDER = "createFolder";
- public final static String ACTION_ID_BOOKMARK_FOLDER = "bookmarkFolder";
- public final static String ACTION_ID_SHARE_FOLDER = "shareFolder";
- public final static String ACTION_ID_DOWNLOAD_FOLDER = "downloadFolder";
- public final static String ACTION_ID_DELETE = "delete";
- public final static String ACTION_ID_UPLOAD_FILE = "uploadFiles";
- public final static String ACTION_ID_OPEN = "open";
-
- // Local context
- private final CmsFsBrowser browser;
- // private final Viewer viewer;
- private final static String KEY_ACTION_ID = "actionId";
- private final static String[] DEFAULT_ACTIONS = { ACTION_ID_CREATE_FOLDER, ACTION_ID_BOOKMARK_FOLDER,
- ACTION_ID_SHARE_FOLDER, ACTION_ID_DOWNLOAD_FOLDER, ACTION_ID_DELETE, ACTION_ID_UPLOAD_FILE,
- ACTION_ID_OPEN };
- private Map<String, Button> actionButtons = new HashMap<String, Button>();
-
- private Path currFolderPath;
-
- public FsContextMenu(CmsFsBrowser browser) { // Viewer viewer, Display
- // display) {
- super(browser.getDisplay(), SWT.NO_TRIM | SWT.BORDER | SWT.ON_TOP);
- this.browser = browser;
- setLayout(EclipseUiUtils.noSpaceGridLayout());
-
- Composite boxCmp = new Composite(this, SWT.NO_FOCUS | SWT.BORDER);
- boxCmp.setLayout(EclipseUiUtils.noSpaceGridLayout());
- CmsSwtUtils.style(boxCmp, FsStyles.CONTEXT_MENU_BOX);
- createContextMenu(boxCmp);
-
- addShellListener(new ActionsShellListener());
- }
-
- protected void createContextMenu(Composite boxCmp) {
- ActionsSelListener asl = new ActionsSelListener();
- for (String actionId : DEFAULT_ACTIONS) {
- Button btn = new Button(boxCmp, SWT.FLAT | SWT.PUSH | SWT.LEAD);
- btn.setText(getLabel(actionId));
- btn.setLayoutData(EclipseUiUtils.fillWidth());
- CmsSwtUtils.markup(btn);
- CmsSwtUtils.style(btn, actionId + FsStyles.BUTTON_SUFFIX);
- btn.setData(KEY_ACTION_ID, actionId);
- btn.addSelectionListener(asl);
- actionButtons.put(actionId, btn);
- }
- }
-
- protected String getLabel(String actionId) {
- switch (actionId) {
- case ACTION_ID_CREATE_FOLDER:
- return "Create Folder";
- case ACTION_ID_BOOKMARK_FOLDER:
- return "Bookmark Folder";
- case ACTION_ID_SHARE_FOLDER:
- return "Share Folder";
- case ACTION_ID_DOWNLOAD_FOLDER:
- return "Download as zip archive";
- case ACTION_ID_DELETE:
- return "Delete";
- case ACTION_ID_UPLOAD_FILE:
- return "Upload Files";
- case ACTION_ID_OPEN:
- return "Open";
- default:
- throw new IllegalArgumentException("Unknown action ID " + actionId);
- }
- }
-
- protected void aboutToShow(Control source, Point location) {
- IStructuredSelection selection = ((IStructuredSelection) browser.getViewer().getSelection());
- boolean emptySel = true;
- boolean multiSel = false;
- boolean isFolder = true;
- if (selection != null && !selection.isEmpty()) {
- emptySel = false;
- multiSel = selection.size() > 1;
- if (!multiSel && selection.getFirstElement() instanceof Path) {
- isFolder = Files.isDirectory((Path) selection.getFirstElement());
- }
- }
- if (emptySel) {
- setVisible(true, ACTION_ID_CREATE_FOLDER, ACTION_ID_UPLOAD_FILE);
- setVisible(false, ACTION_ID_SHARE_FOLDER, ACTION_ID_DOWNLOAD_FOLDER, ACTION_ID_DELETE, ACTION_ID_OPEN,
- // to be implemented
- ACTION_ID_BOOKMARK_FOLDER);
- } else if (multiSel) {
- setVisible(true, ACTION_ID_CREATE_FOLDER, ACTION_ID_UPLOAD_FILE, ACTION_ID_DELETE);
- setVisible(false, ACTION_ID_SHARE_FOLDER, ACTION_ID_DOWNLOAD_FOLDER, ACTION_ID_OPEN,
- // to be implemented
- ACTION_ID_BOOKMARK_FOLDER);
- } else if (isFolder) {
- setVisible(true, ACTION_ID_CREATE_FOLDER, ACTION_ID_UPLOAD_FILE, ACTION_ID_DELETE);
- setVisible(false, ACTION_ID_OPEN,
- // to be implemented
- ACTION_ID_SHARE_FOLDER, ACTION_ID_DOWNLOAD_FOLDER, ACTION_ID_BOOKMARK_FOLDER);
- } else {
- setVisible(true, ACTION_ID_CREATE_FOLDER, ACTION_ID_UPLOAD_FILE, ACTION_ID_OPEN, ACTION_ID_DELETE);
- setVisible(false, ACTION_ID_SHARE_FOLDER, ACTION_ID_DOWNLOAD_FOLDER,
- // to be implemented
- ACTION_ID_BOOKMARK_FOLDER);
- }
- }
-
- private void setVisible(boolean visible, String... buttonIds) {
- for (String id : buttonIds) {
- Button button = actionButtons.get(id);
- button.setVisible(visible);
- GridData gd = (GridData) button.getLayoutData();
- gd.heightHint = visible ? SWT.DEFAULT : 0;
- }
- }
-
- public void show(Control source, Point location, Path currFolderPath) {
- if (isVisible())
- setVisible(false);
- // TODO find a better way to retrieve the parent path (cannot be deduced
- // from table content because it will fail on an empty folder)
- this.currFolderPath = currFolderPath;
- aboutToShow(source, location);
- pack();
- layout();
- if (source instanceof Control)
- setLocation(((Control) source).toDisplay(location.x, location.y));
- open();
- }
-
- class StyleButton extends Label {
- private static final long serialVersionUID = 7731102609123946115L;
-
- public StyleButton(Composite parent, int swtStyle) {
- super(parent, swtStyle);
- }
-
- }
-
- // class ActionsMouseListener extends MouseAdapter {
- // private static final long serialVersionUID = -1041871937815812149L;
- //
- // @Override
- // public void mouseDown(MouseEvent e) {
- // Object eventSource = e.getSource();
- // if (e.button == 1) {
- // if (eventSource instanceof Button) {
- // Button pressedBtn = (Button) eventSource;
- // String actionId = (String) pressedBtn.getData(KEY_ACTION_ID);
- // switch (actionId) {
- // case ACTION_ID_CREATE_FOLDER:
- // createFolder();
- // break;
- // case ACTION_ID_DELETE:
- // deleteItems();
- // break;
- // default:
- // throw new IllegalArgumentException("Unimplemented action " + actionId);
- // // case ACTION_ID_SHARE_FOLDER:
- // // return "Share Folder";
- // // case ACTION_ID_DOWNLOAD_FOLDER:
- // // return "Download as zip archive";
- // // case ACTION_ID_UPLOAD_FILE:
- // // return "Upload Files";
- // // case ACTION_ID_OPEN:
- // // return "Open";
- // }
- // }
- // }
- // viewer.getControl().setFocus();
- // // setVisible(false);
- // }
- // }
-
- class ActionsSelListener extends SelectionAdapter {
- private static final long serialVersionUID = -1041871937815812149L;
-
- @Override
- public void widgetSelected(SelectionEvent e) {
- Object eventSource = e.getSource();
- if (eventSource instanceof Button) {
- Button pressedBtn = (Button) eventSource;
- String actionId = (String) pressedBtn.getData(KEY_ACTION_ID);
- switch (actionId) {
- case ACTION_ID_CREATE_FOLDER:
- createFolder();
- break;
- case ACTION_ID_DELETE:
- deleteItems();
- break;
- case ACTION_ID_OPEN:
- openFile();
- break;
- case ACTION_ID_UPLOAD_FILE:
- uploadFiles();
- break;
- default:
- throw new IllegalArgumentException("Unimplemented action " + actionId);
- // case ACTION_ID_SHARE_FOLDER:
- // return "Share Folder";
- // case ACTION_ID_DOWNLOAD_FOLDER:
- // return "Download as zip archive";
- // case ACTION_ID_OPEN:
- // return "Open";
- }
- }
- browser.setFocus();
- // viewer.getControl().setFocus();
- // setVisible(false);
-
- }
- }
-
- class ActionsShellListener extends org.eclipse.swt.events.ShellAdapter {
- private static final long serialVersionUID = -5092341449523150827L;
-
- @Override
- public void shellDeactivated(ShellEvent e) {
- setVisible(false);
- }
- }
-
- private void openFile() {
- log.warn("Implement single sourced, workbench independant \"Open File\" action");
- }
-
- private void deleteItems() {
- IStructuredSelection selection = ((IStructuredSelection) browser.getViewer().getSelection());
- if (selection.isEmpty())
- return;
-
- StringBuilder builder = new StringBuilder();
- @SuppressWarnings("unchecked")
- Iterator<Object> iterator = selection.iterator();
- List<Path> paths = new ArrayList<>();
-
- while (iterator.hasNext()) {
- Path path = (Path) iterator.next();
- builder.append(path.getFileName() + ", ");
- paths.add(path);
- }
- String msg = "You are about to delete following elements: " + builder.substring(0, builder.length() - 2)
- + ". Are you sure?";
- if (MessageDialog.openConfirm(this, "Confirm deletion", msg)) {
- for (Path path : paths) {
- try {
- // Might have already been deleted if we are in a tree
- Files.deleteIfExists(path);
- } catch (IOException e) {
- throw new CmsException("Cannot delete path " + path, e);
- }
- }
- browser.refresh();
- }
- }
-
- private void createFolder() {
- String msg = "Please provide a name.";
- String name = SingleValue.ask("Create folder", msg);
- // TODO enhance check of name validity
- if (EclipseUiUtils.notEmpty(name)) {
- try {
- Path child = currFolderPath.resolve(name);
- if (Files.exists(child))
- throw new CmsException("An item with name " + name + " already exists at "
- + currFolderPath.toString() + ", cannot create");
- else
- Files.createDirectories(child);
- browser.refresh();
- } catch (IOException e) {
- throw new CmsException("Cannot create folder " + name + " at " + currFolderPath.toString(), e);
- }
- }
- }
-
- private void uploadFiles() {
- try {
- FileDialog dialog = new FileDialog(browser.getShell(), SWT.MULTI);
- dialog.setText("Choose one or more files to upload");
-
- if (EclipseUiUtils.notEmpty(dialog.open())) {
- String[] names = dialog.getFileNames();
- // Workaround small differences between RAP and RCP
- // 1. returned names are absolute path on RAP and
- // relative in RCP
- // 2. in RCP we must use getFilterPath that does not
- // exists on RAP
- Method filterMethod = null;
- Path parPath = null;
- try {
- filterMethod = dialog.getClass().getDeclaredMethod("getFilterPath");
- String filterPath = (String) filterMethod.invoke(dialog);
- parPath = Paths.get(filterPath);
- } catch (NoSuchMethodException nsme) { // RAP
- }
- if (names.length == 0)
- return;
- else {
- loop: for (String name : names) {
- Path tmpPath = Paths.get(name);
- if (parPath != null)
- tmpPath = parPath.resolve(tmpPath);
- if (Files.exists(tmpPath)) {
- URI uri = tmpPath.toUri();
- String uriStr = uri.toString();
-
- if (Files.isDirectory(tmpPath)) {
- MessageDialog.openError(browser.getShell(), "Unimplemented directory import",
- "Upload of directories in the system is not yet implemented");
- continue loop;
- }
- Path targetPath = currFolderPath.resolve(tmpPath.getFileName().toString());
- InputStream in = null;
- try {
- in = new ByteArrayInputStream(Files.readAllBytes(tmpPath));
- Files.copy(in, targetPath);
- Files.delete(tmpPath);
- } finally {
- IOUtils.closeQuietly(in);
- }
- if (log.isDebugEnabled())
- log.debug("copied uploaded file " + uriStr + " to " + targetPath.toString());
- } else {
- String msg = "Cannot copy tmp file from " + tmpPath.toString();
- if (parPath != null)
- msg += "\nPlease remember that file upload fails when choosing files from the \"Recently Used\" bookmarks on some OS";
- MessageDialog.openError(browser.getShell(), "Missing file", msg);
- continue loop;
- }
- }
- browser.refresh();
- }
- }
- } catch (Exception e) {
- e.printStackTrace();
- MessageDialog.openError(getShell(), "Upload has failed", "Cannot import files to " + currFolderPath);
- }
- }
-
- public void setCurrFolderPath(Path currFolderPath) {
- this.currFolderPath = currFolderPath;
- }
-}
+++ /dev/null
-package org.argeo.cms.ui.fs;
-
-/** FS Ui specific CSS styles */
-public interface FsStyles {
- String BREAD_CRUMB_BTN = "breadCrumb_btn";
- String CONTEXT_MENU_BOX = "contextMenu_box";
- String BUTTON_SUFFIX = "_btn";
-}
+++ /dev/null
-/** SWT/JFace file system components. */
-package org.argeo.cms.ui.fs;
\ No newline at end of file
+++ /dev/null
-package org.argeo.cms.ui.internal;
-
-import org.argeo.api.cms.CmsState;
-import org.osgi.framework.BundleActivator;
-import org.osgi.framework.BundleContext;
-import org.osgi.util.tracker.ServiceTracker;
-
-public class Activator implements BundleActivator {
-
- // avoid dependency to RWT OSGi
- private final static String CONTEXT_NAME_PROP = "contextName";
-
- private static ServiceTracker<CmsState, CmsState> nodeState;
-
- // @Override
- public void start(BundleContext bc) throws Exception {
- // UI
-// bc.registerService(ApplicationConfiguration.class, new MaintenanceUi(),
-// LangUtils.dico(CONTEXT_NAME_PROP, "system"));
-// bc.registerService(ApplicationConfiguration.class, new UserUi(), LangUtils.dico(CONTEXT_NAME_PROP, "user"));
-
- nodeState = new ServiceTracker<>(bc, CmsState.class, null);
- nodeState.open();
- }
-
- @Override
- public void stop(BundleContext context) throws Exception {
- if (nodeState != null) {
- nodeState.close();
- nodeState = null;
- }
- }
-
- public static CmsState getNodeState() {
- return nodeState.getService();
- }
-}
+++ /dev/null
-package org.argeo.cms.ui.internal;
-
-import java.util.ArrayList;
-
-import javax.jcr.Node;
-import javax.jcr.NodeIterator;
-import javax.jcr.RepositoryException;
-
-import org.argeo.cms.CmsException;
-import org.eclipse.jface.viewers.ITreeContentProvider;
-import org.eclipse.jface.viewers.Viewer;
-
-@Deprecated
-class JcrContentProvider implements ITreeContentProvider {
- private static final long serialVersionUID = -1333678161322488674L;
-
- @Override
- public void dispose() {
- }
-
- @Override
- public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
- if (newInput == null)
- return;
- if (!(newInput instanceof Node))
- throw new CmsException("Input " + newInput + " must be a node");
- }
-
- @Override
- public Object[] getElements(Object inputElement) {
- try {
- Node node = (Node) inputElement;
- ArrayList<Node> arr = new ArrayList<Node>();
- NodeIterator nit = node.getNodes();
- while (nit.hasNext()) {
- arr.add(nit.nextNode());
- }
- return arr.toArray();
- } catch (RepositoryException e) {
- throw new CmsException("Cannot get elements", e);
- }
- }
-
- @Override
- public Object[] getChildren(Object parentElement) {
- try {
- Node node = (Node) parentElement;
- ArrayList<Node> arr = new ArrayList<Node>();
- NodeIterator nit = node.getNodes();
- while (nit.hasNext()) {
- arr.add(nit.nextNode());
- }
- return arr.toArray();
- } catch (RepositoryException e) {
- throw new CmsException("Cannot get elements", e);
- }
- }
-
- @Override
- public Object getParent(Object element) {
- try {
- Node node = (Node) element;
- if (node.getName().equals(""))
- return null;
- else
- return node.getParent();
- } catch (RepositoryException e) {
- throw new CmsException("Cannot get elements", e);
- }
- }
-
- @Override
- public boolean hasChildren(Object element) {
- try {
- Node node = (Node) element;
- return node.hasNodes();
- } catch (RepositoryException e) {
- throw new CmsException("Cannot get elements", e);
- }
- }
-}
+++ /dev/null
-package org.argeo.cms.ui.internal;
-
-import static javax.jcr.nodetype.NodeType.NT_FILE;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-import javax.jcr.Node;
-import javax.jcr.Property;
-import javax.jcr.RepositoryException;
-import javax.jcr.nodetype.NodeType;
-
-import org.apache.commons.io.FilenameUtils;
-import org.argeo.api.cms.CmsImageManager;
-import org.argeo.cms.ui.widgets.Img;
-import org.argeo.jcr.JcrException;
-import org.argeo.jcr.JcrUtils;
-import org.eclipse.rap.fileupload.FileDetails;
-import org.eclipse.rap.fileupload.FileUploadReceiver;
-
-public class JcrFileUploadReceiver extends FileUploadReceiver {
- private Img img;
- private final Node parentNode;
- private final String nodeName;
- private final CmsImageManager imageManager;
-
- /** If nodeName is null, use the uploaded file name */
- public JcrFileUploadReceiver(Img img, Node parentNode, String nodeName, CmsImageManager imageManager) {
- super();
- this.img = img;
- this.parentNode = parentNode;
- this.nodeName = nodeName;
- this.imageManager = imageManager;
- }
-
- @Override
- public void receive(InputStream stream, FileDetails details) throws IOException {
- try {
- String fileName = nodeName != null ? nodeName : details.getFileName();
- String contentType = details.getContentType();
- if (isImage(details.getFileName(), contentType)) {
- imageManager.uploadImage(img.getNode(),parentNode, fileName, stream, contentType);
- return;
- }
-
- Node fileNode;
- if (parentNode.hasNode(fileName)) {
- fileNode = parentNode.getNode(fileName);
- if (!fileNode.isNodeType(NT_FILE))
- fileNode.remove();
- }
- fileNode = JcrUtils.copyStreamAsFile(parentNode, fileName, stream);
-
- if (contentType != null) {
- fileNode.addMixin(NodeType.MIX_MIMETYPE);
- fileNode.setProperty(Property.JCR_MIMETYPE, contentType);
- }
- processNewFile(fileNode);
- fileNode.getSession().save();
- } catch (RepositoryException e) {
- throw new JcrException("Cannot receive " + details, e);
- }
- }
-
- protected Boolean isImage(String fileName, String contentType) {
- String ext = FilenameUtils.getExtension(fileName);
- return ext != null && (ext.equals("png") || ext.equalsIgnoreCase("jpg"));
- }
-
- protected void processNewFile(Node node) {
-
- }
-
-}
+++ /dev/null
-package org.argeo.cms.ui.internal;
-
-import javax.jcr.RepositoryException;
-
-import org.argeo.api.cms.Cms2DSize;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.cms.ui.util.CmsUiUtils;
-import org.argeo.cms.ui.widgets.EditableImage;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Text;
-
-/** NOT working yet. */
-public class SimpleEditableImage extends EditableImage {
- private static final long serialVersionUID = -5689145523114022890L;
-
- private String src;
- private Cms2DSize imageSize;
-
- public SimpleEditableImage(Composite parent, int swtStyle) {
- super(parent, swtStyle);
- // load(getControl());
- getParent().layout();
- }
-
- public SimpleEditableImage(Composite parent, int swtStyle, String src, Cms2DSize imageSize) {
- super(parent, swtStyle);
- this.src = src;
- this.imageSize = imageSize;
- }
-
- @Override
- protected Control createControl(Composite box, String style) {
- if (isEditing()) {
- return createText(box, style);
- } else {
- return createLabel(box, style);
- }
- }
-
- protected String createImgTag() throws RepositoryException {
- String imgTag;
- if (src != null)
- imgTag = CmsUiUtils.img(src, imageSize);
- else
- imgTag = CmsUiUtils.noImg(imageSize != null ? imageSize : NO_IMAGE_SIZE);
- return imgTag;
- }
-
- protected Text createText(Composite box, String style) {
- Text text = new Text(box, getStyle());
- CmsSwtUtils.style(text, style);
- return text;
- }
-
- public String getSrc() {
- return src;
- }
-
- public void setSrc(String src) {
- this.src = src;
- }
-
- public Cms2DSize getImageSize() {
- return imageSize;
- }
-
- public void setImageSize(Cms2DSize imageSize) {
- this.imageSize = imageSize;
- }
-
-}
+++ /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.eclipse.ui.EclipseUiException;
-import org.argeo.eclipse.ui.TreeParent;
-
-/** 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.eclipse.ui.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.cms.ui.CmsUiConstants;
-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;
-
- // Utils
- protected DateFormat timeFormatter = new SimpleDateFormat(CmsUiConstants.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.eclipse.ui.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.eclipse.ui.EclipseUiException;
-import org.argeo.eclipse.ui.TreeParent;
-
-/** 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.eclipse.ui.EclipseUiException;
-import org.argeo.eclipse.ui.TreeParent;
-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.eclipse.ui.EclipseUiException;
-import org.argeo.eclipse.ui.TreeParent;
-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.eclipse.ui.EclipseUiException;
-import org.argeo.eclipse.ui.TreeParent;
-
-/**
- * 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.eclipse.ui.EclipseUiException;
-import org.argeo.eclipse.ui.TreeParent;
-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
-/** SWT/JFace components for Argeo CMS. */
-package org.argeo.cms.ui;
\ No newline at end of file
+++ /dev/null
-package org.argeo.cms.ui.util;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.MalformedURLException;
-import java.net.URL;
-
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-
-import org.argeo.api.cms.CmsStyle;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.cms.auth.CurrentUser;
-import org.argeo.cms.jcr.CmsJcrUtils;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.cms.ui.CmsUiProvider;
-import org.argeo.jcr.JcrException;
-import org.eclipse.rap.rwt.RWT;
-import org.eclipse.rap.rwt.service.ResourceManager;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.events.MouseListener;
-import org.eclipse.swt.graphics.ImageData;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Label;
-import org.osgi.framework.BundleContext;
-
-/** A link to an internal or external location. */
-public class CmsLink implements CmsUiProvider {
- private final static CmsLog log = CmsLog.getLog(CmsLink.class);
- private BundleContext bundleContext;
-
- private String label;
- private String style;
- private String target;
- private String image;
- private boolean openNew = false;
- private MouseListener mouseListener;
-
- private int horizontalAlignment = SWT.CENTER;
- private int verticalAlignment = SWT.CENTER;
-
- private String loggedInLabel = null;
- private String loggedInTarget = null;
-
- // internal
- // private Boolean isUrl = false;
- private Integer imageWidth, imageHeight;
-
- public CmsLink() {
- super();
- }
-
- public CmsLink(String label, String target) {
- this(label, target, (String) null);
- }
-
- public CmsLink(String label, String target, CmsStyle style) {
- this(label, target, style != null ? style.style() : null);
- }
-
- public CmsLink(String label, String target, String style) {
- super();
- this.label = label;
- this.target = target;
- this.style = style;
- init();
- }
-
- public void init() {
- if (image != null) {
- ImageData image = loadImage();
- if (imageHeight == null && imageWidth == null) {
- imageWidth = image.width;
- imageHeight = image.height;
- } else if (imageHeight == null) {
- imageHeight = (imageWidth * image.height) / image.width;
- } else if (imageWidth == null) {
- imageWidth = (imageHeight * image.width) / image.height;
- }
- }
- }
-
- /** @return {@link Composite} with a single {@link Label} child. */
- @Override
- public Control createUi(final Composite parent, Node context) {
-// if (image != null && (imageWidth == null || imageHeight == null)) {
-// throw new CmsException("Image is not properly configured."
-// + " Make sure bundleContext property is set and init() method has been called.");
-// }
-
- Composite comp = new Composite(parent, SWT.NONE);
- comp.setLayout(CmsSwtUtils.noSpaceGridLayout());
-
- Label link = new Label(comp, SWT.NONE);
- CmsSwtUtils.markup(link);
- GridData layoutData = new GridData(horizontalAlignment, verticalAlignment, false, false);
- if (image != null) {
- if (imageHeight != null)
- layoutData.heightHint = imageHeight;
- if (label == null)
- if (imageWidth != null)
- layoutData.widthHint = imageWidth;
- }
-
- link.setLayoutData(layoutData);
- CmsSwtUtils.style(comp, style != null ? style : getDefaultStyle());
- CmsSwtUtils.style(link, style != null ? style : getDefaultStyle());
-
- // label
- StringBuilder labelText = new StringBuilder();
- if (loggedInTarget != null && isLoggedIn()) {
- labelText.append("<a style='color:inherit;text-decoration:inherit;' href=\"");
- if (loggedInTarget.equals("")) {
- try {
- Node homeNode = CmsJcrUtils.getUserHome(context.getSession());
- String homePath = homeNode.getPath();
- labelText.append("/#" + homePath);
- } catch (RepositoryException e) {
- throw new JcrException("Cannot get home path", e);
- }
- } else {
- labelText.append(loggedInTarget);
- }
- labelText.append("\">");
- } else if (target != null) {
- labelText.append("<a style='color:inherit;text-decoration:inherit;' href='");
- labelText.append(target).append("'");
- if (openNew) {
- labelText.append(" target='_blank'");
- }
- labelText.append(">");
- }
- if (image != null) {
- registerImageIfNeeded();
- String imageLocation = RWT.getResourceManager().getLocation(image);
- labelText.append("<img");
- if (imageWidth != null)
- labelText.append(" width='").append(imageWidth).append('\'');
- if (imageHeight != null)
- labelText.append(" height='").append(imageHeight).append('\'');
- labelText.append(" src=\"").append(imageLocation).append("\"/>");
-
- }
-
- if (loggedInLabel != null && isLoggedIn()) {
- labelText.append(' ').append(loggedInLabel);
- } else if (label != null) {
- labelText.append(' ').append(label);
- }
-
- if ((loggedInTarget != null && isLoggedIn()) || target != null)
- labelText.append("</a>");
-
- link.setText(labelText.toString());
-
- if (mouseListener != null)
- link.addMouseListener(mouseListener);
-
- return comp;
- }
-
- private void registerImageIfNeeded() {
- ResourceManager resourceManager = RWT.getResourceManager();
- if (!resourceManager.isRegistered(image)) {
- URL res = getImageUrl();
- try (InputStream inputStream = res.openStream()) {
- resourceManager.register(image, inputStream);
- if (log.isTraceEnabled())
- log.trace("Registered image " + image);
- } catch (IOException e) {
- throw new RuntimeException("Cannot load image " + image, e);
- }
- }
- }
-
- private ImageData loadImage() {
- URL url = getImageUrl();
- ImageData result = null;
- try (InputStream inputStream = url.openStream()) {
- result = new ImageData(inputStream);
- if (log.isTraceEnabled())
- log.trace("Loaded image " + image);
- } catch (IOException e) {
- throw new RuntimeException("Cannot load image " + image, e);
- }
- return result;
- }
-
- private URL getImageUrl() {
- URL url;
- try {
- // pure URL
- url = new URL(image);
- } catch (MalformedURLException e1) {
- url = bundleContext.getBundle().getResource(image);
- }
-
- if (url == null)
- throw new IllegalStateException("No image " + image + " available.");
-
- return url;
- }
-
- public void setBundleContext(BundleContext bundleContext) {
- this.bundleContext = bundleContext;
- }
-
- public void setLabel(String label) {
- this.label = label;
- }
-
- public void setStyle(String style) {
- this.style = style;
- }
-
- /** @deprecated Use {@link #setStyle(String)} instead. */
- @Deprecated
- public void setCustom(String custom) {
- this.style = custom;
- }
-
- public void setTarget(String target) {
- this.target = target;
- // try {
- // new URL(target);
- // isUrl = true;
- // } catch (MalformedURLException e1) {
- // isUrl = false;
- // }
- }
-
- public void setImage(String image) {
- this.image = image;
- }
-
- public void setLoggedInLabel(String loggedInLabel) {
- this.loggedInLabel = loggedInLabel;
- }
-
- public void setLoggedInTarget(String loggedInTarget) {
- this.loggedInTarget = loggedInTarget;
- }
-
- public void setMouseListener(MouseListener mouseListener) {
- this.mouseListener = mouseListener;
- }
-
- public void setvAlign(String vAlign) {
- if ("bottom".equals(vAlign)) {
- verticalAlignment = SWT.BOTTOM;
- } else if ("top".equals(vAlign)) {
- verticalAlignment = SWT.TOP;
- } else if ("center".equals(vAlign)) {
- verticalAlignment = SWT.CENTER;
- } else {
- throw new IllegalArgumentException(
- "Unsupported vertical alignment " + vAlign + " (must be: top, bottom or center)");
- }
- }
-
- protected boolean isLoggedIn() {
- return !CurrentUser.isAnonymous();
- }
-
- public void setImageWidth(Integer imageWidth) {
- this.imageWidth = imageWidth;
- }
-
- public void setImageHeight(Integer imageHeight) {
- this.imageHeight = imageHeight;
- }
-
- public void setOpenNew(boolean openNew) {
- this.openNew = openNew;
- }
-
- protected String getDefaultStyle() {
- return SimpleStyle.link.name();
- }
-}
+++ /dev/null
-package org.argeo.cms.ui.util;
-
-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.layout.RowLayout;
-import org.eclipse.swt.widgets.Composite;
-
-/** The main pane of a CMS display, with QA and support areas. */
-public class CmsPane {
-
- private Composite mainArea;
- private Composite qaArea;
- private Composite supportArea;
-
- public CmsPane(Composite parent, int style) {
- parent.setLayout(CmsSwtUtils.noSpaceGridLayout());
-
-// qaArea = new Composite(parent, SWT.NONE);
-// qaArea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
-// RowLayout qaLayout = new RowLayout();
-// qaLayout.spacing = 0;
-// qaArea.setLayout(qaLayout);
-
- mainArea = new Composite(parent, SWT.NONE);
- mainArea.setLayout(new GridLayout());
- mainArea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
-
-// supportArea = new Composite(parent, SWT.NONE);
-// supportArea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
-// RowLayout supportLayout = new RowLayout();
-// supportLayout.spacing = 0;
-// supportArea.setLayout(supportLayout);
- }
-
- public Composite getMainArea() {
- return mainArea;
- }
-
- public Composite getQaArea() {
- return qaArea;
- }
-
- public Composite getSupportArea() {
- return supportArea;
- }
-
-}
+++ /dev/null
-package org.argeo.cms.ui.util;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.net.URLEncoder;
-import java.nio.charset.StandardCharsets;
-import java.util.StringTokenizer;
-
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-import javax.servlet.http.HttpServletRequest;
-
-import org.argeo.api.cms.Cms2DSize;
-import org.argeo.api.cms.CmsView;
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.cms.jcr.CmsJcrUtils;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.cms.ui.CmsUiConstants;
-import org.argeo.jcr.JcrUtils;
-import org.eclipse.rap.rwt.RWT;
-import org.eclipse.rap.rwt.service.ResourceManager;
-import org.eclipse.swt.graphics.Image;
-import org.eclipse.swt.graphics.ImageData;
-import org.eclipse.swt.layout.RowData;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Display;
-import org.eclipse.swt.widgets.Table;
-
-/** Static utilities for the CMS framework. */
-public class CmsUiUtils {
- // private final static Log log = LogFactory.getLog(CmsUiUtils.class);
-
- /*
- * CMS VIEW
- */
-
- /**
- * The CMS view related to this display, or null if none is available from this
- * call.
- *
- * @deprecated Use {@link CmsSwtUtils#getCmsView(Composite)} instead.
- */
- @Deprecated
- public static CmsView getCmsView() {
-// return UiContext.getData(CmsView.class.getName());
- return CmsSwtUtils.getCmsView(Display.getCurrent().getActiveShell());
- }
-
- public static StringBuilder getServerBaseUrl(HttpServletRequest request) {
- try {
- URL url = new URL(request.getRequestURL().toString());
- StringBuilder buf = new StringBuilder();
- buf.append(url.getProtocol()).append("://").append(url.getHost());
- if (url.getPort() != -1)
- buf.append(':').append(url.getPort());
- return buf;
- } catch (MalformedURLException e) {
- throw new IllegalArgumentException("Cannot extract server base URL from " + request.getRequestURL(), e);
- }
- }
-
- //
- public static String getDataUrl(Node node, HttpServletRequest request) {
- try {
- StringBuilder buf = getServerBaseUrl(request);
- buf.append(getDataPath(node));
- return new URL(buf.toString()).toString();
- } catch (MalformedURLException e) {
- throw new IllegalArgumentException("Cannot build data URL for " + node, e);
- }
- }
-
- /** A path in the node repository */
- public static String getDataPath(Node node) {
- return getDataPath(CmsConstants.EGO_REPOSITORY, node);
- }
-
- public static String getDataPath(String cn, Node node) {
- return CmsJcrUtils.getDataPath(cn, node);
- }
-
- /** Clean reserved URL characters for use in HTTP links. */
- public static String getDataPathForUrl(Node node) {
- return cleanPathForUrl(getDataPath(node));
- }
-
- /** Clean reserved URL characters for use in HTTP links. */
- public static String cleanPathForUrl(String path) {
- StringTokenizer st = new StringTokenizer(path, "/");
- StringBuilder sb = new StringBuilder();
- while (st.hasMoreElements()) {
- sb.append('/');
- String encoded = URLEncoder.encode(st.nextToken(), StandardCharsets.UTF_8);
- encoded = encoded.replace("+", "%20");
- sb.append(encoded);
-
- }
- return sb.toString();
- }
-
- /** @deprecated Use rowData16px() instead. GridData should not be reused. */
- @Deprecated
- public static RowData ROW_DATA_16px = new RowData(16, 16);
-
-
-
- /*
- * FORM LAYOUT
- */
-
-
-
- @Deprecated
- public static void setItemHeight(Table table, int height) {
- table.setData(CmsUiConstants.ITEM_HEIGHT, height);
- }
-
- //
- // JCR
- //
- public static Node getOrAddEmptyFile(Node parent, Enum<?> child) throws RepositoryException {
- if (has(parent, child))
- return child(parent, child);
- return JcrUtils.copyBytesAsFile(parent, child.name(), new byte[0]);
- }
-
- public static Node child(Node parent, Enum<?> en) throws RepositoryException {
- return parent.getNode(en.name());
- }
-
- public static Boolean has(Node parent, Enum<?> en) throws RepositoryException {
- return parent.hasNode(en.name());
- }
-
- public static Node getOrAdd(Node parent, Enum<?> en) throws RepositoryException {
- return getOrAdd(parent, en, null);
- }
-
- public static Node getOrAdd(Node parent, Enum<?> en, String primaryType) throws RepositoryException {
- if (has(parent, en))
- return child(parent, en);
- else if (primaryType == null)
- return parent.addNode(en.name());
- else
- return parent.addNode(en.name(), primaryType);
- }
-
- // IMAGES
-
- public static String img(Node fileNode, String width, String height) {
- return img(null, fileNode, width, height);
- }
-
- public static String img(String serverBase, Node fileNode, String width, String height) {
-// String src = (serverBase != null ? serverBase : "") + NodeUtils.getDataPath(fileNode);
- String src;
- src = (serverBase != null ? serverBase : "") + getDataPathForUrl(fileNode);
- return imgBuilder(src, width, height).append("/>").toString();
- }
-
- public static String img(String src, String width, String height) {
- return imgBuilder(src, width, height).append("/>").toString();
- }
-
- public static String img(String src, Cms2DSize size) {
- return img(src, Integer.toString(size.getWidth()), Integer.toString(size.getHeight()));
- }
-
- public static StringBuilder imgBuilder(String src, String width, String height) {
- return new StringBuilder(64).append("<img width='").append(width).append("' height='").append(height)
- .append("' src='").append(src).append("'");
- }
-
- public static String noImg(Cms2DSize size) {
- ResourceManager rm = RWT.getResourceManager();
- return CmsUiUtils.img(rm.getLocation(CmsUiConstants.NO_IMAGE), size);
- }
-
- public static String noImg() {
- return noImg(CmsUiConstants.NO_IMAGE_SIZE);
- }
-
- public static Image noImage(Cms2DSize size) {
- ResourceManager rm = RWT.getResourceManager();
- InputStream in = null;
- try {
- in = rm.getRegisteredContent(CmsUiConstants.NO_IMAGE);
- ImageData id = new ImageData(in);
- ImageData scaled = id.scaledTo(size.getWidth(), size.getHeight());
- Image image = new Image(Display.getCurrent(), scaled);
- return image;
- } finally {
- try {
- in.close();
- } catch (IOException e) {
- // silent
- }
- }
- }
-
- /** Lorem ipsum text to be used during development. */
- public final static String LOREM_IPSUM = "Lorem ipsum dolor sit amet, consectetur adipiscing elit."
- + " Etiam eleifend hendrerit sem, ac ultricies massa ornare ac."
- + " Cras aliquam sodales risus, vitae varius lacus molestie quis."
- + " Vivamus consequat, leo id lacinia volutpat, eros diam efficitur urna, finibus interdum risus turpis at nisi."
- + " Curabitur vulputate nulla quis scelerisque fringilla. Integer consectetur turpis id lobortis accumsan."
- + " Pellentesque commodo turpis ac diam ultricies dignissim."
- + " Curabitur sit amet dolor volutpat lacus aliquam ornare quis sed velit."
- + " Integer varius quis est et tristique."
- + " Suspendisse pharetra porttitor purus, eget condimentum magna."
- + " Duis vitae turpis eros. Sed tincidunt lacinia rutrum."
- + " Aliquam velit velit, rutrum ut augue sed, condimentum lacinia augue.";
-
- /** Singleton. */
- private CmsUiUtils() {
- }
-
-}
+++ /dev/null
-package org.argeo.cms.ui.util;
-
-import static javax.jcr.Node.JCR_CONTENT;
-import static javax.jcr.Property.JCR_DATA;
-import static javax.jcr.nodetype.NodeType.NT_FILE;
-import static javax.jcr.nodetype.NodeType.NT_RESOURCE;
-import static org.argeo.cms.ui.CmsUiConstants.NO_IMAGE_SIZE;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.file.Files;
-import java.nio.file.Paths;
-
-import javax.jcr.Binary;
-import javax.jcr.Node;
-import javax.jcr.Property;
-import javax.jcr.RepositoryException;
-
-import org.apache.commons.io.IOUtils;
-import org.argeo.api.cms.Cms2DSize;
-import org.argeo.api.cms.CmsImageManager;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.jcr.JcrException;
-import org.argeo.jcr.JcrUtils;
-import org.eclipse.rap.rwt.RWT;
-import org.eclipse.rap.rwt.service.ResourceManager;
-import org.eclipse.rap.rwt.widgets.FileUpload;
-import org.eclipse.swt.graphics.Image;
-import org.eclipse.swt.graphics.ImageData;
-import org.eclipse.swt.graphics.Point;
-import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Display;
-import org.eclipse.swt.widgets.Label;
-
-/** Manages only public images so far. */
-public class DefaultImageManager implements CmsImageManager<Control, Node> {
- private final static CmsLog log = CmsLog.getLog(DefaultImageManager.class);
-// private MimetypesFileTypeMap fileTypeMap = new MimetypesFileTypeMap();
-
- public Boolean load(Node node, Control control, Cms2DSize preferredSize) {
- Cms2DSize imageSize = getImageSize(node);
- Cms2DSize size;
- String imgTag = null;
- if (preferredSize == null || imageSize.getWidth() == 0 || imageSize.getHeight() == 0
- || (preferredSize.getWidth() == 0 && preferredSize.getHeight() == 0)) {
- if (imageSize.getWidth() != 0 && imageSize.getHeight() != 0) {
- // actual image size if completely known
- size = imageSize;
- } else {
- // no image if not completely known
- size = resizeTo(NO_IMAGE_SIZE, preferredSize != null ? preferredSize : imageSize);
- imgTag = CmsUiUtils.noImg(size);
- }
-
- } else if (preferredSize.getWidth() != 0 && preferredSize.getHeight() != 0) {
- // given size if completely provided
- size = preferredSize;
- } else {
- // at this stage :
- // image is completely known
- assert imageSize.getWidth() != 0 && imageSize.getHeight() != 0;
- // one and only one of the dimension as been specified
- assert preferredSize.getWidth() == 0 || preferredSize.getHeight() == 0;
- size = resizeTo(imageSize, preferredSize);
- }
-
- boolean loaded = false;
- if (control == null)
- return loaded;
-
- if (control instanceof Label) {
- if (imgTag == null) {
- // IMAGE RETRIEVED HERE
- imgTag = getImageTag(node, size);
- //
- if (imgTag == null)
- imgTag = CmsUiUtils.noImg(size);
- else
- loaded = true;
- }
-
- Label lbl = (Label) control;
- lbl.setText(imgTag);
- // lbl.setSize(size);
- } else if (control instanceof FileUpload) {
- FileUpload lbl = (FileUpload) control;
- lbl.setImage(CmsUiUtils.noImage(size));
- lbl.setSize(new Point(size.getWidth(), size.getHeight()));
- return loaded;
- } else
- loaded = false;
-
- return loaded;
- }
-
- private Cms2DSize resizeTo(Cms2DSize orig, Cms2DSize constraints) {
- if (constraints.getWidth() != 0 && constraints.getHeight() != 0) {
- return constraints;
- } else if (constraints.getWidth() == 0 && constraints.getHeight() == 0) {
- return orig;
- } else if (constraints.getHeight() == 0) {// force width
- return new Cms2DSize(constraints.getWidth(),
- scale(orig.getHeight(), orig.getWidth(), constraints.getWidth()));
- } else if (constraints.getWidth() == 0) {// force height
- return new Cms2DSize(scale(orig.getWidth(), orig.getHeight(), constraints.getHeight()),
- constraints.getHeight());
- }
- throw new IllegalArgumentException("Cannot resize " + orig + " to " + constraints);
- }
-
- private int scale(int origDimension, int otherDimension, int otherConstraint) {
- return Math.round(origDimension * divide(otherConstraint, otherDimension));
- }
-
- private float divide(int a, int b) {
- return ((float) a) / ((float) b);
- }
-
- public Cms2DSize getImageSize(Node node) {
- // TODO optimise
- Image image = getSwtImage(node);
- return new Cms2DSize(image.getBounds().width, image.getBounds().height);
- }
-
- /** @return null if not available */
- @Override
- public String getImageTag(Node node) {
- return getImageTag(node, getImageSize(node));
- }
-
- private String getImageTag(Node node, Cms2DSize size) {
- StringBuilder buf = getImageTagBuilder(node, size);
- if (buf == null)
- return null;
- return buf.append("/>").toString();
- }
-
- /** @return null if not available */
- @Override
- public StringBuilder getImageTagBuilder(Node node, Cms2DSize size) {
- return getImageTagBuilder(node, Integer.toString(size.getWidth()), Integer.toString(size.getHeight()));
- }
-
- /** @return null if not available */
- private StringBuilder getImageTagBuilder(Node node, String width, String height) {
- String url = getImageUrl(node);
- if (url == null)
- return null;
- return CmsUiUtils.imgBuilder(url, width, height);
- }
-
- /** @return null if not available */
- @Override
- public String getImageUrl(Node node) {
- return CmsUiUtils.getDataPathForUrl(node);
- }
-
- protected String getResourceName(Node node) {
- try {
- String workspace = node.getSession().getWorkspace().getName();
- if (node.hasNode(JCR_CONTENT))
- return workspace + '_' + node.getNode(JCR_CONTENT).getIdentifier();
- else
- return workspace + '_' + node.getIdentifier();
- } catch (RepositoryException e) {
- throw new JcrException(e);
- }
- }
-
- public Binary getImageBinary(Node node) {
- try {
- if (node.isNodeType(NT_FILE)) {
- return node.getNode(JCR_CONTENT).getProperty(JCR_DATA).getBinary();
- } else {
- return null;
- }
- } catch (RepositoryException e) {
- throw new JcrException(e);
- }
- }
-
- public Image getSwtImage(Node node) {
- InputStream inputStream = null;
- Binary binary = getImageBinary(node);
- if (binary == null)
- return null;
- try {
- inputStream = binary.getStream();
- return new Image(Display.getCurrent(), inputStream);
- } catch (RepositoryException e) {
- throw new JcrException(e);
- } finally {
- IOUtils.closeQuietly(inputStream);
- JcrUtils.closeQuietly(binary);
- }
- }
-
- @Override
- public String uploadImage(Node context, Node parentNode, String fileName, InputStream in, String contentType) {
- InputStream inputStream = null;
- try {
- String previousResourceName = null;
- if (parentNode.hasNode(fileName)) {
- Node node = parentNode.getNode(fileName);
- previousResourceName = getResourceName(node);
- if (node.hasNode(JCR_CONTENT)) {
- node.getNode(JCR_CONTENT).remove();
- node.addNode(JCR_CONTENT, NT_RESOURCE);
- }
- }
-
- byte[] arr = IOUtils.toByteArray(in);
- Node fileNode = JcrUtils.copyBytesAsFile(parentNode, fileName, arr);
- inputStream = new ByteArrayInputStream(arr);
- ImageData id = new ImageData(inputStream);
- processNewImageFile(context, fileNode, id);
-
- String mime = contentType != null ? contentType : Files.probeContentType(Paths.get(fileName));
- if (mime != null) {
- fileNode.getNode(JCR_CONTENT).setProperty(Property.JCR_MIMETYPE, mime);
- }
- fileNode.getSession().save();
-
- // reset resource manager
- ResourceManager resourceManager = RWT.getResourceManager();
- if (previousResourceName != null && resourceManager.isRegistered(previousResourceName)) {
- resourceManager.unregister(previousResourceName);
- if (log.isDebugEnabled())
- log.debug("Unregistered image " + previousResourceName);
- }
- return CmsUiUtils.getDataPath(fileNode);
- } catch (IOException e) {
- throw new RuntimeException("Cannot upload image " + fileName + " in " + parentNode, e);
- } catch (RepositoryException e) {
- throw new JcrException(e);
- } finally {
- IOUtils.closeQuietly(inputStream);
- }
- }
-
- /** Does nothing by default. */
- protected void processNewImageFile(Node context, Node fileNode, ImageData id)
- throws RepositoryException, IOException {
- }
-}
+++ /dev/null
-package org.argeo.cms.ui.util;
-
-import org.argeo.cms.swt.CmsStyles;
-
-/**
- * Convenience class setting the custom style {@link CmsStyles#CMS_MENU_LINK} on
- * a {@link CmsLink} when simple menus are used.
- */
-public class MenuLink extends CmsLink {
- public MenuLink() {
- setCustom(CmsStyles.CMS_MENU_LINK);
- }
-
- public MenuLink(String label, String target, String custom) {
- super(label, target, custom);
- }
-
- public MenuLink(String label, String target) {
- super(label, target, CmsStyles.CMS_MENU_LINK);
- }
-
-}
+++ /dev/null
-package org.argeo.cms.ui.util;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-
-import org.argeo.cms.CmsException;
-import org.argeo.cms.swt.CmsStyles;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.cms.ui.CmsUiProvider;
-import org.eclipse.rap.rwt.RWT;
-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;
-
-/** A header in three parts */
-public class SimpleCmsHeader implements CmsUiProvider {
- private List<CmsUiProvider> lead = new ArrayList<CmsUiProvider>();
- private List<CmsUiProvider> center = new ArrayList<CmsUiProvider>();
- private List<CmsUiProvider> end = new ArrayList<CmsUiProvider>();
-
- private Boolean subPartsSameWidth = false;
-
- @Override
- public Control createUi(Composite parent, Node context) throws RepositoryException {
- Composite header = new Composite(parent, SWT.NONE);
- header.setData(RWT.CUSTOM_VARIANT, CmsStyles.CMS_HEADER);
- header.setBackgroundMode(SWT.INHERIT_DEFAULT);
- header.setLayout(CmsSwtUtils.noSpaceGridLayout(new GridLayout(3, false)));
-
- configurePart(context, header, lead);
- configurePart(context, header, center);
- configurePart(context, header, end);
- return header;
- }
-
- protected void configurePart(Node context, Composite parent, List<CmsUiProvider> partProviders)
- throws RepositoryException {
- final int style;
- final String custom;
- if (lead == partProviders) {
- style = SWT.LEAD;
- custom = CmsStyles.CMS_HEADER_LEAD;
- } else if (center == partProviders) {
- style = SWT.CENTER;
- custom = CmsStyles.CMS_HEADER_CENTER;
- } else if (end == partProviders) {
- style = SWT.END;
- custom = CmsStyles.CMS_HEADER_END;
- } else {
- throw new CmsException("Unsupported part providers " + partProviders);
- }
-
- Composite part = new Composite(parent, SWT.NONE);
- part.setData(RWT.CUSTOM_VARIANT, custom);
- GridData gridData = new GridData(style, SWT.FILL, true, true);
- part.setLayoutData(gridData);
- part.setLayout(CmsSwtUtils.noSpaceGridLayout(new GridLayout(partProviders.size(), subPartsSameWidth)));
- for (CmsUiProvider uiProvider : partProviders) {
- Control subPart = uiProvider.createUi(part, context);
- subPart.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
- }
- }
-
- public void setLead(List<CmsUiProvider> lead) {
- this.lead = lead;
- }
-
- public void setCenter(List<CmsUiProvider> center) {
- this.center = center;
- }
-
- public void setEnd(List<CmsUiProvider> end) {
- this.end = end;
- }
-
- public void setSubPartsSameWidth(Boolean subPartsSameWidth) {
- this.subPartsSameWidth = subPartsSameWidth;
- }
-
- public List<CmsUiProvider> getLead() {
- return lead;
- }
-
- public List<CmsUiProvider> getCenter() {
- return center;
- }
-
- public List<CmsUiProvider> getEnd() {
- return end;
- }
-
-}
+++ /dev/null
-package org.argeo.cms.ui.util;
-
-import java.text.DateFormat;
-import java.text.SimpleDateFormat;
-
-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.cms.CmsException;
-import org.argeo.cms.ui.CmsUiProvider;
-import org.argeo.jcr.JcrUtils;
-import org.eclipse.rap.rwt.RWT;
-import org.eclipse.swt.SWT;
-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 SimpleDynamicPages implements CmsUiProvider {
-
- @Override
- public Control createUi(Composite parent, Node context)
- throws RepositoryException {
- if (context == null)
- throw new CmsException("Context cannot be null");
- parent.setLayout(new GridLayout(2, false));
-
- // parent
- if (!context.getPath().equals("/")) {
- new CmsLink("..", context.getParent().getPath()).createUi(parent,
- context);
- new Label(parent, SWT.NONE).setText(context.getParent()
- .getPrimaryNodeType().getName());
- }
-
- // context
- Label contextL = new Label(parent, SWT.NONE);
- contextL.setData(RWT.MARKUP_ENABLED, true);
- contextL.setText("<b>" + context.getName() + "</b>");
- new Label(parent, SWT.NONE).setText(context.getPrimaryNodeType()
- .getName());
-
- // children
- // Label childrenL = new Label(parent, SWT.NONE);
- // childrenL.setData(RWT.MARKUP_ENABLED, true);
- // childrenL.setText("<i>Children:</i>");
- // childrenL.setLayoutData(new GridData(SWT.LEAD, SWT.CENTER, false,
- // false, 2, 1));
-
- for (NodeIterator nIt = context.getNodes(); nIt.hasNext();) {
- Node child = nIt.nextNode();
- new CmsLink(child.getName(), child.getPath()).createUi(parent,
- context);
-
- new Label(parent, SWT.NONE).setText(child.getPrimaryNodeType()
- .getName());
- }
-
- // properties
- // Label propsL = new Label(parent, SWT.NONE);
- // propsL.setData(RWT.MARKUP_ENABLED, true);
- // propsL.setText("<i>Properties:</i>");
- // propsL.setLayoutData(new GridData(SWT.LEAD, SWT.CENTER, false, false,
- // 2, 1));
- 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 String getPropAsString(Property property)
- throws RepositoryException {
- String result = "";
- DateFormat timeFormatter = new SimpleDateFormat("");
- 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();
- }
-}
\ No newline at end of file
+++ /dev/null
-package org.argeo.cms.ui.util;
-
-public class SimpleImageManager extends DefaultImageManager {
-
-}
+++ /dev/null
-package org.argeo.cms.ui.util;
-
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-
-import org.argeo.cms.swt.CmsStyles;
-import org.argeo.cms.ui.CmsUiProvider;
-import org.eclipse.rap.rwt.RWT;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Label;
-
-public class SimpleStaticPage implements CmsUiProvider {
- private String text;
-
- @Override
- public Control createUi(Composite parent, Node context)
- throws RepositoryException {
- Label textC = new Label(parent, SWT.WRAP);
- textC.setData(RWT.CUSTOM_VARIANT, CmsStyles.CMS_STATIC_TEXT);
- textC.setData(RWT.MARKUP_ENABLED, Boolean.TRUE);
- textC.setText(text);
-
- return textC;
- }
-
- public void setText(String text) {
- this.text = text;
- }
-
-}
+++ /dev/null
-package org.argeo.cms.ui.util;
-
-import org.argeo.api.cms.CmsStyle;
-
-/** Simple styles used by the CMS UI utilities. */
-public enum SimpleStyle implements CmsStyle {
- link;
-}
+++ /dev/null
-package org.argeo.cms.ui.util;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.URL;
-import java.util.LinkedHashMap;
-import java.util.Map;
-
-import org.apache.commons.io.IOUtils;
-import org.argeo.cms.CmsException;
-import org.eclipse.rap.rwt.service.ResourceLoader;
-import org.osgi.framework.Bundle;
-
-/** {@link ResourceLoader} caching stylesheets. */
-public class StyleSheetResourceLoader implements ResourceLoader {
- private Bundle themeBundle;
- private Map<String, StyleSheet> stylesheets = new LinkedHashMap<String, StyleSheet>();
-
- public StyleSheetResourceLoader(Bundle themeBundle) {
- this.themeBundle = themeBundle;
- }
-
- @Override
- public InputStream getResourceAsStream(String resourceName) throws IOException {
- if (!stylesheets.containsKey(resourceName)) {
- // TODO deal with other bundles
- // Bundle bundle = bundleContext.getBundle();
- // String location =
- // bundle.getLocation().substring("initial@reference:".length());
- // if (location.startsWith("file:")) {
- // Path path = null;
- // try {
- // path = Paths.get(new URI(location));
- // } catch (URISyntaxException e) {
- // e.printStackTrace();
- // }
- // if (path != null) {
- // Path resourcePath = path.resolve(resourceName);
- // if (Files.exists(resourcePath))
- // return Files.newInputStream(resourcePath);
- // }
- // }
-
- URL res = themeBundle.getEntry(resourceName);
- if (res == null)
- throw new CmsException(
- "Entry " + resourceName + " not found in bundle " + themeBundle.getSymbolicName());
- ByteArrayOutputStream out = new ByteArrayOutputStream();
- IOUtils.copy(res.openStream(), out);
- stylesheets.put(resourceName, new StyleSheet(out.toByteArray()));
- }
- return new ByteArrayInputStream(stylesheets.get(resourceName).getData());
- // return res.openStream();
- }
-
- private class StyleSheet {
- private byte[] data;
-
- public StyleSheet(byte[] data) {
- super();
- this.data = data;
- }
-
- public byte[] getData() {
- return data;
- }
-
- }
-}
+++ /dev/null
-package org.argeo.cms.ui.util;
-
-import java.io.PrintWriter;
-import java.io.StringWriter;
-import java.io.UnsupportedEncodingException;
-import java.net.URLEncoder;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-
-import org.apache.commons.io.IOUtils;
-import org.argeo.cms.CmsException;
-import org.argeo.cms.swt.CmsStyles;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.eclipse.rap.rwt.RWT;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.events.MouseEvent;
-import org.eclipse.swt.events.MouseListener;
-import org.eclipse.swt.events.ShellAdapter;
-import org.eclipse.swt.events.ShellEvent;
-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.Shell;
-
-/** Shell displaying system notifications such as exceptions */
-public class SystemNotifications extends Shell implements CmsStyles,
- MouseListener {
- private static final long serialVersionUID = -8129377525216022683L;
-
- private Control source;
-
- public SystemNotifications(Control source) {
- super(source.getDisplay(), SWT.NO_TRIM | SWT.BORDER | SWT.ON_TOP);
- setData(RWT.CUSTOM_VARIANT, CMS_USER_MENU);
-
- this.source = source;
-
- // TODO UI
- // setLocation(source.toDisplay(source.getSize().x - getSize().x,
- // source.getSize().y));
- setLayout(new GridLayout());
- addMouseListener(this);
-
- addShellListener(new ShellAdapter() {
- private static final long serialVersionUID = 5178980294808435833L;
-
- @Override
- public void shellDeactivated(ShellEvent e) {
- close();
- dispose();
- }
- });
-
- }
-
- public void notifyException(Throwable exception) {
- Composite pane = this;
-
- Label lbl = new Label(pane, SWT.NONE);
- lbl.setText(exception.getLocalizedMessage()
- + (exception instanceof CmsException ? "" : "("
- + exception.getClass().getName() + ")") + "\n");
- lbl.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
- lbl.addMouseListener(this);
- if (exception.getCause() != null)
- appendCause(pane, exception.getCause());
-
- StringBuilder mailToUrl = new StringBuilder("mailto:?");
- try {
- mailToUrl.append("subject=").append(
- URLEncoder.encode(
- "Exception "
- + new SimpleDateFormat("yyyy-MM-dd hh:mm")
- .format(new Date()), "UTF-8")
- .replace("+", "%20"));
-
- StringWriter sw = new StringWriter();
- exception.printStackTrace(new PrintWriter(sw));
- IOUtils.closeQuietly(sw);
-
- // see
- // http://stackoverflow.com/questions/4737841/urlencoder-not-able-to-translate-space-character
- String encoded = URLEncoder.encode(sw.toString(), "UTF-8").replace(
- "+", "%20");
- mailToUrl.append("&body=").append(encoded);
- } catch (UnsupportedEncodingException e) {
- mailToUrl.append("&body=").append("Could not encode: ")
- .append(e.getMessage());
- }
- Label mailTo = new Label(pane, SWT.NONE);
- CmsSwtUtils.markup(mailTo);
- mailTo.setText("<a href=\"" + mailToUrl + "\">Send details</a>");
- mailTo.setLayoutData(new GridData(SWT.END, SWT.FILL, true, false));
-
- pack();
- layout();
-
- setLocation(source.toDisplay(source.getSize().x - getSize().x,
- source.getSize().y - getSize().y));
- open();
- }
-
- private void appendCause(Composite parent, Throwable e) {
- Label lbl = new Label(parent, SWT.NONE);
- lbl.setText(" caused by: " + e.getLocalizedMessage() + " ("
- + e.getClass().getName() + ")" + "\n");
- lbl.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
- lbl.addMouseListener(this);
- if (e.getCause() != null)
- appendCause(parent, e.getCause());
- }
-
- @Override
- public void mouseDoubleClick(MouseEvent e) {
- }
-
- @Override
- public void mouseDown(MouseEvent e) {
- close();
- dispose();
- }
-
- @Override
- public void mouseUp(MouseEvent e) {
- }
-
-}
+++ /dev/null
-package org.argeo.cms.ui.util;
-
-import javax.jcr.Node;
-
-import org.argeo.cms.CmsException;
-import org.argeo.cms.swt.auth.CmsLoginShell;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.events.ShellAdapter;
-import org.eclipse.swt.events.ShellEvent;
-import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Display;
-import org.eclipse.swt.widgets.Shell;
-
-/** The site-related user menu */
-public class UserMenu extends CmsLoginShell {
- private final Control source;
- private final Node context;
-
- public UserMenu(Control source, Node context) {
- super(CmsUiUtils.getCmsView());
- this.context = context;
- createUi();
- if (source == null)
- throw new CmsException("Source control cannot be null.");
- this.source = source;
- open();
- }
-
- @Override
- protected Shell createShell() {
- return new Shell(Display.getCurrent(), SWT.NO_TRIM | SWT.BORDER | SWT.ON_TOP);
- }
-
- @Override
- public void open() {
- Shell shell = getShell();
- shell.pack();
- shell.layout();
- shell.setLocation(source.toDisplay(source.getSize().x - shell.getSize().x, source.getSize().y));
- shell.addShellListener(new ShellAdapter() {
- private static final long serialVersionUID = 5178980294808435833L;
-
- @Override
- public void shellDeactivated(ShellEvent e) {
- closeShell();
- }
- });
- super.open();
- }
-
- protected Node getContext() {
- return context;
- }
-
-}
+++ /dev/null
-package org.argeo.cms.ui.util;
-
-import javax.jcr.Node;
-
-import org.argeo.cms.CmsMsg;
-import org.argeo.cms.auth.CurrentUser;
-import org.argeo.cms.swt.CmsStyles;
-import org.argeo.cms.swt.auth.CmsLoginShell;
-import org.eclipse.swt.events.DisposeEvent;
-import org.eclipse.swt.events.DisposeListener;
-import org.eclipse.swt.events.MouseEvent;
-import org.eclipse.swt.events.MouseListener;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Label;
-
-/** Open the user menu when clicked */
-public class UserMenuLink extends MenuLink {
-
- public UserMenuLink() {
- setCustom(CmsStyles.CMS_USER_MENU_LINK);
- }
-
- @Override
- public Control createUi(Composite parent, Node context) {
- if (CurrentUser.isAnonymous())
- setLabel(CmsMsg.login.lead());
- else {
- setLabel(CurrentUser.getDisplayName());
- }
- Label link = (Label) ((Composite) super.createUi(parent, context)).getChildren()[0];
- link.addMouseListener(new UserMenuLinkController(context));
- return link.getParent();
- }
-
- protected CmsLoginShell createUserMenu(Control source, Node context) {
- return new UserMenu(source.getParent(), context);
- }
-
- private class UserMenuLinkController implements MouseListener, DisposeListener {
- private static final long serialVersionUID = 3634864186295639792L;
-
- private CmsLoginShell userMenu = null;
- private long lastDisposeTS = 0l;
-
- private final Node context;
-
- public UserMenuLinkController(Node context) {
- this.context = context;
- }
-
- //
- // MOUSE LISTENER
- //
- @Override
- public void mouseDown(MouseEvent e) {
- if (e.button == 1) {
- Control source = (Control) e.getSource();
- if (userMenu == null) {
- long durationSinceLastDispose = System.currentTimeMillis() - lastDisposeTS;
- // avoid to reopen the menu, if one has clicked gain
- if (durationSinceLastDispose > 200) {
- userMenu = createUserMenu(source, context);
- userMenu.getShell().addDisposeListener(this);
- }
- }
- }
- }
-
- @Override
- public void mouseDoubleClick(MouseEvent e) {
- }
-
- @Override
- public void mouseUp(MouseEvent e) {
- }
-
- @Override
- public void widgetDisposed(DisposeEvent event) {
- userMenu = null;
- lastDisposeTS = System.currentTimeMillis();
- }
- }
-}
+++ /dev/null
-package org.argeo.cms.ui.util;
-
-import java.util.ArrayList;
-import java.util.List;
-
-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.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-
-public class VerticalMenu implements CmsUiProvider {
- private List<CmsUiProvider> items = new ArrayList<CmsUiProvider>();
-
- @Override
- public Control createUi(Composite parent, Node context) throws RepositoryException {
- Composite part = new Composite(parent, SWT.NONE);
- part.setLayoutData(new GridData(SWT.LEAD, SWT.TOP, false, false));
-// part.setData(RWT.CUSTOM_VARIANT, custom);
- part.setLayout(CmsSwtUtils.noSpaceGridLayout());
- for (CmsUiProvider uiProvider : items) {
- Control subPart = uiProvider.createUi(part, context);
- subPart.setLayoutData(new GridData(SWT.LEAD, SWT.TOP, false, false));
- }
- return part;
- }
-
- public void add(CmsUiProvider uiProvider) {
- items.add(uiProvider);
- }
-
- public List<CmsUiProvider> getItems() {
- return items;
- }
-
- public void setItems(List<CmsUiProvider> items) {
- this.items = items;
- }
-
-}
+++ /dev/null
-/** Argeo CMS UI utilities. */
-package org.argeo.cms.ui.util;
\ No newline at end of file
+++ /dev/null
-package org.argeo.cms.ui.viewers;
-
-import java.security.AccessControlContext;
-import java.security.AccessController;
-import java.security.PrivilegedAction;
-import java.util.Observable;
-import java.util.Observer;
-
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.security.auth.Subject;
-
-import org.argeo.api.cms.CmsEditable;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.cms.ui.widgets.ScrolledPage;
-import org.argeo.jcr.JcrException;
-import org.eclipse.jface.viewers.ContentViewer;
-import org.eclipse.jface.viewers.ISelection;
-import org.eclipse.jface.viewers.StructuredSelection;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.events.FocusEvent;
-import org.eclipse.swt.events.FocusListener;
-import org.eclipse.swt.events.MouseAdapter;
-import org.eclipse.swt.events.MouseListener;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Widget;
-import org.xml.sax.SAXParseException;
-
-/** Base class for viewers related to a page */
-public abstract class AbstractPageViewer extends ContentViewer implements Observer {
- private static final long serialVersionUID = 5438688173410341485L;
-
- private final static CmsLog log = CmsLog.getLog(AbstractPageViewer.class);
-
- private final boolean readOnly;
- /** The basis for the layouts, typically a ScrolledPage. */
- private final Composite page;
- private final CmsEditable cmsEditable;
-
- private MouseListener mouseListener;
- private FocusListener focusListener;
-
- private EditablePart edited;
- private ISelection selection = StructuredSelection.EMPTY;
-
- private AccessControlContext accessControlContext;
-
- protected AbstractPageViewer(Section parent, int style, CmsEditable cmsEditable) {
- // read only at UI level
- readOnly = SWT.READ_ONLY == (style & SWT.READ_ONLY);
-
- this.cmsEditable = cmsEditable == null ? CmsEditable.NON_EDITABLE : cmsEditable;
- if (this.cmsEditable instanceof Observable)
- ((Observable) this.cmsEditable).addObserver(this);
-
- if (cmsEditable.canEdit()) {
- mouseListener = createMouseListener();
- focusListener = createFocusListener();
- }
- page = findPage(parent);
- accessControlContext = AccessController.getContext();
- }
-
- /**
- * Can be called to simplify the called to isModelInitialized() and initModel()
- */
- protected void initModelIfNeeded(Node node) {
- try {
- if (!isModelInitialized(node))
- if (getCmsEditable().canEdit()) {
- initModel(node);
- node.getSession().save();
- }
- } catch (RepositoryException e) {
- throw new JcrException("Cannot initialize model", e);
- }
- }
-
- /** Called if user can edit and model is not initialized */
- protected Boolean isModelInitialized(Node node) throws RepositoryException {
- return true;
- }
-
- /** Called if user can edit and model is not initialized */
- protected void initModel(Node node) throws RepositoryException {
- }
-
- /** Create (retrieve) the MouseListener to use. */
- protected MouseListener createMouseListener() {
- return new MouseAdapter() {
- private static final long serialVersionUID = 1L;
- };
- }
-
- /** Create (retrieve) the FocusListener to use. */
- protected FocusListener createFocusListener() {
- return new FocusListener() {
- private static final long serialVersionUID = 1L;
-
- @Override
- public void focusLost(FocusEvent event) {
- }
-
- @Override
- public void focusGained(FocusEvent event) {
- }
- };
- }
-
- protected Composite findPage(Composite composite) {
- if (composite instanceof ScrolledPage) {
- return (ScrolledPage) composite;
- } else {
- if (composite.getParent() == null)
- return composite;
- return findPage(composite.getParent());
- }
- }
-
- public void layoutPage() {
- if (page != null)
- page.layout(true, true);
- }
-
- protected void showControl(Control control) {
- if (page != null && (page instanceof ScrolledPage))
- ((ScrolledPage) page).showControl(control);
- }
-
- @Override
- public void update(Observable o, Object arg) {
- if (o == cmsEditable)
- editingStateChanged(cmsEditable);
- }
-
- /** To be overridden in order to provide the actual refresh */
- protected void refresh(Control control) throws RepositoryException {
- }
-
- /** To be overridden.Save the edited part. */
- protected void save(EditablePart part) throws RepositoryException {
- }
-
- /** Prepare the edited part */
- protected void prepare(EditablePart part, Object caretPosition) {
- }
-
- /** Notified when the editing state changed. Does nothing, to be overridden */
- protected void editingStateChanged(CmsEditable cmsEditable) {
- }
-
- @Override
- public void refresh() {
- // TODO check actual context in order to notice a discrepancy
- Subject viewerSubject = getViewerSubject();
- Subject.doAs(viewerSubject, (PrivilegedAction<Void>) () -> {
- try {
- if (cmsEditable.canEdit() && !readOnly)
- mouseListener = createMouseListener();
- else
- mouseListener = null;
- refresh(getControl());
- // layout(getControl());
- if (!getControl().isDisposed())
- layoutPage();
- } catch (RepositoryException e) {
- throw new JcrException("Cannot refresh", e);
- }
- return null;
- });
- }
-
- @Override
- public void setSelection(ISelection selection, boolean reveal) {
- this.selection = selection;
- }
-
- protected void updateContent(EditablePart part) throws RepositoryException {
- }
-
- // LOW LEVEL EDITION
- protected void edit(EditablePart part, Object caretPosition) {
- try {
- if (edited == part)
- return;
-
- if (edited != null && edited != part) {
- EditablePart previouslyEdited = edited;
- try {
- stopEditing(true);
- } catch (Exception e) {
- notifyEditionException(e);
- edit(previouslyEdited, caretPosition);
- return;
- }
- }
-
- part.startEditing();
- edited = part;
- updateContent(part);
- prepare(part, caretPosition);
- edited.getControl().addFocusListener(new FocusListener() {
- private static final long serialVersionUID = 6883521812717097017L;
-
- @Override
- public void focusLost(FocusEvent event) {
- stopEditing(true);
- }
-
- @Override
- public void focusGained(FocusEvent event) {
- }
- });
-
- layout(part.getControl());
- showControl(part.getControl());
- } catch (RepositoryException e) {
- throw new JcrException("Cannot edit " + part, e);
- }
- }
-
- protected void stopEditing(Boolean save) {
- if (edited instanceof Widget && ((Widget) edited).isDisposed()) {
- edited = null;
- return;
- }
-
- assert edited != null;
- if (edited == null) {
- if (log.isTraceEnabled())
- log.warn("Told to stop editing while not editing anything");
- return;
- }
-
- try {
- if (save)
- save(edited);
-
- edited.stopEditing();
- EditablePart editablePart = edited;
- Control control = ((EditablePart) edited).getControl();
- edited = null;
- // TODO make edited state management more robust
- updateContent(editablePart);
- layout(control);
- } catch (RepositoryException e) {
- throw new JcrException("Cannot stop editing", e);
- } finally {
- edited = null;
- }
- }
-
- // METHODS AVAILABLE TO EXTENDING CLASSES
- protected void saveEdit() {
- if (edited != null)
- stopEditing(true);
- }
-
- protected void cancelEdit() {
- if (edited != null)
- stopEditing(false);
- }
-
- /** Layout this controls from the related base page. */
- public void layout(Control... controls) {
- page.layout(controls);
- }
-
- /**
- * Find the first {@link EditablePart} in the parents hierarchy of this control
- */
- protected EditablePart findDataParent(Control parent) {
- if (parent instanceof EditablePart) {
- return (EditablePart) parent;
- }
- if (parent.getParent() != null)
- return findDataParent(parent.getParent());
- else
- throw new IllegalStateException("No data parent found");
- }
-
- // UTILITIES
- /** Check whether the edited part is in a proper state */
- protected void checkEdited() {
- if (edited == null || (edited instanceof Widget) && ((Widget) edited).isDisposed())
- throw new IllegalStateException("Edited should not be null or disposed at this stage");
- }
-
- /** Persist all changes. */
- protected void persistChanges(Session session) throws RepositoryException {
- session.save();
- session.refresh(false);
- // TODO notify that changes have been persisted
- }
-
- /** Convenience method using a Node in order to save the underlying session. */
- protected void persistChanges(Node anyNode) throws RepositoryException {
- persistChanges(anyNode.getSession());
- }
-
- /** Notify edition exception */
- protected void notifyEditionException(Throwable e) {
- Throwable eToLog = e;
- if (e instanceof IllegalArgumentException)
- if (e.getCause() instanceof SAXParseException)
- eToLog = e.getCause();
- log.error(eToLog.getMessage(), eToLog);
-// if (log.isTraceEnabled())
-// log.trace("Full stack of " + eToLog.getMessage(), e);
- // TODO Light error notification popup
- }
-
- protected Subject getViewerSubject() {
- Subject res = null;
- if (accessControlContext != null) {
- res = Subject.getSubject(accessControlContext);
- }
- if (res == null)
- throw new IllegalStateException("No subject associated with this viewer");
- return res;
- }
-
- // GETTERS / SETTERS
- public boolean isReadOnly() {
- return readOnly;
- }
-
- protected EditablePart getEdited() {
- return edited;
- }
-
- public MouseListener getMouseListener() {
- return mouseListener;
- }
-
- public FocusListener getFocusListener() {
- return focusListener;
- }
-
- public CmsEditable getCmsEditable() {
- return cmsEditable;
- }
-
- @Override
- public ISelection getSelection() {
- return selection;
- }
-}
\ No newline at end of file
+++ /dev/null
-package org.argeo.cms.ui.viewers;
-
-import org.eclipse.swt.widgets.Control;
-
-/** Manages whether an editable or non editable control is shown. */
-public interface EditablePart {
- public void startEditing();
-
- public void stopEditing();
-
- public Control getControl();
-}
+++ /dev/null
-package org.argeo.cms.ui.viewers;
-
-import javax.jcr.Item;
-import javax.jcr.RepositoryException;
-
-/** An editable part related to a JCR Item */
-public interface ItemPart<T extends Item> {
- public Item getItem() throws RepositoryException;
-}
+++ /dev/null
-package org.argeo.cms.ui.viewers;
-
-import java.util.Observable;
-
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.nodetype.NodeType;
-import javax.jcr.version.VersionManager;
-
-import org.argeo.api.cms.CmsEditable;
-import org.argeo.cms.CmsException;
-import org.argeo.cms.ui.CmsEditionEvent;
-import org.eclipse.rap.rwt.RWT;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.widgets.Display;
-import org.eclipse.swt.widgets.Event;
-import org.eclipse.swt.widgets.Listener;
-
-/** Provides the CmsEditable semantic based on JCR versioning. */
-public class JcrVersionCmsEditable extends Observable implements CmsEditable {
- private final String nodePath;// cache
- private final VersionManager versionManager;
- private final Boolean canEdit;
-
- public JcrVersionCmsEditable(Node node) throws RepositoryException {
- this.nodePath = node.getPath();
- if (node.getSession().hasPermission(node.getPath(),
- Session.ACTION_SET_PROPERTY)) {
- // was Session.ACTION_ADD_NODE
- canEdit = true;
- if (!node.isNodeType(NodeType.MIX_VERSIONABLE)) {
- node.addMixin(NodeType.MIX_VERSIONABLE);
- node.getSession().save();
- }
- versionManager = node.getSession().getWorkspace()
- .getVersionManager();
- } else {
- canEdit = false;
- versionManager = null;
- }
-
- // bind keys
- if (canEdit) {
- Display display = Display.getCurrent();
- display.setData(RWT.ACTIVE_KEYS, new String[] { "CTRL+RETURN",
- "CTRL+E" });
- display.addFilter(SWT.KeyDown, new Listener() {
- private static final long serialVersionUID = -4378653870463187318L;
-
- public void handleEvent(Event e) {
- boolean ctrlPressed = (e.stateMask & SWT.CTRL) != 0;
- if (ctrlPressed && e.keyCode == '\r')
- stopEditing();
- else if (ctrlPressed && e.keyCode == 'E')
- stopEditing();
- }
- });
- }
- }
-
- @Override
- public Boolean canEdit() {
- return canEdit;
- }
-
- public Boolean isEditing() {
- try {
- if (!canEdit())
- return false;
- return versionManager.isCheckedOut(nodePath);
- } catch (RepositoryException e) {
- throw new CmsException("Cannot check whether " + nodePath
- + " is editing", e);
- }
- }
-
- @Override
- public void startEditing() {
- try {
- versionManager.checkout(nodePath);
- setChanged();
- } catch (RepositoryException e1) {
- throw new CmsException("Cannot publish " + nodePath);
- }
- notifyObservers(new CmsEditionEvent(nodePath,
- CmsEditionEvent.START_EDITING));
- }
-
- @Override
- public void stopEditing() {
- try {
- versionManager.checkin(nodePath);
- setChanged();
- } catch (RepositoryException e1) {
- throw new CmsException("Cannot publish " + nodePath, e1);
- }
- notifyObservers(new CmsEditionEvent(nodePath,
- CmsEditionEvent.STOP_EDITING));
- }
-}
+++ /dev/null
-package org.argeo.cms.ui.viewers;
-
-import javax.jcr.Node;
-
-/** An editable part related to a node */
-public interface NodePart extends ItemPart<Node> {
- public Node getNode();
-}
+++ /dev/null
-package org.argeo.cms.ui.viewers;
-
-import javax.jcr.Property;
-
-/** An editable part related to a JCR Property */
-public interface PropertyPart extends ItemPart<Property> {
- public Property getProperty();
-}
+++ /dev/null
-package org.argeo.cms.ui.viewers;
-
-import java.util.Collections;
-import java.util.LinkedHashMap;
-import java.util.Map;
-
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.cms.ui.widgets.JcrComposite;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-
-/** A structured UI related to a JCR context. */
-public class Section extends JcrComposite {
- private static final long serialVersionUID = -5933796173755739207L;
-
- private final Section parentSection;
- private Composite sectionHeader;
- private final Integer relativeDepth;
-
- public Section(Composite parent, int style, Node node) {
- this(parent, findSection(parent), style, node);
- }
-
- public Section(Section section, int style, Node node) {
- this(section, section, style, node);
- }
-
- protected Section(Composite parent, Section parentSection, int style, Node node) {
- super(parent, style, node);
- try {
- this.parentSection = parentSection;
- if (parentSection != null) {
- relativeDepth = getNode().getDepth() - parentSection.getNode().getDepth();
- } else {
- relativeDepth = 0;
- }
- setLayout(CmsSwtUtils.noSpaceGridLayout());
- } catch (RepositoryException e) {
- throw new IllegalStateException("Cannot create section from " + node, e);
- }
- }
-
- public Map<String, Section> getSubSections() throws RepositoryException {
- LinkedHashMap<String, Section> result = new LinkedHashMap<String, Section>();
- for (Control child : getChildren()) {
- if (child instanceof Composite) {
- collectDirectSubSections((Composite) child, result);
- }
- }
- return Collections.unmodifiableMap(result);
- }
-
- private void collectDirectSubSections(Composite composite, LinkedHashMap<String, Section> subSections)
- throws RepositoryException {
- if (composite == sectionHeader || composite instanceof EditablePart)
- return;
- if (composite instanceof Section) {
- Section section = (Section) composite;
- subSections.put(section.getNodeId(), section);
- return;
- }
-
- for (Control child : composite.getChildren())
- if (child instanceof Composite)
- collectDirectSubSections((Composite) child, subSections);
- }
-
- public Composite createHeader() {
- return createHeader(this);
- }
-
- public Composite createHeader(Composite parent) {
- if (sectionHeader != null)
- sectionHeader.dispose();
-
- sectionHeader = new Composite(parent, SWT.NONE);
- sectionHeader.setLayoutData(CmsSwtUtils.fillWidth());
- sectionHeader.setLayout(CmsSwtUtils.noSpaceGridLayout());
- // sectionHeader.moveAbove(null);
- // layout();
- return sectionHeader;
- }
-
- public Composite getHeader() {
- if (sectionHeader != null && sectionHeader.isDisposed())
- sectionHeader = null;
- return sectionHeader;
- }
-
- // SECTION PARTS
- public SectionPart getSectionPart(String partId) {
- for (Control child : getChildren()) {
- if (child instanceof SectionPart) {
- SectionPart sectionPart = (SectionPart) child;
- if (sectionPart.getPartId().equals(partId))
- return sectionPart;
- }
- }
- return null;
- }
-
- public SectionPart nextSectionPart(SectionPart sectionPart) {
- Control[] children = getChildren();
- for (int i = 0; i < children.length; i++) {
- if (sectionPart == children[i]) {
- for (int j = i + 1; j < children.length; j++) {
- if (children[i + 1] instanceof SectionPart) {
- return (SectionPart) children[i + 1];
- }
- }
-
-// if (i + 1 < children.length) {
-// Composite next = (Composite) children[i + 1];
-// return (SectionPart) next;
-// } else {
-// // next section
-// }
- }
- }
- return null;
- }
-
- public SectionPart previousSectionPart(SectionPart sectionPart) {
- Control[] children = getChildren();
- for (int i = 0; i < children.length; i++) {
- if (sectionPart == children[i])
- if (i != 0) {
- Composite previous = (Composite) children[i - 1];
- return (SectionPart) previous;
- } else {
- // previous section
- }
- }
- return null;
- }
-
- @Override
- public String toString() {
- if (parentSection == null)
- return "Main section " + getNode();
- return "Section " + getNode();
- }
-
- public Section getParentSection() {
- return parentSection;
- }
-
- public Integer getRelativeDepth() {
- return relativeDepth;
- }
-
- /** Recursively finds the related section in the parents (can be itself) */
- public static Section findSection(Control control) {
- if (control == null)
- return null;
- if (control instanceof Section)
- return (Section) control;
- else
- return findSection(control.getParent());
- }
-}
+++ /dev/null
-package org.argeo.cms.ui.viewers;
-
-
-/** An editable part dynamically related to a Section */
-public interface SectionPart extends EditablePart, NodePart {
- public String getPartId();
-
- public Section getSection();
-}
+++ /dev/null
-/** Argeo CMS generic viewers, based on JFace. */
-package org.argeo.cms.ui.viewers;
\ No newline at end of file
+++ /dev/null
-package org.argeo.cms.ui.widgets;
-
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.events.ShellAdapter;
-import org.eclipse.swt.events.ShellEvent;
-import org.eclipse.swt.graphics.Point;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Shell;
-
-/**
- * Manages a lightweight shell which is related to a {@link Control}, typically
- * in order to reproduce a dropdown semantic, but with more flexibility.
- */
-public class ContextOverlay extends ScrolledPage {
- private static final long serialVersionUID = 6702077429573324009L;
-
-// private Shell shell;
- private Control control;
-
- private int maxHeight = 400;
-
- public ContextOverlay(Control control, int style) {
- super(createShell(control, style), SWT.NONE);
- Shell shell = getShell();
- setLayoutData(CmsSwtUtils.fillAll());
- // TODO make autohide configurable?
- //shell.addShellListener(new AutoHideShellListener());
- this.control = control;
- control.addDisposeListener((e) -> {
- dispose();
- shell.dispose();
- });
- }
-
- private static Composite createShell(Control control, int style) {
- if (control == null)
- throw new IllegalArgumentException("Control cannot be null");
- if (control.isDisposed())
- throw new IllegalArgumentException("Control is disposed");
- Shell shell = new Shell(control.getShell(), SWT.NO_TRIM);
- shell.setLayout(CmsSwtUtils.noSpaceGridLayout());
- Composite placeholder = new Composite(shell, SWT.BORDER);
- placeholder.setLayoutData(CmsSwtUtils.fillAll());
- placeholder.setLayout(CmsSwtUtils.noSpaceGridLayout());
- return placeholder;
- }
-
- public void show() {
- Point relativeControlLocation = control.getLocation();
- Point controlLocation = control.toDisplay(relativeControlLocation.x, relativeControlLocation.y);
-
- int controlWidth = control.getBounds().width;
-
- Shell shell = getShell();
-
- layout(true, true);
- shell.pack();
- shell.layout(true, true);
- int targetShellWidth = shell.getSize().x < controlWidth ? controlWidth : shell.getSize().x;
- if (shell.getSize().y > maxHeight) {
- shell.setSize(targetShellWidth, maxHeight);
- } else {
- shell.setSize(targetShellWidth, shell.getSize().y);
- }
-
- int shellHeight = shell.getSize().y;
- int controlHeight = control.getBounds().height;
- Point shellLocation = new Point(controlLocation.x, controlLocation.y + controlHeight);
- int displayHeight = shell.getDisplay().getBounds().height;
- if (shellLocation.y + shellHeight > displayHeight) {// bottom of page
- shellLocation = new Point(controlLocation.x, controlLocation.y - shellHeight);
- }
- shell.setLocation(shellLocation);
-
- if (getChildren().length != 0)
- shell.open();
- if (!control.isDisposed())
- control.setFocus();
- }
-
- public void hide() {
- getShell().setVisible(false);
- onHide();
- }
-
- public boolean isShellVisible() {
- if (isDisposed())
- return false;
- return getShell().isVisible();
- }
-
- /** to be overridden */
- protected void onHide() {
- // does nothing by default.
- }
-
- private class AutoHideShellListener extends ShellAdapter {
- private static final long serialVersionUID = 7743287433907938099L;
-
- @Override
- public void shellDeactivated(ShellEvent e) {
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e1) {
- // silent
- }
- if (!control.isDisposed() && !control.isFocusControl())
- hide();
- }
- }
-}
+++ /dev/null
-package org.argeo.cms.ui.widgets;
-
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-
-import org.argeo.api.cms.Cms2DSize;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.cms.ui.util.CmsUiUtils;
-import org.eclipse.swt.graphics.Point;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Label;
-import org.eclipse.swt.widgets.Text;
-
-/** A stylable and editable image. */
-public abstract class EditableImage extends StyledControl {
- private static final long serialVersionUID = -5689145523114022890L;
- private final static CmsLog log = CmsLog.getLog(EditableImage.class);
-
- private Cms2DSize preferredImageSize;
- private Boolean loaded = false;
-
- public EditableImage(Composite parent, int swtStyle) {
- super(parent, swtStyle);
- }
-
- public EditableImage(Composite parent, int swtStyle, Cms2DSize preferredImageSize) {
- super(parent, swtStyle);
- this.preferredImageSize = preferredImageSize;
- }
-
- public EditableImage(Composite parent, int style, Node node, boolean cacheImmediately, Cms2DSize preferredImageSize)
- throws RepositoryException {
- super(parent, style, node, cacheImmediately);
- this.preferredImageSize = preferredImageSize;
- }
-
- @Override
- protected void setContainerLayoutData(Composite composite) {
- // composite.setLayoutData(fillWidth());
- }
-
- @Override
- protected void setControlLayoutData(Control control) {
- // control.setLayoutData(fillWidth());
- }
-
- /** To be overriden. */
- protected String createImgTag() throws RepositoryException {
- return CmsUiUtils
- .noImg(preferredImageSize != null ? preferredImageSize : new Cms2DSize(getSize().x, getSize().y));
- }
-
- protected Label createLabel(Composite box, String style) {
- Label lbl = new Label(box, getStyle());
- // lbl.setLayoutData(CmsUiUtils.fillWidth());
- CmsSwtUtils.markup(lbl);
- CmsSwtUtils.style(lbl, style);
- if (mouseListener != null)
- lbl.addMouseListener(mouseListener);
- load(lbl);
- return lbl;
- }
-
- /** To be overriden. */
- protected synchronized Boolean load(Control control) {
- String imgTag;
- try {
- imgTag = createImgTag();
- } catch (Exception e) {
- // throw new CmsException("Cannot retrieve image", e);
- log.error("Cannot retrieve image", e);
- imgTag = CmsUiUtils.noImg(preferredImageSize);
- loaded = false;
- }
-
- if (imgTag == null) {
- loaded = false;
- imgTag = CmsUiUtils.noImg(preferredImageSize);
- } else
- loaded = true;
- if (control != null) {
- ((Label) control).setText(imgTag);
- control.setSize(preferredImageSize != null
- ? new Point(preferredImageSize.getWidth(), preferredImageSize.getHeight())
- : getSize());
- } else {
- loaded = false;
- }
- getParent().layout();
- return loaded;
- }
-
- public void setPreferredSize(Cms2DSize size) {
- this.preferredImageSize = size;
- if (!loaded) {
- load((Label) getControl());
- }
- }
-
- protected Text createText(Composite box, String style) {
- Text text = new Text(box, getStyle());
- CmsSwtUtils.style(text, style);
- return text;
- }
-
- public Cms2DSize getPreferredImageSize() {
- return preferredImageSize;
- }
-
-}
+++ /dev/null
-package org.argeo.cms.ui.widgets;
-
-import javax.jcr.Item;
-import javax.jcr.RepositoryException;
-
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.graphics.Color;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Label;
-import org.eclipse.swt.widgets.Text;
-
-/** Editable text part displaying styled text. */
-public class EditableText extends StyledControl {
- private static final long serialVersionUID = -6372283442330912755L;
-
- private boolean editable = true;
-
- private Color highlightColor;
- private Composite highlight;
-
- private boolean useTextAsLabel = false;
-
- public EditableText(Composite parent, int style) {
- super(parent, style);
- editable = !(SWT.READ_ONLY == (style & SWT.READ_ONLY));
- highlightColor = parent.getDisplay().getSystemColor(SWT.COLOR_GRAY);
- }
-
- public EditableText(Composite parent, int style, Item item) throws RepositoryException {
- this(parent, style, item, false);
- }
-
- public EditableText(Composite parent, int style, Item item, boolean cacheImmediately) throws RepositoryException {
- super(parent, style, item, cacheImmediately);
- editable = !(SWT.READ_ONLY == (style & SWT.READ_ONLY));
- highlightColor = parent.getDisplay().getSystemColor(SWT.COLOR_GRAY);
- }
-
- @Override
- protected Control createControl(Composite box, String style) {
- if (isEditing() && getEditable()) {
- return createText(box, style, true);
- } else {
- if (useTextAsLabel) {
- return createTextLabel(box, style);
- } else {
- return createLabel(box, style);
- }
- }
- }
-
- protected Label createLabel(Composite box, String style) {
- Label lbl = new Label(box, getStyle() | SWT.WRAP);
- lbl.setLayoutData(CmsSwtUtils.fillWidth());
- if (style != null)
- CmsSwtUtils.style(lbl, style);
- CmsSwtUtils.markup(lbl);
- if (mouseListener != null)
- lbl.addMouseListener(mouseListener);
- return lbl;
- }
-
- protected Text createTextLabel(Composite box, String style) {
- Text lbl = new Text(box, getStyle() | SWT.MULTI);
- lbl.setEditable(false);
- lbl.setLayoutData(CmsSwtUtils.fillWidth());
- if (style != null)
- CmsSwtUtils.style(lbl, style);
- CmsSwtUtils.markup(lbl);
- if (mouseListener != null)
- lbl.addMouseListener(mouseListener);
- return lbl;
- }
-
- protected Text createText(Composite box, String style, boolean editable) {
- highlight = new Composite(box, SWT.NONE);
- highlight.setBackground(highlightColor);
- GridData highlightGd = new GridData(SWT.FILL, SWT.FILL, false, false);
- highlightGd.widthHint = 5;
- highlightGd.heightHint = 3;
- highlight.setLayoutData(highlightGd);
-
- final Text text = new Text(box, getStyle() | SWT.MULTI | SWT.WRAP);
- text.setEditable(editable);
- GridData textLayoutData = CmsSwtUtils.fillWidth();
- // textLayoutData.heightHint = preferredHeight;
- text.setLayoutData(textLayoutData);
- if (style != null)
- CmsSwtUtils.style(text, style);
- text.setFocus();
- return text;
- }
-
- @Override
- protected void clear(boolean deep) {
- if (highlight != null)
- highlight.dispose();
- super.clear(deep);
- }
-
- public void setText(String text) {
- Control child = getControl();
- if (child instanceof Label)
- ((Label) child).setText(text);
- else if (child instanceof Text)
- ((Text) child).setText(text);
- }
-
- public Text getAsText() {
- return (Text) getControl();
- }
-
- public Label getAsLabel() {
- return (Label) getControl();
- }
-
- public String getText() {
- Control child = getControl();
-
- if (child instanceof Label)
- return ((Label) child).getText();
- else if (child instanceof Text)
- return ((Text) child).getText();
- else
- throw new IllegalStateException("Unsupported control " + child.getClass());
- }
-
- /** @deprecated Use {@link #isEditable()} instead. */
- @Deprecated
- public boolean getEditable() {
- return isEditable();
- }
-
- public boolean isEditable() {
- return editable;
- }
-
- public void setUseTextAsLabel(boolean useTextAsLabel) {
- this.useTextAsLabel = useTextAsLabel;
- }
-
-}
+++ /dev/null
-package org.argeo.cms.ui.widgets;
-
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-
-import org.argeo.api.cms.Cms2DSize;
-import org.argeo.api.cms.CmsImageManager;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.cms.ui.internal.JcrFileUploadReceiver;
-import org.argeo.cms.ui.viewers.NodePart;
-import org.argeo.cms.ui.viewers.Section;
-import org.argeo.cms.ui.viewers.SectionPart;
-import org.argeo.jcr.Jcr;
-import org.argeo.jcr.JcrException;
-import org.eclipse.rap.fileupload.FileUploadHandler;
-import org.eclipse.rap.fileupload.FileUploadListener;
-import org.eclipse.rap.fileupload.FileUploadReceiver;
-import org.eclipse.rap.rwt.service.ServerPushSession;
-import org.eclipse.rap.rwt.widgets.FileUpload;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.events.SelectionAdapter;
-import org.eclipse.swt.events.SelectionEvent;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-
-/** An image within the Argeo Text framework */
-public class Img extends EditableImage implements SectionPart, NodePart {
- private static final long serialVersionUID = 6233572783968188476L;
-
- private final Section section;
-
- private final CmsImageManager<Control, Node> imageManager;
- private FileUploadHandler currentUploadHandler = null;
- private FileUploadListener fileUploadListener;
-
- public Img(Composite parent, int swtStyle, Node imgNode, Cms2DSize preferredImageSize) throws RepositoryException {
- this(Section.findSection(parent), parent, swtStyle, imgNode, preferredImageSize, null);
- setStyle(TextStyles.TEXT_IMAGE);
- }
-
- public Img(Composite parent, int swtStyle, Node imgNode) throws RepositoryException {
- this(Section.findSection(parent), parent, swtStyle, imgNode, null, null);
- setStyle(TextStyles.TEXT_IMAGE);
- }
-
- public Img(Composite parent, int swtStyle, Node imgNode, CmsImageManager<Control, Node> imageManager)
- throws RepositoryException {
- this(Section.findSection(parent), parent, swtStyle, imgNode, null, imageManager);
- setStyle(TextStyles.TEXT_IMAGE);
- }
-
- Img(Section section, Composite parent, int swtStyle, Node imgNode, Cms2DSize preferredImageSize,
- CmsImageManager<Control, Node> imageManager) throws RepositoryException {
- super(parent, swtStyle, imgNode, false, preferredImageSize);
- this.section = section;
- this.imageManager = imageManager != null ? imageManager
- : (CmsImageManager<Control, Node>) CmsSwtUtils.getCmsView(section).getImageManager();
- CmsSwtUtils.style(this, TextStyles.TEXT_IMG);
- }
-
- @Override
- protected Control createControl(Composite box, String style) {
- if (isEditing()) {
- try {
- return createImageChooser(box, style);
- } catch (RepositoryException e) {
- throw new JcrException("Cannot create image chooser", e);
- }
- } else {
- return createLabel(box, style);
- }
- }
-
- @Override
- public synchronized void stopEditing() {
- super.stopEditing();
- fileUploadListener = null;
- }
-
- @Override
- protected synchronized Boolean load(Control lbl) {
- Node imgNode = getNode();
- boolean loaded = imageManager.load(imgNode, lbl, getPreferredImageSize());
- // getParent().layout();
- return loaded;
- }
-
- protected Node getUploadFolder() {
- return Jcr.getParent(getNode());
- }
-
- protected String getUploadName() {
- Node node = getNode();
- return Jcr.getName(node) + '[' + Jcr.getIndex(node) + ']';
- }
-
- protected CmsImageManager<Control, Node> getImageManager() {
- return imageManager;
- }
-
- protected Control createImageChooser(Composite box, String style) throws RepositoryException {
- JcrFileUploadReceiver receiver = new JcrFileUploadReceiver(this, getUploadFolder(), getUploadName(),
- imageManager);
- if (currentUploadHandler != null)
- currentUploadHandler.dispose();
- currentUploadHandler = prepareUpload(receiver);
- final ServerPushSession pushSession = new ServerPushSession();
- final FileUpload fileUpload = new FileUpload(box, SWT.NONE);
- CmsSwtUtils.style(fileUpload, style);
- fileUpload.addSelectionListener(new SelectionAdapter() {
- private static final long serialVersionUID = -9158471843941668562L;
-
- @Override
- public void widgetSelected(SelectionEvent e) {
- pushSession.start();
- fileUpload.submit(currentUploadHandler.getUploadUrl());
- }
- });
- return fileUpload;
- }
-
- protected FileUploadHandler prepareUpload(FileUploadReceiver receiver) {
- final FileUploadHandler uploadHandler = new FileUploadHandler(receiver);
- if (fileUploadListener != null)
- uploadHandler.addUploadListener(fileUploadListener);
- return uploadHandler;
- }
-
- @Override
- public Section getSection() {
- return section;
- }
-
- public void setFileUploadListener(FileUploadListener fileUploadListener) {
- this.fileUploadListener = fileUploadListener;
- if (currentUploadHandler != null)
- currentUploadHandler.addUploadListener(fileUploadListener);
- }
-
- @Override
- public Node getItem() throws RepositoryException {
- return getNode();
- }
-
- @Override
- public String getPartId() {
- return getNodeId();
- }
-
- @Override
- public String toString() {
- return "Img #" + getPartId();
- }
-
-}
+++ /dev/null
-package org.argeo.cms.ui.widgets;
-
-import javax.jcr.Item;
-import javax.jcr.ItemNotFoundException;
-import javax.jcr.Node;
-import javax.jcr.Property;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.jcr.JcrException;
-import org.eclipse.swt.widgets.Composite;
-
-/** A composite which can (optionally) manage a JCR Item. */
-public class JcrComposite extends Composite {
- private static final long serialVersionUID = -1447009015451153367L;
-
- private Session session;
-
- private String nodeId;
- private String property = null;
- private Node cache;
-
- /** Regular composite constructor. No layout is set. */
- public JcrComposite(Composite parent, int style) {
- super(parent, style);
- session = null;
- nodeId = null;
- }
-
- public JcrComposite(Composite parent, int style, Item item) {
- this(parent, style, item, false);
- }
-
- public JcrComposite(Composite parent, int style, Item item, boolean cacheImmediately) {
- super(parent, style);
- if (item != null)
- try {
- this.session = item.getSession();
-// if (!cacheImmediately && (SWT.READ_ONLY == (style & SWT.READ_ONLY))) {
-// // (useless?) optimization: we only save a pointer to the session,
-// // not even a reference to the item
-// this.nodeId = null;
-// } else {
- Node node;
- Property property = null;
- if (item instanceof Node) {
- node = (Node) item;
- } else {// Property
- property = (Property) item;
- if (property.isMultiple())// TODO manage property index
- throw new UnsupportedOperationException("Multiple properties not supported yet.");
- this.property = property.getName();
- node = property.getParent();
- }
- this.nodeId = node.getIdentifier();
- if (cacheImmediately)
- this.cache = node;
-// }
- setLayout(CmsSwtUtils.noSpaceGridLayout());
- } catch (RepositoryException e) {
- throw new IllegalStateException("Cannot create composite from " + item, e);
- }
- }
-
- public synchronized Node getNode() {
- try {
- if (!itemIsNode())
- throw new IllegalStateException("Item is not a Node");
- return getNodeInternal();
- } catch (RepositoryException e) {
- throw new JcrException("Cannot get node " + nodeId, e);
- }
- }
-
- private synchronized Node getNodeInternal() throws RepositoryException {
- if (cache != null)
- return cache;
- else if (session != null)
- if (nodeId != null)
- return session.getNodeByIdentifier(nodeId);
- else
- return null;
- else
- return null;
- }
-
- public synchronized String getPropertyName() {
- try {
- return getProperty().getName();
- } catch (RepositoryException e) {
- throw new JcrException("Cannot get property name", e);
- }
- }
-
- public synchronized Node getPropertyNode() {
- try {
- return getProperty().getNode();
- } catch (RepositoryException e) {
- throw new JcrException("Cannot get property name", e);
- }
- }
-
- public synchronized Property getProperty() {
- try {
- if (itemIsNode())
- throw new IllegalStateException("Item is not a Property");
- Node node = getNodeInternal();
- if (!node.hasProperty(property))
- throw new IllegalStateException("Property " + property + " is not set on " + node);
- return node.getProperty(property);
- } catch (RepositoryException e) {
- throw new JcrException("Cannot get property " + property + " from node " + nodeId, e);
- }
- }
-
- public synchronized boolean itemIsNode() {
- return property == null;
- }
-
- public synchronized boolean itemExists() {
- if (session == null)
- return false;
- try {
- Node n = session.getNodeByIdentifier(nodeId);
- if (!itemIsNode())
- return n.hasProperty(property);
- else
- return true;
- } catch (ItemNotFoundException e) {
- return false;
- } catch (RepositoryException e) {
- throw new JcrException("Cannot check whether node exists", e);
- }
- }
-
- /** Set/update the cache or change the node */
- public synchronized void setNode(Node node) {
- if (!itemIsNode())
- throw new IllegalArgumentException("Cannot set a Node on a Property");
-
- if (node == null) {// clear cache
- this.cache = null;
- return;
- }
-
- try {
-// if (session != null || session != node.getSession())// check session
-// throw new IllegalArgumentException("Uncompatible session");
-// if (session == null)
- session = node.getSession();
- if (nodeId == null || !nodeId.equals(node.getIdentifier())) {
- nodeId = node.getIdentifier();
- cache = node;
- itemUpdated();
- } else {
- cache = node;// set/update cache
- }
- } catch (RepositoryException e) {
- throw new IllegalStateException(e);
- }
- }
-
- /** Set/update the cache or change the property */
- public synchronized void setProperty(Property prop) {
- if (itemIsNode())
- throw new IllegalArgumentException("Cannot set a Property on a Node");
-
- if (prop == null) {// clear cache
- this.cache = null;
- return;
- }
-
- try {
- if (session == null || session != prop.getSession())// check session
- throw new IllegalArgumentException("Uncompatible session");
-
- Node node = prop.getNode();
- if (nodeId == null || !nodeId.equals(node.getIdentifier()) || !property.equals(prop.getName())) {
- nodeId = node.getIdentifier();
- property = prop.getName();
- cache = node;
- itemUpdated();
- } else {
- cache = node;// set/update cache
- }
- } catch (RepositoryException e) {
- throw new IllegalStateException(e);
- }
- }
-
- public synchronized String getNodeId() {
- return nodeId;
- }
-
- /** Change the node, does nothing if same. */
- public synchronized void setNodeId(String nodeId) throws RepositoryException {
- if (this.nodeId != null && this.nodeId.equals(nodeId))
- return;
- this.nodeId = nodeId;
- if (cache != null)
- cache = session.getNodeByIdentifier(this.nodeId);
- itemUpdated();
- }
-
- protected synchronized void itemUpdated() {
- layout();
- }
-
- public Session getSession() {
- return session;
- }
-}
+++ /dev/null
-package org.argeo.cms.ui.widgets;
-
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.custom.ScrolledComposite;
-import org.eclipse.swt.events.ControlEvent;
-import org.eclipse.swt.graphics.Point;
-import org.eclipse.swt.graphics.Rectangle;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-
-/**
- * A composite that can be scrolled vertically. It wraps a
- * {@link ScrolledComposite} (and is being wrapped by it), simplifying its
- * configuration.
- */
-public class ScrolledPage extends Composite {
- private static final long serialVersionUID = 1593536965663574437L;
-
- private ScrolledComposite scrolledComposite;
-
- public ScrolledPage(Composite parent, int style) {
- this(parent, style, false);
- }
-
- public ScrolledPage(Composite parent, int style, boolean alwaysShowScroll) {
- super(createScrolledComposite(parent, alwaysShowScroll), style);
- scrolledComposite = (ScrolledComposite) getParent();
- scrolledComposite.setContent(this);
-
- scrolledComposite.setExpandVertical(true);
- scrolledComposite.setExpandHorizontal(true);
- scrolledComposite.addControlListener(new ScrollControlListener());
- }
-
- private static ScrolledComposite createScrolledComposite(Composite parent, boolean alwaysShowScroll) {
- ScrolledComposite scrolledComposite = new ScrolledComposite(parent, SWT.V_SCROLL);
- scrolledComposite.setAlwaysShowScrollBars(alwaysShowScroll);
- return scrolledComposite;
- }
-
- @Override
- public void layout(boolean changed, boolean all) {
- updateScroll();
- super.layout(changed, all);
- }
-
- public void showControl(Control control) {
- scrolledComposite.showControl(control);
- }
-
- protected void updateScroll() {
- Rectangle r = scrolledComposite.getClientArea();
- Point preferredSize = computeSize(r.width, SWT.DEFAULT);
- scrolledComposite.setMinHeight(preferredSize.y);
- }
-
- // public ScrolledComposite getScrolledComposite() {
- // return this.scrolledComposite;
- // }
-
- /** Set it on the wrapping scrolled composite */
- @Override
- public void setLayoutData(Object layoutData) {
- scrolledComposite.setLayoutData(layoutData);
- }
-
- private class ScrollControlListener extends org.eclipse.swt.events.ControlAdapter {
- private static final long serialVersionUID = -3586986238567483316L;
-
- public void controlResized(ControlEvent e) {
- updateScroll();
- }
- }
-}
+++ /dev/null
-package org.argeo.cms.ui.widgets;
-
-import javax.jcr.Item;
-
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.cms.ui.CmsUiConstants;
-import org.argeo.eclipse.ui.specific.EclipseUiSpecificUtils;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.events.FocusListener;
-import org.eclipse.swt.events.MouseListener;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-
-/** Editable text part displaying styled text. */
-public abstract class StyledControl extends JcrComposite implements CmsUiConstants {
- private static final long serialVersionUID = -6372283442330912755L;
- private Control control;
-
- private Composite container;
- private Composite box;
-
- protected MouseListener mouseListener;
- protected FocusListener focusListener;
-
- private Boolean editing = Boolean.FALSE;
-
- private Composite ancestorToLayout;
-
- public StyledControl(Composite parent, int swtStyle) {
- super(parent, swtStyle);
- setLayout(CmsSwtUtils.noSpaceGridLayout());
- }
-
- public StyledControl(Composite parent, int style, Item item) {
- super(parent, style, item);
- }
-
- public StyledControl(Composite parent, int style, Item item, boolean cacheImmediately) {
- super(parent, style, item, cacheImmediately);
- }
-
- protected abstract Control createControl(Composite box, String style);
-
- protected Composite createBox() {
- Composite box = new Composite(container, SWT.INHERIT_DEFAULT);
- setContainerLayoutData(box);
- box.setLayout(CmsSwtUtils.noSpaceGridLayout(3));
- return box;
- }
-
- protected Composite createContainer() {
- Composite container = new Composite(this, SWT.INHERIT_DEFAULT);
- setContainerLayoutData(container);
- container.setLayout(CmsSwtUtils.noSpaceGridLayout());
- return container;
- }
-
- public Control getControl() {
- return control;
- }
-
- protected synchronized Boolean isEditing() {
- return editing;
- }
-
- public synchronized void startEditing() {
- assert !isEditing();
- editing = true;
- // int height = control.getSize().y;
- String style = (String) EclipseUiSpecificUtils.getStyleData(control);
- clear(false);
- refreshControl(style);
-
- // add the focus listener to the newly created edition control
- if (focusListener != null)
- control.addFocusListener(focusListener);
- }
-
- public synchronized void stopEditing() {
- assert isEditing();
- editing = false;
- String style = (String) EclipseUiSpecificUtils.getStyleData(control);
- clear(false);
- refreshControl(style);
- }
-
- protected void refreshControl(String style) {
- control = createControl(box, style);
- setControlLayoutData(control);
- if (ancestorToLayout != null)
- ancestorToLayout.layout(true, true);
- else
- getParent().layout(true, true);
- }
-
- public void setStyle(String style) {
- Object currentStyle = null;
- if (control != null)
- currentStyle = EclipseUiSpecificUtils.getStyleData(control);
- if (currentStyle != null && currentStyle.equals(style))
- return;
-
- clear(true);
- refreshControl(style);
-
- if (style != null) {
- CmsSwtUtils.style(box, style + "_box");
- CmsSwtUtils.style(container, style + "_container");
- }
- }
-
- /** To be overridden */
- protected void setControlLayoutData(Control control) {
- control.setLayoutData(CmsSwtUtils.fillWidth());
- }
-
- /** To be overridden */
- protected void setContainerLayoutData(Composite composite) {
- composite.setLayoutData(CmsSwtUtils.fillWidth());
- }
-
- protected void clear(boolean deep) {
- if (deep) {
- for (Control control : getChildren())
- control.dispose();
- container = createContainer();
- box = createBox();
- } else {
- control.dispose();
- }
- }
-
- public void setMouseListener(MouseListener mouseListener) {
- if (this.mouseListener != null && control != null)
- control.removeMouseListener(this.mouseListener);
- this.mouseListener = mouseListener;
- if (control != null && this.mouseListener != null)
- control.addMouseListener(mouseListener);
- }
-
- public void setFocusListener(FocusListener focusListener) {
- if (this.focusListener != null && control != null)
- control.removeFocusListener(this.focusListener);
- this.focusListener = focusListener;
- if (control != null && this.focusListener != null)
- control.addFocusListener(focusListener);
- }
-
- public void setAncestorToLayout(Composite ancestorToLayout) {
- this.ancestorToLayout = ancestorToLayout;
- }
-
-}
+++ /dev/null
-package org.argeo.cms.ui.widgets;
-
-/** Styles references in the CSS. */
-public interface TextStyles {
- /** The whole page area */
- public final static String TEXT_AREA = "text_area";
- /** Area providing controls for editing text */
- public final static String TEXT_EDITOR_HEADER = "text_editor_header";
- /** The styled composite for editing the text */
- public final static String TEXT_STYLED_COMPOSITE = "text_styled_composite";
- /** A section */
- public final static String TEXT_SECTION = "text_section";
- /** A paragraph */
- public final static String TEXT_PARAGRAPH = "text_paragraph";
- /** An image */
- public final static String TEXT_IMG = "text_img";
- /** The dialog to edit styled paragraph */
- public final static String TEXT_STYLED_TOOLS_DIALOG = "text_styled_tools_dialog";
-
- /*
- * DEFAULT TEXT STYLES
- */
- /** Default style for text body */
- public final static String TEXT_DEFAULT = "text_default";
- /** Fixed-width, typically code */
- public final static String TEXT_PRE = "text_pre";
- /** Quote */
- public final static String TEXT_QUOTE = "text_quote";
- /** Title */
- public final static String TEXT_TITLE = "text_title";
- /** Header (to be dynamically completed with the depth, e.g. text_h1) */
- public final static String TEXT_H = "text_h";
-
- /** Default style for images */
- public final static String TEXT_IMAGE = "text_image";
-
-}
+++ /dev/null
-/** Argeo CMS generic widgets, based on SWT. */
-package org.argeo.cms.ui.widgets;
\ No newline at end of file
+++ /dev/null
-package org.argeo.eclipse.ui.jcr;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.jcr.Node;
-import javax.jcr.NodeIterator;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-
-import org.argeo.api.cms.CmsLog;
-import org.argeo.eclipse.ui.AbstractTreeContentProvider;
-import org.argeo.eclipse.ui.EclipseUiException;
-
-/** Canonical implementation of tree content provider manipulating JCR nodes. */
-public abstract class AbstractNodeContentProvider extends
- AbstractTreeContentProvider {
- private static final long serialVersionUID = -4905836490027272569L;
-
- private final static CmsLog log = CmsLog
- .getLog(AbstractNodeContentProvider.class);
-
- private Session session;
-
- public AbstractNodeContentProvider(Session session) {
- this.session = session;
- }
-
- /**
- * Whether this path is a base path (and thus has no parent). By default it
- * returns true if path is '/' (root node)
- */
- protected Boolean isBasePath(String path) {
- // root node
- return path.equals("/");
- }
-
- @Override
- public Object[] getChildren(Object element) {
- Object[] children;
- if (element instanceof Node) {
- try {
- Node node = (Node) element;
- children = getChildren(node);
- } catch (RepositoryException e) {
- throw new EclipseUiException("Cannot get children of " + element, e);
- }
- } else if (element instanceof WrappedNode) {
- WrappedNode wrappedNode = (WrappedNode) element;
- try {
- children = getChildren(wrappedNode.getNode());
- } catch (RepositoryException e) {
- throw new EclipseUiException("Cannot get children of "
- + wrappedNode, e);
- }
- } else if (element instanceof NodesWrapper) {
- NodesWrapper node = (NodesWrapper) element;
- children = node.getChildren();
- } else {
- children = super.getChildren(element);
- }
-
- children = sort(element, children);
- return children;
- }
-
- /** Do not sort by default. To be overidden to provide custom sort. */
- protected Object[] sort(Object parent, Object[] children) {
- return children;
- }
-
- /**
- * To be overridden in order to filter out some nodes. Does nothing by
- * default. The provided list is a temporary one and can thus be modified
- * directly . (e.g. via an iterator)
- */
- protected List<Node> filterChildren(List<Node> children)
- throws RepositoryException {
- return children;
- }
-
- protected Object[] getChildren(Node node) throws RepositoryException {
- List<Node> nodes = new ArrayList<Node>();
- for (NodeIterator nit = node.getNodes(); nit.hasNext();)
- nodes.add(nit.nextNode());
- nodes = filterChildren(nodes);
- return nodes.toArray();
- }
-
- @Override
- public Object getParent(Object element) {
- if (element instanceof Node) {
- Node node = (Node) element;
- try {
- String path = node.getPath();
- if (isBasePath(path))
- return null;
- else
- return node.getParent();
- } catch (RepositoryException e) {
- log.warn("Cannot get parent of " + element + ": " + e);
- return null;
- }
- } else if (element instanceof WrappedNode) {
- WrappedNode wrappedNode = (WrappedNode) element;
- return wrappedNode.getParent();
- } else if (element instanceof NodesWrapper) {
- NodesWrapper nodesWrapper = (NodesWrapper) element;
- return this.getParent(nodesWrapper.getNode());
- }
- return super.getParent(element);
- }
-
- @Override
- public boolean hasChildren(Object element) {
- try {
- if (element instanceof Node) {
- Node node = (Node) element;
- return node.hasNodes();
- } else if (element instanceof WrappedNode) {
- WrappedNode wrappedNode = (WrappedNode) element;
- return wrappedNode.getNode().hasNodes();
- } else if (element instanceof NodesWrapper) {
- NodesWrapper nodesWrapper = (NodesWrapper) element;
- return nodesWrapper.hasChildren();
- }
-
- } catch (RepositoryException e) {
- throw new EclipseUiException("Cannot check whether " + element
- + " has children", e);
- }
- return super.hasChildren(element);
- }
-
- public Session getSession() {
- return session;
- }
-}
\ No newline at end of file
+++ /dev/null
-package org.argeo.eclipse.ui.jcr;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.jcr.RepositoryException;
-import javax.jcr.observation.Event;
-import javax.jcr.observation.EventIterator;
-import javax.jcr.observation.EventListener;
-
-import org.argeo.api.cms.CmsLog;
-import org.argeo.eclipse.ui.EclipseUiException;
-import org.eclipse.swt.widgets.Display;
-
-/**
- * {@link EventListener} which simplifies running actions within the UI thread.
- */
-public abstract class AsyncUiEventListener implements EventListener {
- // private final static Log logSuper = LogFactory
- // .getLog(AsyncUiEventListener.class);
- private final CmsLog logThis = CmsLog.getLog(getClass());
-
- private final Display display;
-
- public AsyncUiEventListener(Display display) {
- super();
- this.display = display;
- }
-
- /** Called asynchronously in the UI thread. */
- protected abstract void onEventInUiThread(List<Event> events) throws RepositoryException;
-
- /**
- * Whether these events should be processed in the UI or skipped with no UI
- * job created.
- */
- protected Boolean willProcessInUiThread(List<Event> events) throws RepositoryException {
- return true;
- }
-
- protected CmsLog getLog() {
- return logThis;
- }
-
- public final void onEvent(final EventIterator eventIterator) {
- final List<Event> events = new ArrayList<Event>();
- while (eventIterator.hasNext())
- events.add(eventIterator.nextEvent());
-
- if (logThis.isTraceEnabled())
- logThis.trace("Received " + events.size() + " events");
-
- try {
- if (!willProcessInUiThread(events))
- return;
- } catch (RepositoryException e) {
- throw new EclipseUiException("Cannot test skip events " + events, e);
- }
-
- // Job job = new Job("JCR Events") {
- // protected IStatus run(IProgressMonitor monitor) {
- // if (display.isDisposed()) {
- // logSuper.warn("Display is disposed cannot update UI");
- // return Status.CANCEL_STATUS;
- // }
-
- if (!display.isDisposed())
- display.asyncExec(new Runnable() {
- public void run() {
- try {
- onEventInUiThread(events);
- } catch (RepositoryException e) {
- throw new EclipseUiException("Cannot process events " + events, e);
- }
- }
- });
-
- // return Status.OK_STATUS;
- // }
- // };
- // job.schedule();
- }
-}
+++ /dev/null
-package org.argeo.eclipse.ui.jcr;
-
-import javax.jcr.Node;
-import javax.jcr.Property;
-import javax.jcr.RepositoryException;
-import javax.jcr.nodetype.NodeType;
-
-import org.argeo.eclipse.ui.EclipseUiException;
-import org.eclipse.jface.viewers.ColumnLabelProvider;
-import org.eclipse.swt.graphics.Image;
-
-/**
- * Default label provider to manage node and corresponding UI objects. It
- * provides reasonable overwrite-able default for known JCR types.
- */
-public class DefaultNodeLabelProvider extends ColumnLabelProvider {
- private static final long serialVersionUID = 1216182332792151235L;
-
- public String getText(Object element) {
- try {
- if (element instanceof Node) {
- return getText((Node) element);
- } else if (element instanceof WrappedNode) {
- return getText(((WrappedNode) element).getNode());
- } else if (element instanceof NodesWrapper) {
- return getText(((NodesWrapper) element).getNode());
- }
- return super.getText(element);
- } catch (RepositoryException e) {
- throw new EclipseUiException("Cannot get text for of " + element, e);
- }
- }
-
- protected String getText(Node node) throws RepositoryException {
- if (node.isNodeType(NodeType.MIX_TITLE)
- && node.hasProperty(Property.JCR_TITLE))
- return node.getProperty(Property.JCR_TITLE).getString();
- else
- return node.getName();
- }
-
- @Override
- public Image getImage(Object element) {
- try {
- 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());
- }
- } catch (RepositoryException e) {
- throw new EclipseUiException("Cannot retrieve image for " + element, e);
- }
- return super.getImage(element);
- }
-
- protected Image getImage(Node node) throws RepositoryException {
- // FIXME who uses that?
- return null;
- }
-
- @Override
- public String getToolTipText(Object element) {
- try {
- if (element instanceof Node) {
- return getToolTipText((Node) element);
- } else if (element instanceof WrappedNode) {
- return getToolTipText(((WrappedNode) element).getNode());
- } else if (element instanceof NodesWrapper) {
- return getToolTipText(((NodesWrapper) element).getNode());
- }
- } catch (RepositoryException e) {
- throw new EclipseUiException("Cannot get tooltip for " + element, e);
- }
- return super.getToolTipText(element);
- }
-
- protected String getToolTipText(Node node) throws RepositoryException {
- return null;
- }
-}
\ No newline at end of file
+++ /dev/null
-package org.argeo.eclipse.ui.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.eclipse.ui.jcr;
-
-import java.util.Calendar;
-
-import javax.jcr.Node;
-import javax.jcr.PropertyType;
-import javax.jcr.RepositoryException;
-
-import org.argeo.eclipse.ui.EclipseUiException;
-import org.argeo.eclipse.ui.jcr.lists.NodeViewerComparator;
-import org.argeo.eclipse.ui.jcr.lists.RowViewerComparator;
-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.widgets.Table;
-
-/** Utility methods to simplify UI development using SWT (or RWT), jface and JCR. */
-public class JcrUiUtils {
-
- /**
- * Centralizes management of updating property value. Among other to avoid
- * infinite loop when the new value is the same as the ones that is already
- * stored in JCR.
- *
- * @return true if the value as changed
- */
- public static boolean setJcrProperty(Node node, String propName,
- int propertyType, Object value) {
- try {
- switch (propertyType) {
- case PropertyType.STRING:
- if ("".equals((String) value)
- && (!node.hasProperty(propName) || node
- .hasProperty(propName)
- && "".equals(node.getProperty(propName)
- .getString())))
- // workaround the fact that the Text widget value cannot be
- // set to null
- return false;
- else if (node.hasProperty(propName)
- && node.getProperty(propName).getString()
- .equals((String) value))
- // nothing changed yet
- return false;
- else {
- node.setProperty(propName, (String) value);
- return true;
- }
- case PropertyType.BOOLEAN:
- if (node.hasProperty(propName)
- && node.getProperty(propName).getBoolean() == (Boolean) value)
- // nothing changed yet
- return false;
- else {
- node.setProperty(propName, (Boolean) value);
- return true;
- }
- case PropertyType.DATE:
- if (node.hasProperty(propName)
- && node.getProperty(propName).getDate()
- .equals((Calendar) value))
- // nothing changed yet
- return false;
- else {
- node.setProperty(propName, (Calendar) value);
- return true;
- }
- case PropertyType.LONG:
- Long lgValue = (Long) value;
-
- if (lgValue == null)
- lgValue = 0L;
-
- if (node.hasProperty(propName)
- && node.getProperty(propName).getLong() == lgValue)
- // nothing changed yet
- return false;
- else {
- node.setProperty(propName, lgValue);
- return true;
- }
-
- default:
- throw new EclipseUiException("Unimplemented property save");
- }
- } catch (RepositoryException re) {
- throw new EclipseUiException("Unexpected error while setting property",
- re);
- }
- }
-
- /**
- * Creates a new selection adapter in order to provide sorting abitily on a
- * SWT Table that display a row list
- **/
- public static SelectionAdapter getRowSelectionAdapter(final int index,
- final int propertyType, final String selectorName,
- final String propertyName, final RowViewerComparator comparator,
- final TableViewer viewer) {
- SelectionAdapter selectionAdapter = new SelectionAdapter() {
- private static final long serialVersionUID = -5738918304901437720L;
-
- @Override
- public void widgetSelected(SelectionEvent e) {
- Table table = viewer.getTable();
- comparator.setColumn(propertyType, selectorName, propertyName);
- int dir = table.getSortDirection();
- if (table.getSortColumn() == table.getColumn(index)) {
- dir = dir == SWT.UP ? SWT.DOWN : SWT.UP;
- } else {
- dir = SWT.DOWN;
- }
- table.setSortDirection(dir);
- table.setSortColumn(table.getColumn(index));
- viewer.refresh();
- }
- };
- return selectionAdapter;
- }
-
- /**
- * Creates a new selection adapter in order to provide sorting abitily on a
- * swt table that display a row list
- **/
- public static SelectionAdapter getNodeSelectionAdapter(final int index,
- final int propertyType, final String propertyName,
- final NodeViewerComparator comparator, final TableViewer viewer) {
- SelectionAdapter selectionAdapter = new SelectionAdapter() {
- private static final long serialVersionUID = -1683220869195484625L;
-
- @Override
- public void widgetSelected(SelectionEvent e) {
- Table table = viewer.getTable();
- comparator.setColumn(propertyType, propertyName);
- int dir = table.getSortDirection();
- if (table.getSortColumn() == table.getColumn(index)) {
- dir = dir == SWT.UP ? SWT.DOWN : SWT.UP;
- } else {
- dir = SWT.DOWN;
- }
- table.setSortDirection(dir);
- table.setSortColumn(table.getColumn(index));
- viewer.refresh();
- }
- };
- return selectionAdapter;
- }
-}
+++ /dev/null
-package org.argeo.eclipse.ui.jcr;
-
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-
-import org.eclipse.jface.viewers.ColumnLabelProvider;
-import org.eclipse.swt.graphics.Color;
-import org.eclipse.swt.graphics.Font;
-import org.eclipse.swt.graphics.Image;
-
-/** Simplifies writing JCR-based column label provider. */
-public class NodeColumnLabelProvider extends ColumnLabelProvider {
- private static final long serialVersionUID = -6586692836928505358L;
-
- protected String getNodeText(Node node) throws RepositoryException {
- return super.getText(node);
- }
-
- protected String getNodeToolTipText(Node node) throws RepositoryException {
- return super.getToolTipText(node);
- }
-
- protected Image getNodeImage(Node node) throws RepositoryException {
- return super.getImage(node);
- }
-
- protected Font getNodeFont(Node node) throws RepositoryException {
- return super.getFont(node);
- }
-
- public Color getNodeBackground(Node node) throws RepositoryException {
- return super.getBackground(node);
- }
-
- public Color getNodeForeground(Node node) throws RepositoryException {
- return super.getForeground(node);
- }
-
- @Override
- public String getText(Object element) {
- try {
- if (element instanceof Node)
- return getNodeText((Node) element);
- else if (element instanceof NodeElement)
- return getNodeText(((NodeElement) element).getNode());
- else
- throw new IllegalArgumentException("Unsupported element type " + element.getClass());
- } catch (RepositoryException e) {
- throw new IllegalStateException("Repository exception when accessing " + element, e);
- }
- }
-
- @Override
- public Image getImage(Object element) {
- try {
- if (element instanceof Node)
- return getNodeImage((Node) element);
- else if (element instanceof NodeElement)
- return getNodeImage(((NodeElement) element).getNode());
- else
- throw new IllegalArgumentException("Unsupported element type " + element.getClass());
- } catch (RepositoryException e) {
- throw new IllegalStateException("Repository exception when accessing " + element, e);
- }
- }
-
- @Override
- public String getToolTipText(Object element) {
- try {
- if (element instanceof Node)
- return getNodeToolTipText((Node) element);
- else if (element instanceof NodeElement)
- return getNodeToolTipText(((NodeElement) element).getNode());
- else
- throw new IllegalArgumentException("Unsupported element type " + element.getClass());
- } catch (RepositoryException e) {
- throw new IllegalStateException("Repository exception when accessing " + element, e);
- }
- }
-
- @Override
- public Font getFont(Object element) {
- try {
- if (element instanceof Node)
- return getNodeFont((Node) element);
- else if (element instanceof NodeElement)
- return getNodeFont(((NodeElement) element).getNode());
- else
- throw new IllegalArgumentException("Unsupported element type " + element.getClass());
- } catch (RepositoryException e) {
- throw new IllegalStateException("Repository exception when accessing " + element, e);
- }
- }
-
- @Override
- public Color getBackground(Object element) {
- try {
- if (element instanceof Node)
- return getNodeBackground((Node) element);
- else if (element instanceof NodeElement)
- return getNodeBackground(((NodeElement) element).getNode());
- else
- throw new IllegalArgumentException("Unsupported element type " + element.getClass());
- } catch (RepositoryException e) {
- throw new IllegalStateException("Repository exception when accessing " + element, e);
- }
- }
-
- @Override
- public Color getForeground(Object element) {
- try {
- if (element instanceof Node)
- return getNodeForeground((Node) element);
- else if (element instanceof NodeElement)
- return getNodeForeground(((NodeElement) element).getNode());
- else
- throw new IllegalArgumentException("Unsupported element type " + element.getClass());
- } catch (RepositoryException e) {
- throw new IllegalStateException("Repository exception when accessing " + element, e);
- }
- }
-
-}
+++ /dev/null
-package org.argeo.eclipse.ui.jcr;
-
-import javax.jcr.Node;
-
-/** An element which is related to a JCR {@link Node}. */
-public interface NodeElement {
- Node getNode();
-}
+++ /dev/null
-package org.argeo.eclipse.ui.jcr;\r
-\r
-import javax.jcr.Node;\r
-import javax.jcr.RepositoryException;\r
-\r
-import org.argeo.eclipse.ui.EclipseUiException;\r
-import org.eclipse.jface.viewers.IElementComparer;\r
-\r
-/** Element comparer for JCR node, to be used in JFace viewers. */\r
-public class NodeElementComparer implements IElementComparer {\r
-\r
- public boolean equals(Object a, Object b) {\r
- try {\r
- if ((a instanceof Node) && (b instanceof Node)) {\r
- Node nodeA = (Node) a;\r
- Node nodeB = (Node) b;\r
- return nodeA.getIdentifier().equals(nodeB.getIdentifier());\r
- } else {\r
- return a.equals(b);\r
- }\r
- } catch (RepositoryException e) {\r
- throw new EclipseUiException("Cannot compare nodes", e);\r
- }\r
- }\r
-\r
- public int hashCode(Object element) {\r
- try {\r
- if (element instanceof Node)\r
- return ((Node) element).getIdentifier().hashCode();\r
- return element.hashCode();\r
- } catch (RepositoryException e) {\r
- throw new EclipseUiException("Cannot get hash code", e);\r
- }\r
- }\r
-\r
-}\r
+++ /dev/null
-package org.argeo.eclipse.ui.jcr;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.jcr.Node;
-import javax.jcr.NodeIterator;
-import javax.jcr.RepositoryException;
-
-import org.argeo.eclipse.ui.EclipseUiException;
-
-/**
- * Element of tree which is based on a node, but whose children are not
- * necessarily this node children.
- */
-public class NodesWrapper {
- private final Node node;
-
- public NodesWrapper(Node node) {
- super();
- this.node = node;
- }
-
- protected NodeIterator getNodeIterator() throws RepositoryException {
- return node.getNodes();
- }
-
- protected List<WrappedNode> getWrappedNodes() throws RepositoryException {
- List<WrappedNode> nodes = new ArrayList<WrappedNode>();
- for (NodeIterator nit = getNodeIterator(); nit.hasNext();)
- nodes.add(new WrappedNode(this, nit.nextNode()));
- return nodes;
- }
-
- public Object[] getChildren() {
- try {
- return getWrappedNodes().toArray();
- } catch (RepositoryException e) {
- throw new EclipseUiException("Cannot get wrapped children", e);
- }
- }
-
- /**
- * @return true by default because we don't want to compute the wrapped
- * nodes twice
- */
- public Boolean hasChildren() {
- return true;
- }
-
- public Node getNode() {
- return node;
- }
-
- @Override
- public int hashCode() {
- return node.hashCode();
- }
-
- @Override
- public boolean equals(Object obj) {
- if (obj instanceof NodesWrapper)
- return node.equals(((NodesWrapper) obj).getNode());
- else
- return false;
- }
-
- public String toString() {
- return "nodes wrapper based on " + node;
- }
-}
+++ /dev/null
-package org.argeo.eclipse.ui.jcr;
-
-import javax.jcr.NodeIterator;
-import javax.jcr.RepositoryException;
-import javax.jcr.query.Query;
-
-import org.argeo.jcr.JcrException;
-import org.argeo.jcr.JcrUtils;
-import org.eclipse.jface.viewers.IStructuredContentProvider;
-import org.eclipse.jface.viewers.Viewer;
-
-/** Content provider based on a JCR {@link Query}. */
-public class QueryTableContentProvider implements IStructuredContentProvider {
- private static final long serialVersionUID = 760371460907204722L;
-
- @Override
- public void dispose() {
- }
-
- @Override
- public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
- }
-
- @Override
- public Object[] getElements(Object inputElement) {
- Query query = (Query) inputElement;
- try {
- NodeIterator nit = query.execute().getNodes();
- return JcrUtils.nodeIteratorToList(nit).toArray();
- } catch (RepositoryException e) {
- throw new JcrException(e);
- }
- }
-
-}
+++ /dev/null
-package org.argeo.eclipse.ui.jcr;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-
-import org.argeo.eclipse.ui.EclipseUiException;
-import org.argeo.jcr.JcrUtils;
-
-/** Simple JCR node content provider taking a list of String as base path. */
-public class SimpleNodeContentProvider extends AbstractNodeContentProvider {
- private static final long serialVersionUID = -8245193308831384269L;
- private final List<String> basePaths;
- private Boolean mkdirs = false;
-
- public SimpleNodeContentProvider(Session session, String... basePaths) {
- this(session, Arrays.asList(basePaths));
- }
-
- public SimpleNodeContentProvider(Session session, List<String> basePaths) {
- super(session);
- this.basePaths = basePaths;
- }
-
- @Override
- protected Boolean isBasePath(String path) {
- if (basePaths.contains(path))
- return true;
- return super.isBasePath(path);
- }
-
- public Object[] getElements(Object inputElement) {
- try {
- List<Node> baseNodes = new ArrayList<Node>();
- for (String basePath : basePaths)
- if (mkdirs && !getSession().itemExists(basePath))
- baseNodes.add(JcrUtils.mkdirs(getSession(), basePath));
- else
- baseNodes.add(getSession().getNode(basePath));
- return baseNodes.toArray();
- } catch (RepositoryException e) {
- throw new EclipseUiException("Cannot get base nodes for " + basePaths,
- e);
- }
- }
-
- public List<String> getBasePaths() {
- return basePaths;
- }
-
- public void setMkdirs(Boolean mkdirs) {
- this.mkdirs = mkdirs;
- }
-
-}
+++ /dev/null
-package org.argeo.eclipse.ui.jcr;
-
-import javax.jcr.Node;
-import javax.jcr.Property;
-import javax.jcr.RepositoryException;
-import javax.jcr.version.Version;
-
-import org.eclipse.jface.viewers.ColumnLabelProvider;
-import org.eclipse.swt.graphics.Image;
-
-/** Simplifies writing JCR-based column label provider. */
-public class VersionColumnLabelProvider extends ColumnLabelProvider {
- private static final long serialVersionUID = -6117690082313161159L;
-
- protected String getVersionText(Version version) throws RepositoryException {
- return super.getText(version);
- }
-
- protected String getVersionToolTipText(Version version) throws RepositoryException {
- return super.getToolTipText(version);
- }
-
- protected Image getVersionImage(Version version) throws RepositoryException {
- return super.getImage(version);
- }
-
- protected String getUserName(Version version) throws RepositoryException {
- Node node = version.getFrozenNode();
- if(node.hasProperty(Property.JCR_LAST_MODIFIED_BY))
- return node.getProperty(Property.JCR_LAST_MODIFIED_BY).getString();
- if(node.hasProperty(Property.JCR_CREATED_BY))
- return node.getProperty(Property.JCR_CREATED_BY).getString();
- return null;
- }
-
-// protected String getActivityTitle(Version version) throws RepositoryException {
-// Node activity = getActivity(version);
-// if (activity == null)
-// return null;
-// if (activity.hasProperty("jcr:activityTitle"))
-// return activity.getProperty("jcr:activityTitle").getString();
-// else
-// return activity.getName();
-// }
-//
-// protected Node getActivity(Version version) throws RepositoryException {
-// if (version.hasProperty(Property.JCR_ACTIVITY)) {
-// return version.getProperty(Property.JCR_ACTIVITY).getNode();
-// } else
-// return null;
-// }
-
- @Override
- public String getText(Object element) {
- try {
- return getVersionText((Version) element);
- } catch (RepositoryException e) {
- throw new RuntimeException("Runtime repository exception when accessing " + element, e);
- }
- }
-
- @Override
- public Image getImage(Object element) {
- try {
- return getVersionImage((Version) element);
- } catch (RepositoryException e) {
- throw new RuntimeException("Runtime repository exception when accessing " + element, e);
- }
- }
-
- @Override
- public String getToolTipText(Object element) {
- try {
- return getVersionToolTipText((Version) element);
- } catch (RepositoryException e) {
- throw new RuntimeException("Runtime repository exception when accessing " + element, e);
- }
- }
-
-}
+++ /dev/null
-package org.argeo.eclipse.ui.jcr;
-
-import javax.jcr.version.VersionHistory;
-
-import org.argeo.jcr.Jcr;
-import org.eclipse.jface.viewers.IStructuredContentProvider;
-import org.eclipse.jface.viewers.Viewer;
-
-/** Content provider based on a {@link VersionHistory}. */
-public class VersionHistoryContentProvider implements IStructuredContentProvider {
- private static final long serialVersionUID = -4921107883428887012L;
-
- @Override
- public void dispose() {
- }
-
- @Override
- public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
- }
-
- @Override
- public Object[] getElements(Object inputElement) {
- VersionHistory versionHistory = (VersionHistory) inputElement;
- return Jcr.getLinearVersions(versionHistory).toArray();
- }
-
-}
+++ /dev/null
-package org.argeo.eclipse.ui.jcr;
-
-import javax.jcr.Node;
-
-/** Wrap a node (created from a {@link NodesWrapper}) */
-public class WrappedNode {
- private final NodesWrapper parent;
- private final Node node;
-
- public WrappedNode(NodesWrapper parent, Node node) {
- super();
- this.parent = parent;
- this.node = node;
- }
-
- public NodesWrapper getParent() {
- return parent;
- }
-
- public Node getNode() {
- return node;
- }
-
- public String toString() {
- return "wrapped " + node;
- }
-
- @Override
- public int hashCode() {
- return node.hashCode();
- }
-
- @Override
- public boolean equals(Object obj) {
- if (obj instanceof WrappedNode)
- return node.equals(((WrappedNode) obj).getNode());
- else
- return false;
- }
-
-}
+++ /dev/null
-package org.argeo.eclipse.ui.jcr.lists;
-
-import javax.jcr.Node;
-import javax.jcr.query.Row;
-
-import org.argeo.eclipse.ui.ColumnDefinition;
-
-/**
- * Utility object to manage column in various tables and extracts displaying
- * data from JCR
- */
-public class JcrColumnDefinition extends ColumnDefinition {
- private final static int DEFAULT_COLUMN_SIZE = 120;
-
- private String selectorName;
- private String propertyName;
- private int propertyType;
- private int columnSize;
-
- /**
- * Use this kind of columns to configure a table that displays JCR
- * {@link Row}
- *
- * @param selectorName
- * @param propertyName
- * @param propertyType
- * @param headerLabel
- */
- public JcrColumnDefinition(String selectorName, String propertyName,
- int propertyType, String headerLabel) {
- super(new SimpleJcrRowLabelProvider(selectorName, propertyName),
- headerLabel);
- this.selectorName = selectorName;
- this.propertyName = propertyName;
- this.propertyType = propertyType;
- this.columnSize = DEFAULT_COLUMN_SIZE;
- }
-
- /**
- * Use this kind of columns to configure a table that displays JCR
- * {@link Row}
- *
- * @param selectorName
- * @param propertyName
- * @param propertyType
- * @param headerLabel
- * @param columnSize
- */
- public JcrColumnDefinition(String selectorName, String propertyName,
- int propertyType, String headerLabel, int columnSize) {
- super(new SimpleJcrRowLabelProvider(selectorName, propertyName),
- headerLabel, columnSize);
- this.selectorName = selectorName;
- this.propertyName = propertyName;
- this.propertyType = propertyType;
- this.columnSize = columnSize;
- }
-
- /**
- * Use this kind of columns to configure a table that displays JCR
- * {@link Node}
- *
- * @param propertyName
- * @param propertyType
- * @param headerLabel
- * @param columnSize
- */
- public JcrColumnDefinition(String propertyName, int propertyType,
- String headerLabel, int columnSize) {
- super(new SimpleJcrNodeLabelProvider(propertyName), headerLabel,
- columnSize);
- this.propertyName = propertyName;
- this.propertyType = propertyType;
- this.columnSize = columnSize;
- }
-
- public String getSelectorName() {
- return selectorName;
- }
-
- public void setSelectorName(String selectorName) {
- this.selectorName = selectorName;
- }
-
- public String getPropertyName() {
- return propertyName;
- }
-
- public void setPropertyName(String propertyName) {
- this.propertyName = propertyName;
- }
-
- public int getPropertyType() {
- return propertyType;
- }
-
- public void setPropertyType(int propertyType) {
- this.propertyType = propertyType;
- }
-
- public int getColumnSize() {
- return columnSize;
- }
-
- public void setColumnSize(int columnSize) {
- this.columnSize = columnSize;
- }
-
- public String getHeaderLabel() {
- return super.getLabel();
- }
-
- public void setHeaderLabel(String headerLabel) {
- super.setLabel(headerLabel);
- }
-}
+++ /dev/null
-package org.argeo.eclipse.ui.jcr.lists;
-
-import java.math.BigDecimal;
-import java.util.Calendar;
-
-import javax.jcr.Node;
-import javax.jcr.PropertyType;
-import javax.jcr.RepositoryException;
-import javax.jcr.Value;
-import javax.jcr.ValueFormatException;
-
-import org.argeo.eclipse.ui.EclipseUiException;
-import org.eclipse.jface.viewers.Viewer;
-import org.eclipse.jface.viewers.ViewerComparator;
-
-/**
- * Base comparator to enable ordering on Table or Tree viewer that display Jcr
- * Nodes.
- *
- * Note that the following snippet must be added before setting the comparator
- * to the corresponding control: <code>
- * // IMPORTANT: initialize comparator before setting it
- * JcrColumnDefinition firstCol = colDefs.get(0);
- * comparator.setColumn(firstCol.getPropertyType(),
- * firstCol.getPropertyName());
- * viewer.setComparator(comparator); </code>
- */
-public class NodeViewerComparator extends ViewerComparator {
- private static final long serialVersionUID = -7782916140737279027L;
-
- protected String propertyName;
-
- protected int propertyType;
- public static final int ASCENDING = 0, DESCENDING = 1;
- protected int direction = DESCENDING;
-
- public NodeViewerComparator() {
- }
-
- /**
- * e1 and e2 must both be Jcr nodes.
- *
- * @param viewer
- * @param e1
- * @param e2
- * @return
- */
- @Override
- public int compare(Viewer viewer, Object e1, Object e2) {
- int rc = 0;
- long lc = 0;
-
- try {
- Node n1 = (Node) e1;
- Node n2 = (Node) e2;
-
- Value v1 = null;
- Value v2 = null;
- if (n1.hasProperty(propertyName))
- v1 = n1.getProperty(propertyName).getValue();
- if (n2.hasProperty(propertyName))
- v2 = n2.getProperty(propertyName).getValue();
-
- if (v2 == null && v1 == null)
- return 0;
- else if (v2 == null)
- return -1;
- else if (v1 == null)
- return 1;
-
- switch (propertyType) {
- case PropertyType.STRING:
- rc = v1.getString().compareTo(v2.getString());
- break;
- case PropertyType.BOOLEAN:
- boolean b1 = v1.getBoolean();
- boolean b2 = v2.getBoolean();
- if (b1 == b2)
- rc = 0;
- else
- // we assume true is greater than false
- rc = b1 ? 1 : -1;
- break;
- case PropertyType.DATE:
- Calendar c1 = v1.getDate();
- Calendar c2 = v2.getDate();
- if (c1 == null || c2 == null)
- // log.trace("undefined date");
- ;
- lc = c1.getTimeInMillis() - c2.getTimeInMillis();
- if (lc < Integer.MIN_VALUE)
- rc = -1;
- else if (lc > Integer.MAX_VALUE)
- rc = 1;
- else
- rc = (int) lc;
- break;
- case PropertyType.LONG:
- long l1;
- long l2;
- // TODO Sometimes an empty string is set instead of a long
- try {
- l1 = v1.getLong();
- } catch (ValueFormatException ve) {
- l1 = 0;
- }
- try {
- l2 = v2.getLong();
- } catch (ValueFormatException ve) {
- l2 = 0;
- }
-
- lc = l1 - l2;
- if (lc < Integer.MIN_VALUE)
- rc = -1;
- else if (lc > Integer.MAX_VALUE)
- rc = 1;
- else
- rc = (int) lc;
- break;
- case PropertyType.DECIMAL:
- BigDecimal bd1 = v1.getDecimal();
- BigDecimal bd2 = v2.getDecimal();
- rc = bd1.compareTo(bd2);
- break;
- case PropertyType.DOUBLE:
- Double d1 = v1.getDouble();
- Double d2 = v2.getDouble();
- rc = d1.compareTo(d2);
- break;
- default:
- throw new EclipseUiException(
- "Unimplemented comparaison for PropertyType "
- + propertyType);
- }
- // If descending order, flip the direction
- if (direction == DESCENDING) {
- rc = -rc;
- }
-
- } catch (RepositoryException re) {
- throw new EclipseUiException("Unexpected error "
- + "while comparing nodes", re);
- }
- return rc;
- }
-
- /**
- * @param propertyType
- * Corresponding JCR type
- * @param propertyName
- * name of the property to use.
- */
- public void setColumn(int propertyType, String propertyName) {
- if (this.propertyName != null && this.propertyName.equals(propertyName)) {
- // Same column as last sort; toggle the direction
- direction = 1 - direction;
- } else {
- // New column; do an ascending sort
- this.propertyType = propertyType;
- this.propertyName = propertyName;
- direction = ASCENDING;
- }
- }
-
- // Getters and setters
- protected String getPropertyName() {
- return propertyName;
- }
-
- protected void setPropertyName(String propertyName) {
- this.propertyName = propertyName;
- }
-
- protected int getPropertyType() {
- return propertyType;
- }
-
- protected void setPropertyType(int propertyType) {
- this.propertyType = propertyType;
- }
-
- protected int getDirection() {
- return direction;
- }
-
- protected void setDirection(int direction) {
- this.direction = direction;
- }
-}
+++ /dev/null
-package org.argeo.eclipse.ui.jcr.lists;
-
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-import javax.jcr.query.Row;
-
-import org.argeo.eclipse.ui.EclipseUiException;
-import org.eclipse.jface.viewers.Viewer;
-
-/**
- * Base comparator to enable ordering on Table or Tree viewer that display Jcr
- * rows
- */
-public class RowViewerComparator extends NodeViewerComparator {
- private static final long serialVersionUID = 7020939505172625113L;
- protected String selectorName;
-
- public RowViewerComparator() {
- }
-
- /**
- * e1 and e2 must both be Jcr rows.
- *
- * @param viewer
- * @param e1
- * @param e2
- * @return
- */
- @Override
- public int compare(Viewer viewer, Object e1, Object e2) {
- try {
- Node n1 = ((Row) e1).getNode(selectorName);
- Node n2 = ((Row) e2).getNode(selectorName);
- return super.compare(viewer, n1, n2);
- } catch (RepositoryException re) {
- throw new EclipseUiException("Unexpected error "
- + "while comparing nodes", re);
- }
- }
-
- /**
- * @param propertyType
- * Corresponding JCR type
- * @param propertyName
- * name of the property to use.
- */
- public void setColumn(int propertyType, String selectorName,
- String propertyName) {
- if (this.selectorName != null && getPropertyName() != null
- && this.selectorName.equals(selectorName)
- && this.getPropertyName().equals(propertyName)) {
- // Same column as last sort; toggle the direction
- setDirection(1 - getDirection());
- } else {
- // New column; do a descending sort
- setPropertyType(propertyType);
- setPropertyName(propertyName);
- this.selectorName = selectorName;
- setDirection(NodeViewerComparator.ASCENDING);
- }
- }
-}
+++ /dev/null
-package org.argeo.eclipse.ui.jcr.lists;
-
-import java.text.DateFormat;
-import java.text.DecimalFormat;
-import java.text.NumberFormat;
-import java.text.SimpleDateFormat;
-
-import javax.jcr.Node;
-import javax.jcr.PropertyType;
-import javax.jcr.RepositoryException;
-import javax.jcr.Value;
-
-import org.argeo.eclipse.ui.EclipseUiException;
-import org.eclipse.jface.viewers.ColumnLabelProvider;
-
-/** Base implementation of a label provider for controls that display JCR Nodes */
-public class SimpleJcrNodeLabelProvider extends ColumnLabelProvider {
- private static final long serialVersionUID = -5215787695436221993L;
-
- private final static String DEFAULT_DATE_FORMAT = "EEE, dd MMM yyyy";
- private final static String DEFAULT_NUMBER_FORMAT = "#,##0.0";
-
- private DateFormat dateFormat;
- private NumberFormat numberFormat;
-
- final private String propertyName;
-
- /**
- * Default Label provider for a given property of a node. Using default
- * pattern for date and number formating
- */
- public SimpleJcrNodeLabelProvider(String propertyName) {
- this.propertyName = propertyName;
- dateFormat = new SimpleDateFormat(DEFAULT_DATE_FORMAT);
- numberFormat = DecimalFormat.getInstance();
- ((DecimalFormat) numberFormat).applyPattern(DEFAULT_NUMBER_FORMAT);
- }
-
- /**
- * Label provider for a given property of a node optionally precising date
- * and/or number format patterns
- */
- public SimpleJcrNodeLabelProvider(String propertyName,
- String dateFormatPattern, String numberFormatPattern) {
- this.propertyName = propertyName;
- dateFormat = new SimpleDateFormat(
- dateFormatPattern == null ? DEFAULT_DATE_FORMAT
- : dateFormatPattern);
- numberFormat = DecimalFormat.getInstance();
- ((DecimalFormat) numberFormat)
- .applyPattern(numberFormatPattern == null ? DEFAULT_NUMBER_FORMAT
- : numberFormatPattern);
- }
-
- @Override
- public String getText(Object element) {
- try {
- Node currNode = (Node) element;
-
- if (currNode.hasProperty(propertyName)) {
- if (currNode.getProperty(propertyName).isMultiple()) {
- StringBuilder builder = new StringBuilder();
- for (Value value : currNode.getProperty(propertyName)
- .getValues()) {
- String currStr = getSingleValueAsString(value);
- if (notEmptyString(currStr))
- builder.append(currStr).append("; ");
- }
- if (builder.length() > 0)
- builder.deleteCharAt(builder.length() - 2);
-
- return builder.toString();
- } else
- return getSingleValueAsString(currNode.getProperty(
- propertyName).getValue());
- } else
- return "";
- } catch (RepositoryException re) {
- throw new EclipseUiException("Unable to get text from row", re);
- }
- }
-
- private String getSingleValueAsString(Value value)
- throws RepositoryException {
- switch (value.getType()) {
- case PropertyType.STRING:
- return value.getString();
- case PropertyType.BOOLEAN:
- return "" + value.getBoolean();
- case PropertyType.DATE:
- return dateFormat.format(value.getDate().getTime());
- case PropertyType.LONG:
- return "" + value.getLong();
- case PropertyType.DECIMAL:
- return numberFormat.format(value.getDecimal());
- case PropertyType.DOUBLE:
- return numberFormat.format(value.getDouble());
- case PropertyType.NAME:
- return value.getString();
- default:
- throw new EclipseUiException("Unimplemented label provider "
- + "for property type " + value.getType()
- + " while getting property " + propertyName + " - value: "
- + value.getString());
-
- }
- }
-
- private boolean notEmptyString(String string) {
- return string != null && !"".equals(string.trim());
- }
-
- public void setDateFormat(String dateFormatPattern) {
- dateFormat = new SimpleDateFormat(dateFormatPattern);
- }
-
- public void setNumberFormat(String numberFormatPattern) {
- ((DecimalFormat) numberFormat).applyPattern(numberFormatPattern);
- }
-}
+++ /dev/null
-package org.argeo.eclipse.ui.jcr.lists;
-
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-import javax.jcr.query.Row;
-
-import org.argeo.eclipse.ui.EclipseUiException;
-
-/**
- * Base implementation of a label provider for widgets that display JCR Rows.
- */
-public class SimpleJcrRowLabelProvider extends SimpleJcrNodeLabelProvider {
- private static final long serialVersionUID = -3414654948197181740L;
-
- final private String selectorName;
-
- /**
- * Default Label provider for a given property of a row. Using default
- * pattern for date and number formating
- */
- public SimpleJcrRowLabelProvider(String selectorName, String propertyName) {
- super(propertyName);
- this.selectorName = selectorName;
- }
-
- /**
- * Label provider for a given property of a node optionally precising date
- * and/or number format patterns
- */
- public SimpleJcrRowLabelProvider(String selectorName, String propertyName,
- String dateFormatPattern, String numberFormatPattern) {
- super(propertyName, dateFormatPattern, numberFormatPattern);
- this.selectorName = selectorName;
- }
-
- @Override
- public String getText(Object element) {
- try {
- Row currRow = (Row) element;
- Node currNode = currRow.getNode(selectorName);
- return super.getText(currNode);
- } catch (RepositoryException re) {
- throw new EclipseUiException("Unable to get Node " + selectorName
- + " from row " + element, re);
- }
- }
-}
+++ /dev/null
-/** Generic SWT/JFace JCR utilities for lists. */
-package org.argeo.eclipse.ui.jcr.lists;
\ No newline at end of file
+++ /dev/null
-/** Generic SWT/JFace JCR utilities. */
-package org.argeo.eclipse.ui.jcr;
\ No newline at end of file
+++ /dev/null
-package org.argeo.eclipse.ui.jcr.util;
-
-import java.io.InputStream;
-
-import javax.jcr.Node;
-import javax.jcr.Property;
-import javax.jcr.RepositoryException;
-import javax.jcr.nodetype.NodeType;
-
-import org.apache.commons.io.IOUtils;
-import org.argeo.eclipse.ui.EclipseUiException;
-import org.argeo.eclipse.ui.FileProvider;
-
-/**
- * Implements a FileProvider for UI purposes. Note that it might not be very
- * reliable as long as we have not fixed login and multi repository issues that
- * will be addressed in the next version.
- *
- * NOTE: id used here is the real id of the JCR Node, not the JCR Path
- *
- * Relies on common approach for JCR file handling implementation.
- *
- */
-@SuppressWarnings("deprecation")
-public class JcrFileProvider implements FileProvider {
-
- // private Object[] rootNodes;
- private Node refNode;
-
- /**
- * Must be set in order for the provider to be able to get current session
- * and thus have the ability to get the file node corresponding to a given
- * file ID
- *
- * @param refNode
- */
- public void setReferenceNode(Node refNode) {
- // FIXME : this introduces some concurrency ISSUES.
- this.refNode = refNode;
- }
-
- public byte[] getByteArrayFileFromId(String fileId) {
- InputStream fis = null;
- byte[] ba = null;
- Node child = getFileNodeFromId(fileId);
- try {
- fis = (InputStream) child.getProperty(Property.JCR_DATA).getBinary().getStream();
- ba = IOUtils.toByteArray(fis);
-
- } catch (Exception e) {
- throw new EclipseUiException("Stream error while opening file", e);
- } finally {
- IOUtils.closeQuietly(fis);
- }
- return ba;
- }
-
- public InputStream getInputStreamFromFileId(String fileId) {
- try {
- InputStream fis = null;
-
- Node child = getFileNodeFromId(fileId);
- fis = (InputStream) child.getProperty(Property.JCR_DATA).getBinary().getStream();
- return fis;
- } catch (RepositoryException re) {
- throw new EclipseUiException("Cannot get stream from file node for Id " + fileId, re);
- }
- }
-
- /**
- * Throws an exception if the node is not found in the current repository (a
- * bit like a FileNotFoundException)
- *
- * @param fileId
- * @return Returns the child node of the nt:file node. It is the child node
- * that have the jcr:data property where actual file is stored.
- * never null
- */
- private Node getFileNodeFromId(String fileId) {
- try {
- Node result = refNode.getSession().getNodeByIdentifier(fileId);
-
- // rootNodes: for (int j = 0; j < rootNodes.length; j++) {
- // // in case we have a classic JCR Node
- // if (rootNodes[j] instanceof Node) {
- // Node curNode = (Node) rootNodes[j];
- // if (result != null)
- // break rootNodes;
- // } // Case of a repository Node
- // else if (rootNodes[j] instanceof RepositoryNode) {
- // Object[] nodes = ((RepositoryNode) rootNodes[j])
- // .getChildren();
- // for (int i = 0; i < nodes.length; i++) {
- // Node node = (Node) nodes[i];
- // result = node.getSession().getNodeByIdentifier(fileId);
- // if (result != null)
- // break rootNodes;
- // }
- // }
- // }
-
- // Sanity checks
- if (result == null)
- throw new EclipseUiException("File node not found for ID" + fileId);
-
- Node child = null;
-
- boolean isValid = true;
- if (!result.isNodeType(NodeType.NT_FILE))
- // useless: mandatory child node
- // || !result.hasNode(Property.JCR_CONTENT))
- isValid = false;
- else {
- child = result.getNode(Property.JCR_CONTENT);
- if (!(child.isNodeType(NodeType.NT_RESOURCE) || child.hasProperty(Property.JCR_DATA)))
- isValid = false;
- }
-
- if (!isValid)
- throw new EclipseUiException("ERROR: In the current implemented model, '" + NodeType.NT_FILE
- + "' file node must have a child node named jcr:content "
- + "that has a BINARY Property named jcr:data " + "where the actual data is stored");
- return child;
-
- } catch (RepositoryException re) {
- throw new EclipseUiException("Erreur while getting file node of ID " + fileId, re);
- }
- }
-}
+++ /dev/null
-package org.argeo.eclipse.ui.jcr.util;
-
-import java.util.Comparator;
-
-import javax.jcr.Item;
-import javax.jcr.RepositoryException;
-
-import org.argeo.eclipse.ui.EclipseUiException;
-
-/** Compares two JCR items (node or properties) based on their names. */
-public class JcrItemsComparator implements Comparator<Item> {
- public int compare(Item o1, Item o2) {
- try {
- // TODO: put folder before files
- return o1.getName().toLowerCase().compareTo(o2.getName().toLowerCase());
- } catch (RepositoryException e) {
- throw new EclipseUiException("Cannot compare " + o1 + " and " + o2, e);
- }
- }
-
-}
+++ /dev/null
-package org.argeo.eclipse.ui.jcr.util;
-
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-
-import org.argeo.eclipse.ui.EclipseUiException;
-import org.eclipse.jface.viewers.IElementComparer;
-
-/** Compare JCR nodes based on their JCR identifiers, for use in JFace viewers. */
-public class NodeViewerComparer implements IElementComparer {
-
- // force comparison on Node IDs only.
- public boolean equals(Object elementA, Object elementB) {
- if (!(elementA instanceof Node) || !(elementB instanceof Node)) {
- return elementA == null ? elementB == null : elementA
- .equals(elementB);
- } else {
-
- boolean result = false;
- try {
- String idA = ((Node) elementA).getIdentifier();
- String idB = ((Node) elementB).getIdentifier();
- result = idA == null ? idB == null : idA.equals(idB);
- } catch (RepositoryException re) {
- throw new EclipseUiException("cannot compare nodes", re);
- }
-
- return result;
- }
- }
-
- public int hashCode(Object element) {
- // TODO enhanced this method.
- return element.getClass().toString().hashCode();
- }
-}
\ No newline at end of file
+++ /dev/null
-package org.argeo.eclipse.ui.jcr.util;
-
-import java.io.InputStream;
-
-import javax.jcr.Node;
-import javax.jcr.Property;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.nodetype.NodeType;
-
-import org.apache.commons.io.IOUtils;
-import org.argeo.eclipse.ui.EclipseUiException;
-import org.argeo.eclipse.ui.FileProvider;
-
-/**
- * Implements a FileProvider for UI purposes. Unlike the
- * <code> JcrFileProvider </code>, it relies on a single session and manages
- * nodes with path only.
- *
- * Note that considered id is the JCR path
- *
- * Relies on common approach for JCR file handling implementation.
- */
-@SuppressWarnings("deprecation")
-public class SingleSessionFileProvider implements FileProvider {
-
- private Session session;
-
- public SingleSessionFileProvider(Session session) {
- this.session = session;
- }
-
- public byte[] getByteArrayFileFromId(String fileId) {
- InputStream fis = null;
- byte[] ba = null;
- Node child = getFileNodeFromId(fileId);
- try {
- fis = (InputStream) child.getProperty(Property.JCR_DATA)
- .getBinary().getStream();
- ba = IOUtils.toByteArray(fis);
-
- } catch (Exception e) {
- throw new EclipseUiException("Stream error while opening file", e);
- } finally {
- IOUtils.closeQuietly(fis);
- }
- return ba;
- }
-
- public InputStream getInputStreamFromFileId(String fileId) {
- try {
- InputStream fis = null;
-
- Node child = getFileNodeFromId(fileId);
- fis = (InputStream) child.getProperty(Property.JCR_DATA)
- .getBinary().getStream();
- return fis;
- } catch (RepositoryException re) {
- throw new EclipseUiException("Cannot get stream from file node for Id "
- + fileId, re);
- }
- }
-
- /**
- *
- * @param fileId
- * @return Returns the child node of the nt:file node. It is the child node
- * that have the jcr:data property where actual file is stored.
- * never null
- */
- private Node getFileNodeFromId(String fileId) {
- try {
- Node result = null;
- result = session.getNode(fileId);
-
- // Sanity checks
- if (result == null)
- throw new EclipseUiException("File node not found for ID" + fileId);
-
- // Ensure that the node have the correct type.
- if (!result.isNodeType(NodeType.NT_FILE))
- throw new EclipseUiException(
- "Cannot open file children Node that are not of "
- + NodeType.NT_RESOURCE + " type.");
-
- Node child = result.getNodes().nextNode();
- if (child == null || !child.isNodeType(NodeType.NT_RESOURCE))
- throw new EclipseUiException(
- "ERROR: IN the current implemented model, "
- + NodeType.NT_FILE
- + " file node must have one and only one child of the nt:ressource, where actual data is stored");
- return child;
- } catch (RepositoryException re) {
- throw new EclipseUiException("Erreur while getting file node of ID "
- + fileId, re);
- }
- }
-}
+++ /dev/null
-/** Generic SWT/JFace JCR helpers. */
-package org.argeo.eclipse.ui.jcr.util;
\ No newline at end of file
<module>org.argeo.cms.tp</module>
<module>org.argeo.cms</module>
<module>org.argeo.cms.pgsql</module>
- <module>org.argeo.cms.servlet</module>
- <module>org.argeo.cms.jcr</module>
- <!-- CMS UX -->
- <module>org.argeo.cms.swt</module>
- <module>org.argeo.cms.ui</module>
- <module>org.argeo.cms.e4</module>
+ <!-- CMS SWT -->
+ <module>eclipse</module>
+ <!-- CMS JCR -->
+ <module>jcr</module>
<!-- Eclipse RAP/RCP specific -->
<module>rcp</module>
<module>rap</module>