**/bin/
-**/target/
-**/generated/
**/MANIFEST.MF
-/build/
/sdk.mk
/output/
include sdk.mk
-.PHONY: clean all osgi jni
+.PHONY: clean all osgi
-all: osgi jni move-rap
- $(MAKE) -f Makefile-rcp.mk
-
-move-rap:
- mkdir -p $(A2_OUTPUT)/$(A2_CATEGORY).eclipse.rap
- mv -v $(A2_OUTPUT)/$(A2_CATEGORY)/*.rap.$(MAJOR).$(MINOR).jar $(A2_OUTPUT)/$(A2_CATEGORY).eclipse.rap
- touch $(BUILD_BASE)/*.rap/bnd.bnd
+all: osgi
+ $(MAKE) -f Makefile-rcp.mk all
A2_CATEGORY = org.argeo.cms
BUNDLES = \
org.argeo.init \
-org.argeo.util \
org.argeo.api.uuid \
+org.argeo.api.register \
org.argeo.api.acr \
+org.argeo.api.cli \
org.argeo.api.cms \
org.argeo.cms \
-org.argeo.cms.pgsql \
-eclipse/org.argeo.cms.servlet \
-eclipse/org.argeo.cms.swt \
-eclipse/org.argeo.cms.e4 \
-rap/org.argeo.cms.ui.rap \
-rap/org.argeo.swt.specific.rap \
-rap/org.argeo.cms.e4.rap \
-jcr/org.argeo.cms.jcr \
-jcr/org.argeo.cms.ui \
-
-JAVADOC_BUNDLES = \
-org.argeo.api.uuid \
-org.argeo.api.acr \
-org.argeo.api.cms
-
-JAVADOC_PACKAGES = \
-org.argeo.api.uuid \
-org.argeo.api.acr \
-org.argeo.api.cms
-
-A2_OUTPUT = $(SDK_BUILD_BASE)/a2
-A2_BASE = $(A2_OUTPUT)
-
-VPATH = .:eclipse:rap:jcr
+org.argeo.cms.ux \
+org.argeo.cms.ee \
+org.argeo.cms.lib.jetty \
+org.argeo.cms.lib.sshd \
+org.argeo.cms.cli \
+osgi/equinox/org.argeo.cms.lib.equinox \
+swt/org.argeo.swt.minidesktop \
+swt/org.argeo.cms.swt \
+swt/org.argeo.cms.e4 \
+swt/rap/org.argeo.swt.specific.rap \
+swt/rap/org.argeo.cms.swt.rap \
+swt/rap/org.argeo.cms.e4.rap \
DEP_CATEGORIES = \
org.argeo.tp \
-org.argeo.tp.apache \
+org.argeo.tp.crypto \
org.argeo.tp.jetty \
-org.argeo.tp.eclipse.equinox \
-org.argeo.tp.eclipse.rap \
-org.argeo.tp.jcr
+osgi/api/org.argeo.tp.osgi \
+osgi/equinox/org.argeo.tp.eclipse \
+swt/rap/org.argeo.tp.swt \
+swt/rap/org.argeo.tp.swt.workbench \
+$(A2_CATEGORY) \
+swt/$(A2_CATEGORY) \
+swt/rap/$(A2_CATEGORY) \
-jni:
- $(MAKE) -C jni
+JAVADOC_PACKAGES = \
+org.argeo.api.uuid \
+org.argeo.api.acr \
+org.argeo.api.cms
clean:
rm -rf $(BUILD_BASE)
- $(MAKE) -C jni clean
$(MAKE) -f Makefile-rcp.mk clean
+A2_BUNDLES_CLASSPATH = $(subst $(space),$(pathsep),$(strip $(A2_BUNDLES)))
+
include $(SDK_SRC_BASE)/sdk/argeo-build/osgi.mk
\ No newline at end of file
include sdk.mk
.PHONY: clean all osgi
-all: osgi
+all: osgi
-A2_CATEGORY = org.argeo.cms.eclipse.rcp
+A2_CATEGORY = org.argeo.cms
BUNDLES = \
-rcp/org.argeo.cms.e4.rcp \
-rcp/org.argeo.cms.ui.rcp \
-rcp/org.argeo.swt.minidesktop \
-rcp/org.argeo.swt.specific.rcp \
-
-A2_OUTPUT = $(SDK_BUILD_BASE)/a2
-A2_BASE = $(A2_OUTPUT)
+swt/rcp/org.argeo.swt.specific.rcp \
+swt/rcp/org.argeo.cms.swt.rcp \
+swt/rcp/org.argeo.cms.e4.rcp \
DEP_CATEGORIES = \
org.argeo.cms \
+swt/org.argeo.cms \
org.argeo.tp \
-org.argeo.tp.apache \
+org.argeo.tp.crypto \
org.argeo.tp.jetty \
-org.argeo.tp.eclipse.equinox \
-org.argeo.tp.eclipse.rcp \
-org.argeo.tp.jcr
+osgi/equinox/org.argeo.tp.eclipse \
+osgi/api/org.argeo.tp.osgi \
+swt/rcp/org.argeo.tp.swt \
+lib/linux/x86_64/swt/rcp/org.argeo.tp.swt \
+swt/rcp/org.argeo.tp.swt.workbench \
clean:
rm -rf $(BUILD_BASE)
-VPATH = .:rcp
+VPATH = .:swt/rcp
include $(SDK_SRC_BASE)/sdk/argeo-build/osgi.mk
\ No newline at end of file
-include $(SDK_SRC_BASE)/cnf/testing.bnd
+BRANCH=testing
\ No newline at end of file
+++ /dev/null
--include: \
-${workspace}/cnf/testing.bnd, \
-${workspace}/sdk/argeo-build/argeo.bnd, \
+++ /dev/null
--plugin.osgirepo=aQute.bnd.repository.osgi.OSGiRepository;\
- locations=file://${workspace}/sdk/target/a2/index.xml;\
- max.stale=-1;\
- poll.time=86400;\
- name=local;\
- cache=${build}/cache/local,\
- aQute.bnd.repository.osgi.OSGiRepository;\
- locations=file://${workspace}/sdk/target/sdk-2.3.1-SNAPSHOT-a2-target/index.xml;\
- max.stale=-1;\
- poll.time=86400;\
- name=local;\
- cache=${build}/cache/local
\ No newline at end of file
+++ /dev/null
-MAJOR=2
-MINOR=1
-MICRO=106
-qualifier=.next
-
-category=org.argeo.commons
-Bundle-RequiredExecutionEnvironment=JavaSE-11
-
-argeo.rpm.stagingRepository=/srv/rpmfactory/testing/argeo-osgi-2/argeo
-argeo.rpm.suffix=
+++ /dev/null
-MAJOR=2
-MINOR=3
-MICRO=7
-qualifier=.next
-
-category=org.argeo.commons
-Bundle-RequiredExecutionEnvironment=JavaSE-11
-
-argeo.rpm.stagingRepository=/srv/rpmfactory/unstable/argeo-osgi-2/argeo
-argeo.rpm.suffix=-unstable
#!/bin/sh
-# We build where we are
-SDK_BUILD_BASE=$(pwd -P)/output
-
# Source are located where this script is
SDK_SRC_BASE="$(cd "$(dirname "$0")"; pwd -P)"
-SDK_MK=$SDK_SRC_BASE/sdk.mk
-
-#echo SDK_BUILD_BASE=$SDK_BUILD_BASE
-#echo SDK_SRC_BASE=$SDK_SRC_BASE
-#echo SDK_MK=$SDK_MK
-
-if [ -f "$SDK_MK" ];
-then
-
-echo "File $SDK_MK already exists. Remove it in order to configure a new build location:"
-echo "rm $SDK_MK"
-exit 1
-
-else
-
-if [ -z "$JAVA_HOME" ]
-then
-echo "Environment variable JAVA_HOME must be set"
-exit 1
-fi
-
-# Create build directory, so that it can be used right away
-# and we check whether we have the rights
-mkdir -p $SDK_BUILD_BASE
-if [ -f "$SDK_MK" ];
-then
-echo "Cannot create $SDK_BUILD_BASE, SDK configuration has failed."
-exit 2
-fi
-
-# Generate sdk.mk
-cat > "$SDK_MK" <<EOF
-SDK_SRC_BASE := $SDK_SRC_BASE
-SDK_BUILD_BASE := $SDK_BUILD_BASE
-JAVA_HOME := $JAVA_HOME
-
-include \$(SDK_SRC_BASE)/branch.mk
-EOF
-
-
-echo SDK was configured.
-echo "JAVA_HOME : $JAVA_HOME"
-echo "Base for sources : $SDK_SRC_BASE"
-echo "Base for builds : $SDK_BUILD_BASE"
-exit 0
-fi
-
+# Source the configure script
+. $SDK_SRC_BASE/sdk/argeo-build/configure
+++ /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,\
-org.apache.jackrabbit.*;version="[2,3)",\
-javax.servlet.*;version="[3,5)",\
-*
+++ /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
-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,\
-javax.servlet.*;version="[3,5)",\
-*
-
-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
-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 javax.servlet.http.HttpSession;
-
-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() {
- HttpSession httpSession = request.getSession(false);
- if (httpSession == null)
- return null;
- return new ServletHttpSession(httpSession);
- }
-
- @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,\
-javax.servlet.*;version="[3,5)",\
-*
-
-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
-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
- */
- /** A {@link GridLayout} without any spacing and one column. */
- public static GridLayout noSpaceGridLayout() {
- return noSpaceGridLayout(new GridLayout());
- }
-
- /**
- * A {@link GridLayout} without any spacing and multiple columns of unequal
- * width.
- */
- 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;
-
- private CmsContext cmsContext;
-
- public CmsLogin(CmsView cmsView, CmsContext cmsContext) {
- this.cmsView = cmsView;
- this.cmsContext = cmsContext;
- if (this.cmsContext != null) {
- defaultLocale = this.cmsContext.getDefaultLocale();
- List<Locale> locales = this.cmsContext.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.CmsContext;
-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, CmsContext cmsContext) {
- super(cmsView, cmsContext);
- 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">
- <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;version="[1,3)",\
-org.postgresql;version="[42,43)";resolution:=optional,\
-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.stats;version="[1,4)",\
-org.apache.jackrabbit.api;version="[1,4)",\
-org.apache.jackrabbit.commons;version="[1,4)",\
-org.apache.jackrabbit.spi;version="[1,4)",\
-org.apache.jackrabbit.spi2dav;version="[1,4)",\
-org.apache.jackrabbit.spi2davex;version="[1,4)",\
-org.apache.jackrabbit.webdav.jcr;version="[1,4)",\
-org.apache.jackrabbit.webdav.server;version="[1,4)",\
-org.apache.jackrabbit.webdav.simple;version="[1,4)",\
-org.apache.jackrabbit.*;version="[1,4)",\
-junit.*;resolution:=optional,\
-javax.servlet.*;version="[3,5)",\
-*
-
-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/
-source.. = src/
-additional.bundles = \
-org.apache.jackrabbit.data, \
-org.apache.jackrabbit.spi.commons,\
-
-
\ 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) {
- 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 {
- }
-
- ClassLoader currentCl = Thread.currentThread().getContextClassLoader();
- try {
- Thread.currentThread().setContextClassLoader(CmsJcrUtils.class.getClassLoader());
- 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);
- }
- }
-
- });
- } finally {
- Thread.currentThread().setContextClassLoader(currentCl);
- }
- }
-
- /** 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 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) {
- 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.CmsConstants;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.api.cms.CmsSession;
-import org.argeo.cms.auth.RemoteAuthUtils;
-import org.argeo.cms.servlet.ServletHttpRequest;
-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;
-
- CmsSession cmsSession = RemoteAuthUtils.getCmsSession(new ServletHttpRequest(request));
- // CmsSessionImpl cmsSession = WebCmsSessionImpl.getCmsSession(request);
- 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;
- cmsSession.addOnCloseCallback((sess) -> close());
- }
-
- 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());
- }
-
- protected void close() {
- 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, ConnectionOptions connectionOptions)
-// throws RepositoryException {
-// super(jcrServerURI, defaultWorkspaceName, batchReadConfig, itemInfoCacheSize, connectionOptions);
-// // TODO Auto-generated constructor stub
-// }
-
-
-
-// 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();
- }
- }
-
- // FIXME adapt to changes in Jackrabbit
-// if (maximumHttpConnections > 0) {
-// return new ClientDavexRepositoryService(uri, workspaceNameDefault, brc, itemInfoCacheSize,
-// maximumHttpConnections);
-// } else {
-// return new ClientDavexRepositoryService(uri, workspaceNameDefault, brc, itemInfoCacheSize);
-// }
- return null;
- }
-
-}
+++ /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;
- // FIXME adapt to change in Jackrabbit
-// 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 null;
- }
- };
- 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.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.query.Row;
-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);
- }
-
- public static Node getRowNode(Row row, String selectorName) {
- try {
- return row.getNode(selectorName);
- } catch (RepositoryException e) {
- throw new JcrException("Cannot get node " + selectorName + " from row", e);
- }
- }
-
- /** 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.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,\
-javax.servlet.*;version="[3,5)",\
-*
-
-## 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
-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) {
- // FIXME pass CMS context
- super(CmsUiUtils.getCmsView(), null);
- 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 javax.jcr.RepositoryException;
-import javax.jcr.query.Row;
-
-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 RowColumnLabelProvider extends ColumnLabelProvider {
- private static final long serialVersionUID = -6586692836928505358L;
-
- protected String getRowText(Row row) throws RepositoryException {
- return super.getText(row);
- }
-
- protected String getRowToolTipText(Row row) throws RepositoryException {
- return super.getToolTipText(row);
- }
-
- protected Image getRowImage(Row row) throws RepositoryException {
- return super.getImage(row);
- }
-
- protected Font getRowFont(Row row) throws RepositoryException {
- return super.getFont(row);
- }
-
- public Color getRowBackground(Row row) throws RepositoryException {
- return super.getBackground(row);
- }
-
- public Color getRowForeground(Row row) throws RepositoryException {
- return super.getForeground(row);
- }
-
- @Override
- public String getText(Object element) {
- try {
- if (element instanceof Row)
- return getRowText((Row) element);
- 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 Row)
- return getRowImage((Row) element);
- 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 Row)
- return getRowToolTipText((Row) element);
- 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 Row)
- return getRowFont((Row) element);
- 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 Row)
- return getRowBackground((Row) element);
- 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 Row)
- return getRowForeground((Row) element);
- 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 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" standalone="no"?>
-<?fileVersion 4.0.0?><cproject storage_type_id="org.eclipse.cdt.core.XmlProjectDescriptionStorage">
- <storageModule moduleId="org.eclipse.cdt.core.settings">
- <cconfiguration id="cdt.managedbuild.toolchain.gnu.cross.base.1367283591">
- <storageModule buildSystemId="org.eclipse.cdt.managedbuilder.core.configurationDataProvider" id="cdt.managedbuild.toolchain.gnu.cross.base.1367283591" moduleId="org.eclipse.cdt.core.settings" name="Linux x86_64">
- <externalSettings/>
- <extensions>
- <extension id="org.eclipse.cdt.core.ELF" point="org.eclipse.cdt.core.BinaryParser"/>
- <extension id="org.eclipse.cdt.core.GASErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
- <extension id="org.eclipse.cdt.core.GmakeErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
- <extension id="org.eclipse.cdt.core.GLDErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
- <extension id="org.eclipse.cdt.core.CWDLocator" point="org.eclipse.cdt.core.ErrorParser"/>
- <extension id="org.eclipse.cdt.core.GCCErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
- <extension id="org.eclipse.cdt.core.MakeErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
- <extension id="org.eclipse.cdt.core.VCErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
- </extensions>
- </storageModule>
- <storageModule moduleId="cdtBuildSystem" version="4.0.0">
- <configuration artifactName="${ProjName}" buildProperties="" description="" id="cdt.managedbuild.toolchain.gnu.cross.base.1367283591" name="Linux x86_64" parent="org.eclipse.cdt.build.core.emptycfg">
- <folderInfo id="cdt.managedbuild.toolchain.gnu.cross.base.1367283591.350590933" name="/" resourcePath="">
- <toolChain id="cdt.managedbuild.toolchain.gnu.cross.base.2019979628" name="Cross GCC">
- <option id="cdt.managedbuild.option.gnu.cross.prefix.715257330" name="Prefix" superClass="cdt.managedbuild.option.gnu.cross.prefix"/>
- <option id="cdt.managedbuild.option.gnu.cross.path.1210265928" name="Path" superClass="cdt.managedbuild.option.gnu.cross.path"/>
- <targetPlatform archList="all" binaryParser="org.eclipse.cdt.core.ELF" id="cdt.managedbuild.targetPlatform.gnu.cross.2070205849" isAbstract="false" osList="all"/>
- <builder enableAutoBuild="true" id="cdt.managedbuild.builder.gnu.cross.1468217036" keepEnvironmentInBuildfile="false" managedBuildOn="false" name="Gnu Make Builder"/>
- </toolChain>
- </folderInfo>
- <sourceEntries>
- <entry flags="VALUE_WORKSPACE_PATH" kind="sourcePath" name="org_argeo_api_uuid_libuuid"/>
- </sourceEntries>
- </configuration>
- </storageModule>
- <storageModule moduleId="org.eclipse.cdt.core.externalSettings"/>
- </cconfiguration>
- </storageModule>
- <storageModule moduleId="cdtBuildSystem" version="4.0.0">
- <project id="org.argeo.api.uuid.null.1913821562" name="org.argeo.api.uuid"/>
- </storageModule>
- <storageModule moduleId="org.eclipse.cdt.core.LanguageSettingsProviders"/>
- <storageModule moduleId="refreshScope" versionNumber="2">
- <configuration configurationName="Linux x86_64"/>
- <configuration configurationName="Default">
- <resource resourceType="PROJECT" workspacePath="/Java_org_argeo_api_uuid"/>
- </configuration>
- </storageModule>
- <storageModule moduleId="scannerConfiguration">
- <autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId=""/>
- <scannerConfigBuildInfo instanceId="cdt.managedbuild.toolchain.gnu.cross.base.1367283591;cdt.managedbuild.toolchain.gnu.cross.base.1367283591.350590933;cdt.managedbuild.tool.gnu.cross.c.compiler.453851306;cdt.managedbuild.tool.gnu.c.compiler.input.1102448447">
- <autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId=""/>
- </scannerConfigBuildInfo>
- <scannerConfigBuildInfo instanceId="cdt.managedbuild.toolchain.gnu.cross.base.1367283591;cdt.managedbuild.toolchain.gnu.cross.base.1367283591.350590933;cdt.managedbuild.tool.gnu.cross.cpp.compiler.365551368;cdt.managedbuild.tool.gnu.cpp.compiler.input.916135861">
- <autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId=""/>
- </scannerConfigBuildInfo>
- </storageModule>
- <storageModule moduleId="org.eclipse.cdt.internal.ui.text.commentOwnerProjectMappings"/>
- <storageModule moduleId="org.eclipse.cdt.make.core.buildtargets">
- <buildTargets>
- <target name="ide" path="" targetID="org.eclipse.cdt.build.MakeTargetBuilder">
- <buildCommand>make</buildCommand>
- <buildArguments/>
- <buildTarget>ide</buildTarget>
- <stopOnError>true</stopOnError>
- <useDefaultCommand>true</useDefaultCommand>
- <runAllBuilders>true</runAllBuilders>
- </target>
- </buildTargets>
- </storageModule>
-</cproject>
\ No newline at end of file
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
- <name>jni-commons</name>
- <comment></comment>
- <projects>
- </projects>
- <buildSpec>
- <buildCommand>
- <name>org.eclipse.cdt.managedbuilder.core.genmakebuilder</name>
- <arguments>
- </arguments>
- </buildCommand>
- <buildCommand>
- <name>org.eclipse.cdt.managedbuilder.core.ScannerConfigBuilder</name>
- <triggers>full,incremental,</triggers>
- <arguments>
- </arguments>
- </buildCommand>
- </buildSpec>
- <natures>
- <nature>org.eclipse.cdt.core.cnature</nature>
- <nature>org.eclipse.cdt.core.ccnature</nature>
- <nature>org.eclipse.cdt.managedbuilder.core.managedBuildNature</nature>
- <nature>org.eclipse.cdt.managedbuilder.core.ScannerConfigNature</nature>
- </natures>
-</projectDescription>
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<project>
- <configuration id="cdt.managedbuild.toolchain.gnu.cross.base.1367283591" name="Linux x86_64">
- <extension point="org.eclipse.cdt.core.LanguageSettingsProvider">
- <provider copy-of="extension" id="org.eclipse.cdt.ui.UserLanguageSettingsProvider"/>
- <provider-reference id="org.eclipse.cdt.core.ReferencedProjectsLanguageSettingsProvider" ref="shared-provider"/>
- <provider class="org.eclipse.cdt.managedbuilder.language.settings.providers.GCCBuildCommandParser" id="org.eclipse.cdt.managedbuilder.core.GCCBuildCommandParser" keep-relative-paths="false" name="CDT GCC Build Output Parser" parameter="(g?cc)|([gc]\+\+)|(clang)" prefer-non-shared="true"/>
- <provider-reference id="org.eclipse.cdt.managedbuilder.core.MBSLanguageSettingsProvider" ref="shared-provider"/>
- </extension>
- </configuration>
-</project>
\ No newline at end of file
+++ /dev/null
-doxygen/doxygen_new_line_after_brief=true
-doxygen/doxygen_use_brief_tag=false
-doxygen/doxygen_use_javadoc_tags=true
-doxygen/doxygen_use_pre_tag=false
-doxygen/doxygen_use_structural_commands=false
-eclipse.preferences.version=1
+++ /dev/null
-include ../sdk.mk
-
-JNIDIRS = org_argeo_api_uuid_libuuid
-
-.PHONY: clean all
-
-all:
- $(foreach dir, $(JNIDIRS), $(MAKE) -C $(dir);)
-
-clean:
- rm -rf $(BUILD_DIR) $(SDK_BUILD_BASE)/jni
-
-
-
+++ /dev/null
-TARGET_EXEC := libJava_$(NATIVE_PACKAGE).so
-
-LDFLAGS = -shared -fPIC -Wl,-soname,$(TARGET_EXEC).$(MAJOR).$(MINOR) $(ADDITIONAL_LIBS)
-CFLAGS = -O3 -fPIC
-
-SRC_DIRS := .
-
-#
-# Generic Argeo
-#
-BUILD_DIR := $(SDK_BUILD_BASE)/jni/$(NATIVE_PACKAGE)
-
-# Every folder in ./src will need to be passed to GCC so that it can find header files
-INC_DIRS := $(shell find $(SRC_DIRS) -type d) $(JAVA_HOME)/include $(JAVA_HOME)/include/linux $(ADDITIONAL_INCLUDES)
-
-
-.PHONY: clean all ide
-all: $(SDK_BUILD_BASE)/jni/$(TARGET_EXEC)
-
-# Find all the C and C++ files we want to compile
-# Note the single quotes around the * expressions. Make will incorrectly expand these otherwise.
-SRCS := $(shell find $(SRC_DIRS) -name '*.cpp' -or -name '*.c' -or -name '*.s')
-
-# String substitution for every C/C++ file.
-# As an example, hello.cpp turns into ./build/hello.cpp.o
-OBJS := $(SRCS:%=$(BUILD_DIR)/%.o)
-
-# String substitution (suffix version without %).
-# As an example, ./build/hello.cpp.o turns into ./build/hello.cpp.d
-DEPS := $(OBJS:.o=.d)
-
-# Add a prefix to INC_DIRS. So moduleA would become -ImoduleA. GCC understands this -I flag
-INC_FLAGS := $(addprefix -I,$(INC_DIRS))
-
-# The -MMD and -MP flags together generate Makefiles for us!
-# These files will have .d instead of .o as the output.
-CPPFLAGS := $(INC_FLAGS) -MMD -MP
-
-# The final build step.
-$(SDK_BUILD_BASE)/jni/$(TARGET_EXEC): $(OBJS)
- $(CC) $(OBJS) -o $@ $(LDFLAGS)
-
-# Build step for C source
-$(BUILD_DIR)/%.c.o: %.c
- mkdir -p $(dir $@)
- $(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@
-
-# Build step for C++ source
-$(BUILD_DIR)/%.cpp.o: %.cpp
- mkdir -p $(dir $@)
- $(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $< -o $@
-
-# Include the .d makefiles. The - at the front suppresses the errors of missing
-# Makefiles. Initially, all the .d files will be missing, and we don't want those
-# errors to show up.
--include $(DEPS)
-
-# MAKEFILE_DIR := $(dir $(firstword $(MAKEFILE_LIST)))
+++ /dev/null
-NATIVE_PACKAGE := org_argeo_api_uuid_libuuid
-
-ADDITIONAL_INCLUDES = /usr/include/uuid
-ADDITIONAL_LIBS = -luuid
-
-include ../../sdk.mk
-include ../jni.mk
-
+++ /dev/null
-#include <jni.h>
-#include <uuid.h>
-#include "org_argeo_api_uuid_libuuid_DirectLibuuidFactory.h"
-
-JNIEXPORT void JNICALL Java_org_argeo_api_uuid_libuuid_DirectLibuuidFactory_timeUUID(
- JNIEnv *env, jobject uuidFactory, jobject uuidBuf) {
- uuid_generate_time((*env)->GetDirectBufferAddress(env, uuidBuf));
-}
+++ /dev/null
-/* DO NOT EDIT THIS FILE - it is machine generated */
-#include <jni.h>
-/* Header for class org_argeo_api_uuid_libuuid_DirectLibuuidFactory */
-
-#ifndef _Included_org_argeo_api_uuid_libuuid_DirectLibuuidFactory
-#define _Included_org_argeo_api_uuid_libuuid_DirectLibuuidFactory
-#ifdef __cplusplus
-extern "C" {
-#endif
-/*
- * Class: org_argeo_api_uuid_libuuid_DirectLibuuidFactory
- * Method: timeUUID
- * Signature: (Ljava/nio/ByteBuffer;)V
- */
-JNIEXPORT void JNICALL Java_org_argeo_api_uuid_libuuid_DirectLibuuidFactory_timeUUID
- (JNIEnv *, jobject, jobject);
-
-#ifdef __cplusplus
-}
-#endif
-#endif
+++ /dev/null
-#include <jni.h>
-#include <uuid.h>
-#include "org_argeo_api_uuid_libuuid_LibuuidFactory.h"
-
-/*
- * UTILITIES
- */
-
-static inline jobject fromBytes(JNIEnv *env, uuid_t out) {
- jlong msb = 0;
- jlong lsb = 0;
-
- for (int i = 0; i < 8; i++)
- msb = (msb << 8) | (out[i] & 0xff);
- for (int i = 8; i < 16; i++)
- lsb = (lsb << 8) | (out[i] & 0xff);
-
- jclass uuidClass = (*env)->FindClass(env, "java/util/UUID");
- jmethodID uuidConstructor = (*env)->GetMethodID(env, uuidClass, "<init>",
- "(JJ)V");
-
- jobject jUUID = (*env)->AllocObject(env, uuidClass);
- (*env)->CallVoidMethod(env, jUUID, uuidConstructor, msb, lsb);
-
- return jUUID;
-}
-
-static inline void toBytes(JNIEnv *env, jobject jUUID, uuid_t result) {
-
- jclass uuidClass = (*env)->FindClass(env, "java/util/UUID");
- jmethodID getMostSignificantBits = (*env)->GetMethodID(env, uuidClass,
- "getMostSignificantBits", "()J");
- jmethodID getLeastSignificantBits = (*env)->GetMethodID(env, uuidClass,
- "getLeastSignificantBits", "()J");
-
- jlong msb = (*env)->CallLongMethod(env, jUUID, getMostSignificantBits);
- jlong lsb = (*env)->CallLongMethod(env, jUUID, getLeastSignificantBits);
-
- for (int i = 0; i < 8; i++)
- result[i] = (unsigned char) ((msb >> ((7 - i) * 8)) & 0xff);
- for (int i = 8; i < 16; i++)
- result[i] = (unsigned char) ((lsb >> ((15 - i) * 8)) & 0xff);
-}
-
-/*
- * JNI IMPLEMENTATION
- */
-
-JNIEXPORT jobject JNICALL Java_org_argeo_api_uuid_libuuid_LibuuidFactory_timeUUID(
- JNIEnv *env, jobject uuidFactory) {
- uuid_t out;
-
- uuid_generate_time(out);
- return fromBytes(env, out);
-}
-
-JNIEXPORT jobject JNICALL Java_org_argeo_api_uuid_libuuid_LibuuidFactory_nameUUIDv5(
- JNIEnv *env, jobject uuidFactory, jobject namespaceUuid,
- jbyteArray name) {
- uuid_t ns;
- uuid_t out;
-
- toBytes(env, namespaceUuid, ns);
- jsize length = (*env)->GetArrayLength(env, name);
- jbyte *bytes = (*env)->GetByteArrayElements(env, name, 0);
-
- uuid_generate_sha1(out, ns, bytes, length);
- return fromBytes(env, out);
-}
-
-JNIEXPORT jobject JNICALL Java_org_argeo_api_uuid_libuuid_LibuuidFactory_nameUUIDv3(
- JNIEnv *env, jobject uuidFactory, jobject namespaceUuid,
- jbyteArray name) {
- uuid_t ns;
- uuid_t out;
-
- toBytes(env, namespaceUuid, ns);
- jsize length = (*env)->GetArrayLength(env, name);
- jbyte *bytes = (*env)->GetByteArrayElements(env, name, 0);
-
- uuid_generate_md5(out, ns, bytes, length);
- return fromBytes(env, out);
-}
-
-JNIEXPORT jobject JNICALL Java_org_argeo_api_uuid_libuuid_LibuuidFactory_randomUUIDStrong(
- JNIEnv *env, jobject uuidFactory) {
- uuid_t out;
-
- uuid_generate_random(out);
- return fromBytes(env, out);
-}
+++ /dev/null
-/* DO NOT EDIT THIS FILE - it is machine generated */
-#include <jni.h>
-/* Header for class org_argeo_api_uuid_libuuid_LibuuidFactory */
-
-#ifndef _Included_org_argeo_api_uuid_libuuid_LibuuidFactory
-#define _Included_org_argeo_api_uuid_libuuid_LibuuidFactory
-#ifdef __cplusplus
-extern "C" {
-#endif
-/*
- * Class: org_argeo_api_uuid_libuuid_LibuuidFactory
- * Method: timeUUID
- * Signature: ()Ljava/util/UUID;
- */
-JNIEXPORT jobject JNICALL Java_org_argeo_api_uuid_libuuid_LibuuidFactory_timeUUID
- (JNIEnv *, jobject);
-
-/*
- * Class: org_argeo_api_uuid_libuuid_LibuuidFactory
- * Method: nameUUIDv5
- * Signature: (Ljava/util/UUID;[B)Ljava/util/UUID;
- */
-JNIEXPORT jobject JNICALL Java_org_argeo_api_uuid_libuuid_LibuuidFactory_nameUUIDv5
- (JNIEnv *, jobject, jobject, jbyteArray);
-
-/*
- * Class: org_argeo_api_uuid_libuuid_LibuuidFactory
- * Method: nameUUIDv3
- * Signature: (Ljava/util/UUID;[B)Ljava/util/UUID;
- */
-JNIEXPORT jobject JNICALL Java_org_argeo_api_uuid_libuuid_LibuuidFactory_nameUUIDv3
- (JNIEnv *, jobject, jobject, jbyteArray);
-
-/*
- * Class: org_argeo_api_uuid_libuuid_LibuuidFactory
- * Method: randomUUIDStrong
- * Signature: ()Ljava/util/UUID;
- */
-JNIEXPORT jobject JNICALL Java_org_argeo_api_uuid_libuuid_LibuuidFactory_randomUUIDStrong
- (JNIEnv *, jobject);
-
-#ifdef __cplusplus
-}
-#endif
-#endif
<?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.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-17"/>
<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
<classpathentry kind="src" path="src"/>
<classpathentry kind="output" path="bin"/>
Import-Package: \
-javax.security.*
+javax.security.*, \
+javax.xml.*
Export-Package: org.argeo.api.acr.*
\ No newline at end of file
--- /dev/null
+package org.argeo.api.acr;
+
+/** Namespaces declared by Argeo. */
+public enum ArgeoNamespace {
+ ;
+
+ public final static String CR_NAMESPACE_URI = "http://www.argeo.org/ns/cr";
+ public final static String CR_DEFAULT_PREFIX = "cr";
+ public final static String LDAP_NAMESPACE_URI = "http://www.argeo.org/ns/ldap";
+ public final static String LDAP_DEFAULT_PREFIX = "ldap";
+ public final static String ROLE_NAMESPACE_URI = "http://www.argeo.org/ns/role";
+ public final static String ROLE_DEFAULT_PREFIX = "role";
+
+}
* An attribute type MUST consistently parse a string to an object so that
* <code>parse(obj.toString()).equals(obj)</code> is verified.
* {@link #format(Object)} can be overridden to provide more efficient
- * implementations but the returned
- * <code>String<code> MUST be the same, that is <code>format(obj).equals(obj.toString())</code>
- * is verified.
+ * implementations but the returned <code>String</code> MUST be the same, that
+ * is <code>format(obj).equals(obj.toString())</code> is verified.
*/
public interface AttributeFormatter<T> {
/** Parses a String to a Java object. */
+++ /dev/null
-package org.argeo.api.acr;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Objects;
-import java.util.StringTokenizer;
-
-/** A name that can be expressed with various conventions. */
-public class CompositeString {
- public final static Character UNDERSCORE = Character.valueOf('_');
- public final static Character SPACE = Character.valueOf(' ');
- public final static Character DASH = Character.valueOf('-');
-
- private final String[] parts;
-
- // optimisation
- private final int hashCode;
-
- public CompositeString(String str) {
- Objects.requireNonNull(str, "String cannot be null");
- if ("".equals(str.trim()))
- throw new IllegalArgumentException("String cannot be empty");
- if (!str.equals(str.trim()))
- throw new IllegalArgumentException("String must be trimmed");
- this.parts = toParts(str);
- hashCode = hashCode(this.parts);
- }
-
- public String toString(char separator, boolean upperCase) {
- StringBuilder sb = null;
- for (String part : parts) {
- if (sb == null) {
- sb = new StringBuilder();
- } else {
- sb.append(separator);
- }
- sb.append(upperCase ? part.toUpperCase() : part);
- }
- return sb.toString();
- }
-
- public String toStringCaml(boolean firstCharUpperCase) {
- StringBuilder sb = null;
- for (String part : parts) {
- if (sb == null) {// first
- sb = new StringBuilder();
- sb.append(firstCharUpperCase ? Character.toUpperCase(part.charAt(0)) : part.charAt(0));
- } else {
- sb.append(Character.toUpperCase(part.charAt(0)));
- }
-
- if (part.length() > 1)
- sb.append(part.substring(1));
- }
- return sb.toString();
- }
-
- @Override
- public int hashCode() {
- return hashCode;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (obj == null || !(obj instanceof CompositeString))
- return false;
-
- CompositeString other = (CompositeString) obj;
- return Arrays.equals(parts, other.parts);
- }
-
- @Override
- public String toString() {
- return toString(DASH, false);
- }
-
- public static String[] toParts(String str) {
- Character separator = null;
- if (str.indexOf(UNDERSCORE) >= 0) {
- checkNo(str, SPACE);
- checkNo(str, DASH);
- separator = UNDERSCORE;
- } else if (str.indexOf(DASH) >= 0) {
- checkNo(str, SPACE);
- checkNo(str, UNDERSCORE);
- separator = DASH;
- } else if (str.indexOf(SPACE) >= 0) {
- checkNo(str, DASH);
- checkNo(str, UNDERSCORE);
- separator = SPACE;
- }
-
- List<String> res = new ArrayList<>();
- if (separator != null) {
- StringTokenizer st = new StringTokenizer(str, separator.toString());
- while (st.hasMoreTokens()) {
- res.add(st.nextToken().toLowerCase());
- }
- } else {
- // single
- String strLowerCase = str.toLowerCase();
- if (str.toUpperCase().equals(str) || strLowerCase.equals(str))
- return new String[] { strLowerCase };
-
- // CAML
- StringBuilder current = null;
- for (char c : str.toCharArray()) {
- if (Character.isUpperCase(c)) {
- if (current != null)
- res.add(current.toString());
- current = new StringBuilder();
- }
- if (current == null)// first char is lower case
- current = new StringBuilder();
- current.append(Character.toLowerCase(c));
- }
- res.add(current.toString());
- }
- return res.toArray(new String[res.size()]);
- }
-
- private static void checkNo(String str, Character c) {
- if (str.indexOf(c) >= 0) {
- throw new IllegalArgumentException("Only one kind of sperator is allowed");
- }
- }
-
- private static int hashCode(String[] parts) {
- int hashCode = 0;
- for (String part : parts) {
- hashCode = hashCode + part.hashCode();
- }
- return hashCode;
- }
-
- static boolean smokeTests() {
- CompositeString plainName = new CompositeString("NAME");
- assert "name".equals(plainName.toString());
- assert "NAME".equals(plainName.toString(UNDERSCORE, true));
- assert "name".equals(plainName.toString(UNDERSCORE, false));
- assert "name".equals(plainName.toStringCaml(false));
- assert "Name".equals(plainName.toStringCaml(true));
-
- CompositeString camlName = new CompositeString("myComplexName");
-
- assert new CompositeString("my-complex-name").equals(camlName);
- assert new CompositeString("MY_COMPLEX_NAME").equals(camlName);
- assert new CompositeString("My complex Name").equals(camlName);
- assert new CompositeString("MyComplexName").equals(camlName);
-
- assert "my-complex-name".equals(camlName.toString());
- assert "MY_COMPLEX_NAME".equals(camlName.toString(UNDERSCORE, true));
- assert "my_complex_name".equals(camlName.toString(UNDERSCORE, false));
- assert "myComplexName".equals(camlName.toStringCaml(false));
- assert "MyComplexName".equals(camlName.toStringCaml(true));
-
- return CompositeString.class.desiredAssertionStatus();
- }
-
- public static void main(String[] args) {
- System.out.println(smokeTests());
- }
-}
package org.argeo.api.acr;
+import static org.argeo.api.acr.NamespaceUtils.unqualified;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
+import java.util.concurrent.CompletableFuture;
-import javax.xml.XMLConstants;
import javax.xml.namespace.QName;
/**
<A> Optional<A> get(QName key, Class<A> clss);
- default Object get(String key) {
- if (key.indexOf(':') >= 0)
- throw new IllegalArgumentException("Name " + key + " has a prefix");
- return get(new QName(XMLConstants.NULL_NS_URI, key, XMLConstants.DEFAULT_NS_PREFIX));
+ Class<?> getType(QName key);
+
+ boolean isMultiple(QName key);
+
+ <A> List<A> getMultiple(QName key, Class<A> clss);
+
+ /*
+ * ATTRIBUTES OPERATION HELPERS
+ */
+ default boolean containsKey(QNamed key) {
+ return containsKey(key.qName());
}
- default Object put(String key, Object value) {
- if (key.indexOf(':') >= 0)
- throw new IllegalArgumentException("Name " + key + " has a prefix");
- return put(new QName(XMLConstants.NULL_NS_URI, key, XMLConstants.DEFAULT_NS_PREFIX), value);
+ default <A> Optional<A> get(QNamed key, Class<A> clss) {
+ return get(key.qName(), clss);
}
- default Object remove(String key) {
- if (key.indexOf(':') >= 0)
- throw new IllegalArgumentException("Name " + key + " has a prefix");
- return remove(new QName(XMLConstants.NULL_NS_URI, key, XMLConstants.DEFAULT_NS_PREFIX));
+ default Object get(QNamed key) {
+ return get(key.qName());
}
- Class<?> getType(QName key);
+ default Object put(QNamed key, Object value) {
+ return put(key.qName(), value);
+ }
- boolean isMultiple(QName key);
+ default Object remove(QNamed key) {
+ return remove(key.qName());
+ }
+
+ // TODO do we really need the helpers below?
+
+ default Object get(String key) {
+ return get(unqualified(key));
+ }
+
+ default Object put(String key, Object value) {
+ return put(unqualified(key), value);
+ }
- <A> Optional<List<A>> getMultiple(QName key, Class<A> clss);
+ default Object remove(String key) {
+ return remove(unqualified(key));
+ }
+ @SuppressWarnings("unchecked")
default <A> List<A> getMultiple(QName key) {
Class<A> type;
try {
} catch (ClassCastException e) {
throw new IllegalArgumentException("Requested type is not the default type");
}
- Optional<List<A>> res = getMultiple(key, type);
- if (res == null)
- return null;
- else {
- if (res.isEmpty())
- throw new IllegalStateException("Metadata " + key + " is not availabel as list of type " + type);
- return res.get();
- }
+ List<A> res = getMultiple(key, type);
+ return res;
+// if (res == null)
+// return null;
+// else {
+// if (res.isEmpty())
+// throw new IllegalStateException("Metadata " + key + " is not availabel as list of type " + type);
+// return res.get();
+// }
}
/*
* CONTENT OPERATIONS
*/
+// default CompletionStage<Content> edit(Consumer<Content> work) {
+// return CompletableFuture.supplyAsync(() -> {
+// work.accept(this);
+// return this;
+// }).minimalCompletionStage();
+// }
+
Content add(QName name, QName... classes);
default Content add(String name, QName... classes) {
- if (name.indexOf(':') >= 0)
- throw new IllegalArgumentException("Name " + name + " has a prefix");
- return add(new QName(XMLConstants.NULL_NS_URI, name, XMLConstants.DEFAULT_NS_PREFIX), classes);
+ return add(unqualified(name), classes);
}
void remove();
+ /*
+ * TYPING
+ */
+ List<QName> getContentClasses();
+
+ default void addContentClasses(QName... contentClass) {
+ throw new UnsupportedOperationException("Adding content classes to " + getPath() + " is not supported");
+ }
+
+ /** AND */
+ default boolean isContentClass(QName... contentClass) {
+ List<QName> contentClasses = getContentClasses();
+ for (QName cClass : contentClass) {
+ if (!contentClasses.contains(cClass))
+ return false;
+ }
+ return true;
+ }
+
+ /** AND */
+ default boolean isContentClass(QNamed... contentClass) {
+ List<QName> lst = new ArrayList<>();
+ for (QNamed qNamed : contentClass)
+ lst.add(qNamed.qName());
+ return isContentClass(lst.toArray(new QName[lst.size()]));
+ }
+
+ /** OR */
+ default boolean hasContentClass(QName... contentClass) {
+ List<QName> contentClasses = getContentClasses();
+ for (QName cClass : contentClass) {
+ if (contentClasses.contains(cClass))
+ return true;
+ }
+ return false;
+ }
+
+ /** OR */
+ default boolean hasContentClass(QNamed... contentClass) {
+ List<QName> lst = new ArrayList<>();
+ for (QNamed qNamed : contentClass)
+ lst.add(qNamed.qName());
+ return hasContentClass(lst.toArray(new QName[lst.size()]));
+ }
+
+ /*
+ * SIBLINGS
+ */
+
+ default int getSiblingIndex() {
+ return 1;
+ }
+
/*
* DEFAULT METHODS
*/
- default <A> A adapt(Class<A> clss) throws IllegalArgumentException {
- throw new IllegalArgumentException("Cannot adapt content " + this + " to " + clss.getName());
+ default <A> A adapt(Class<A> clss) {
+ throw new UnsupportedOperationException("Cannot adapt content " + this + " to " + clss.getName());
+ }
+
+ default <C extends Closeable> C open(Class<C> clss) throws IOException {
+ throw new UnsupportedOperationException("Cannot open content " + this + " as " + clss.getName());
}
- default <C extends AutoCloseable> C open(Class<C> clss) throws Exception, IllegalArgumentException {
- throw new IllegalArgumentException("Cannot open content " + this + " as " + clss.getName());
+ default <A> CompletableFuture<A> write(Class<A> clss) {
+ throw new UnsupportedOperationException("Cannot write content " + this + " as " + clss.getName());
}
/*
- * CONVENIENCE METHODS
+ * CHILDREN
*/
-// default String attr(String key) {
-// return get(key, String.class);
-// }
+
+ default boolean hasChild(QName name) {
+ for (Content child : this) {
+ if (child.getName().equals(name))
+ return true;
+ }
+ return false;
+ }
+
+ default boolean hasChild(QNamed name) {
+ return hasChild(name.qName());
+ }
+
+ default Content anyOrAddChild(QName name, QName... classes) {
+ Content child = anyChild(name);
+ if (child != null)
+ return child;
+ return this.add(name, classes);
+ }
+
+ default Content anyOrAddChild(String name, QName... classes) {
+ return anyOrAddChild(unqualified(name), classes);
+ }
+
+ /** Any child with this name, or null if there is none */
+ default Content anyChild(QName name) {
+ for (Content child : this) {
+ if (child.getName().equals(name))
+ return child;
+ }
+ return null;
+ }
+
+ default List<Content> children(QName name) {
+ List<Content> res = new ArrayList<>();
+ for (Content child : this) {
+ if (child.getName().equals(name))
+ res.add(child);
+ }
+ return res;
+ }
+
+ default Optional<Content> soleChild(QName name) {
+ List<Content> res = children(name);
+ if (res.isEmpty())
+ return Optional.empty();
+ if (res.size() > 1)
+ throw new IllegalStateException(this + " has multiple children with name " + name);
+ return Optional.of(res.get(0));
+ }
+
+ default Content child(QName name) {
+ return soleChild(name).orElseThrow();
+ }
+
+ default Content child(QNamed name) {
+ return child(name.qName());
+ }
+
+ /*
+ * ATTR AS STRING
+ */
+ default String attr(QName key) {
+ // TODO check String type?
+ Object obj = get(key);
+ if (obj == null)
+ return null;
+ return obj.toString();
+ }
+
+ default String attr(QNamed key) {
+ return attr(key.qName());
+ }
+
+ default String attr(String key) {
+ return attr(unqualified(key));
+ }
//
// default String attr(Object key) {
// return key != null ? attr(key.toString()) : attr(null);
* The UUID v3 of http://www.w3.org/2000/xmlns/ within the standard DNS
* namespace, to be used as a base for the namespaces.
*
- * @see https://www.w3.org/TR/xml-names/#ns-decl
+ * @see "https://www.w3.org/TR/xml-names/#ns-decl"
*/
// uuidgen --md5 --namespace @dns --name http://www.w3.org/2000/xmlns/
// NOTE : must be declared before default namespaces
// private final UUID uuid;
+ public ContentName(String namespaceURI, String localPart) {
+ super(namespaceURI, localPart, checkPrefix(RuntimeNamespaceContext.getNamespaceContext(), namespaceURI));
+ }
+
public ContentName(String namespaceURI, String localPart, NamespaceContext nsContext) {
super(namespaceURI, localPart, checkPrefix(nsContext, namespaceURI));
}
private static String checkPrefix(NamespaceContext nsContext, String namespaceURI) {
Objects.requireNonNull(nsContext, "Namespace context cannot be null");
Objects.requireNonNull(namespaceURI, "Namespace URI cannot be null");
- String prefix = nsContext.getNamespaceURI(namespaceURI);
+ String prefix = nsContext.getPrefix(namespaceURI);
if (prefix == null)
throw new IllegalStateException("No prefix found for " + namespaceURI + " from context " + nsContext);
return prefix;
@Override
public String toString() {
- return toPrefixedString();
+ return toQNameString();
}
@Override
+++ /dev/null
-package org.argeo.api.acr;
-
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.function.Supplier;
-
-import javax.xml.XMLConstants;
-import javax.xml.namespace.NamespaceContext;
-
-public interface ContentNameSupplier extends Supplier<ContentName>, NamespaceContext {
- String name();
-
- String getNamespaceURI();
-
- String getDefaultPrefix();
-
- @Override
- default ContentName get() {
- return toContentName();
- }
-
- default ContentName toContentName() {
- CompositeString cs = new CompositeString(name());
- String camlName = cs.toStringCaml(false);
- return new ContentName(getNamespaceURI(), camlName, this);
- }
-
-// default String getNamespaceURI() {
-// return XMLConstants.NULL_NS_URI;
-// }
-//
-// default String getDefaultPrefix() {
-// return XMLConstants.DEFAULT_NS_PREFIX;
-// }
-
-// static ContentName toContentName(String namespaceURI, String localName, String prefix) {
-// CompositeString cs = new CompositeString(localName);
-// String camlName = cs.toStringCaml(false);
-// return new ContentName(namespaceURI, camlName, this);
-// }
-
- /*
- * NAMESPACE CONTEXT
- */
-
- @Override
- default String getNamespaceURI(String prefix) {
- String namespaceURI = getStandardNamespaceURI(prefix);
- if (namespaceURI != null)
- return namespaceURI;
- if (prefix.equals(getDefaultPrefix()))
- return getNamespaceURI();
- return XMLConstants.NULL_NS_URI;
- }
-
- @Override
- default String getPrefix(String namespaceURI) {
- String prefix = getStandardPrefix(namespaceURI);
- if (prefix != null)
- return prefix;
- if (namespaceURI.equals(getNamespaceURI()))
- return getDefaultPrefix();
- return null;
- }
-
- @Override
- default Iterator<String> getPrefixes(String namespaceURI) {
- Iterator<String> it = getStandardPrefixes(namespaceURI);
- if (it != null)
- return it;
- if (namespaceURI.equals(getNamespaceURI()))
- return Collections.singleton(getDefaultPrefix()).iterator();
- return Collections.emptyIterator();
- }
-
- /*
- * DEFAULT NAMESPACE CONTEXT OPERATIONS as specified in NamespaceContext
- */
- static String getStandardPrefix(String namespaceURI) {
- if (namespaceURI == null)
- throw new IllegalArgumentException("Namespace URI cannot be null");
- if (XMLConstants.XML_NS_URI.equals(namespaceURI))
- return XMLConstants.XML_NS_PREFIX;
- else if (XMLConstants.XMLNS_ATTRIBUTE_NS_URI.equals(namespaceURI))
- return XMLConstants.XMLNS_ATTRIBUTE;
- return null;
- }
-
- static Iterator<String> getStandardPrefixes(String namespaceURI) {
- String prefix = ContentNameSupplier.getStandardPrefix(namespaceURI);
- if (prefix == null)
- return null;
- return Collections.singleton(prefix).iterator();
- }
-
- static String getStandardNamespaceURI(String prefix) {
- if (prefix == null)
- throw new IllegalArgumentException("Prefix cannot be null");
- if (XMLConstants.XML_NS_PREFIX.equals(prefix))
- return XMLConstants.XML_NS_URI;
- else if (XMLConstants.XMLNS_ATTRIBUTE.equals(prefix))
- return XMLConstants.XMLNS_ATTRIBUTE_NS_URI;
- return null;
- }
-
-}
package org.argeo.api.acr;
-/** When a countent was requested which does not exists, equivalent to HTTP code 404.*/
+/**
+ * When a content was requested which does not exists, equivalent to HTTP code
+ * 404.
+ */
public class ContentNotFoundException extends RuntimeException {
private static final long serialVersionUID = -8629074900713760886L;
- public ContentNotFoundException(String message, Throwable cause) {
- super(message, cause);
+ private final String path;
+
+ public ContentNotFoundException(ContentSession session, String path, Throwable cause) {
+ super(message(session, path), cause);
+ this.path = path;
+ // we don't keep reference to the session for security reasons
+ }
+
+ public ContentNotFoundException(ContentSession session, String path) {
+ this(session, path, (String) null);
}
- public ContentNotFoundException(String message) {
- super(message);
+ public ContentNotFoundException(ContentSession session, String path, String message) {
+ super(message != null ? message : message(session, path));
+ this.path = path;
+ // we don't keep reference to the session for security reasons
}
-
+ private static String message(ContentSession session, String path) {
+ return "Content " + path + "cannot be found.";
+ }
+
+ public String getPath() {
+ return path;
+ }
}
package org.argeo.api.acr;
import java.util.Locale;
-import java.util.Objects;
+import java.util.concurrent.CompletionStage;
+import java.util.function.Consumer;
import javax.security.auth.Subject;
-import javax.xml.XMLConstants;
import javax.xml.namespace.NamespaceContext;
-import javax.xml.namespace.QName;
public interface ContentSession extends NamespaceContext {
Subject getSubject();
Locale getLocale();
Content get(String path);
+
+ boolean exists(String path);
- /*
- * NAMESPACE CONTEXT
- */
-
- default ContentName parsePrefixedName(String nameWithPrefix) {
- Objects.requireNonNull(nameWithPrefix, "Name cannot be null");
- if (nameWithPrefix.charAt(0) == '{') {
- return new ContentName(QName.valueOf(nameWithPrefix), this);
- }
- int index = nameWithPrefix.indexOf(':');
- if (index < 0) {
- return new ContentName(nameWithPrefix);
- }
- String prefix = nameWithPrefix.substring(0, index);
- // TODO deal with empty name?
- String localName = nameWithPrefix.substring(index + 1);
- String namespaceURI = getNamespaceURI(prefix);
- if (XMLConstants.NULL_NS_URI.equals(namespaceURI))
- throw new IllegalStateException("Prefix " + prefix + " is unbound.");
- return new ContentName(namespaceURI, localName, prefix);
- }
-
- default String toPrefixedName(QName name) {
- if (XMLConstants.NULL_NS_URI.equals(name.getNamespaceURI()))
- return name.getLocalPart();
- String prefix = getPrefix(name.getNamespaceURI());
- if (prefix == null)
- throw new IllegalStateException("Namespace " + name.getNamespaceURI() + " is unbound.");
- return prefix + ":" + name.getLocalPart();
- }
-
+ CompletionStage<ContentSession> edit(Consumer<ContentSession> work);
}
+++ /dev/null
-package org.argeo.api.acr;
-
-import java.io.PrintStream;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.Arrays;
-import java.util.Base64;
-import java.util.List;
-import java.util.function.BiConsumer;
-
-import javax.xml.namespace.QName;
-
-public class ContentUtils {
- public static void traverse(Content content, BiConsumer<Content, Integer> doIt) {
- traverse(content, doIt, 0);
- }
-
- public static void traverse(Content content, BiConsumer<Content, Integer> doIt, int currentDepth) {
- doIt.accept(content, currentDepth);
- int nextDepth = currentDepth + 1;
- for (Content child : content) {
- traverse(child, doIt, nextDepth);
- }
- }
-
- public static void print(Content content, PrintStream out, int depth, boolean printText) {
- StringBuilder sb = new StringBuilder();
- for (int i = 0; i < depth; i++) {
- sb.append(" ");
- }
- String prefix = sb.toString();
- out.println(prefix + content.getName());
- for (QName key : content.keySet()) {
- out.println(prefix + " " + key + "=" + content.get(key));
- }
- if (printText) {
- if (content.hasText()) {
- out.println("<![CDATA[" + content.getText().trim() + "]]>");
- }
- }
- }
-
- public static URI bytesToDataURI(byte[] arr) {
- String base64Str = Base64.getEncoder().encodeToString(arr);
- try {
- final String PREFIX = "data:application/octet-stream;base64,";
- return new URI(PREFIX + base64Str);
- } catch (URISyntaxException e) {
- throw new IllegalStateException("Cannot serialize bytes a Base64 data URI", e);
- }
-
- }
-
- public static byte[] bytesFromDataURI(URI uri) {
- if (!"data".equals(uri.getScheme()))
- throw new IllegalArgumentException("URI must have 'data' as a scheme");
- String schemeSpecificPart = uri.getSchemeSpecificPart();
- int commaIndex = schemeSpecificPart.indexOf(',');
- String prefix = schemeSpecificPart.substring(0, commaIndex);
- List<String> info = Arrays.asList(prefix.split(";"));
- if (!info.contains("base64"))
- throw new IllegalArgumentException("URI must specify base64");
-
- String base64Str = schemeSpecificPart.substring(commaIndex + 1);
- return Base64.getDecoder().decode(base64Str);
-
- }
-
- public static <T> boolean isString(T t) {
- return t instanceof String;
- }
-
- /** Singleton. */
- private ContentUtils() {
-
- }
-}
package org.argeo.api.acr;
+import static javax.xml.XMLConstants.W3C_XML_SCHEMA_NS_URI;
+
import java.net.URI;
import java.net.URISyntaxException;
import java.time.Instant;
import java.time.format.DateTimeParseException;
+import java.util.Arrays;
+import java.util.Base64;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
import java.util.UUID;
-import javax.xml.XMLConstants;
+import javax.xml.namespace.QName;
/**
* Minimal standard attribute types that MUST be supported. All related classes
* belong to java.base and can be implicitly derived form a given
- * <code>String<code>.
+ * <code>String</code>.
*/
-public enum CrAttributeType implements ContentNameSupplier {
- BOOLEAN(Boolean.class, new BooleanFormatter()), //
- INTEGER(Integer.class, new IntegerFormatter()), //
- LONG(Long.class, new LongFormatter()), //
- DOUBLE(Double.class, new DoubleFormatter()), //
+public enum CrAttributeType {
+ BOOLEAN(Boolean.class, W3C_XML_SCHEMA_NS_URI, "boolean", new BooleanFormatter()), //
+ INTEGER(Integer.class, W3C_XML_SCHEMA_NS_URI, "integer", new IntegerFormatter()), //
+ LONG(Long.class, W3C_XML_SCHEMA_NS_URI, "long", new LongFormatter()), //
+ DOUBLE(Double.class, W3C_XML_SCHEMA_NS_URI, "double", new DoubleFormatter()), //
// we do not support short and float, like recent additions to Java
// (e.g. optional primitives)
- DATE_TIME(Instant.class, new InstantFormatter()), //
- UUID(UUID.class, new UuidFormatter()), //
- ANY_URI(URI.class, new UriFormatter()), //
- STRING(String.class, new StringFormatter()), //
+ DATE_TIME(Instant.class, W3C_XML_SCHEMA_NS_URI, "dateTime", new InstantFormatter()), //
+ UUID(UUID.class, ArgeoNamespace.CR_NAMESPACE_URI, "uuid", new UuidFormatter()), //
+ ANY_URI(URI.class, W3C_XML_SCHEMA_NS_URI, "anyUri", new UriFormatter()), //
+ STRING(String.class, W3C_XML_SCHEMA_NS_URI, "string", new StringFormatter()), //
;
private final Class<?> clss;
private final AttributeFormatter<?> formatter;
- private <T> CrAttributeType(Class<T> clss, AttributeFormatter<T> formatter) {
+ private final ContentName qName;
+
+ private <T> CrAttributeType(Class<T> clss, String namespaceUri, String localName, AttributeFormatter<T> formatter) {
this.clss = clss;
this.formatter = formatter;
+
+ qName = new ContentName(namespaceUri, localName, RuntimeNamespaceContext.getNamespaceContext());
+ }
+
+ public QName getqName() {
+ return qName;
}
public Class<?> getClss() {
return formatter;
}
- @Override
- public String getDefaultPrefix() {
- if (equals(UUID))
- return CrName.CR_DEFAULT_PREFIX;
- else
- return "xs";
- }
-
- @Override
- public String getNamespaceURI() {
- if (equals(UUID))
- return CrName.CR_NAMESPACE_URI;
- else
- return XMLConstants.W3C_XML_SCHEMA_NS_URI;
- }
+// @Override
+// public String getDefaultPrefix() {
+// if (equals(UUID))
+// return CrName.CR_DEFAULT_PREFIX;
+// else
+// return "xs";
+// }
+//
+// @Override
+// public String getNamespaceURI() {
+// if (equals(UUID))
+// return CrName.CR_NAMESPACE_URI;
+// else
+// return XMLConstants.W3C_XML_SCHEMA_NS_URI;
+// }
+ /** Default parsing procedure from a String to an object. */
public static Object parse(String str) {
if (str == null)
throw new IllegalArgumentException("String cannot be null");
// silent
}
+ // TODO support QName as a type? It would require a NamespaceContext
+ // see https://www.oreilly.com/library/view/xml-schema/0596002521/re91.html
+
// default
return STRING.getFormatter().parse(str);
}
+ /**
+ * Cast well know java types based on {@link Object#toString()} of the provided
+ * object.
+ *
+ */
+ @SuppressWarnings("unchecked")
+ public static <T> Optional<T> cast(Class<T> clss, Object value) {
+ // TODO Or should we?
+ Objects.requireNonNull(value, "Cannot cast a null value");
+ if (String.class.isAssignableFrom(clss)) {
+ return Optional.of((T) value.toString());
+ }
+ // Numbers
+ else if (Long.class.isAssignableFrom(clss)) {
+ if (value instanceof Long)
+ return Optional.of((T) value);
+ return Optional.of((T) Long.valueOf(value.toString()));
+ } else if (Integer.class.isAssignableFrom(clss)) {
+ if (value instanceof Integer)
+ return Optional.of((T) value);
+ return Optional.of((T) Integer.valueOf(value.toString()));
+ } else if (Double.class.isAssignableFrom(clss)) {
+ if (value instanceof Double)
+ return Optional.of((T) value);
+ return Optional.of((T) Double.valueOf(value.toString()));
+ }
+ // Numbers
+// else if (Number.class.isAssignableFrom(clss)) {
+// if (value instanceof Number)
+// return Optional.of((T) value);
+// return Optional.of((T) Number.valueOf(value.toString()));
+// }
+ return Optional.empty();
+ }
+
+ /** Utility to convert a data: URI to bytes. */
+ public static byte[] bytesFromDataURI(URI uri) {
+ if (!"data".equals(uri.getScheme()))
+ throw new IllegalArgumentException("URI must have 'data' as a scheme");
+ String schemeSpecificPart = uri.getSchemeSpecificPart();
+ int commaIndex = schemeSpecificPart.indexOf(',');
+ String prefix = schemeSpecificPart.substring(0, commaIndex);
+ List<String> info = Arrays.asList(prefix.split(";"));
+ if (!info.contains("base64"))
+ throw new IllegalArgumentException("URI must specify base64");
+
+ String base64Str = schemeSpecificPart.substring(commaIndex + 1);
+ return Base64.getDecoder().decode(base64Str);
+
+ }
+
+ /** Utility to convert bytes to a data: URI. */
+ public static URI bytesToDataURI(byte[] arr) {
+ String base64Str = Base64.getEncoder().encodeToString(arr);
+ try {
+ final String PREFIX = "data:application/octet-stream;base64,";
+ return new URI(PREFIX + base64Str);
+ } catch (URISyntaxException e) {
+ throw new IllegalStateException("Cannot serialize bytes a Base64 data URI", e);
+ }
+
+ }
+
static class BooleanFormatter implements AttributeFormatter<Boolean> {
/**
package org.argeo.api.acr;
/** Standard names. */
-public enum CrName implements ContentNameSupplier {
+public enum CrName implements QNamed {
/*
* TYPES
*/
- COLLECTION, // a collection type
+// collection, // a collection type
/*
* ATTRIBUTES
*/
- UUID, // the UUID of a content
+ uuid, // the UUID of a content
+ mount, // a mount point
+// cc, // content class
/*
* ATTRIBUTES FROM FILE SEMANTICS
*/
- CREATION_TIME, //
- LAST_MODIFIED_TIME, //
- SIZE, //
- FILE_KEY, //
- OWNER, //
- GROUP, //
- PERMISSIONS, //
+// creationTime, //
+// lastModifiedTime, //
+// size, //
+ fileKey, //
+// owner, //
+// group, //
+ permissions, //
/*
* CONTENT NAMES
*/
- ROOT,
+ root,
//
;
- public final static String CR_NAMESPACE_URI = "http://argeo.org/ns/cr";
- public final static String CR_DEFAULT_PREFIX = "cr";
- private final ContentName value;
+
- CrName() {
- value = toContentName();
- }
+// private final ContentName value;
- @Override
- public ContentName get() {
- return value;
- }
+// CrName() {
+// value = new ContentName(CR_NAMESPACE_URI, name(), RuntimeNamespaceContext.getNamespaceContext());
+// }
+//
+// public QName qName() {
+// return value;
+// }
@Override
- public String getNamespaceURI() {
- return CR_NAMESPACE_URI;
+ public String getNamespace() {
+ return ArgeoNamespace.CR_NAMESPACE_URI;
}
@Override
public String getDefaultPrefix() {
- return CR_DEFAULT_PREFIX;
+ return ArgeoNamespace.CR_DEFAULT_PREFIX;
}
}
--- /dev/null
+package org.argeo.api.acr;
+
+/**
+ * Name for core concepts with the same semantics as defined in the WebDav
+ * standard and extensions.
+ *
+ * @see "http://www.webdav.org/specs/rfc4918.html"
+ * @see "http://www.webdav.org/specs/rfc3744.html"
+ */
+public enum DName implements QNamed
+
+{
+ // RFC4918 (WebDav) properties used as CR attr
+ creationdate, //
+ displayname, //
+ getcontentlanguage, //
+ getcontentlength, //
+ getcontenttype, //
+ getetag, //
+ getlastmodified, //
+ resourcetype, //
+
+ // RFC4918 (WebDav) value used as CR class
+ collection, //
+
+ // RFC3744 (ACL) properties uase as CR attr
+ owner, //
+ group, //
+ //
+ ;
+
+ public final static String WEBDAV_NAMESPACE_URI = "DAV:";
+ public final static String WEBDAV_DEFAULT_PREFIX = "D";
+
+ @Override
+ public String getNamespace() {
+ return WEBDAV_NAMESPACE_URI;
+ }
+
+ @Override
+ public String getDefaultPrefix() {
+ return WEBDAV_DEFAULT_PREFIX;
+ }
+
+}
--- /dev/null
+package org.argeo.api.acr;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.Objects;
+import java.util.Set;
+import java.util.function.Function;
+
+import javax.xml.XMLConstants;
+import javax.xml.namespace.NamespaceContext;
+import javax.xml.namespace.QName;
+
+public class NamespaceUtils {
+
+ public static ContentName parsePrefixedName(String nameWithPrefix) {
+ return parsePrefixedName(RuntimeNamespaceContext.getNamespaceContext(), nameWithPrefix);
+ }
+
+ public static ContentName parsePrefixedName(NamespaceContext nameSpaceContext, String nameWithPrefix) {
+ Objects.requireNonNull(nameWithPrefix, "Name cannot be null");
+ if (nameWithPrefix.charAt(0) == '{') {
+ return new ContentName(QName.valueOf(nameWithPrefix), nameSpaceContext);
+ }
+ int index = nameWithPrefix.indexOf(':');
+ if (index < 0) {
+ return new ContentName(nameWithPrefix);
+ }
+ String prefix = nameWithPrefix.substring(0, index);
+ // TODO deal with empty name?
+ String localName = nameWithPrefix.substring(index + 1);
+ String namespaceURI = nameSpaceContext.getNamespaceURI(prefix);
+ if (XMLConstants.NULL_NS_URI.equals(namespaceURI))
+ throw new IllegalStateException("Prefix " + prefix + " is unbound.");
+ return new ContentName(namespaceURI, localName, prefix);
+ }
+
+ public static String toPrefixedName(QName name) {
+ return toPrefixedName(RuntimeNamespaceContext.getNamespaceContext(), name);
+ }
+
+ public static String toPrefixedName(NamespaceContext nameSpaceContext, QName name) {
+ if (XMLConstants.NULL_NS_URI.equals(name.getNamespaceURI()))
+ return name.getLocalPart();
+ String prefix = nameSpaceContext.getPrefix(name.getNamespaceURI());
+ if (prefix == null)
+ throw new IllegalStateException("Namespace " + name.getNamespaceURI() + " is unbound.");
+ return prefix + ":" + name.getLocalPart();
+ }
+
+ public final static Comparator<QName> QNAME_COMPARATOR = new Comparator<QName>() {
+
+ @Override
+ public int compare(QName qn1, QName qn2) {
+ if (Objects.equals(qn1.getNamespaceURI(), qn2.getNamespaceURI())) {// same namespace
+ return qn1.getLocalPart().compareTo(qn2.getLocalPart());
+ } else {
+ return qn1.getNamespaceURI().compareTo(qn2.getNamespaceURI());
+ }
+ }
+
+ };
+
+ public static boolean hasNamespace(QName qName) {
+ return !qName.getNamespaceURI().equals(XMLConstants.NULL_NS_URI);
+ }
+
+ public static void checkNoPrefix(String unqualified) {
+ if (unqualified.indexOf(':') >= 0)
+ throw new IllegalArgumentException("Name " + unqualified + " has a prefix");
+ }
+
+ public static QName unqualified(String name) {
+ checkNoPrefix(name);
+ return new ContentName(XMLConstants.NULL_NS_URI, name, XMLConstants.DEFAULT_NS_PREFIX);
+
+ }
+
+ /*
+ * DEFAULT NAMESPACE CONTEXT OPERATIONS as specified in NamespaceContext
+ */
+ public static String getStandardPrefix(String namespaceURI) {
+ if (namespaceURI == null)
+ throw new IllegalArgumentException("Namespace URI cannot be null");
+ if (XMLConstants.XML_NS_URI.equals(namespaceURI))
+ return XMLConstants.XML_NS_PREFIX;
+ else if (XMLConstants.XMLNS_ATTRIBUTE_NS_URI.equals(namespaceURI))
+ return XMLConstants.XMLNS_ATTRIBUTE;
+ return null;
+ }
+
+ public static Iterator<String> getStandardPrefixes(String namespaceURI) {
+ String prefix = getStandardPrefix(namespaceURI);
+ if (prefix == null)
+ return null;
+ return Collections.singleton(prefix).iterator();
+ }
+
+ public static String getStandardNamespaceURI(String prefix) {
+ if (prefix == null)
+ throw new IllegalArgumentException("Prefix cannot be null");
+ if (XMLConstants.XML_NS_PREFIX.equals(prefix))
+ return XMLConstants.XML_NS_URI;
+ else if (XMLConstants.XMLNS_ATTRIBUTE.equals(prefix))
+ return XMLConstants.XMLNS_ATTRIBUTE_NS_URI;
+ return null;
+ }
+
+ public static String getNamespaceURI(Function<String, String> mapping, String prefix) {
+ String namespaceURI = NamespaceUtils.getStandardNamespaceURI(prefix);
+ if (namespaceURI != null)
+ return namespaceURI;
+ if (XMLConstants.DEFAULT_NS_PREFIX.equals(prefix))
+ return XMLConstants.NULL_NS_URI;
+ namespaceURI = mapping.apply(prefix);
+ if (namespaceURI != null)
+ return namespaceURI;
+ return XMLConstants.NULL_NS_URI;
+ }
+
+ public static String getPrefix(Function<String, String> mapping, String namespaceURI) {
+ String prefix = NamespaceUtils.getStandardPrefix(namespaceURI);
+ if (prefix != null)
+ return prefix;
+ if (XMLConstants.NULL_NS_URI.equals(namespaceURI))
+ return XMLConstants.DEFAULT_NS_PREFIX;
+ return mapping.apply(namespaceURI);
+ }
+
+ public static Iterator<String> getPrefixes(Function<String, Set<String>> mapping, String namespaceURI) {
+ Iterator<String> standard = NamespaceUtils.getStandardPrefixes(namespaceURI);
+ if (standard != null)
+ return standard;
+ if (XMLConstants.NULL_NS_URI.equals(namespaceURI))
+ return Collections.singleton(XMLConstants.DEFAULT_NS_PREFIX).iterator();
+ Set<String> prefixes = mapping.apply(namespaceURI);
+ assert prefixes != null;
+ return prefixes.iterator();
+ }
+
+ /** singleton */
+ private NamespaceUtils() {
+ }
+
+}
--- /dev/null
+package org.argeo.api.acr;
+
+import java.util.function.Supplier;
+
+import javax.xml.XMLConstants;
+import javax.xml.namespace.NamespaceContext;
+import javax.xml.namespace.QName;
+
+/** An optionally qualified name. Primarily meant to be used in enums. */
+public interface QNamed extends Supplier<String> {
+ String name();
+
+ /** To be overridden when XML naming is not compatible with Java naming. */
+ default String localName() {
+ return name();
+ }
+
+ default QName qName() {
+ return new ContentName(getNamespace(), localName(), getDefaultPrefix());
+ }
+
+ default String get(NamespaceContext namespaceContext) {
+ return namespaceContext.getPrefix(getNamespace()) + ":" + localName();
+ }
+
+ default String get() {
+ return getDefaultPrefix() + ":" + localName();
+ }
+
+ String getNamespace();
+
+ String getDefaultPrefix();
+
+ /** To be used by enums without namespace (typically XML attributes). */
+ static interface Unqualified extends QNamed {
+ @Override
+ default String getNamespace() {
+ return XMLConstants.NULL_NS_URI;
+ }
+
+ @Override
+ default String getDefaultPrefix() {
+ return XMLConstants.DEFAULT_NS_PREFIX;
+ }
+
+ }
+}
--- /dev/null
+package org.argeo.api.acr;
+
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.NavigableMap;
+import java.util.TreeMap;
+
+import javax.xml.XMLConstants;
+import javax.xml.namespace.NamespaceContext;
+
+/**
+ * Programmatically defined {@link NamespaceContext}, code contributing
+ * namespaces MUST register here with a single default prefix.
+ */
+public class RuntimeNamespaceContext implements NamespaceContext {
+ public final static String XSD_DEFAULT_PREFIX = "xs";
+ public final static String XSD_INSTANCE_DEFAULT_PREFIX = "xsi";
+
+ private NavigableMap<String, String> prefixes = new TreeMap<>();
+ private NavigableMap<String, String> namespaces = new TreeMap<>();
+
+ @Override
+ public String getPrefix(String namespaceURI) {
+ return NamespaceUtils.getPrefix((ns) -> {
+ String prefix = namespaces.get(ns);
+ if (prefix == null)
+ throw new IllegalStateException("Namespace " + ns + " is not registered.");
+ return prefix;
+ }, namespaceURI);
+ }
+
+ @Override
+ public String getNamespaceURI(String prefix) {
+ return NamespaceUtils.getNamespaceURI((p) -> {
+ String ns = prefixes.get(p);
+ if (ns == null)
+ throw new IllegalStateException("Prefix " + p + " is not registered.");
+ return ns;
+ }, prefix);
+ }
+
+ @Override
+ public Iterator<String> getPrefixes(String namespaceURI) {
+ return Collections.singleton(getPrefix(namespaceURI)).iterator();
+ }
+
+ /*
+ * STATIC
+ */
+
+ private final static RuntimeNamespaceContext INSTANCE = new RuntimeNamespaceContext();
+
+ static {
+ // Standard
+ register(XMLConstants.XML_NS_URI, XMLConstants.XML_NS_PREFIX);
+ register(XMLConstants.XMLNS_ATTRIBUTE_NS_URI, XMLConstants.XMLNS_ATTRIBUTE);
+
+ // Common
+ register(XMLConstants.W3C_XML_SCHEMA_NS_URI, XSD_DEFAULT_PREFIX);
+ register(XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI, XSD_INSTANCE_DEFAULT_PREFIX);
+
+ // Argeo specific
+ register(ArgeoNamespace.CR_NAMESPACE_URI, ArgeoNamespace.CR_DEFAULT_PREFIX);
+ register(ArgeoNamespace.LDAP_NAMESPACE_URI, ArgeoNamespace.LDAP_DEFAULT_PREFIX);
+ register(ArgeoNamespace.ROLE_NAMESPACE_URI, ArgeoNamespace.ROLE_DEFAULT_PREFIX);
+ }
+
+ public static NamespaceContext getNamespaceContext() {
+ return INSTANCE;
+ }
+
+ public static Map<String, String> getPrefixes() {
+ return Collections.unmodifiableNavigableMap(INSTANCE.prefixes);
+ }
+
+ public synchronized static void register(String namespaceURI, String prefix) {
+ NavigableMap<String, String> prefixes = INSTANCE.prefixes;
+ NavigableMap<String, String> namespaces = INSTANCE.namespaces;
+ if (prefixes.containsKey(prefix)) {
+ String ns = prefixes.get(prefix);
+ if (ns.equals(namespaceURI))
+ return; // ignore silently
+ throw new IllegalStateException("Prefix " + prefix + " is already registered with namespace URI " + ns);
+ }
+ if (namespaces.containsKey(namespaceURI)) {
+ String p = namespaces.get(namespaceURI);
+ if (p.equals(prefix))
+ return; // ignore silently
+ throw new IllegalStateException("Namespace " + namespaceURI + " is already registered with prefix " + p);
+ }
+ prefixes.put(prefix, namespaceURI);
+ namespaces.put(namespaceURI, prefix);
+ }
+}
--- /dev/null
+package org.argeo.api.acr.ldap;
+
+import java.util.EnumSet;
+import java.util.Set;
+import java.util.TreeSet;
+
+import javax.naming.InvalidNameException;
+import javax.naming.ldap.LdapName;
+
+/**
+ * An object that can be identified with an X.500 distinguished name.
+ *
+ * @see "https://tools.ietf.org/html/rfc1779"
+ */
+public interface Distinguished {
+ /** The related distinguished name. */
+ String dn();
+
+ /** The related distinguished name as an {@link LdapName}. */
+ default LdapName distinguishedName() {
+ try {
+ return new LdapName(dn());
+ } catch (InvalidNameException e) {
+ throw new IllegalArgumentException("Distinguished name " + dn() + " is not properly formatted.", e);
+ }
+ }
+
+ /** List all DNs of an enumeration as strings. */
+ static Set<String> enumToDns(EnumSet<? extends Distinguished> enumSet) {
+ Set<String> res = new TreeSet<>();
+ for (Enum<? extends Distinguished> enm : enumSet) {
+ res.add(((Distinguished) enm).dn());
+ }
+ return res;
+ }
+}
--- /dev/null
+package org.argeo.api.acr.ldap;
+
+import java.util.Locale;
+
+import javax.xml.namespace.QName;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.acr.ContentName;
+
+/** Utilities around ACR and LDAP conventions. */
+public class LdapAcrUtils {
+
+ /** singleton */
+ private LdapAcrUtils() {
+ }
+
+ public static Object getLocalized(Content content, QName key, Locale locale) {
+ if (locale == null)
+ throw new IllegalArgumentException("A locale must be specified");
+ Object value = null;
+ if (locale.getCountry() != null && !locale.getCountry().equals(""))
+ value = content.get(new ContentName(key.getNamespaceURI(),
+ key.getLocalPart() + ";lang-" + locale.getLanguage() + "-" + locale.getCountry()));
+ if (value == null)
+ value = content
+ .get(new ContentName(key.getNamespaceURI(), key.getLocalPart() + ";lang-" + locale.getLanguage()));
+ if (value == null)
+ value = content.get(key);
+ return value;
+ }
+}
--- /dev/null
+package org.argeo.api.acr.ldap;
+
+import static org.argeo.api.acr.ArgeoNamespace.LDAP_DEFAULT_PREFIX;
+import static org.argeo.api.acr.ArgeoNamespace.LDAP_NAMESPACE_URI;
+
+import javax.xml.namespace.QName;
+
+import org.argeo.api.acr.ContentName;
+import org.argeo.api.acr.QNamed;
+import org.argeo.api.acr.RuntimeNamespaceContext;
+
+/**
+ * Standard LDAP attributes as per:<br>
+ * - <a href= "https://www.ldap.com/ldap-oid-reference">Standard LDAP</a><br>
+ * - <a href=
+ * "https://github.com/krb5/krb5/blob/master/src/plugins/kdb/ldap/libkdb_ldap/kerberos.schema">Kerberos
+ * LDAP (partial)</a>
+ */
+public enum LdapAttr implements QNamed, SpecifiedName {
+ /** */
+ uid("0.9.2342.19200300.100.1.1", "RFC 4519"),
+ /** */
+ mail("0.9.2342.19200300.100.1.3", "RFC 4524"),
+ /** */
+ info("0.9.2342.19200300.100.1.4", "RFC 4524"),
+ /** */
+ drink("0.9.2342.19200300.100.1.5", "RFC 4524"),
+ /** */
+ roomNumber("0.9.2342.19200300.100.1.6", "RFC 4524"),
+ /** */
+ photo("0.9.2342.19200300.100.1.7", "RFC 2798"),
+ /** */
+ userClass("0.9.2342.19200300.100.1.8", "RFC 4524"),
+ /** */
+ host("0.9.2342.19200300.100.1.9", "RFC 4524"),
+ /** */
+ manager("0.9.2342.19200300.100.1.10", "RFC 4524"),
+ /** */
+ documentIdentifier("0.9.2342.19200300.100.1.11", "RFC 4524"),
+ /** */
+ documentTitle("0.9.2342.19200300.100.1.12", "RFC 4524"),
+ /** */
+ documentVersion("0.9.2342.19200300.100.1.13", "RFC 4524"),
+ /** */
+ documentAuthor("0.9.2342.19200300.100.1.14", "RFC 4524"),
+ /** */
+ documentLocation("0.9.2342.19200300.100.1.15", "RFC 4524"),
+ /** */
+ homePhone("0.9.2342.19200300.100.1.20", "RFC 4524"),
+ /** */
+ secretary("0.9.2342.19200300.100.1.21", "RFC 4524"),
+ /** */
+ dc("0.9.2342.19200300.100.1.25", "RFC 4519"),
+ /** */
+ associatedDomain("0.9.2342.19200300.100.1.37", "RFC 4524"),
+ /** */
+ associatedName("0.9.2342.19200300.100.1.38", "RFC 4524"),
+ /** */
+ homePostalAddress("0.9.2342.19200300.100.1.39", "RFC 4524"),
+ /** */
+ personalTitle("0.9.2342.19200300.100.1.40", "RFC 4524"),
+ /** */
+ mobile("0.9.2342.19200300.100.1.41", "RFC 4524"),
+ /** */
+ pager("0.9.2342.19200300.100.1.42", "RFC 4524"),
+ /** */
+ co("0.9.2342.19200300.100.1.43", "RFC 4524"),
+ /** */
+ uniqueIdentifier("0.9.2342.19200300.100.1.44", "RFC 4524"),
+ /** */
+ organizationalStatus("0.9.2342.19200300.100.1.45", "RFC 4524"),
+ /** */
+ buildingName("0.9.2342.19200300.100.1.48", "RFC 4524"),
+ /** */
+ audio("0.9.2342.19200300.100.1.55", "RFC 2798"),
+ /** */
+ documentPublisher("0.9.2342.19200300.100.1.56", "RFC 4524"),
+ /** */
+ jpegPhoto("0.9.2342.19200300.100.1.60", "RFC 2798"),
+ /** */
+ vendorName("1.3.6.1.1.4", "RFC 3045"),
+ /** */
+ vendorVersion("1.3.6.1.1.5", "RFC 3045"),
+ /** */
+ entryUUID("1.3.6.1.1.16.4", "RFC 4530"),
+ /** */
+ entryDN("1.3.6.1.1.20", "RFC 5020"),
+ /** */
+ labeledURI("1.3.6.1.4.1.250.1.57", "RFC 2798"),
+ /** */
+ numSubordinates("1.3.6.1.4.1.453.16.2.103", "draft-ietf-boreham-numsubordinates"),
+ /** */
+ namingContexts("1.3.6.1.4.1.1466.101.120.5", "RFC 4512"),
+ /** */
+ altServer("1.3.6.1.4.1.1466.101.120.6", "RFC 4512"),
+ /** */
+ supportedExtension("1.3.6.1.4.1.1466.101.120.7", "RFC 4512"),
+ /** */
+ supportedControl("1.3.6.1.4.1.1466.101.120.13", "RFC 4512"),
+ /** */
+ supportedSASLMechanisms("1.3.6.1.4.1.1466.101.120.14", "RFC 4512"),
+ /** */
+ supportedLDAPVersion("1.3.6.1.4.1.1466.101.120.15", "RFC 4512"),
+ /** */
+ ldapSyntaxes("1.3.6.1.4.1.1466.101.120.16", "RFC 4512"),
+ /** */
+ supportedAuthPasswordSchemes("1.3.6.1.4.1.4203.1.3.3", "RFC 3112"),
+ /** */
+ authPassword("1.3.6.1.4.1.4203.1.3.4", "RFC 3112"),
+ /** */
+ supportedFeatures("1.3.6.1.4.1.4203.1.3.5", "RFC 4512"),
+ /** */
+ inheritable("1.3.6.1.4.1.7628.5.4.1", "draft-ietf-ldup-subentry"),
+ /** */
+ blockInheritance("1.3.6.1.4.1.7628.5.4.2", "draft-ietf-ldup-subentry"),
+ /** */
+ objectClass("2.5.4.0", "RFC 4512"),
+ /** */
+ aliasedObjectName("2.5.4.1", "RFC 4512"),
+ /** */
+ cn("2.5.4.3", "RFC 4519"),
+ /** */
+ sn("2.5.4.4", "RFC 4519"),
+ /** */
+ serialNumber("2.5.4.5", "RFC 4519"),
+ /** */
+ c("2.5.4.6", "RFC 4519"),
+ /** */
+ l("2.5.4.7", "RFC 4519"),
+ /** */
+ st("2.5.4.8", "RFC 4519"),
+ /** */
+ street("2.5.4.9", "RFC 4519"),
+ /** */
+ o("2.5.4.10", "RFC 4519"),
+ /** */
+ ou("2.5.4.11", "RFC 4519"),
+ /** */
+ title("2.5.4.12", "RFC 4519"),
+ /** */
+ description("2.5.4.13", "RFC 4519"),
+ /** */
+ searchGuide("2.5.4.14", "RFC 4519"),
+ /** */
+ businessCategory("2.5.4.15", "RFC 4519"),
+ /** */
+ postalAddress("2.5.4.16", "RFC 4519"),
+ /** */
+ postalCode("2.5.4.17", "RFC 4519"),
+ /** */
+ postOfficeBox("2.5.4.18", "RFC 4519"),
+ /** */
+ physicalDeliveryOfficeName("2.5.4.19", "RFC 4519"),
+ /** */
+ telephoneNumber("2.5.4.20", "RFC 4519"),
+ /** */
+ telexNumber("2.5.4.21", "RFC 4519"),
+ /** */
+ teletexTerminalIdentifier("2.5.4.22", "RFC 4519"),
+ /** */
+ facsimileTelephoneNumber("2.5.4.23", "RFC 4519"),
+ /** */
+ x121Address("2.5.4.24", "RFC 4519"),
+ /** */
+ internationalISDNNumber("2.5.4.25", "RFC 4519"),
+ /** */
+ registeredAddress("2.5.4.26", "RFC 4519"),
+ /** */
+ destinationIndicator("2.5.4.27", "RFC 4519"),
+ /** */
+ preferredDeliveryMethod("2.5.4.28", "RFC 4519"),
+ /** */
+ member("2.5.4.31", "RFC 4519"),
+ /** */
+ owner("2.5.4.32", "RFC 4519"),
+ /** */
+ roleOccupant("2.5.4.33", "RFC 4519"),
+ /** */
+ seeAlso("2.5.4.34", "RFC 4519"),
+ /** */
+ userPassword("2.5.4.35", "RFC 4519"),
+ /** */
+ userCertificate("2.5.4.36", "RFC 4523"),
+ /** */
+ cACertificate("2.5.4.37", "RFC 4523"),
+ /** */
+ authorityRevocationList("2.5.4.38", "RFC 4523"),
+ /** */
+ certificateRevocationList("2.5.4.39", "RFC 4523"),
+ /** */
+ crossCertificatePair("2.5.4.40", "RFC 4523"),
+ /** */
+ name("2.5.4.41", "RFC 4519"),
+ /** */
+ givenName("2.5.4.42", "RFC 4519"),
+ /** */
+ initials("2.5.4.43", "RFC 4519"),
+ /** */
+ generationQualifier("2.5.4.44", "RFC 4519"),
+ /** */
+ x500UniqueIdentifier("2.5.4.45", "RFC 4519"),
+ /** */
+ dnQualifier("2.5.4.46", "RFC 4519"),
+ /** */
+ enhancedSearchGuide("2.5.4.47", "RFC 4519"),
+ /** */
+ distinguishedName("2.5.4.49", "RFC 4519"),
+ /** */
+ uniqueMember("2.5.4.50", "RFC 4519"),
+ /** */
+ houseIdentifier("2.5.4.51", "RFC 4519"),
+ /** */
+ supportedAlgorithms("2.5.4.52", "RFC 4523"),
+ /** */
+ deltaRevocationList("2.5.4.53", "RFC 4523"),
+ /** */
+ createTimestamp("2.5.18.1", "RFC 4512"),
+ /** */
+ modifyTimestamp("2.5.18.2", "RFC 4512"),
+ /** */
+ creatorsName("2.5.18.3", "RFC 4512"),
+ /** */
+ modifiersName("2.5.18.4", "RFC 4512"),
+ /** */
+ subschemaSubentry("2.5.18.10", "RFC 4512"),
+ /** */
+ dITStructureRules("2.5.21.1", "RFC 4512"),
+ /** */
+ dITContentRules("2.5.21.2", "RFC 4512"),
+ /** */
+ matchingRules("2.5.21.4", "RFC 4512"),
+ /** */
+ attributeTypes("2.5.21.5", "RFC 4512"),
+ /** */
+ objectClasses("2.5.21.6", "RFC 4512"),
+ /** */
+ nameForms("2.5.21.7", "RFC 4512"),
+ /** */
+ matchingRuleUse("2.5.21.8", "RFC 4512"),
+ /** */
+ structuralObjectClass("2.5.21.9", "RFC 4512"),
+ /** */
+ governingStructureRule("2.5.21.10", "RFC 4512"),
+ /** */
+ carLicense("2.16.840.1.113730.3.1.1", "RFC 2798"),
+ /** */
+ departmentNumber("2.16.840.1.113730.3.1.2", "RFC 2798"),
+ /** */
+ employeeNumber("2.16.840.1.113730.3.1.3", "RFC 2798"),
+ /** */
+ employeeType("2.16.840.1.113730.3.1.4", "RFC 2798"),
+ /** */
+ changeNumber("2.16.840.1.113730.3.1.5", "draft-good-ldap-changelog"),
+ /** */
+ targetDN("2.16.840.1.113730.3.1.6", "draft-good-ldap-changelog"),
+ /** */
+ changeType("2.16.840.1.113730.3.1.7", "draft-good-ldap-changelog"),
+ /** */
+ changes("2.16.840.1.113730.3.1.8", "draft-good-ldap-changelog"),
+ /** */
+ newRDN("2.16.840.1.113730.3.1.9", "draft-good-ldap-changelog"),
+ /** */
+ deleteOldRDN("2.16.840.1.113730.3.1.10", "draft-good-ldap-changelog"),
+ /** */
+ newSuperior("2.16.840.1.113730.3.1.11", "draft-good-ldap-changelog"),
+ /** */
+ ref("2.16.840.1.113730.3.1.34", "RFC 3296"),
+ /** */
+ changelog("2.16.840.1.113730.3.1.35", "draft-good-ldap-changelog"),
+ /** */
+ preferredLanguage("2.16.840.1.113730.3.1.39", "RFC 2798"),
+ /** */
+ userSMIMECertificate("2.16.840.1.113730.3.1.40", "RFC 2798"),
+ /** */
+ userPKCS12("2.16.840.1.113730.3.1.216", "RFC 2798"),
+ /** */
+ displayName("2.16.840.1.113730.3.1.241", "RFC 2798"),
+
+ // Sun memberOf
+ memberOf("1.2.840.113556.1.2.102", "389 DS memberOf"),
+
+ // KERBEROS (partial)
+ krbPrincipalName("2.16.840.1.113719.1.301.6.8.1", "Novell Kerberos Schema Definitions"),
+
+ // RFC 2985 and RFC 3039 (partial)
+ dateOfBirth("1.3.6.1.5.5.7.9.1", "RFC 2985"),
+ /** */
+ placeOfBirth("1.3.6.1.5.5.7.9.2", "RFC 2985"),
+ /** */
+ gender("1.3.6.1.5.5.7.9.3", "RFC 2985"),
+ /** */
+ countryOfCitizenship("1.3.6.1.5.5.7.9.4", "RFC 2985"),
+ /** */
+ countryOfResidence("1.3.6.1.5.5.7.9.5", "RFC 2985"),
+
+ // RFC 2307bis (partial)
+ /** */
+ uidNumber("1.3.6.1.1.1.1.0", "RFC 2307bis"),
+ /** */
+ gidNumber("1.3.6.1.1.1.1.1", "RFC 2307bis"),
+ /** */
+ homeDirectory("1.3.6.1.1.1.1.3", "RFC 2307bis"),
+ /** */
+ loginShell("1.3.6.1.1.1.1.4", "RFC 2307bis"),
+ /** */
+ memberUid("1.3.6.1.1.1.1.12", "RFC 2307bis"),
+
+ //
+ ;
+
+ public final static String DN = "dn";
+
+ private final String oid, spec;
+ private final QName value;
+
+ LdapAttr(String oid, String spec) {
+ this.oid = oid;
+ this.spec = spec;
+ this.value = new ContentName(LDAP_NAMESPACE_URI, name());
+ }
+
+ public QName qName() {
+ return value;
+ }
+
+ @Override
+ public String getID() {
+ return oid;
+ }
+
+ @Override
+ public String getSpec() {
+ return spec;
+ }
+
+ @Deprecated
+ public String property() {
+ return get();
+ }
+
+ @Deprecated
+ public String qualified() {
+ return get();
+ }
+
+ /** #deprecated use {@link #qName()} instead. */
+// @Deprecated
+ public String get() {
+ return RuntimeNamespaceContext.getNamespaceContext().getPrefix(LDAP_NAMESPACE_URI) + ":" + name();
+ }
+
+ @Override
+ public final String toString() {
+ // must return the name
+ return name();
+ }
+
+ @Override
+ public String getNamespace() {
+ return LDAP_NAMESPACE_URI;
+ }
+
+ @Override
+ public String getDefaultPrefix() {
+ return LDAP_DEFAULT_PREFIX;
+ }
+
+}
--- /dev/null
+package org.argeo.api.acr.ldap;
+
+import static org.argeo.api.acr.ArgeoNamespace.LDAP_DEFAULT_PREFIX;
+import static org.argeo.api.acr.ArgeoNamespace.LDAP_NAMESPACE_URI;
+
+import javax.xml.namespace.QName;
+
+import org.argeo.api.acr.ArgeoNamespace;
+import org.argeo.api.acr.ContentName;
+import org.argeo.api.acr.QNamed;
+import org.argeo.api.acr.RuntimeNamespaceContext;
+
+/**
+ * Standard LDAP object classes as per
+ * <a href="https://www.ldap.com/ldap-oid-reference">https://www.ldap.com/ldap-
+ * oid-reference</a>
+ */
+public enum LdapObj implements QNamed, SpecifiedName {
+ account("0.9.2342.19200300.100.4.5", "RFC 4524"),
+ /** */
+ document("0.9.2342.19200300.100.4.6", "RFC 4524"),
+ /** */
+ room("0.9.2342.19200300.100.4.7", "RFC 4524"),
+ /** */
+ documentSeries("0.9.2342.19200300.100.4.9", "RFC 4524"),
+ /** */
+ domain("0.9.2342.19200300.100.4.13", "RFC 4524"),
+ /** */
+ rFC822localPart("0.9.2342.19200300.100.4.14", "RFC 4524"),
+ /** */
+ domainRelatedObject("0.9.2342.19200300.100.4.17", "RFC 4524"),
+ /** */
+ friendlyCountry("0.9.2342.19200300.100.4.18", "RFC 4524"),
+ /** */
+ simpleSecurityObject("0.9.2342.19200300.100.4.19", "RFC 4524"),
+ /** */
+ uidObject("1.3.6.1.1.3.1", "RFC 4519"),
+ /** */
+ extensibleObject("1.3.6.1.4.1.1466.101.120.111", "RFC 4512"),
+ /** */
+ dcObject("1.3.6.1.4.1.1466.344", "RFC 4519"),
+ /** */
+ authPasswordObject("1.3.6.1.4.1.4203.1.4.7", "RFC 3112"),
+ /** */
+ namedObject("1.3.6.1.4.1.5322.13.1.1", "draft-howard-namedobject"),
+ /** */
+ inheritableLDAPSubEntry("1.3.6.1.4.1.7628.5.6.1.1", "draft-ietf-ldup-subentry"),
+ /** */
+ top("2.5.6.0", "RFC 4512"),
+ /** */
+ alias("2.5.6.1", "RFC 4512"),
+ /** */
+ country("2.5.6.2", "RFC 4519"),
+ /** */
+ locality("2.5.6.3", "RFC 4519"),
+ /** */
+ organization("2.5.6.4", "RFC 4519"),
+ /** */
+ organizationalUnit("2.5.6.5", "RFC 4519"),
+ /** */
+ person("2.5.6.6", "RFC 4519"),
+ /** */
+ organizationalPerson("2.5.6.7", "RFC 4519"),
+ /** */
+ organizationalRole("2.5.6.8", "RFC 4519"),
+ /** */
+ groupOfNames("2.5.6.9", "RFC 4519"),
+ /** */
+ residentialPerson("2.5.6.10", "RFC 4519"),
+ /** */
+ applicationProcess("2.5.6.11", "RFC 4519"),
+ /** */
+ device("2.5.6.14", "RFC 4519"),
+ /** */
+ strongAuthenticationUser("2.5.6.15", "RFC 4523"),
+ /** */
+ certificationAuthority("2.5.6.16", "RFC 4523"),
+ // /** Should be certificationAuthority-V2 */
+ // certificationAuthority_V2("2.5.6.16.2", "RFC 4523") {
+ // },
+ /** */
+ groupOfUniqueNames("2.5.6.17", "RFC 4519"),
+ /** */
+ userSecurityInformation("2.5.6.18", "RFC 4523"),
+ /** */
+ cRLDistributionPoint("2.5.6.19", "RFC 4523"),
+ /** */
+ pkiUser("2.5.6.21", "RFC 4523"),
+ /** */
+ pkiCA("2.5.6.22", "RFC 4523"),
+ /** */
+ deltaCRL("2.5.6.23", "RFC 4523"),
+ /** */
+ subschema("2.5.20.1", "RFC 4512"),
+ /** */
+ ldapSubEntry("2.16.840.1.113719.2.142.6.1.1", "draft-ietf-ldup-subentry"),
+ /** */
+ changeLogEntry("2.16.840.1.113730.3.2.1", "draft-good-ldap-changelog"),
+ /** */
+ inetOrgPerson("2.16.840.1.113730.3.2.2", "RFC 2798"),
+ /** */
+ referral("2.16.840.1.113730.3.2.6", "RFC 3296"),
+
+ // RFC 2307bis (partial)
+ /** */
+ posixAccount("1.3.6.1.1.1.2.0", "RFC 2307bis"),
+ /** */
+ posixGroup("1.3.6.1.1.1.2.2", "RFC 2307bis"),
+
+ //
+ ;
+
+ private final String oid, spec;
+ private final QName value;
+
+ private LdapObj(String oid, String spec) {
+ this.oid = oid;
+ this.spec = spec;
+ this.value = new ContentName(ArgeoNamespace.LDAP_NAMESPACE_URI, name());
+ }
+
+ public QName qName() {
+ return value;
+ }
+
+ public String getOid() {
+ return oid;
+ }
+
+ public String getSpec() {
+ return spec;
+ }
+
+ @Deprecated
+ public String property() {
+ return get();
+ }
+
+ /** #deprecated use {@link #qName()} instead. */
+// @Deprecated
+ public String get() {
+ return RuntimeNamespaceContext.getNamespaceContext().getPrefix(LDAP_NAMESPACE_URI) + ":" + name();
+ }
+
+ @Override
+ public String getNamespace() {
+ return LDAP_NAMESPACE_URI;
+ }
+
+ @Override
+ public String getDefaultPrefix() {
+ return LDAP_DEFAULT_PREFIX;
+ }
+
+}
--- /dev/null
+package org.argeo.api.acr.ldap;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URI;
+import java.net.URLDecoder;
+import java.nio.charset.StandardCharsets;
+import java.time.Instant;
+import java.time.OffsetDateTime;
+import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
+import java.time.temporal.ChronoField;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+public class NamingUtils {
+ /** As per https://tools.ietf.org/html/rfc4517#section-3.3.13 */
+ private final static DateTimeFormatter utcLdapDate = DateTimeFormatter.ofPattern("uuuuMMddHHmmssX")
+ .withZone(ZoneOffset.UTC);
+
+ /** @return null if not parseable */
+ public static Instant ldapDateToInstant(String ldapDate) {
+ try {
+ return OffsetDateTime.parse(ldapDate, utcLdapDate).toInstant();
+ } catch (DateTimeParseException e) {
+ return null;
+ }
+ }
+
+ /** @return null if not parseable */
+ public static ZonedDateTime ldapDateToZonedDateTime(String ldapDate) {
+ try {
+ return OffsetDateTime.parse(ldapDate, utcLdapDate).toZonedDateTime();
+ } catch (DateTimeParseException e) {
+ return null;
+ }
+ }
+
+ public static Calendar ldapDateToCalendar(String ldapDate) {
+ OffsetDateTime instant = OffsetDateTime.parse(ldapDate, utcLdapDate);
+ GregorianCalendar calendar = new GregorianCalendar();
+ calendar.set(Calendar.DAY_OF_MONTH, instant.get(ChronoField.DAY_OF_MONTH));
+ calendar.set(Calendar.MONTH, instant.get(ChronoField.MONTH_OF_YEAR));
+ calendar.set(Calendar.YEAR, instant.get(ChronoField.YEAR));
+ return calendar;
+ }
+
+ public static String instantToLdapDate(ZonedDateTime instant) {
+ return utcLdapDate.format(instant.withZoneSameInstant(ZoneOffset.UTC));
+ }
+
+ public static String getQueryValue(Map<String, List<String>> query, String key) {
+ if (!query.containsKey(key))
+ return null;
+ List<String> val = query.get(key);
+ if (val.size() == 1)
+ return val.get(0);
+ else
+ throw new IllegalArgumentException("There are " + val.size() + " value(s) for " + key);
+ }
+
+ public static Map<String, List<String>> queryToMap(URI uri) {
+ return queryToMap(uri.getQuery());
+ }
+
+ private static Map<String, List<String>> queryToMap(String queryPart) {
+ try {
+ final Map<String, List<String>> query_pairs = new LinkedHashMap<String, List<String>>();
+ if (queryPart == null)
+ return query_pairs;
+ final String[] pairs = queryPart.split("&");
+ for (String pair : pairs) {
+ final int idx = pair.indexOf("=");
+ final String key = idx > 0 ? URLDecoder.decode(pair.substring(0, idx), StandardCharsets.UTF_8.name())
+ : pair;
+ if (!query_pairs.containsKey(key)) {
+ query_pairs.put(key, new LinkedList<String>());
+ }
+ final String value = idx > 0 && pair.length() > idx + 1
+ ? URLDecoder.decode(pair.substring(idx + 1), StandardCharsets.UTF_8.name())
+ : null;
+ query_pairs.get(key).add(value);
+ }
+ return query_pairs;
+ } catch (UnsupportedEncodingException e) {
+ throw new IllegalArgumentException("Cannot convert " + queryPart + " to map", e);
+ }
+ }
+
+ private NamingUtils() {
+
+ }
+
+// public static void main(String args[]) {
+// ZonedDateTime now = ZonedDateTime.now().withZoneSameInstant(ZoneOffset.UTC);
+// String str = utcLdapDate.format(now);
+// System.out.println(str);
+// utcLdapDate.parse(str);
+// utcLdapDate.parse("19520512000000Z");
+// }
+}
--- /dev/null
+package org.argeo.api.acr.ldap;
+
+interface NodeOID {
+ String BASE = "1.3.6.1.4.1" + ".48308" + ".1";
+
+ // uuidgen --md5 --namespace @oid --name 1.3.6.1.4.1.48308
+ String BASE_UUID_V3 = "6869e86b-96b7-3d49-b6ab-ffffc5ad95fb";
+
+ // uuidgen --sha1 --namespace @oid --name 1.3.6.1.4.1.48308
+ String BASE_UUID_V5 = "58873947-460c-59a6-a7b4-28a97def5f27";
+
+ // ATTRIBUTE TYPES
+ String ATTRIBUTE_TYPES = BASE + ".4";
+
+ // OBJECT CLASSES
+ String OBJECT_CLASSES = BASE + ".6";
+}
--- /dev/null
+package org.argeo.api.acr.ldap;
+
+/**
+ * A name which has been specified and for which an id has been defined
+ * (typically an OID).
+ */
+interface SpecifiedName {
+ /** The name */
+ String name();
+
+ /** An RFC or the URLof some specification */
+ default String getSpec() {
+ return null;
+ }
+
+ /** Typically an OID */
+ default String getID() {
+ return getClass().getName() + "." + name();
+ }
+}
+++ /dev/null
-package org.argeo.api.acr.spi;
-
-import java.util.AbstractMap;
-import java.util.AbstractSet;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.Set;
-
-import javax.xml.namespace.QName;
-
-import org.argeo.api.acr.Content;
-import org.argeo.api.acr.CrName;
-
-public abstract class AbstractContent extends AbstractMap<QName, Object> implements Content {
-
- /*
- * ATTRIBUTES OPERATIONS
- */
- protected abstract Iterable<QName> keys();
-
- protected abstract void removeAttr(QName key);
-
- @Override
- public Set<Entry<QName, Object>> entrySet() {
- Set<Entry<QName, Object>> result = new AttrSet();
- return result;
- }
-
- @Override
- public Class<?> getType(QName key) {
- return String.class;
- }
-
- @Override
- public boolean isMultiple(QName key) {
- return false;
- }
-
- @Override
- public <A> Optional<List<A>> getMultiple(QName key, Class<A> clss) {
- Object value = get(key);
- if (value == null)
- return null;
- if (value instanceof List) {
- try {
- List<A> res = (List<A>) value;
- return Optional.of(res);
- } catch (ClassCastException e) {
- List<A> res = new ArrayList<>();
- List<?> lst = (List<?>) value;
- try {
- for (Object o : lst) {
- A item = (A) o;
- res.add(item);
- }
- return Optional.of(res);
- } catch (ClassCastException e1) {
- return Optional.empty();
- }
- }
- } else {// singleton
- try {
- A res = (A) value;
- return Optional.of(Collections.singletonList(res));
- } catch (ClassCastException e) {
- return Optional.empty();
- }
- }
- }
-
- /*
- * CONTENT OPERATIONS
- */
-
- @Override
- public String getPath() {
- List<Content> ancestors = new ArrayList<>();
- collectAncestors(ancestors, this);
- StringBuilder path = new StringBuilder();
- for (Content c : ancestors) {
- QName name = c.getName();
- // FIXME
- if (!CrName.ROOT.get().equals(name))
- path.append('/').append(name);
- }
- return path.toString();
- }
-
- private void collectAncestors(List<Content> ancestors, Content content) {
- if (content == null)
- return;
- ancestors.add(0, content);
- collectAncestors(ancestors, content.getParent());
- }
-
- /*
- * UTILITIES
- */
- protected boolean isDefaultAttrTypeRequested(Class<?> clss) {
- // check whether clss is Object.class
- return clss.isAssignableFrom(Object.class);
- }
-
- @Override
- public String toString() {
- return "content " + getPath();
- }
-
- /*
- * SUB CLASSES
- */
-
- class AttrSet extends AbstractSet<Entry<QName, Object>> {
-
- @Override
- public Iterator<Entry<QName, Object>> iterator() {
- final Iterator<QName> keys = keys().iterator();
- Iterator<Entry<QName, Object>> it = new Iterator<Map.Entry<QName, Object>>() {
-
- QName key = null;
-
- @Override
- public boolean hasNext() {
- return keys.hasNext();
- }
-
- @Override
- public Entry<QName, Object> next() {
- key = keys.next();
- // TODO check type
- Optional<?> value = get(key, Object.class);
- assert !value.isEmpty();
- AbstractMap.SimpleEntry<QName, Object> entry = new SimpleEntry<>(key, value.get());
- return entry;
- }
-
- @Override
- public void remove() {
- if (key != null) {
- AbstractContent.this.removeAttr(key);
- } else {
- throw new IllegalStateException("Iteration has not started");
- }
- }
-
- };
- return it;
- }
-
- @Override
- public int size() {
- int count = 0;
- for (QName key : keys()) {
- count++;
- }
- return count;
- }
-
- }
-}
--- /dev/null
+package org.argeo.api.acr.spi;
+
+import java.net.URL;
+
+/** A namespace and its default prefix, possibly with a schema definition. */
+public interface ContentNamespace {
+ String getDefaultPrefix();
+
+ String getNamespaceURI();
+
+ URL getSchemaResource();
+
+}
package org.argeo.api.acr.spi;
-import org.argeo.api.acr.Content;
+import java.util.Iterator;
-public interface ContentProvider {
+import javax.xml.namespace.NamespaceContext;
- Content get(ProvidedSession session, String mountPath, String relativePath);
+public interface ContentProvider extends NamespaceContext {
+
+ ProvidedContent get(ProvidedSession session, String relativePath);
+
+ boolean exists(ProvidedSession session, String relativePath);
+
+ String getMountPath();
+
+ /*
+ * NAMESPACE CONTEXT
+ */
+ @Override
+ default String getPrefix(String namespaceURI) {
+ Iterator<String> prefixes = getPrefixes(namespaceURI);
+ return prefixes.hasNext() ? prefixes.next() : null;
+ }
+
+// default ContentName parsePrefixedName(String nameWithPrefix) {
+// return NamespaceUtils.parsePrefixedName(this, nameWithPrefix);
+// }
+//
+// default String toPrefixedName(QName name) {
+// return NamespaceUtils.toPrefixedName(this, name);
+// }
}
import org.argeo.api.acr.Content;
+/** A {@link Content} implementation. */
public interface ProvidedContent extends Content {
+ final static String ROOT_PATH = "/";
+
ProvidedSession getSession();
ContentProvider getProvider();
+
+ int getDepth();
+
+ /**
+ * An opaque ID which is guaranteed to uniquely identify this content within the
+ * session return by {@link #getSession()}. Typically used for UI.
+ */
+ String getSessionLocalId();
+
+ default ProvidedContent getMountPoint(String relativePath) {
+ throw new UnsupportedOperationException("This content doe not support mount");
+ }
+
+ default ProvidedContent getContent(String path) {
+ Content fileNode;
+ if (path.startsWith(ROOT_PATH)) {// absolute
+ fileNode = getSession().get(path);
+ } else {// relative
+ String absolutePath = getPath() + '/' + path;
+ fileNode = getSession().get(absolutePath);
+ }
+ return (ProvidedContent) fileNode;
+ }
}
package org.argeo.api.acr.spi;
+import javax.xml.namespace.QName;
+
+import org.argeo.api.acr.Content;
import org.argeo.api.acr.ContentRepository;
+/** A {@link ContentRepository} implementation. */
public interface ProvidedRepository extends ContentRepository {
+ void registerTypes(ContentNamespace... namespaces);
+
+ ContentProvider getMountContentProvider(Content mountPoint, boolean initialize, QName... types);
+
+ boolean shouldMount(QName... types);
+
+ void addProvider(ContentProvider provider);
}
package org.argeo.api.acr.spi;
-import java.util.Collections;
import java.util.Iterator;
-import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.CompletionStage;
-import javax.xml.XMLConstants;
-import javax.xml.namespace.NamespaceContext;
-
-import org.argeo.api.acr.ContentNameSupplier;
+import org.argeo.api.acr.Content;
import org.argeo.api.acr.ContentSession;
+import org.argeo.api.acr.RuntimeNamespaceContext;
-public interface ProvidedSession extends ContentSession, NamespaceContext {
+/** A {@link ContentSession} implementation. */
+public interface ProvidedSession extends ContentSession {
ProvidedRepository getRepository();
+ CompletionStage<ProvidedSession> onClose();
+
+ Content getMountPoint(String path);
+
+ boolean isEditing();
+
+ void notifyModification(ProvidedContent content);
+
+ UUID getUuid();
+
+// Content getSessionRunDir();
+
/*
* NAMESPACE CONTEXT
*/
- /** @return the bound namespace or null if not found */
- String findNamespace(String prefix);
-
- // TODO find the default prefix?
- Set<String> findPrefixes(String namespaceURI);
-
- /** To be overridden for optimisation, as it will be called a lot */
- default String findPrefix(String namespaceURI) {
- Set<String> prefixes = findPrefixes(namespaceURI);
- if (prefixes.isEmpty())
- return null;
- return prefixes.iterator().next();
- }
@Override
- default String getNamespaceURI(String prefix) {
- String namespaceURI = ContentNameSupplier.getStandardNamespaceURI(prefix);
- if (namespaceURI != null)
- return namespaceURI;
- if (XMLConstants.DEFAULT_NS_PREFIX.equals(prefix))
- return XMLConstants.NULL_NS_URI;
- namespaceURI = findNamespace(prefix);
- if (namespaceURI != null)
- return namespaceURI;
- return XMLConstants.NULL_NS_URI;
+ default String getPrefix(String namespaceURI) {
+ return RuntimeNamespaceContext.getNamespaceContext().getPrefix(namespaceURI);
}
@Override
- default String getPrefix(String namespaceURI) {
- String prefix = ContentNameSupplier.getStandardPrefix(namespaceURI);
- if (prefix != null)
- return prefix;
- if (XMLConstants.NULL_NS_URI.equals(namespaceURI))
- return XMLConstants.DEFAULT_NS_PREFIX;
- return findPrefix(namespaceURI);
+ default String getNamespaceURI(String prefix) {
+ return RuntimeNamespaceContext.getNamespaceContext().getNamespaceURI(prefix);
}
@Override
default Iterator<String> getPrefixes(String namespaceURI) {
- Iterator<String> standard = ContentNameSupplier.getStandardPrefixes(namespaceURI);
- if (standard != null)
- return standard;
- if (XMLConstants.NULL_NS_URI.equals(namespaceURI))
- return Collections.singleton(XMLConstants.DEFAULT_NS_PREFIX).iterator();
- Set<String> prefixes = findPrefixes(namespaceURI);
- assert prefixes != null;
- return prefixes.iterator();
+ return RuntimeNamespaceContext.getNamespaceContext().getPrefixes(namespaceURI);
}
-
}
--- /dev/null
+package org.argeo.api.acr.tabular;
+
+import java.util.List;
+
+/** Minimal tabular row wrapping an {@link Object} array */
+public class ArrayTabularRow implements TabularRow {
+ private final Object[] arr;
+
+ public ArrayTabularRow(List<?> objs) {
+ this.arr = objs.toArray();
+ }
+
+ public Object get(Integer col) {
+ return arr[col];
+ }
+
+ public int size() {
+ return arr.length;
+ }
+
+ public Object[] toArray() {
+ return arr;
+ }
+
+}
--- /dev/null
+package org.argeo.api.acr.tabular;
+
+/** The column in a tabular content */
+public class TabularColumn {
+ private String name;
+ /**
+ * JCR types, see
+ * http://www.day.com/maven/javax.jcr/javadocs/jcr-2.0/index.html
+ * ?javax/jcr/PropertyType.html
+ */
+ private Integer type;
+
+ /** column with default type */
+ public TabularColumn(String name) {
+ super();
+ this.name = name;
+ }
+
+ public TabularColumn(String name, Integer type) {
+ super();
+ this.name = name;
+ this.type = type;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public Integer getType() {
+ return type;
+ }
+
+ public void setType(Integer type) {
+ this.type = type;
+ }
+
+}
--- /dev/null
+package org.argeo.api.acr.tabular;
+
+import java.util.List;
+
+/**
+ * Content organized as a table, possibly with headers. Only JCR types are
+ * supported even though there is not direct dependency on JCR.
+ */
+public interface TabularContent {
+ /** The headers of this table or <code>null</code> is none available. */
+ public List<TabularColumn> getColumns();
+
+ public TabularRowIterator read();
+}
--- /dev/null
+package org.argeo.api.acr.tabular;
+
+/** A row of tabular data */
+public interface TabularRow {
+ /** The value at this column index */
+ public Object get(Integer col);
+
+ /** The raw objects (direct references) */
+ public Object[] toArray();
+
+ /** Number of columns */
+ public int size();
+}
--- /dev/null
+package org.argeo.api.acr.tabular;
+
+import java.util.Iterator;
+
+/** Navigation of rows */
+public interface TabularRowIterator extends Iterator<TabularRow> {
+ /**
+ * Current row number, has to be incremented by each call to next() ; starts at 0, will
+ * therefore be 1 for the first row returned.
+ */
+ public Long getCurrentRowNumber();
+}
--- /dev/null
+package org.argeo.api.acr.tabular;
+
+
+/** Write to a tabular content */
+public interface TabularWriter {
+ /** Append a new row of data */
+ public void appendRow(Object[] row);
+
+ /** Finish persisting data and release resources */
+ public void close();
+}
--- /dev/null
+/** Tabular format API. */
+package org.argeo.api.acr.tabular;
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-17"/>
+ <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+ <classpathentry kind="src" path="src"/>
+ <classpathentry kind="output" path="bin"/>
+</classpath>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>org.argeo.api.cli</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
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+ .
--- /dev/null
+package org.argeo.api.cli;
+
+/** Exception thrown when the provided arguments are not correct. */
+public class CommandArgsException extends IllegalArgumentException {
+ private static final long serialVersionUID = -7271050747105253935L;
+ private String commandName;
+ private volatile CommandsCli commandsCli;
+
+ public CommandArgsException(Exception cause) {
+ super(cause.getMessage(), cause);
+ }
+
+ public CommandArgsException(String message) {
+ super(message);
+ }
+
+ public String getCommandName() {
+ return commandName;
+ }
+
+ public void setCommandName(String commandName) {
+ this.commandName = commandName;
+ }
+
+ public CommandsCli getCommandsCli() {
+ return commandsCli;
+ }
+
+ public void setCommandsCli(CommandsCli commandsCli) {
+ this.commandsCli = commandsCli;
+ }
+
+}
--- /dev/null
+package org.argeo.api.cli;
+
+import java.util.List;
+
+/** {@link RuntimeException} referring during a command run. */
+public class CommandRuntimeException extends RuntimeException {
+ private static final long serialVersionUID = 5595999301269377128L;
+
+ private final DescribedCommand<?> command;
+ private final List<String> arguments;
+
+ public CommandRuntimeException(Throwable e, DescribedCommand<?> command, List<String> arguments) {
+ this(null, e, command, arguments);
+ }
+
+ public CommandRuntimeException(String message, DescribedCommand<?> command, List<String> arguments) {
+ this(message, null, command, arguments);
+ }
+
+ public CommandRuntimeException(String message, Throwable e, DescribedCommand<?> command, List<String> arguments) {
+ super(message == null ? "(" + command.getClass().getName() + " " + arguments.toString() + ")"
+ : message + " (" + command.getClass().getName() + " " + arguments.toString() + ")", e);
+ this.command = command;
+ this.arguments = arguments;
+ }
+
+ public DescribedCommand<?> getCommand() {
+ return command;
+ }
+
+ public List<String> getArguments() {
+ return arguments;
+ }
+
+}
--- /dev/null
+package org.argeo.api.cli;
+
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.function.Function;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.CommandLineParser;
+import org.apache.commons.cli.DefaultParser;
+import org.apache.commons.cli.MissingOptionException;
+import org.apache.commons.cli.Options;
+import org.apache.commons.cli.ParseException;
+
+/** Base class for a CLI managing sub commands. */
+public abstract class CommandsCli implements DescribedCommand<Object> {
+ private final String commandName;
+ private Map<String, Function<List<String>, ?>> commands = new TreeMap<>();
+
+ protected final Options options = new Options();
+
+ public CommandsCli(String commandName) {
+ this.commandName = commandName;
+ }
+
+ @Override
+ public Object apply(List<String> args) {
+ String cmd = null;
+ List<String> newArgs = new ArrayList<>();
+ boolean isHelpOption = false;
+ try {
+ CommandLineParser clParser = new DefaultParser();
+ CommandLine commonCl = clParser.parse(getOptions(), args.toArray(new String[args.size()]), true);
+ List<String> leftOvers = commonCl.getArgList();
+ for (String arg : leftOvers) {
+ if (arg.equals("--" + HelpCommand.HELP_OPTION.getLongOpt())) {
+ isHelpOption = true;
+ // TODO break?
+ }
+
+ if (!arg.startsWith("-") && cmd == null) {
+ cmd = arg;
+ } else {
+ newArgs.add(arg);
+ }
+ }
+ } catch (ParseException e) {
+ CommandArgsException cae = new CommandArgsException(e);
+ throw cae;
+ }
+
+ Function<List<String>, ?> function = cmd != null ? getCommand(cmd) : getDefaultCommand();
+
+ // --help option
+ if (!(function instanceof CommandsCli))
+ if (function instanceof DescribedCommand<?> command)
+ if (isHelpOption) {
+ throw new PrintHelpRequestException(cmd, this);
+// StringWriter out = new StringWriter();
+// HelpCommand.printHelp(command, out);
+// System.out.println(out.toString());
+// return null;
+ }
+
+ if (function == null)
+ throw new IllegalArgumentException("Uknown command " + cmd);
+ try {
+ Object value = function.apply(newArgs);
+ return value != null ? value.toString() : null;
+ } catch (CommandArgsException e) {
+ if (e.getCommandName() == null) {
+ e.setCommandName(cmd);
+ e.setCommandsCli(this);
+ }
+ throw e;
+ } catch (IllegalArgumentException e) {
+ CommandArgsException cae = new CommandArgsException(e);
+ cae.setCommandName(cmd);
+ throw cae;
+ }
+ }
+
+ @Override
+ public Options getOptions() {
+ return options;
+ }
+
+ protected void addCommand(String cmd, Function<List<String>, ?> function) {
+ commands.put(cmd, function);
+
+ }
+
+ @Override
+ public String getUsage() {
+ return "[command]";
+ }
+
+ protected void addCommandsCli(CommandsCli commandsCli) {
+ addCommand(commandsCli.getCommandName(), commandsCli);
+ commandsCli.addCommand(HelpCommand.HELP, new HelpCommand(this, commandsCli));
+ }
+
+ public String getCommandName() {
+ return commandName;
+ }
+
+ public Set<String> getSubCommands() {
+ return commands.keySet();
+ }
+
+ public Function<List<String>, ?> getCommand(String command) {
+ return commands.get(command);
+ }
+
+ public HelpCommand getHelpCommand() {
+ return (HelpCommand) getCommand(HelpCommand.HELP);
+ }
+
+ public Function<List<String>, String> getDefaultCommand() {
+ return getHelpCommand();
+ }
+
+ /** In order to implement quickly a main method. */
+ public static void mainImpl(CommandsCli cli, String[] args) {
+ try {
+ cli.addCommand(HelpCommand.HELP, new HelpCommand(null, cli));
+ Object output = cli.apply(Arrays.asList(args));
+ if (output != null)
+ System.out.println(output);
+ System.exit(0);
+ } catch (PrintHelpRequestException e) {
+ StringWriter out = new StringWriter();
+ HelpCommand.printHelp(e.getCommandsCli(), e.getCommandName(), out);
+ System.out.println(out.toString());
+ } catch (CommandArgsException e) {
+ System.err.println("Wrong arguments " + Arrays.toString(args) + ": " + e.getMessage());
+ Throwable cause = e.getCause();
+ if (!(cause instanceof MissingOptionException))
+ e.printStackTrace();
+ if (e.getCommandName() != null) {
+ StringWriter out = new StringWriter();
+ HelpCommand.printHelp(e.getCommandsCli(), e.getCommandName(), out);
+ System.err.println(out.toString());
+ } else {
+ e.printStackTrace();
+ }
+ System.exit(1);
+ } catch (Exception e) {
+ e.printStackTrace();
+ System.exit(1);
+ }
+ }
+}
--- /dev/null
+package org.argeo.api.cli;
+
+import java.io.StringWriter;
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.Function;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.DefaultParser;
+import org.apache.commons.cli.Options;
+import org.apache.commons.cli.ParseException;
+
+/** A command that can be described. */
+public interface DescribedCommand<T> extends Function<List<String>, T> {
+ default Options getOptions() {
+ return new Options();
+ }
+
+ String getDescription();
+
+ default String getUsage() {
+ return null;
+ }
+
+ default String getExamples() {
+ return null;
+ }
+
+ default CommandLine toCommandLine(List<String> args) {
+ try {
+ DefaultParser parser = new DefaultParser();
+ return parser.parse(getOptions(), args.toArray(new String[args.size()]));
+ } catch (ParseException e) {
+ throw new CommandArgsException(e);
+ }
+ }
+
+ /** In order to implement quickly a main method. */
+ public static void mainImpl(DescribedCommand<?> command, String[] args) {
+ try {
+ Object output = command.apply(Arrays.asList(args));
+ System.out.println(output);
+ System.exit(0);
+ } catch (PrintHelpRequestException e) {
+ StringWriter out = new StringWriter();
+ HelpCommand.printHelp(command, out);
+ System.out.println(out.toString());
+ System.exit(1);
+ } catch (IllegalArgumentException e) {
+ StringWriter out = new StringWriter();
+ HelpCommand.printHelp(command, out);
+ System.err.println(out.toString());
+ System.exit(1);
+ } catch (Exception e) {
+ e.printStackTrace();
+ System.exit(1);
+ }
+ }
+
+}
--- /dev/null
+package org.argeo.api.cli;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.List;
+import java.util.function.Function;
+
+import org.apache.commons.cli.HelpFormatter;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.Options;
+
+/** A special command that can describe {@link DescribedCommand}. */
+public class HelpCommand implements DescribedCommand<String> {
+ /**
+ * System property forcing the root command to this value (typically the name of
+ * a script).
+ */
+ public final static String ROOT_COMMAND_PROPERTY = "org.argeo.api.cli.rootCommand";
+
+ final static String HELP = "help";
+ final static Option HELP_OPTION = Option.builder().longOpt(HELP).desc("print this help").build();
+
+ private CommandsCli commandsCli;
+ private CommandsCli parentCommandsCli;
+
+ // Help formatting
+ private static int helpWidth = 80;
+ private static int helpLeftPad = 4;
+ private static int helpDescPad = 20;
+
+ public HelpCommand(CommandsCli parentCommandsCli, CommandsCli commandsCli) {
+ super();
+ this.parentCommandsCli = parentCommandsCli;
+ this.commandsCli = commandsCli;
+ }
+
+ @Override
+ public String apply(List<String> args) {
+ StringWriter out = new StringWriter();
+
+ if (args.size() == 0) {// overview
+ printHelp(commandsCli, out);
+ } else {
+ String cmd = args.get(0);
+ Function<List<String>, ?> function = commandsCli.getCommand(cmd);
+ if (function == null)
+ return "Command " + cmd + " not found.";
+ Options options;
+ String examples;
+ DescribedCommand<?> command = null;
+ if (function instanceof DescribedCommand) {
+ command = (DescribedCommand<?>) function;
+ options = command.getOptions();
+ examples = command.getExamples();
+ } else {
+ options = new Options();
+ examples = null;
+ }
+ String description = getShortDescription(function);
+ String commandCall = getCommandUsage(cmd, command);
+ HelpFormatter formatter = new HelpFormatter();
+ formatter.printHelp(new PrintWriter(out), helpWidth, commandCall, description, options, helpLeftPad,
+ helpDescPad, examples, false);
+ }
+ return out.toString();
+ }
+
+ private static String getShortDescription(Function<List<String>, ?> function) {
+ if (function instanceof DescribedCommand) {
+ return ((DescribedCommand<?>) function).getDescription();
+ } else {
+ return function.toString();
+ }
+ }
+
+ public String getCommandUsage(String cmd, DescribedCommand<?> command) {
+ String commandCall = getCommandCall(commandsCli) + " " + cmd;
+ assert command != null;
+ if (command != null && command.getUsage() != null) {
+ commandCall = commandCall + " " + command.getUsage();
+ }
+ return commandCall;
+ }
+
+ @Override
+ public String getDescription() {
+ return "Shows this help or describes a command";
+ }
+
+ @Override
+ public String getUsage() {
+ return "[command]";
+ }
+
+ public CommandsCli getParentCommandsCli() {
+ return parentCommandsCli;
+ }
+
+ protected String getCommandCall(CommandsCli commandsCli) {
+ HelpCommand hc = commandsCli.getHelpCommand();
+ if (hc.getParentCommandsCli() != null) {
+ return getCommandCall(hc.getParentCommandsCli()) + " " + commandsCli.getCommandName();
+ } else {
+ String rootCommand = System.getProperty(ROOT_COMMAND_PROPERTY);
+ if (rootCommand != null)
+ return rootCommand;
+ return commandsCli.getCommandName();
+ }
+ }
+
+ public static void printHelp(DescribedCommand<?> command, StringWriter out) {
+ String usage = "java " + command.getClass().getName()
+ + (command.getUsage() != null ? " " + command.getUsage() : "");
+ HelpFormatter formatter = new HelpFormatter();
+ Options options = command.getOptions();
+ options.addOption(HelpCommand.HELP_OPTION);
+ formatter.printHelp(new PrintWriter(out), helpWidth, usage, command.getDescription(), options, helpLeftPad,
+ helpDescPad, command.getExamples(), false);
+
+ }
+
+ public static void printHelp(CommandsCli commandsCli, String commandName, StringWriter out) {
+ if (commandName == null) {
+ printHelp(commandsCli, out);
+ return;
+ }
+ DescribedCommand<?> command = (DescribedCommand<?>) commandsCli.getCommand(commandName);
+ String usage = commandsCli.getHelpCommand().getCommandUsage(commandName, command);
+ HelpFormatter formatter = new HelpFormatter();
+ Options options = command.getOptions();
+ options.addOption(HelpCommand.HELP_OPTION);
+ formatter.printHelp(new PrintWriter(out), helpWidth, usage, command.getDescription(), options, helpLeftPad,
+ helpDescPad, command.getExamples(), false);
+
+ }
+
+ public static void printHelp(CommandsCli commandsCli, StringWriter out) {
+ out.append(commandsCli.getDescription()).append('\n');
+ String leftPad = spaces(helpLeftPad);
+ for (String cmd : commandsCli.getSubCommands()) {
+ Function<List<String>, ?> function = commandsCli.getCommand(cmd);
+ assert function != null;
+ out.append(leftPad);
+ out.append(cmd);
+ // TODO deal with long commands
+ out.append(spaces(helpDescPad - cmd.length()));
+ out.append(getShortDescription(function));
+ out.append('\n');
+ }
+ }
+
+ private static String spaces(int count) {
+ // Java 11
+ // return " ".repeat(count);
+ if (count <= 0)
+ return "";
+ else {
+ StringBuilder sb = new StringBuilder(count);
+ for (int i = 0; i < count; i++)
+ sb.append(' ');
+ return sb.toString();
+ }
+ }
+}
--- /dev/null
+package org.argeo.api.cli;
+
+/** An exception indicating that help should be printed. */
+class PrintHelpRequestException extends RuntimeException {
+
+ private static final long serialVersionUID = -9029122270660656639L;
+
+ private String commandName;
+ private volatile CommandsCli commandsCli;
+
+ public PrintHelpRequestException(String commandName, CommandsCli commandsCli) {
+ super();
+ this.commandName = commandName;
+ this.commandsCli = commandsCli;
+ }
+
+ public static long getSerialversionuid() {
+ return serialVersionUID;
+ }
+
+ public String getCommandName() {
+ return commandName;
+ }
+
+ public CommandsCli getCommandsCli() {
+ return commandsCli;
+ }
+
+}
--- /dev/null
+/** Command line API. */
+package org.argeo.api.cli;
\ No newline at end of file
<?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.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-17"/>
<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
<classpathentry kind="src" path="src"/>
<classpathentry kind="output" path="bin"/>
Import-Package: \
-javax.security.*
+javax.transaction.xa,\
+javax.security.*,\
+org.osgi.service.useradmin,\
Export-Package: org.argeo.api.cms.*
\ No newline at end of file
+++ /dev/null
-package org.argeo.api.cms;
-
-/** A 2D size. */
-public class Cms2DSize {
- private Integer width;
- private Integer height;
-
- public Cms2DSize() {
-
- }
-
- public Cms2DSize(Integer width, Integer height) {
- super();
- this.width = width;
- this.height = height;
- }
-
- public Integer getWidth() {
- return width;
- }
-
- public void setWidth(Integer width) {
- this.width = width;
- }
-
- public Integer getHeight() {
- return height;
- }
-
- public void setHeight(Integer height) {
- this.height = height;
- }
-
-}
package org.argeo.api.cms;
+import java.util.Map;
import java.util.Set;
+import org.argeo.api.cms.ux.CmsTheme;
+import org.argeo.api.cms.ux.CmsUi;
+
/** An extensible user interface base on the CMS backend. */
-public interface CmsApp {
+public interface CmsApp extends CmsEventSubscriber {
/**
* If {@link CmsUi#setData(String, Object)} is set with this property, it
* indicates a different UI (typically with another theming. The {@link CmsApp}
*/
final static String UI_NAME_PROPERTY = CmsApp.class.getName() + ".ui.name";
+ final static String CONTEXT_NAME_PROPERTY = "argeo.cms.app.contextName";
+
Set<String> getUiNames();
CmsUi initUi(Object uiParent);
void addCmsAppListener(CmsAppListener listener);
void removeCmsAppListener(CmsAppListener listener);
+
+ CmsContext getCmsContext();
+
+ @Override
+ default void onEvent(String topic, Map<String, Object> properties) {
+ }
+
+
}
package org.argeo.api.cms;
+import javax.security.auth.Subject;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;
return new LoginContext(getLoginContextName(), callbackHandler);
}
+ public LoginContext newLoginContext(Subject subject, CallbackHandler callbackHandler) throws LoginException {
+ return new LoginContext(getLoginContextName(), subject, callbackHandler);
+ }
+
+ public LoginContext newLoginContext(Subject subject) throws LoginException {
+ return new LoginContext(getLoginContextName(), subject);
+ }
+
+ public LoginContext newLoginContext() throws LoginException {
+ return new LoginContext(getLoginContextName());
+ }
+
/*
* LOGIN CONTEXTS
*/
String GUESTS_WORKSPACE = "guests";
String PUBLIC_WORKSPACE = "public";
String SECURITY_WORKSPACE = "security";
+ String MIGRATION_WORKSPACE = "migration";
+
+ /*
+ * ACR CONVENTIONS
+ */
+ String SRV_BASE = "/srv";
/*
* BASE DNs
/*
* RESERVED ROLES
*/
- String ROLES_BASEDN = "ou=roles,ou=node";
+ String NODE_BASEDN = "ou=node";
+ String SYSTEM_ROLES_BASEDN = "ou=roles," + NODE_BASEDN;
String TOKENS_BASEDN = "ou=tokens,ou=node";
- String ROLE_ADMIN = "cn=admin," + ROLES_BASEDN;
- String ROLE_USER_ADMIN = "cn=userAdmin," + ROLES_BASEDN;
- String ROLE_DATA_ADMIN = "cn=dataAdmin," + ROLES_BASEDN;
+ String ROLE_ADMIN = "cn=admin," + SYSTEM_ROLES_BASEDN;
+ String ROLE_USER_ADMIN = "cn=userAdmin," + SYSTEM_ROLES_BASEDN;
+ String ROLE_DATA_ADMIN = "cn=dataAdmin," + SYSTEM_ROLES_BASEDN;
// Special system groups that cannot be edited:
// user U anonymous = everyone
- String ROLE_USER = "cn=user," + ROLES_BASEDN;
- String ROLE_ANONYMOUS = "cn=anonymous," + ROLES_BASEDN;
+ String ROLE_USER = "cn=user," + SYSTEM_ROLES_BASEDN;
+ String ROLE_ANONYMOUS = "cn=anonymous," + SYSTEM_ROLES_BASEDN;
// Account lifecycle
- String ROLE_REGISTERING = "cn=registering," + ROLES_BASEDN;
+ String ROLE_REGISTERING = "cn=registering," + SYSTEM_ROLES_BASEDN;
/*
* PATHS
String PATH_JCR = "/jcr";
String PATH_FILES = "/files";
// String PATH_JCR_PUB = "/pub";
+ String PATH_API_ACR = "/api/acr";
/*
* FILE SYSTEMS
String NODE_SERVICE = NODE;
/*
- * INIT FRAMEWORK PROPERTIES
+ * COMPONENT PROPERTIES
*/
- String NODE_INIT = "argeo.node.init";
- String I18N_DEFAULT_LOCALE = "argeo.i18n.defaultLocale";
- String I18N_LOCALES = "argeo.i18n.locales";
- // Node Security
- String ROLES_URI = "argeo.node.roles.uri";
- String TOKENS_URI = "argeo.node.tokens.uri";
- /** URI to an LDIF file or LDAP server used as initialization or backend */
- String USERADMIN_URIS = "argeo.node.useradmin.uris";
- // Transaction manager
- String TRANSACTION_MANAGER = "argeo.node.transaction.manager";
- String TRANSACTION_MANAGER_SIMPLE = "simple";
- String TRANSACTION_MANAGER_BITRONIX = "bitronix";
- // Node
- /** Properties configuring the node repository */
- String NODE_REPO_PROP_PREFIX = "argeo.node.repo.";
- /** Additional standalone repositories, related to data models. */
- String NODE_REPOS_PROP_PREFIX = "argeo.node.repos.";
- // HTTP
- String HTTP_PORT = "org.osgi.service.http.port";
- String HTTP_PORT_SECURE = "org.osgi.service.http.port.secure";
- /**
- * The HTTP header used to convey the DN of a client verified by a reverse
- * proxy. Typically SSL_CLIENT_S_DN for Apache.
+ String CONTEXT_PATH = "context.path";
+ String CONTEXT_PUBLIC = "context.public";
+ String EVENT_TOPICS = "event.topics";
+ String ACR_MOUNT_PATH = "acr.mount.path";
+
+ /*
+ * FILE SYSTEM
*/
- String HTTP_PROXY_SSL_DN = "argeo.http.proxy.ssl.dn";
+ String CMS_FS_SCHEME = "cms";
/*
* PIDs
import java.util.List;
import java.util.Locale;
+import java.util.UUID;
+
+import javax.security.auth.Subject;
/**
* A logical view on this CMS instance, independently of a particular launch or
Long getAvailableSince();
-
/** Mark this group as a workgroup */
void createWorkgroup(String groupDn);
+
+ /** Get the CMS session of this subject. */
+ CmsSession getCmsSession(Subject subject);
+
+ CmsEventBus getCmsEventBus();
+
+ /** A new time based {@link UUID} (v1) using the current time */
+ UUID timeUUID();
+
}
package org.argeo.api.cms;
-import java.util.Dictionary;
-
/** A configured node deployment. */
public interface CmsDeployment {
- void addFactoryDeployConfig(String factoryPid, Dictionary<String, Object> props);
-
- Dictionary<String, Object> getProps(String factoryPid, String cn);
+// void addFactoryDeployConfig(String factoryPid, Dictionary<String, Object> props);
+//
+// Dictionary<String, Object> getProps(String factoryPid, String cn);
}
+++ /dev/null
-package org.argeo.api.cms;
-
-/** Abstraction of a simple edition life cycle. */
-public interface CmsEditable {
-
- /** Whether the calling thread can edit, the value is immutable */
- public Boolean canEdit();
-
- public Boolean isEditing();
-
- public void startEditing();
-
- public void stopEditing();
-
- public static CmsEditable NON_EDITABLE = new CmsEditable() {
-
- @Override
- public void stopEditing() {
- }
-
- @Override
- public void startEditing() {
- }
-
- @Override
- public Boolean isEditing() {
- return false;
- }
-
- @Override
- public Boolean canEdit() {
- return false;
- }
- };
-
- public static CmsEditable ALWAYS_EDITING = new CmsEditable() {
-
- @Override
- public void stopEditing() {
- }
-
- @Override
- public void startEditing() {
- }
-
- @Override
- public Boolean isEditing() {
- return true;
- }
-
- @Override
- public Boolean canEdit() {
- return true;
- }
- };
-
-}
package org.argeo.api.cms;
+import org.argeo.api.cms.ux.CmsView;
+
/**
* Can be applied to {@link Enum}s in order to define events used by
* {@link CmsView#sendEvent(String, java.util.Map)}.
String name();
default String topic() {
- return getTopicBase() + "/" + name();
+ return getTopicBase() + "." + name();
}
- default String getTopicBase() {
- return "argeo/cms";
+ default String getTopicBase() {
+ return "argeo.cms";
}
-
}
--- /dev/null
+package org.argeo.api.cms;
+
+import java.util.Map;
+
+public interface CmsEventBus {
+ void sendEvent(String topic, Map<String, Object> event);
+
+ void addEventSubscriber(String topic, CmsEventSubscriber eventSubscriber);
+
+ void removeEventSubscriber(String topic, CmsEventSubscriber eventSubscriber);
+
+}
--- /dev/null
+package org.argeo.api.cms;
+
+import java.util.Map;
+
+public interface CmsEventSubscriber {
+
+ void onEvent(String topic, Map<String, Object> properties);
+}
+++ /dev/null
-package org.argeo.api.cms;
-
-import java.io.InputStream;
-
-/** Read and write access to images. */
-public interface CmsImageManager<V, M> {
- /** Load image in control */
- public Boolean load(M node, V control, Cms2DSize size);
-
- /** @return (0,0) if not available */
- public Cms2DSize getImageSize(M node);
-
- /**
- * The related <img> tag, with src, width and height set.
- *
- * @return null if not available
- */
- public String getImageTag(M node);
-
- /**
- * The related <img> tag, with url, width and height set. Caller must
- * close the tag (or add additional attributes).
- *
- * @return null if not available
- */
- public StringBuilder getImageTagBuilder(M node, Cms2DSize size);
-
- /**
- * Returns the remotely accessible URL of the image (registering it if
- * needed) @return null if not available
- */
- public String getImageUrl(M node);
-
-// public Binary getImageBinary(Node node) throws RepositoryException;
-
-// public Image getSwtImage(Node node) throws RepositoryException;
-
- /** @return URL */
- public String uploadImage(M context, M uploadFolder, String fileName, InputStream in, String contentType);
-
- @Deprecated
- default String uploadImage(M uploadFolder, String fileName, InputStream in) {
- System.err.println("Context must be provided to " + CmsImageManager.class.getName());
- return uploadImage(null, uploadFolder, fileName, in, null);
- }
-}
package org.argeo.api.cms;
import java.lang.System.Logger;
-import java.lang.System.Logger.Level;
+import java.text.MessageFormat;
import java.util.Objects;
import java.util.function.Supplier;
/**
- * A Commons Logging / SLF4J style logging utilities wrapping a standard Java
- * platform {@link Logger}.
+ * A Commons Logging / SLF4J style logging utilities usually wrapping a standard
+ * Java platform {@link Logger}, but which can fallback to other mechanism, if a
+ * system logger is not available.
*/
public interface CmsLog {
- Logger getLogger();
+ /*
+ * SYSTEM LOGGER STYLE METHODS
+ */
+ boolean isLoggable(Level level);
+
+ void log(Level level, Supplier<String> msgSupplier, Throwable thrown);
+
+ void log(Level level, String msg, Throwable thrown);
+
+ void log(Level level, String format, Object... params);
+
+ default void log(Level level, String msg) {
+ log(level, msg, (Throwable) null);
+ }
+
+ default void log(Level level, Supplier<String> msgSupplier) {
+ log(level, msgSupplier, (Throwable) null);
+ }
+
+ default void log(Level level, Object obj) {
+ Objects.requireNonNull(obj);
+ log(level, obj.toString());
+ }
+
+ /*
+ * SLF4j / COMMONS LOGGING STYLE METHODS
+ */
+ @Deprecated
+ CmsLog getLogger();
default boolean isDebugEnabled() {
return getLogger().isLoggable(Level.DEBUG);
getLogger().log(Level.ERROR, format, arguments);
}
+ /**
+ * Exact mapping of ${java.lang.System.Logger.Level}, in case it is not
+ * available.
+ */
+ public static enum Level {
+ ALL(Integer.MIN_VALUE), //
+ TRACE(400), //
+ DEBUG(500), //
+ INFO(800), //
+ WARNING(900), //
+ ERROR(1000), //
+ OFF(Integer.MAX_VALUE); //
+
+ final int severity;
+
+ private Level(int severity) {
+ this.severity = severity;
+ }
+
+ public final int getSeverity() {
+ return severity;
+ }
+ }
+
/*
* STATIC UTILITIES
*/
}
static CmsLog getLog(String name) {
- Logger logger = System.getLogger(Objects.requireNonNull(name));
- return new LoggerWrapper(logger);
+ if (isSystemLoggerAvailable) {
+ return new SystemCmsLog(name);
+ } else { // typically Android
+ return new FallBackCmsLog();
+ }
}
- /** A trivial implementation wrapping a platform logger. */
- static class LoggerWrapper implements CmsLog {
- private final Logger logger;
+ static final boolean isSystemLoggerAvailable = isSystemLoggerAvailable();
- LoggerWrapper(Logger logger) {
- this.logger = logger;
+ static boolean isSystemLoggerAvailable() {
+ try {
+ Logger logger = System.getLogger(CmsLog.class.getName());
+ logger.log(java.lang.System.Logger.Level.TRACE, () -> "System logger is available.");
+ return true;
+ } catch (NoSuchMethodError | NoClassDefFoundError e) {// Android
+ return false;
}
+ }
+}
- @Override
- public Logger getLogger() {
- return logger;
+/**
+ * Uses {@link System.Logger}, should be used on proper implementations of the
+ * Java platform.
+ */
+class SystemCmsLog implements CmsLog {
+ private final Logger logger;
+
+ SystemCmsLog(String name) {
+ logger = System.getLogger(name);
+ }
+
+ @Override
+ public boolean isLoggable(Level level) {
+ return logger.isLoggable(convertSystemLevel(level));
+ }
+
+ @Override
+ public void log(Level level, Supplier<String> msgSupplier, Throwable thrown) {
+ logger.log(convertSystemLevel(level), msgSupplier, thrown);
+ }
+
+ @Override
+ public void log(Level level, String msg, Throwable thrown) {
+ logger.log(convertSystemLevel(level), msg, thrown);
+ }
+
+ java.lang.System.Logger.Level convertSystemLevel(Level level) {
+ switch (level.severity) {
+ case Integer.MIN_VALUE:
+ return java.lang.System.Logger.Level.ALL;
+ case 400:
+ return java.lang.System.Logger.Level.TRACE;
+ case 500:
+ return java.lang.System.Logger.Level.DEBUG;
+ case 800:
+ return java.lang.System.Logger.Level.INFO;
+ case 900:
+ return java.lang.System.Logger.Level.WARNING;
+ case 1000:
+ return java.lang.System.Logger.Level.ERROR;
+ case Integer.MAX_VALUE:
+ return java.lang.System.Logger.Level.OFF;
+ default:
+ throw new IllegalArgumentException("Unexpected value: " + level.severity);
}
+ }
+
+ @Override
+ public void log(Level level, String format, Object... params) {
+ logger.log(convertSystemLevel(level), format, params);
+ }
+ @Override
+ public CmsLog getLogger() {
+ return this;
}
+};
+/** Dummy fallback for non-standard platforms such as Android. */
+class FallBackCmsLog implements CmsLog {
+ @Override
+ public boolean isLoggable(Level level) {
+ return level.getSeverity() >= 800;// INFO and higher
+ }
+
+ @Override
+ public void log(Level level, Supplier<String> msgSupplier, Throwable thrown) {
+ if (isLoggable(level))
+ if (thrown != null || level.getSeverity() >= 900) {
+ System.err.println(msgSupplier.get());
+ thrown.printStackTrace();
+ } else {
+ System.out.println(msgSupplier.get());
+ }
+ }
+
+ @Override
+ public void log(Level level, String msg, Throwable thrown) {
+ if (isLoggable(level))
+ if (thrown != null || level.getSeverity() >= 900) {
+ System.err.println(msg);
+ thrown.printStackTrace();
+ } else {
+ System.out.println(msg);
+ }
+ }
+
+ @Override
+ public void log(Level level, String format, Object... params) {
+ if (format == null)
+ return;
+ String msg = MessageFormat.format(format, params);
+ log(level, msg);
+ }
+
+ @Override
+ public CmsLog getLogger() {
+ return this;
+ }
}
import java.util.UUID;
import java.util.function.Consumer;
-import javax.naming.ldap.LdapName;
import javax.security.auth.Subject;
/** An authenticated user session. */
String getUserRole();
- LdapName getUserDn();
+ String getUserDn();
String getLocalId();
void registerView(String uid, Object view);
void addOnCloseCallback(Consumer<CmsSession> onClose);
+
+ public static boolean hasCmsSession(Subject subject) {
+ return !subject.getPrivateCredentials(CmsSessionId.class).isEmpty();
+ }
}
package org.argeo.api.cms;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.UUID;
+
/** A running node process. */
public interface CmsState {
String getHostname();
Long getAvailableSince();
+ UUID getUuid();
+
+ String getDeployProperty(String property);
+
+ /**
+ * A list of size of the max count for this property, with null values when the
+ * property is not set, or an empty list (size 0) if this property is unknown.
+ */
+ List<String> getDeployProperties(String property);
+
+ Path getDataPath(String relativePath);
}
+++ /dev/null
-package org.argeo.api.cms;
-
-/** Can be applied to {@link Enum}s in order to generate (CSS) class names. */
-public interface CmsStyle {
- String name();
-
- /** @deprecated use {@link #style()} instead. */
- @Deprecated
- default String toStyleClass() {
- return style();
- }
-
- default String style() {
- String classPrefix = getClassPrefix();
- return "".equals(classPrefix) ? name() : classPrefix + "-" + name();
- }
-
- default String getClassPrefix() {
- return "";
- }
-
-}
+++ /dev/null
-package org.argeo.api.cms;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.Set;
-
-/** A CMS theme which can be applied to web apps as well as desktop apps. */
-public interface CmsTheme {
- /** Unique ID of this theme. */
- String getThemeId();
-
- /**
- * Load a resource as an input stream, base don its relative path, or
- * <code>null</code> if not found
- */
- InputStream getResourceAsStream(String resourceName) throws IOException;
-
- /** Relative paths to standard web CSS. */
- Set<String> getWebCssPaths();
-
- /** Relative paths to RAP specific CSS. */
- Set<String> getRapCssPaths();
-
- /** Relative paths to SWT specific CSS. */
- Set<String> getSwtCssPaths();
-
- /** Relative paths to images such as icons. */
- Set<String> getImagesPaths();
-
- /** Relative paths to fonts. */
- Set<String> getFontsPaths();
-
- /** Tags to be added to the header section of the HTML page. */
- String getHtmlHeaders();
-
- /** The HTML body to use. */
- String getBodyHtml();
-
- /** The default icon size (typically the smallest). */
- Integer getDefaultIconSize();
-
- /** Loads one of the relative path provided by the other methods. */
- InputStream loadPath(String path) throws IOException;
-
-}
+++ /dev/null
-package org.argeo.api.cms;
-
-public interface CmsUi {
- Object getData(String key);
- void setData(String key, Object value);
-
-}
+++ /dev/null
-package org.argeo.api.cms;
-
-import java.security.PrivilegedAction;
-import java.util.HashMap;
-import java.util.Map;
-
-import javax.security.auth.login.LoginContext;
-
-/** Provides interaction with the CMS system. */
-public interface CmsView {
- final static String CMS_VIEW_UID_PROPERTY = "argeo.cms.view.uid";
- // String KEY = "org.argeo.cms.ui.view";
-
- String getUid();
-
- UxContext getUxContext();
-
- // NAVIGATION
- void navigateTo(String state);
-
- // SECURITY
- void authChange(LoginContext loginContext);
-
- void logout();
-
- // void registerCallbackHandler(CallbackHandler callbackHandler);
-
- // SERVICES
- void exception(Throwable e);
-
- CmsImageManager<?, ?> getImageManager();
-
- boolean isAnonymous();
-
- /**
- * Send an event to this topic. Does nothing by default., but if implemented it
- * MUST set the {@link #CMS_VIEW_UID_PROPERTY} in the properties.
- */
- default void sendEvent(String topic, Map<String, Object> properties) {
-
- }
-
- /**
- * Convenience methods for when {@link #sendEvent(String, Map)} only requires
- * one single parameter.
- */
- default void sendEvent(String topic, String param, Object value) {
- Map<String, Object> properties = new HashMap<>();
- properties.put(param, value);
- sendEvent(topic, properties);
- }
-
- default void applyStyles(Object widget) {
-
- }
-
- default <T> T doAs(PrivilegedAction<T> action) {
- throw new UnsupportedOperationException();
- }
-
- default Void runAs(Runnable runnable) {
- return doAs(new PrivilegedAction<Void>() {
-
- @Override
- public Void run() {
- if (runnable != null)
- runnable.run();
- return null;
- }
- });
- }
-
- default void stateChanged(String state, String title) {
- }
-
- default CmsSession getCmsSession() {
- throw new UnsupportedOperationException();
- }
-
- default Object getData(String key) {
- throw new UnsupportedOperationException();
- }
-
- @SuppressWarnings("unchecked")
- default <T> T getUiContext(Class<T> clss) {
- return (T) getData(clss.getName());
- }
-
- default <T> void setUiContext(Class<T> clss, T instance) {
- setData(clss.getName(), instance);
- }
-
- default void setData(String key, Object value) {
- throw new UnsupportedOperationException();
- }
-
-}
import java.security.Principal;
+import javax.security.auth.Subject;
+
/** Allows to modify any data. */
public final class DataAdminPrincipal implements Principal {
private final String name = CmsConstants.ROLE_DATA_ADMIN;
return name.toString();
}
+ public static boolean isDataAdmin(Subject subject) {
+ return !subject.getPrincipals(DataAdminPrincipal.class).isEmpty();
+ }
}
+++ /dev/null
-package org.argeo.api.cms;
-
-import java.util.function.BiFunction;
-
-/**
- * Stateless UI part creator. Takes a parent view (V) and a model context (M) in
- * order to create a view part (W) which can then be further configured. Such
- * object can be used as services and reference other part of the model which
- * are relevant for all created UI part.
- */
-@FunctionalInterface
-public interface MvcProvider<V, M, W> extends BiFunction<V, M, W> {
- W createUiPart(V parent, M context);
-
- /**
- * Whether this parent view is supported.
- *
- * @return true by default.
- */
- default boolean isViewSupported(V parent) {
- return true;
- }
-
- /**
- * Whether this context is supported.
- *
- * @return true by default.
- */
- default boolean isModelSupported(M context) {
- return true;
- }
-
- default W apply(V parent, M context) {
- if (!isViewSupported(parent))
- throw new IllegalArgumentException("Parent view " + parent + "is not supported.");
- if (!isModelSupported(context))
- throw new IllegalArgumentException("Model context " + context + "is not supported.");
- return createUiPart(parent, context);
- }
-
- default W createUiPart(V parent) {
- return createUiPart(parent, null);
- }
-}
+++ /dev/null
-package org.argeo.api.cms;
-
-public interface UxContext {
- boolean isPortrait();
-
- boolean isLandscape();
-
- boolean isSquare();
-
- boolean isSmall();
-
- /**
- * Is a production environment (must be false by default, and be explicitly
- * set during the CMS deployment). When false, it can activate additional UI
- * capabilities in order to facilitate QA.
- */
- boolean isMasterData();
-}
--- /dev/null
+package org.argeo.api.cms.directory;
+
+import org.osgi.service.useradmin.Authorization;
+
+/** An authorisation to a CMS system. */
+public interface CmsAuthorization extends Authorization {
+ /** The role which did imply this role, <code>null</code> if a direct role. */
+ default String getImplyingRole(String role) {
+ return null;
+ }
+}
--- /dev/null
+package org.argeo.api.cms.directory;
+
+import java.util.Optional;
+
+import org.argeo.api.cms.transaction.WorkControl;
+
+/** An information directory (typically LDAP). */
+public interface CmsDirectory extends HierarchyUnit {
+ String getName();
+
+ /** Whether this directory is read only. */
+ boolean isReadOnly();
+
+ /** Whether this directory is disabled. */
+ boolean isDisabled();
+
+ /** The realm (typically Kerberos) of this directory. */
+ Optional<String> getRealm();
+
+ /** Sets the transaction control used by this directory when editing. */
+ void setTransactionControl(WorkControl transactionControl);
+
+ /*
+ * HIERARCHY
+ */
+
+ /** The hierarchy unit at this path. */
+ HierarchyUnit getHierarchyUnit(String path);
+
+ /** Create a new hierarchy unit. */
+ HierarchyUnit createHierarchyUnit(String path);
+}
--- /dev/null
+package org.argeo.api.cms.directory;
+
+import org.osgi.service.useradmin.Group;
+
+/** A group in a user directroy. */
+public interface CmsGroup extends Group, CmsUser {
+// List<LdapName> getMemberNames();
+}
--- /dev/null
+package org.argeo.api.cms.directory;
+
+import org.osgi.service.useradmin.User;
+
+/**
+ * An entity with credentials which can log in to a system. Can be a real person
+ * or not.
+ */
+public interface CmsUser extends User {
+}
--- /dev/null
+package org.argeo.api.cms.directory;
+
+import java.time.ZonedDateTime;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.security.auth.Subject;
+import javax.xml.namespace.QName;
+
+import org.osgi.service.useradmin.Role;
+import org.osgi.service.useradmin.User;
+
+/**
+ * Provide method interfaces to manage user concepts without accessing directly
+ * the userAdmin.
+ */
+public interface CmsUserManager {
+ Map<String, String> getKnownBaseDns(boolean onlyWritable);
+
+ Set<UserDirectory> getUserDirectories();
+
+ // CurrentUser
+ /** Returns the e-mail of the current logged in user */
+ String getMyMail();
+
+ // Other users
+ /** Returns a {@link User} given a username */
+ CmsUser getUser(String username);
+
+ /** Can be a group or a user */
+ String getUserDisplayName(String dn);
+
+ /** Can be a group or a user */
+ String getUserMail(String dn);
+
+ /** Lists all roles of the given user */
+ String[] getUserRoles(String dn);
+
+ /** Checks if the passed user belongs to the passed role */
+ boolean isUserInRole(String userDn, String roleDn);
+
+ // Search
+ /** Returns a filtered list of roles */
+ Role[] getRoles(String filter);
+
+ /** Recursively lists users in a given group. */
+ Set<CmsUser> listUsersInGroup(String groupDn, String filter);
+
+ /** Search among groups including system roles and users if needed */
+ List<CmsUser> listGroups(String filter, boolean includeUsers, boolean includeSystemRoles);
+
+// /**
+// * Lists functional accounts, that is users with regular access to the system
+// * under this functional hierarchy unit (which probably have technical direct
+// * sub hierarchy units), excluding groups which are not explicitly users.
+// */
+// Set<User> listAccounts(HierarchyUnit hierarchyUnit, boolean deep);
+
+ /*
+ * EDITION
+ */
+ /** Creates a new user. */
+ CmsUser createUser(String username, Map<String, Object> properties, Map<String, Object> credentials);
+
+ /** Created a new group. */
+ CmsGroup createGroup(String dn);
+
+ /** Creates a group. */
+ CmsGroup getOrCreateGroup(HierarchyUnit groups, String commonName);
+
+ /** Creates a new system role. */
+ CmsGroup getOrCreateSystemRole(HierarchyUnit roles, QName systemRole);
+
+ /** Add additional object classes to this role. */
+ void addObjectClasses(Role role, Set<String> objectClasses, Map<String, Object> additionalProperties);
+
+ /** Add additional object classes to this hierarchy unit. */
+ void addObjectClasses(HierarchyUnit hierarchyUnit, Set<String> objectClasses,
+ Map<String, Object> additionalProperties);
+
+ /** Add a member to this group. */
+ void addMember(CmsGroup group, Role role);
+
+ /** Remove a member from this group. */
+ void removeMember(CmsGroup group, Role role);
+
+ void edit(Runnable action);
+
+ /* MISCELLANEOUS */
+ /** Returns the dn of a role given its local ID */
+ String buildDefaultDN(String localId, int type);
+
+ /** Exposes the main default domain name for this instance */
+ String getDefaultDomainName();
+
+ /**
+ * Search for a {@link User} (might also be a group) whose uid or cn is equals
+ * to localId within the various user repositories defined in the current
+ * context.
+ */
+ CmsUser getUserFromLocalId(String localId);
+
+ void changeOwnPassword(char[] oldPassword, char[] newPassword);
+
+ void resetPassword(String username, char[] newPassword);
+
+ @Deprecated
+ String addSharedSecret(String username, int hours);
+
+// String addSharedSecret(String username, String authInfo, String authToken);
+
+ void addAuthToken(String userDn, String token, Integer hours, String... roles);
+
+ void addAuthToken(String userDn, String token, ZonedDateTime expiryDate, String... roles);
+
+ void expireAuthToken(String token);
+
+ void expireAuthTokens(Subject subject);
+
+ UserDirectory getDirectory(Role role);
+
+ /** Create a new hierarchy unit. Does nothing if it already exists. */
+ HierarchyUnit getOrCreateHierarchyUnit(UserDirectory directory, String path);
+}
\ No newline at end of file
--- /dev/null
+package org.argeo.api.cms.directory;
+
+import java.math.BigInteger;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.KeySpec;
+import java.util.Arrays;
+
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.PBEKeySpec;
+
+/** Utilities around digests, mostly those related to passwords. */
+public class DirectoryDigestUtils {
+ public final static String PASSWORD_SCHEME_SHA = "SHA";
+ public final static String PASSWORD_SCHEME_PBKDF2_SHA256 = "PBKDF2_SHA256";
+
+ public static byte[] sha1(byte[] bytes) {
+ try {
+ MessageDigest digest = MessageDigest.getInstance("SHA1");
+ digest.update(bytes);
+ byte[] checksum = digest.digest();
+ return checksum;
+ } catch (NoSuchAlgorithmException e) {
+ throw new IllegalStateException("Cannot SHA1 digest", e);
+ }
+ }
+
+ public static byte[] toPasswordScheme(String passwordScheme, char[] password, byte[] salt, Integer iterations,
+ Integer keyLength) {
+ try {
+ if (PASSWORD_SCHEME_SHA.equals(passwordScheme)) {
+ MessageDigest digest = MessageDigest.getInstance("SHA1");
+ byte[] bytes = charsToBytes(password);
+ digest.update(bytes);
+ return digest.digest();
+ } else if (PASSWORD_SCHEME_PBKDF2_SHA256.equals(passwordScheme)) {
+ KeySpec spec = new PBEKeySpec(password, salt, iterations, keyLength);
+
+ SecretKeyFactory f = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
+ final int ITERATION_LENGTH = 4;
+ byte[] key = f.generateSecret(spec).getEncoded();
+ byte[] result = new byte[ITERATION_LENGTH + salt.length + key.length];
+ byte iterationsArr[] = new BigInteger(iterations.toString()).toByteArray();
+ if (iterationsArr.length < ITERATION_LENGTH) {
+ Arrays.fill(result, 0, ITERATION_LENGTH - iterationsArr.length, (byte) 0);
+ System.arraycopy(iterationsArr, 0, result, ITERATION_LENGTH - iterationsArr.length,
+ iterationsArr.length);
+ } else {
+ System.arraycopy(iterationsArr, 0, result, 0, ITERATION_LENGTH);
+ }
+ System.arraycopy(salt, 0, result, ITERATION_LENGTH, salt.length);
+ System.arraycopy(key, 0, result, ITERATION_LENGTH + salt.length, key.length);
+ return result;
+ } else {
+ throw new UnsupportedOperationException("Unkown password scheme " + passwordScheme);
+ }
+ } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
+ throw new IllegalStateException("Cannot digest", e);
+ }
+ }
+
+ public static char[] bytesToChars(Object obj) {
+ if (obj instanceof char[])
+ return (char[]) obj;
+ if (!(obj instanceof byte[]))
+ throw new IllegalArgumentException(obj.getClass() + " is not a byte array");
+ ByteBuffer fromBuffer = ByteBuffer.wrap((byte[]) obj);
+ CharBuffer toBuffer = StandardCharsets.UTF_8.decode(fromBuffer);
+ char[] res = Arrays.copyOfRange(toBuffer.array(), toBuffer.position(), toBuffer.limit());
+ // Arrays.fill(fromBuffer.array(), (byte) 0); // clear sensitive data
+ // Arrays.fill((byte[]) obj, (byte) 0); // clear sensitive data
+ // Arrays.fill(toBuffer.array(), '\u0000'); // clear sensitive data
+ return res;
+ }
+
+ public static byte[] charsToBytes(char[] chars) {
+ CharBuffer charBuffer = CharBuffer.wrap(chars);
+ ByteBuffer byteBuffer = StandardCharsets.UTF_8.encode(charBuffer);
+ byte[] bytes = Arrays.copyOfRange(byteBuffer.array(), byteBuffer.position(), byteBuffer.limit());
+ // Arrays.fill(charBuffer.array(), '\u0000'); // clear sensitive data
+ // Arrays.fill(byteBuffer.array(), (byte) 0); // clear sensitive data
+ return bytes;
+ }
+
+ public static String sha1str(String str) {
+ byte[] hash = sha1(str.getBytes(StandardCharsets.UTF_8));
+ return encodeHexString(hash);
+ }
+
+ final private static char[] hexArray = "0123456789abcdef".toCharArray();
+
+ /**
+ * From
+ * http://stackoverflow.com/questions/9655181/how-to-convert-a-byte-array-to
+ * -a-hex-string-in-java
+ */
+ public static String encodeHexString(byte[] bytes) {
+ 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);
+ }
+
+ /** singleton */
+ private DirectoryDigestUtils() {
+ }
+}
--- /dev/null
+package org.argeo.api.cms.directory;
+
+import java.util.Dictionary;
+import java.util.Locale;
+
+/** A unit within the high-level organisational structure of a directory. */
+public interface HierarchyUnit {
+ /** Name to use in paths. */
+ String getHierarchyUnitName();
+
+ /** Name to use in UI. */
+ String getHierarchyUnitLabel(Locale locale);
+
+ /**
+ * The parent {@link HierarchyUnit}, or <code>null</code> if a
+ * {@link CmsDirectory}.
+ */
+ HierarchyUnit getParent();
+
+ /** Direct children {@link HierarchyUnit}s. */
+ Iterable<HierarchyUnit> getDirectHierarchyUnits(boolean functionalOnly);
+
+ /**
+ * Whether this is an arbitrary named and placed {@link HierarchyUnit}.
+ *
+ * @return <code>true</code> if functional, <code>false</code> is technical
+ * (e.g. People, Groups, etc.)
+ */
+ default boolean isFunctional() {
+ return isType(Type.FUNCTIONAL);
+ }
+
+ boolean isType(Type type);
+
+ /** A technical direct child. */
+ HierarchyUnit getDirectChild(Type type);
+
+ /**
+ * The base of this organisational unit within the hierarchy. This would
+ * typically be an LDAP base DN.
+ */
+ String getBase();
+
+ /** The related {@link CmsDirectory}. */
+ CmsDirectory getDirectory();
+
+ /** Its metadata (typically LDAP attributes). */
+ Dictionary<String, Object> getProperties();
+
+ enum Type {
+ PEOPLE, //
+ GROUPS, //
+ ROLES, //
+ FUNCTIONAL;
+ }
+}
--- /dev/null
+package org.argeo.api.cms.directory;
+
+import org.osgi.service.useradmin.Role;
+
+/** Information about a user directory. */
+public interface UserDirectory extends CmsDirectory {
+
+ HierarchyUnit getHierarchyUnit(Role role);
+
+ Iterable<? extends Role> getHierarchyUnitRoles(HierarchyUnit hierarchyUnit, String filter, boolean deep);
+
+ String getRolePath(Role role);
+
+ String getRoleSimpleName(Role role);
+
+ Role getRoleByPath(String path);
+}
--- /dev/null
+package org.argeo.api.cms.keyring;
+
+/**
+ * Marker interface for an advanced keyring based on cryptography.
+ */
+public interface CryptoKeyring extends Keyring {
+ public void changePassword(char[] oldPassword, char[] newPassword);
+
+ public void unlock(char[] password);
+}
--- /dev/null
+package org.argeo.api.cms.keyring;
+
+import java.io.InputStream;
+
+/**
+ * Access to private (typically encrypted) data. The keyring is responsible for
+ * retrieving the necessary credentials. <b>Experimental. This API may
+ * change.</b>
+ */
+public interface Keyring {
+ /**
+ * Returns the confidential information as chars. Must ask for it if it is
+ * not stored.
+ */
+ public char[] getAsChars(String path);
+
+ /**
+ * Returns the confidential information as a stream. Must ask for it if it
+ * is not stored.
+ */
+ public InputStream getAsStream(String path);
+
+ public void set(String path, char[] arr);
+
+ public void set(String path, InputStream in);
+}
--- /dev/null
+package org.argeo.api.cms.keyring;
+
+import javax.crypto.spec.PBEKeySpec;
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.PasswordCallback;
+
+/**
+ * All information required to set up a {@link PBEKeySpec} bar the password
+ * itself (use a {@link PasswordCallback})
+ */
+public class PBEKeySpecCallback implements Callback {
+ private String secretKeyFactory;
+ private byte[] salt;
+ private Integer iterationCount;
+ /** Can be null for some algorithms */
+ private Integer keyLength;
+ /** Can be null, will trigger secret key encryption if not */
+ private String secretKeyEncryption;
+
+ private String encryptedPasswordHashCipher;
+ private byte[] encryptedPasswordHash;
+
+ public void set(String secretKeyFactory, byte[] salt,
+ Integer iterationCount, Integer keyLength,
+ String secretKeyEncryption) {
+ this.secretKeyFactory = secretKeyFactory;
+ this.salt = salt;
+ this.iterationCount = iterationCount;
+ this.keyLength = keyLength;
+ this.secretKeyEncryption = secretKeyEncryption;
+// this.encryptedPasswordHashCipher = encryptedPasswordHashCipher;
+// this.encryptedPasswordHash = encryptedPasswordHash;
+ }
+
+ public String getSecretKeyFactory() {
+ return secretKeyFactory;
+ }
+
+ public byte[] getSalt() {
+ return salt;
+ }
+
+ public Integer getIterationCount() {
+ return iterationCount;
+ }
+
+ public Integer getKeyLength() {
+ return keyLength;
+ }
+
+ public String getSecretKeyEncryption() {
+ return secretKeyEncryption;
+ }
+
+ public String getEncryptedPasswordHashCipher() {
+ return encryptedPasswordHashCipher;
+ }
+
+ public byte[] getEncryptedPasswordHash() {
+ return encryptedPasswordHash;
+ }
+
+}
--- /dev/null
+/** Argeo CMS reusable security components. */
+package org.argeo.api.cms.keyring;
\ No newline at end of file
--- /dev/null
+package org.argeo.api.cms.transaction;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public abstract class AbstractWorkingCopy<DATA, ATTR, ID> implements WorkingCopy<DATA, ATTR, ID> {
+ private Map<ID, DATA> newData = new HashMap<ID, DATA>();
+ private Map<ID, ATTR> modifiedData = new HashMap<ID, ATTR>();
+ private Map<ID, DATA> deletedData = new HashMap<ID, DATA>();
+
+ protected abstract ID getId(DATA data);
+
+ protected abstract ATTR cloneAttributes(DATA data);
+
+ public void cleanUp() {
+ // clean collections
+ newData.clear();
+ newData = null;
+ modifiedData.clear();
+ modifiedData = null;
+ deletedData.clear();
+ deletedData = null;
+ }
+
+ public boolean noModifications() {
+ return newData.size() == 0 && modifiedData.size() == 0 && deletedData.size() == 0;
+ }
+
+ public void startEditing(DATA user) {
+ ID id = getId(user);
+ if (modifiedData.containsKey(id))
+ throw new IllegalStateException("Already editing " + id);
+ modifiedData.put(id, cloneAttributes(user));
+ }
+
+ public Map<ID, DATA> getNewData() {
+ return newData;
+ }
+
+ public Map<ID, DATA> getDeletedData() {
+ return deletedData;
+ }
+
+ public Map<ID, ATTR> getModifiedData() {
+ return modifiedData;
+ }
+
+}
--- /dev/null
+package org.argeo.api.cms.transaction;
+
+/** JTA transaction status. */
+public class JtaStatusAdapter implements TransactionStatusAdapter<Integer> {
+ private static final Integer STATUS_ACTIVE = 0;
+ private static final Integer STATUS_COMMITTED = 3;
+ private static final Integer STATUS_COMMITTING = 8;
+ private static final Integer STATUS_MARKED_ROLLBACK = 1;
+ private static final Integer STATUS_NO_TRANSACTION = 6;
+ private static final Integer STATUS_PREPARED = 2;
+ private static final Integer STATUS_PREPARING = 7;
+ private static final Integer STATUS_ROLLEDBACK = 4;
+ private static final Integer STATUS_ROLLING_BACK = 9;
+// private static final Integer STATUS_UNKNOWN = 5;
+
+ @Override
+ public Integer getActiveStatus() {
+ return STATUS_ACTIVE;
+ }
+
+ @Override
+ public Integer getPreparingStatus() {
+ return STATUS_PREPARING;
+ }
+
+ @Override
+ public Integer getMarkedRollbackStatus() {
+ return STATUS_MARKED_ROLLBACK;
+ }
+
+ @Override
+ public Integer getPreparedStatus() {
+ return STATUS_PREPARED;
+ }
+
+ @Override
+ public Integer getCommittingStatus() {
+ return STATUS_COMMITTING;
+ }
+
+ @Override
+ public Integer getCommittedStatus() {
+ return STATUS_COMMITTED;
+ }
+
+ @Override
+ public Integer getRollingBackStatus() {
+ return STATUS_ROLLING_BACK;
+ }
+
+ @Override
+ public Integer getRolledBackStatus() {
+ return STATUS_ROLLEDBACK;
+ }
+
+ @Override
+ public Integer getNoTransactionStatus() {
+ return STATUS_NO_TRANSACTION;
+ }
+
+}
--- /dev/null
+package org.argeo.api.cms.transaction;
+
+/** Internal unchecked rollback exception. */
+class SimpleRollbackException extends RuntimeException {
+ private static final long serialVersionUID = 8055601819719780566L;
+
+ public SimpleRollbackException() {
+ super();
+ }
+
+ public SimpleRollbackException(Throwable cause) {
+ super(cause);
+ }
+
+}
--- /dev/null
+package org.argeo.api.cms.transaction;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.transaction.xa.XAException;
+import javax.transaction.xa.XAResource;
+import javax.transaction.xa.Xid;
+
+/** Simple implementation of an XA transaction. */
+class SimpleTransaction<T>
+//implements Transaction, Status
+{
+ private final Xid xid;
+ private T status;
+ private final List<XAResource> xaResources = new ArrayList<XAResource>();
+
+ private final SimpleTransactionManager transactionManager;
+ private TransactionStatusAdapter<T> tsa;
+
+ public SimpleTransaction(SimpleTransactionManager transactionManager, TransactionStatusAdapter<T> tsa) {
+ this.tsa = tsa;
+ this.status = tsa.getActiveStatus();
+ this.xid = new UuidXid();
+ this.transactionManager = transactionManager;
+ }
+
+ public synchronized void commit()
+// throws RollbackException, HeuristicMixedException, HeuristicRollbackException,
+// SecurityException, IllegalStateException, SystemException
+ {
+ status = tsa.getPreparingStatus();
+ for (XAResource xaRes : xaResources) {
+ if (status.equals(tsa.getMarkedRollbackStatus()))
+ break;
+ try {
+ xaRes.prepare(xid);
+ } catch (XAException e) {
+ status = tsa.getMarkedRollbackStatus();
+ error("Cannot prepare " + xaRes + " for " + xid, e);
+ }
+ }
+ if (status.equals(tsa.getMarkedRollbackStatus())) {
+ rollback();
+ throw new SimpleRollbackException();
+ }
+ status = tsa.getPreparedStatus();
+
+ status = tsa.getCommittingStatus();
+ for (XAResource xaRes : xaResources) {
+ if (status.equals(tsa.getMarkedRollbackStatus()))
+ break;
+ try {
+ xaRes.commit(xid, false);
+ } catch (XAException e) {
+ status = tsa.getMarkedRollbackStatus();
+ error("Cannot prepare " + xaRes + " for " + xid, e);
+ }
+ }
+ if (status.equals(tsa.getMarkedRollbackStatus())) {
+ rollback();
+ throw new SimpleRollbackException();
+ }
+
+ // complete
+ status = tsa.getCommittedStatus();
+ clearResources(XAResource.TMSUCCESS);
+ transactionManager.unregister(xid);
+ }
+
+ public synchronized void rollback()
+// throws IllegalStateException, SystemException
+ {
+ status = tsa.getRollingBackStatus();
+ for (XAResource xaRes : xaResources) {
+ try {
+ xaRes.rollback(xid);
+ } catch (XAException e) {
+ error("Cannot rollback " + xaRes + " for " + xid, e);
+ }
+ }
+
+ // complete
+ status = tsa.getRolledBackStatus();
+ clearResources(XAResource.TMFAIL);
+ transactionManager.unregister(xid);
+ }
+
+ public synchronized boolean enlistResource(XAResource xaRes)
+// throws RollbackException, IllegalStateException, SystemException
+ {
+ if (xaResources.add(xaRes)) {
+ try {
+ xaRes.start(getXid(), XAResource.TMNOFLAGS);
+ return true;
+ } catch (XAException e) {
+ error("Cannot enlist " + xaRes, e);
+ return false;
+ }
+ } else
+ return false;
+ }
+
+ public synchronized boolean delistResource(XAResource xaRes, int flag)
+// throws IllegalStateException, SystemException
+ {
+ if (xaResources.remove(xaRes)) {
+ try {
+ xaRes.end(getXid(), flag);
+ } catch (XAException e) {
+ error("Cannot delist " + xaRes, e);
+ return false;
+ }
+ return true;
+ } else
+ return false;
+ }
+
+ protected void clearResources(int flag) {
+ for (XAResource xaRes : xaResources)
+ try {
+ xaRes.end(getXid(), flag);
+ } catch (XAException e) {
+ error("Cannot end " + xaRes, e);
+ }
+ xaResources.clear();
+ }
+
+ protected void error(Object obj, Exception e) {
+ System.err.println(obj);
+ e.printStackTrace();
+ }
+
+ public synchronized T getStatus()
+// throws SystemException
+ {
+ return status;
+ }
+
+// public void registerSynchronization(Synchronization sync)
+// throws RollbackException, IllegalStateException, SystemException {
+// throw new UnsupportedOperationException();
+// }
+
+ public void setRollbackOnly()
+// throws IllegalStateException, SystemException
+ {
+ status = tsa.getMarkedRollbackStatus();
+ }
+
+ @Override
+ public int hashCode() {
+ return xid.hashCode();
+ }
+
+ Xid getXid() {
+ return xid;
+ }
+
+}
--- /dev/null
+package org.argeo.api.cms.transaction;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.Callable;
+
+import javax.transaction.xa.XAResource;
+import javax.transaction.xa.Xid;
+
+/**
+ * Simple implementation of an XA transaction manager.
+ */
+public class SimpleTransactionManager
+// implements TransactionManager, UserTransaction
+ implements WorkControl, WorkTransaction {
+ private ThreadLocal<SimpleTransaction<Integer>> current = new ThreadLocal<SimpleTransaction<Integer>>();
+
+ private Map<Xid, SimpleTransaction<Integer>> knownTransactions = Collections
+ .synchronizedMap(new HashMap<Xid, SimpleTransaction<Integer>>());
+ private TransactionStatusAdapter<Integer> tsa = new JtaStatusAdapter();
+// private SyncRegistry syncRegistry = new SyncRegistry();
+
+ /*
+ * WORK IMPLEMENTATION
+ */
+ @Override
+ public <T> T required(Callable<T> work) {
+ T res;
+ begin();
+ try {
+ res = work.call();
+ commit();
+ } catch (Exception e) {
+ rollback();
+ throw new SimpleRollbackException(e);
+ }
+ return res;
+ }
+
+ @Override
+ public WorkContext getWorkContext() {
+ return new WorkContext() {
+
+ @Override
+ public void registerXAResource(XAResource resource, String recoveryId) {
+ getTransaction().enlistResource(resource);
+ }
+ };
+ }
+
+ /*
+ * WORK TRANSACTION IMPLEMENTATION
+ */
+
+ @Override
+ public boolean isNoTransactionStatus() {
+ return tsa.getNoTransactionStatus().equals(getStatus());
+ }
+
+ /*
+ * JTA IMPLEMENTATION
+ */
+
+ public void begin()
+// throws NotSupportedException, SystemException
+ {
+ if (getCurrent() != null)
+ throw new UnsupportedOperationException("Nested transactions are not supported");
+ SimpleTransaction<Integer> transaction = new SimpleTransaction<Integer>(this, tsa);
+ knownTransactions.put(transaction.getXid(), transaction);
+ current.set(transaction);
+ }
+
+ public void commit()
+// throws RollbackException, HeuristicMixedException, HeuristicRollbackException,
+// SecurityException, IllegalStateException, SystemException
+ {
+ if (getCurrent() == null)
+ throw new IllegalStateException("No transaction registered with the current thread.");
+ getCurrent().commit();
+ }
+
+ public int getStatus()
+// throws SystemException
+ {
+ if (getCurrent() == null)
+ return tsa.getNoTransactionStatus();
+ return getTransaction().getStatus();
+ }
+
+ public SimpleTransaction<Integer> getTransaction()
+// throws SystemException
+ {
+ return getCurrent();
+ }
+
+ protected SimpleTransaction<Integer> getCurrent()
+// throws SystemException
+ {
+ SimpleTransaction<Integer> transaction = current.get();
+ if (transaction == null)
+ return null;
+ Integer status = transaction.getStatus();
+ if (status.equals(tsa.getCommittedStatus()) || status.equals(tsa.getRolledBackStatus())) {
+ current.remove();
+ return null;
+ }
+ return transaction;
+ }
+
+ void unregister(Xid xid) {
+ knownTransactions.remove(xid);
+ }
+
+ public void resume(SimpleTransaction<Integer> tobj)
+// throws InvalidTransactionException, IllegalStateException, SystemException
+ {
+ if (getCurrent() != null)
+ throw new IllegalStateException("Transaction " + current.get() + " already registered");
+ current.set(tobj);
+ }
+
+ public void rollback()
+// throws IllegalStateException, SecurityException, SystemException
+ {
+ if (getCurrent() == null)
+ throw new IllegalStateException("No transaction registered with the current thread.");
+ getCurrent().rollback();
+ }
+
+ public void setRollbackOnly()
+// throws IllegalStateException, SystemException
+ {
+ if (getCurrent() == null)
+ throw new IllegalStateException("No transaction registered with the current thread.");
+ getCurrent().setRollbackOnly();
+ }
+
+ public void setTransactionTimeout(int seconds)
+// throws SystemException
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ public SimpleTransaction<Integer> suspend()
+// throws SystemException
+ {
+ SimpleTransaction<Integer> transaction = getCurrent();
+ current.remove();
+ return transaction;
+ }
+
+// public TransactionSynchronizationRegistry getTsr() {
+// return syncRegistry;
+// }
+//
+// private class SyncRegistry implements TransactionSynchronizationRegistry {
+// @Override
+// public Object getTransactionKey() {
+// try {
+// SimpleTransaction transaction = getCurrent();
+// if (transaction == null)
+// return null;
+// return getCurrent().getXid();
+// } catch (SystemException e) {
+// throw new IllegalStateException("Cannot get transaction key", e);
+// }
+// }
+//
+// @Override
+// public void putResource(Object key, Object value) {
+// throw new UnsupportedOperationException();
+// }
+//
+// @Override
+// public Object getResource(Object key) {
+// throw new UnsupportedOperationException();
+// }
+//
+// @Override
+// public void registerInterposedSynchronization(Synchronization sync) {
+// throw new UnsupportedOperationException();
+// }
+//
+// @Override
+// public int getTransactionStatus() {
+// try {
+// return getStatus();
+// } catch (SystemException e) {
+// throw new IllegalStateException("Cannot get status", e);
+// }
+// }
+//
+// @Override
+// public boolean getRollbackOnly() {
+// try {
+// return getStatus() == Status.STATUS_MARKED_ROLLBACK;
+// } catch (SystemException e) {
+// throw new IllegalStateException("Cannot get status", e);
+// }
+// }
+//
+// @Override
+// public void setRollbackOnly() {
+// try {
+// getCurrent().setRollbackOnly();
+// } catch (Exception e) {
+// throw new IllegalStateException("Cannot set rollback only", e);
+// }
+// }
+//
+// }
+}
--- /dev/null
+package org.argeo.api.cms.transaction;
+
+/** Abstract the various approaches to represent transaction status. */
+public interface TransactionStatusAdapter<T> {
+ T getActiveStatus();
+
+ T getPreparingStatus();
+
+ T getMarkedRollbackStatus();
+
+ T getPreparedStatus();
+
+ T getCommittingStatus();
+
+ T getCommittedStatus();
+
+ T getRollingBackStatus();
+
+ T getRolledBackStatus();
+
+ T getNoTransactionStatus();
+}
--- /dev/null
+package org.argeo.api.cms.transaction;
+
+import java.io.Serializable;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.UUID;
+
+import javax.transaction.xa.Xid;
+
+/**
+ * Implementation of {@link Xid} based on {@link UUID}, using max significant
+ * bits as global transaction id, and least significant bits as branch
+ * qualifier.
+ */
+public class UuidXid implements Xid, Serializable {
+ private static final long serialVersionUID = -5380531989917886819L;
+ public final static int FORMAT = (int) serialVersionUID;
+
+ private final static int BYTES_PER_LONG = Long.SIZE / Byte.SIZE;
+
+ private final int format;
+ private final byte[] globalTransactionId;
+ private final byte[] branchQualifier;
+ private final String uuid;
+ private final int hashCode;
+
+ public UuidXid() {
+ this(UUID.randomUUID());
+ }
+
+ public UuidXid(UUID uuid) {
+ this.format = FORMAT;
+ this.globalTransactionId = uuidToBytes(uuid.getMostSignificantBits());
+ this.branchQualifier = uuidToBytes(uuid.getLeastSignificantBits());
+ this.uuid = uuid.toString();
+ this.hashCode = uuid.hashCode();
+ }
+
+ public UuidXid(Xid xid) {
+ this(xid.getFormatId(), xid.getGlobalTransactionId(), xid
+ .getBranchQualifier());
+ }
+
+ private UuidXid(int format, byte[] globalTransactionId,
+ byte[] branchQualifier) {
+ this.format = format;
+ this.globalTransactionId = globalTransactionId;
+ this.branchQualifier = branchQualifier;
+ this.uuid = bytesToUUID(globalTransactionId, branchQualifier)
+ .toString();
+ this.hashCode = uuid.hashCode();
+ }
+
+ @Override
+ public int getFormatId() {
+ return format;
+ }
+
+ @Override
+ public byte[] getGlobalTransactionId() {
+ return Arrays.copyOf(globalTransactionId, globalTransactionId.length);
+ }
+
+ @Override
+ public byte[] getBranchQualifier() {
+ return Arrays.copyOf(branchQualifier, branchQualifier.length);
+ }
+
+ @Override
+ public int hashCode() {
+ return hashCode;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj instanceof UuidXid) {
+ UuidXid that = (UuidXid) obj;
+ return Arrays.equals(globalTransactionId, that.globalTransactionId)
+ && Arrays.equals(branchQualifier, that.branchQualifier);
+ }
+ if (obj instanceof Xid) {
+ Xid that = (Xid) obj;
+ return Arrays.equals(globalTransactionId,
+ that.getGlobalTransactionId())
+ && Arrays
+ .equals(branchQualifier, that.getBranchQualifier());
+ }
+ return uuid.equals(obj.toString());
+ }
+
+ @Override
+ protected Object clone() throws CloneNotSupportedException {
+ return new UuidXid(format, globalTransactionId, branchQualifier);
+ }
+
+ @Override
+ public String toString() {
+ return uuid;
+ }
+
+ public UUID asUuid() {
+ return bytesToUUID(globalTransactionId, branchQualifier);
+ }
+
+ public static byte[] uuidToBytes(long bits) {
+ ByteBuffer buffer = ByteBuffer.allocate(BYTES_PER_LONG);
+ buffer.putLong(0, bits);
+ return buffer.array();
+ }
+
+ public static UUID bytesToUUID(byte[] most, byte[] least) {
+ if (most.length < BYTES_PER_LONG)
+ most = Arrays.copyOf(most, BYTES_PER_LONG);
+ if (least.length < BYTES_PER_LONG)
+ least = Arrays.copyOf(least, BYTES_PER_LONG);
+ ByteBuffer buffer = ByteBuffer.allocate(2 * BYTES_PER_LONG);
+ buffer.put(most, 0, BYTES_PER_LONG);
+ buffer.put(least, 0, BYTES_PER_LONG);
+ buffer.flip();
+ return new UUID(buffer.getLong(), buffer.getLong());
+ }
+
+ // public static void main(String[] args) {
+ // UUID uuid = UUID.randomUUID();
+ // System.out.println(uuid);
+ // uuid = bytesToUUID(uuidToBytes(uuid.getMostSignificantBits()),
+ // uuidToBytes(uuid.getLeastSignificantBits()));
+ // System.out.println(uuid);
+ // }
+}
--- /dev/null
+package org.argeo.api.cms.transaction;
+
+import javax.transaction.xa.XAResource;
+
+/**
+ * A minimalistic interface similar to OSGi transaction context in order to
+ * register XA resources.
+ */
+public interface WorkContext {
+ void registerXAResource(XAResource resource, String recoveryId);
+}
--- /dev/null
+package org.argeo.api.cms.transaction;
+
+import java.util.concurrent.Callable;
+
+/**
+ * A minimalistic interface inspired by OSGi transaction control in order to
+ * commit units of work externally.
+ */
+public interface WorkControl {
+ <T> T required(Callable<T> work);
+
+ void setRollbackOnly();
+
+ WorkContext getWorkContext();
+}
--- /dev/null
+package org.argeo.api.cms.transaction;
+
+/**
+ * A minimalistic interface inspired by JTA user transaction in order to commit
+ * units of work externally.
+ */
+public interface WorkTransaction {
+ void begin();
+
+ void commit();
+
+ void rollback();
+
+ boolean isNoTransactionStatus();
+}
--- /dev/null
+package org.argeo.api.cms.transaction;
+
+import java.util.Map;
+
+public interface WorkingCopy<DATA, ATTR, ID> {
+ void startEditing(DATA user);
+
+ boolean noModifications();
+
+ void cleanUp();
+
+ Map<ID, DATA> getNewData();
+
+ Map<ID, DATA> getDeletedData();
+
+ Map<ID, ATTR> getModifiedData();
+
+}
--- /dev/null
+package org.argeo.api.cms.transaction;
+
+public interface WorkingCopyProcessor<WC extends WorkingCopy<?, ?, ?>> {
+ void prepare(WC wc);
+
+ void commit(WC wc);
+
+ void rollback(WC wc);
+
+ WC newWorkingCopy();
+}
--- /dev/null
+package org.argeo.api.cms.transaction;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.transaction.xa.XAException;
+import javax.transaction.xa.XAResource;
+import javax.transaction.xa.Xid;
+
+/** {@link XAResource} for a user directory being edited. */
+public class WorkingCopyXaResource<WC extends WorkingCopy<?, ?, ?>> implements XAResource {
+ private final WorkingCopyProcessor<WC> processor;
+
+ private Map<Xid, WC> workingCopies = new HashMap<Xid, WC>();
+ private Xid editingXid = null;
+ private int transactionTimeout = 0;
+
+ public WorkingCopyXaResource(WorkingCopyProcessor<WC> processor) {
+ this.processor = processor;
+ }
+
+ @Override
+ public synchronized void start(Xid xid, int flags) throws XAException {
+ if (editingXid != null)
+ throw new IllegalStateException("Already editing " + editingXid);
+ WC wc = workingCopies.put(xid, processor.newWorkingCopy());
+ if (wc != null)
+ throw new IllegalStateException("There is already a working copy for " + xid);
+ this.editingXid = xid;
+ }
+
+ @Override
+ public void end(Xid xid, int flags) throws XAException {
+ checkXid(xid);
+ }
+
+ private WC wc(Xid xid) {
+ return workingCopies.get(xid);
+ }
+
+ public synchronized WC wc() {
+ if (editingXid == null)
+ return null;
+ WC wc = workingCopies.get(editingXid);
+ if (wc == null)
+ throw new IllegalStateException("No working copy found for " + editingXid);
+ return wc;
+ }
+
+ private synchronized void cleanUp(Xid xid) {
+ WC wc = workingCopies.get(xid);
+ if (wc != null) {
+ wc.cleanUp();
+ workingCopies.remove(xid);
+ }
+ editingXid = null;
+ }
+
+ @Override
+ public int prepare(Xid xid) throws XAException {
+ checkXid(xid);
+ WC wc = wc(xid);
+ if (wc.noModifications())
+ return XA_RDONLY;
+ try {
+ processor.prepare(wc);
+ } catch (Exception e) {
+ e.printStackTrace();
+ throw new XAException(XAException.XAER_RMERR);
+ }
+ return XA_OK;
+ }
+
+ @Override
+ public void commit(Xid xid, boolean onePhase) throws XAException {
+ try {
+ checkXid(xid);
+ WC wc = wc(xid);
+ if (wc.noModifications())
+ return;
+ if (onePhase)
+ processor.prepare(wc);
+ processor.commit(wc);
+ } catch (Exception e) {
+ e.printStackTrace();
+ throw new XAException(XAException.XAER_RMERR);
+ } finally {
+ cleanUp(xid);
+ }
+ }
+
+ @Override
+ public void rollback(Xid xid) throws XAException {
+ try {
+ checkXid(xid);
+ processor.rollback(wc(xid));
+ } catch (Exception e) {
+ e.printStackTrace();
+ throw new XAException(XAException.XAER_RMERR);
+ } finally {
+ cleanUp(xid);
+ }
+ }
+
+ @Override
+ public void forget(Xid xid) throws XAException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean isSameRM(XAResource xares) throws XAException {
+ return xares == this;
+ }
+
+ @Override
+ public Xid[] recover(int flag) throws XAException {
+ return new Xid[0];
+ }
+
+ @Override
+ public int getTransactionTimeout() throws XAException {
+ return transactionTimeout;
+ }
+
+ @Override
+ public boolean setTransactionTimeout(int seconds) throws XAException {
+ transactionTimeout = seconds;
+ return true;
+ }
+
+ private void checkXid(Xid xid) throws XAException {
+ if (xid == null)
+ throw new XAException(XAException.XAER_OUTSIDE);
+ if (!xid.equals(xid))
+ throw new XAException(XAException.XAER_NOTA);
+ }
+
+}
--- /dev/null
+package org.argeo.api.cms.transaction;
+
+import javax.transaction.xa.XAResource;
+
+public interface XAResourceProvider {
+ XAResource getXaResource();
+}
--- /dev/null
+/** Minimalistic and partial XA transaction manager implementation. */
+package org.argeo.api.cms.transaction;
\ No newline at end of file
--- /dev/null
+package org.argeo.api.cms.ux;
+
+/** A 2D size. */
+public class Cms2DSize {
+ private Integer width;
+ private Integer height;
+
+ public Cms2DSize() {
+ }
+
+ public Cms2DSize(Integer width, Integer height) {
+ super();
+ this.width = width;
+ this.height = height;
+ }
+
+ public Integer getWidth() {
+ return width;
+ }
+
+ public void setWidth(Integer width) {
+ this.width = width;
+ }
+
+ public Integer getHeight() {
+ return height;
+ }
+
+ public void setHeight(Integer height) {
+ this.height = height;
+ }
+
+ @Override
+ public String toString() {
+ return Cms2DSize.class.getSimpleName() + "[" + width + "," + height + "]";
+ }
+
+}
--- /dev/null
+package org.argeo.api.cms.ux;
+
+/** Abstraction of a simple edition life cycle. */
+public interface CmsEditable {
+
+ /** Whether the calling thread can edit, the value is immutable */
+ Boolean canEdit();
+
+ Boolean isEditing();
+
+ void startEditing();
+
+ void stopEditing();
+
+ void addCmsEditionListener(CmsEditionListener listener);
+
+ void removeCmsEditionListener(CmsEditionListener listener);
+
+ static CmsEditable NON_EDITABLE = new CmsEditable() {
+
+ @Override
+ public void stopEditing() {
+ }
+
+ @Override
+ public void startEditing() {
+ }
+
+ @Override
+ public Boolean isEditing() {
+ return false;
+ }
+
+ @Override
+ public Boolean canEdit() {
+ return false;
+ }
+
+ @Override
+ public void addCmsEditionListener(CmsEditionListener listener) {
+ }
+
+ @Override
+ public void removeCmsEditionListener(CmsEditionListener listener) {
+ }
+
+ };
+
+ static CmsEditable ALWAYS_EDITING = new CmsEditable() {
+
+ @Override
+ public void stopEditing() {
+ }
+
+ @Override
+ public void startEditing() {
+ }
+
+ @Override
+ public Boolean isEditing() {
+ return true;
+ }
+
+ @Override
+ public Boolean canEdit() {
+ return true;
+ }
+
+ @Override
+ public void addCmsEditionListener(CmsEditionListener listener) {
+ }
+
+ @Override
+ public void removeCmsEditionListener(CmsEditionListener listener) {
+ }
+
+ };
+
+}
--- /dev/null
+package org.argeo.api.cms.ux;
+
+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;
+ private final CmsEditable cmsEditable;
+
+ public CmsEditionEvent(Object source, Integer type, CmsEditable cmsEditable) {
+ super(source);
+ this.type = type;
+ this.cmsEditable = cmsEditable;
+ }
+
+ public Integer getType() {
+ return type;
+ }
+
+ public CmsEditable getCmsEditable() {
+ return cmsEditable;
+ }
+
+}
--- /dev/null
+package org.argeo.api.cms.ux;
+
+public interface CmsEditionListener {
+ void editionStarted(CmsEditionEvent e);
+
+ void editionStopped(CmsEditionEvent e);
+}
--- /dev/null
+package org.argeo.api.cms.ux;
+
+/**
+ * Marker interface to be applied to {@link Enum}s in order to find or generate
+ * icons.
+ */
+public interface CmsIcon {
+ String name();
+
+}
--- /dev/null
+package org.argeo.api.cms.ux;
+
+import java.io.InputStream;
+
+/** Read and write access to images. */
+public interface CmsImageManager<V, M> {
+ /** Load image in control */
+ public Boolean load(M node, V control, Cms2DSize size);
+
+ /** @return (0,0) if not available */
+ public Cms2DSize getImageSize(M node);
+
+ /**
+ * The related <img> tag, with src, width and height set.
+ *
+ * @return null if not available
+ */
+ public String getImageTag(M node);
+
+ /**
+ * The related <img> tag, with url, width and height set. Caller must
+ * close the tag (or add additional attributes).
+ *
+ * @return null if not available
+ */
+ public StringBuilder getImageTagBuilder(M node, Cms2DSize size);
+
+ /**
+ * Returns the remotely accessible URL of the image (registering it if
+ * needed) @return null if not available
+ */
+ public String getImageUrl(M node);
+
+// public Binary getImageBinary(Node node) throws RepositoryException;
+
+// public Image getSwtImage(Node node) throws RepositoryException;
+
+ /** @return URL */
+ public String uploadImage(M context, M uploadFolder, String fileName, InputStream in, String contentType);
+
+ @Deprecated
+ default String uploadImage(M uploadFolder, String fileName, InputStream in) {
+ System.err.println("Context must be provided to " + CmsImageManager.class.getName());
+ return uploadImage(null, uploadFolder, fileName, in, null);
+ }
+}
--- /dev/null
+package org.argeo.api.cms.ux;
+
+/** Can be applied to {@link Enum}s in order to generate (CSS) class names. */
+public interface CmsStyle {
+ String name();
+
+ /** @deprecated use {@link #style()} instead. */
+ @Deprecated
+ default String toStyleClass() {
+ return style();
+ }
+
+ default String style() {
+ String classPrefix = getClassPrefix();
+ return "".equals(classPrefix) ? name() : classPrefix + "-" + name();
+ }
+
+ default String getClassPrefix() {
+ return "";
+ }
+
+}
--- /dev/null
+package org.argeo.api.cms.ux;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Set;
+
+/** A CMS theme which can be applied to web apps as well as desktop apps. */
+public interface CmsTheme {
+ /** Unique ID of this theme. */
+ String getThemeId();
+
+ /**
+ * Load a resource as an input stream, base don its relative path, or
+ * <code>null</code> if not found
+ */
+ InputStream getResourceAsStream(String resourceName) throws IOException;
+
+ /** Relative paths to standard web CSS. */
+ Set<String> getWebCssPaths();
+
+ /** Relative paths to RAP specific CSS. */
+ Set<String> getRapCssPaths();
+
+ /** Relative paths to SWT specific CSS. */
+ Set<String> getSwtCssPaths();
+
+ /** Relative paths to images such as icons. */
+ Set<String> getImagesPaths();
+
+ /** Relative paths to fonts. */
+ Set<String> getFontsPaths();
+
+ /** Tags to be added to the header section of the HTML page. */
+ String getHtmlHeaders();
+
+ /** The HTML body to use. */
+ String getBodyHtml();
+
+ /** The default icon size (typically the smallest). */
+ default int getDefaultIconSize() {
+ return getSmallIconSize();
+ }
+
+ int getSmallIconSize();
+
+ int getBigIconSize();
+
+ /** Loads one of the relative path provided by the other methods. */
+ InputStream loadPath(String path) throws IOException;
+
+}
--- /dev/null
+package org.argeo.api.cms.ux;
+
+/** The actual implementation of a user interface, using a given technology. */
+public interface CmsUi {
+ Object getData(String key);
+
+ void setData(String key, Object value);
+
+ CmsView getCmsView();
+}
--- /dev/null
+package org.argeo.api.cms.ux;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.Callable;
+import java.util.concurrent.Executors;
+
+import javax.security.auth.login.LoginContext;
+
+import org.argeo.api.cms.CmsSession;
+
+/** Provides interaction with the CMS system. */
+public interface CmsView {
+ final static String CMS_VIEW_UID_PROPERTY = "argeo.cms.view.uid";
+ // String KEY = "org.argeo.cms.ui.view";
+
+ String getUid();
+
+ UxContext getUxContext();
+
+ // NAVIGATION
+ void navigateTo(String state);
+
+ // SECURITY
+ void authChange(LoginContext loginContext);
+
+ void logout();
+
+ // void registerCallbackHandler(CallbackHandler callbackHandler);
+
+ // SERVICES
+ void exception(Throwable e);
+
+ CmsImageManager<?, ?> getImageManager();
+
+ boolean isAnonymous();
+
+ /**
+ * Send an event to this topic. Does nothing by default., but if implemented it
+ * MUST set the {@link #CMS_VIEW_UID_PROPERTY} in the properties.
+ */
+ default void sendEvent(String topic, Map<String, Object> properties) {
+
+ }
+
+ /**
+ * Convenience methods for when {@link #sendEvent(String, Map)} only requires
+ * one single parameter.
+ */
+ default void sendEvent(String topic, String param, Object value) {
+ Map<String, Object> properties = new HashMap<>();
+ properties.put(param, value);
+ sendEvent(topic, properties);
+ }
+
+ default void applyStyles(Object widget) {
+
+ }
+
+ /**
+ * Make sure that this action is executed with the proper subject and in a
+ * proper thread.
+ */
+ <T> T doAs(Callable<T> action);
+
+ default void runAs(Runnable runnable) {
+ doAs(Executors.callable(runnable));
+ }
+
+ default void stateChanged(String state, String title) {
+ }
+
+ default CmsSession getCmsSession() {
+ throw new UnsupportedOperationException();
+ }
+
+ default Object getData(String key) {
+ throw new UnsupportedOperationException();
+ }
+
+ @SuppressWarnings("unchecked")
+ default <T> T getUiContext(Class<T> clss) {
+ return (T) getData(clss.getName());
+ }
+
+ default <T> void setUiContext(Class<T> clss, T instance) {
+ setData(clss.getName(), instance);
+ }
+
+ default void setData(String key, Object value) {
+ throw new UnsupportedOperationException();
+ }
+
+}
--- /dev/null
+package org.argeo.api.cms.ux;
+
+import java.util.function.BiFunction;
+
+/**
+ * Stateless UI part creator. Takes a parent view (V) and a model context (M) in
+ * order to create a view part (W) which can then be further configured. Such
+ * object can be used as services and reference other part of the model which
+ * are relevant for all created UI part.
+ */
+@FunctionalInterface
+@Deprecated
+public interface MvcProvider<V, M, W> extends BiFunction<V, M, W> {
+ W createUiPart(V parent, M context);
+
+ /**
+ * Whether this parent view is supported.
+ *
+ * @return true by default.
+ */
+ default boolean isViewSupported(V parent) {
+ return true;
+ }
+
+ /**
+ * Whether this context is supported.
+ *
+ * @return true by default.
+ */
+ default boolean isModelSupported(M context) {
+ return true;
+ }
+
+ default W apply(V parent, M context) {
+ if (!isViewSupported(parent))
+ throw new IllegalArgumentException("Parent view " + parent + "is not supported.");
+ if (!isModelSupported(context))
+ throw new IllegalArgumentException("Model context " + context + "is not supported.");
+ return createUiPart(parent, context);
+ }
+
+ default W createUiPart(V parent) {
+ return createUiPart(parent, null);
+ }
+}
--- /dev/null
+package org.argeo.api.cms.ux;
+
+public interface UxContext {
+ boolean isPortrait();
+
+ boolean isLandscape();
+
+ boolean isSquare();
+
+ boolean isSmall();
+
+ /**
+ * Is a production environment (must be false by default, and be explicitly
+ * set during the CMS deployment). When false, it can activate additional UI
+ * capabilities in order to facilitate QA.
+ */
+ boolean isMasterData();
+}
--- /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-17">
+ <attributes>
+ <attribute name="module" value="true"/>
+ </attributes>
+ </classpathentry>
+ <classpathentry kind="output" path="bin"/>
+</classpath>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>org.argeo.api.register</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.jdt.core.javanature</nature>
+ <nature>org.eclipse.pde.PluginNature</nature>
+ </natures>
+</projectDescription>
--- /dev/null
+/MANIFEST.MF
--- /dev/null
+Import-Package: org.osgi.*;version=0.0.0,\
+!org.apache.commons.logging,\
+*
--- /dev/null
+source.. = src/
\ No newline at end of file
--- /dev/null
+package org.argeo.api.register;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+/**
+ * A wrapper for an object, whose dependencies and life cycle can be managed.
+ */
+public class Component<I> implements Supplier<I>, Comparable<Component<?>> {
+
+ private final I instance;
+
+ private final Runnable init;
+ private final Runnable close;
+
+ private final Map<Class<? super I>, PublishedType<? super I>> types;
+ private final Set<Dependency<?>> dependencies;
+ private final Map<String, Object> properties;
+
+ private CompletableFuture<Void> activationStarted = null;
+ private CompletableFuture<Void> activated = null;
+
+ private CompletableFuture<Void> deactivationStarted = null;
+ private CompletableFuture<Void> deactivated = null;
+
+ // internal
+ private Set<Dependency<?>> dependants = new HashSet<>();
+
+ private RankingKey rankingKey;
+
+ Component(ComponentRegister register, I instance, Runnable init, Runnable close, Set<Dependency<?>> dependencies,
+ Set<Class<? super I>> classes, Map<String, Object> properties) {
+ assert instance != null;
+ assert init != null;
+ assert close != null;
+ assert dependencies != null;
+ assert classes != null;
+
+ this.instance = instance;
+ this.init = init;
+ this.close = close;
+
+ // types
+ Map<Class<? super I>, PublishedType<? super I>> types = new HashMap<>(classes.size());
+ for (Class<? super I> clss : classes) {
+// if (!clss.isAssignableFrom(instance.getClass()))
+// throw new IllegalArgumentException(
+// "Type " + clss.getName() + " is not compatible with " + instance.getClass().getName());
+ types.put(clss, new PublishedType<>(this, clss));
+ }
+ this.types = Collections.unmodifiableMap(types);
+
+ // dependencies
+ this.dependencies = Collections.unmodifiableSet(new HashSet<>(dependencies));
+ for (Dependency<?> dependency : this.dependencies) {
+ dependency.setDependantComponent(this);
+ }
+
+ // deactivated by default
+ deactivated = CompletableFuture.completedFuture(null);
+ deactivationStarted = CompletableFuture.completedFuture(null);
+
+ // TODO check whether context is active, so that we start right away
+ prepareNextActivation();
+
+ long serviceId = register.register(this);
+ Map<String, Object> props = new HashMap<>(properties);
+ props.put(RankingKey.SERVICE_ID, serviceId);
+ this.properties = Collections.unmodifiableMap(props);
+ rankingKey = new RankingKey(properties);
+ }
+
+ private void prepareNextActivation() {
+ activationStarted = new CompletableFuture<Void>();
+ activated = activationStarted //
+ .thenComposeAsync(this::dependenciesActivated) //
+ .thenRun(this.init) //
+ .thenRun(() -> prepareNextDeactivation());
+ }
+
+ private void prepareNextDeactivation() {
+ deactivationStarted = new CompletableFuture<Void>();
+ deactivated = deactivationStarted //
+ .thenComposeAsync(this::dependantsDeactivated) //
+ .thenRun(this.close) //
+ .thenRun(() -> prepareNextActivation());
+ }
+
+ CompletableFuture<Void> getActivated() {
+ return activated;
+ }
+
+ CompletableFuture<Void> getDeactivated() {
+ return deactivated;
+ }
+
+ void startActivating() {
+ if (activated.isDone() || activationStarted.isDone())
+ return;
+ activationStarted.complete(null);
+ }
+
+ void startDeactivating() {
+ if (deactivated.isDone() || deactivationStarted.isDone())
+ return;
+ deactivationStarted.complete(null);
+ }
+
+ CompletableFuture<Void> dependenciesActivated(Void v) {
+ Set<CompletableFuture<?>> constraints = new HashSet<>(this.dependencies.size());
+ for (Dependency<?> dependency : this.dependencies) {
+ CompletableFuture<Void> dependencyActivated = dependency.publisherActivated() //
+ .thenCompose(dependency::set);
+ constraints.add(dependencyActivated);
+ }
+ return CompletableFuture.allOf(constraints.toArray(new CompletableFuture[constraints.size()]));
+ }
+
+ CompletableFuture<Void> dependantsDeactivated(Void v) {
+ Set<CompletableFuture<?>> constraints = new HashSet<>(this.dependants.size());
+ for (Dependency<?> dependant : this.dependants) {
+ CompletableFuture<Void> dependantDeactivated = dependant.dependantDeactivated() //
+ .thenCompose(dependant::unset);
+ constraints.add(dependantDeactivated);
+ }
+ CompletableFuture<Void> dependantsDeactivated = CompletableFuture
+ .allOf(constraints.toArray(new CompletableFuture[constraints.size()]));
+ return dependantsDeactivated;
+
+ }
+
+ void addDependant(Dependency<?> dependant) {
+ dependants.add(dependant);
+ }
+
+ @Override
+ public I get() {
+ return instance;
+ }
+
+ @SuppressWarnings("unchecked")
+ public <T> PublishedType<T> getType(Class<T> clss) {
+ if (!types.containsKey(clss))
+ throw new IllegalArgumentException(clss.getName() + " is not a type published by this component");
+ return (PublishedType<T>) types.get(clss);
+ }
+
+ public <T> boolean isPublishedType(Class<T> clss) {
+ return types.containsKey(clss);
+ }
+
+ public Map<String, Object> getProperties() {
+ return properties;
+ }
+
+ @Override
+ public int compareTo(Component<?> o) {
+ return rankingKey.compareTo(rankingKey);
+ }
+
+ @Override
+ public int hashCode() {
+ Long serviceId = (Long) properties.get(RankingKey.SERVICE_ID);
+ if (serviceId != null)
+ return serviceId.intValue();
+ else
+ return super.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ List<String> classes = new ArrayList<>();
+ for (Class<?> clss : types.keySet()) {
+ classes.add(clss.getName());
+ }
+ return "Component " + classes + " " + properties + "";
+ }
+
+ /** A type which has been explicitly exposed by a component. */
+ public static class PublishedType<T> {
+ private Component<? extends T> component;
+ private Class<T> clss;
+
+ private CompletableFuture<T> value;
+
+ public PublishedType(Component<? extends T> component, Class<T> clss) {
+ this.clss = clss;
+ this.component = component;
+ value = CompletableFuture.completedFuture((T) component.instance);
+ }
+
+ public Component<?> getPublisher() {
+ return component;
+ }
+
+ public Class<T> getType() {
+ return clss;
+ }
+
+ public CompletionStage<T> getValue() {
+ return value.minimalCompletionStage();
+ }
+ }
+
+ /** Builds a {@link Component}. */
+ public static class Builder<I> implements Supplier<I> {
+ private final I instance;
+
+ private Runnable init;
+ private Runnable close;
+
+ private Set<Dependency<?>> dependencies = new HashSet<>();
+ private Set<Class<? super I>> types = new HashSet<>();
+ private final Map<String, Object> properties = new HashMap<>();
+
+ public Builder(I instance) {
+ this.instance = instance;
+ }
+
+ public Component<I> build(ComponentRegister register) {
+ // default values
+ if (types.isEmpty()) {
+ types.add(getInstanceClass());
+ }
+
+ if (init == null)
+ init = () -> {
+ };
+ if (close == null)
+ close = () -> {
+ };
+
+ // instantiation
+ Component<I> component = new Component<I>(register, instance, init, close, dependencies, types, properties);
+ for (Dependency<?> dependency : dependencies) {
+ dependency.type.getPublisher().addDependant(dependency);
+ }
+ return component;
+ }
+
+ public Builder<I> addType(Class<? super I> clss) {
+ types.add(clss);
+ return this;
+ }
+
+ public Builder<I> addActivation(Runnable init) {
+ if (this.init != null)
+ throw new IllegalArgumentException("init method is already set");
+ this.init = init;
+ return this;
+ }
+
+ public Builder<I> addDeactivation(Runnable close) {
+ if (this.close != null)
+ throw new IllegalArgumentException("close method is already set");
+ this.close = close;
+ return this;
+ }
+
+ public <D> Builder<I> addDependency(PublishedType<D> type, Consumer<D> set, Consumer<D> unset) {
+ dependencies.add(new Dependency<D>(type, set, unset));
+ return this;
+ }
+
+ public void addProperty(String key, Object value) {
+ if (properties.containsKey(key))
+ throw new IllegalStateException("Key " + key + " is already set.");
+ properties.put(key, value);
+ }
+
+ @Override
+ public I get() {
+ return instance;
+ }
+
+ @SuppressWarnings("unchecked")
+ private Class<I> getInstanceClass() {
+ return (Class<I>) instance.getClass();
+ }
+
+ }
+
+ static class Dependency<D> {
+ private PublishedType<D> type;
+ private Consumer<D> set;
+ private Consumer<D> unset;
+
+ // live
+ Component<?> dependantComponent;
+ CompletableFuture<Void> setStage;
+ CompletableFuture<Void> unsetStage;
+
+ public Dependency(PublishedType<D> types, Consumer<D> set, Consumer<D> unset) {
+ super();
+ this.type = types;
+ this.set = set != null ? set : t -> {
+ };
+ this.unset = unset != null ? unset : t -> {
+ };
+ }
+
+ // live
+ void setDependantComponent(Component<?> component) {
+ this.dependantComponent = component;
+ }
+
+ CompletableFuture<Void> publisherActivated() {
+ return type.getPublisher().activated.copy();
+ }
+
+ CompletableFuture<Void> dependantDeactivated() {
+ return dependantComponent.deactivated.copy();
+ }
+
+ CompletableFuture<Void> set(Void v) {
+ return type.value.thenAccept(set);
+ }
+
+ CompletableFuture<Void> unset(Void v) {
+ return type.value.thenAccept(unset);
+ }
+
+ }
+}
--- /dev/null
+package org.argeo.api.register;
+
+import java.util.Map;
+import java.util.SortedSet;
+import java.util.function.Predicate;
+
+/** A register of components which can coordinate their activation. */
+public interface ComponentRegister {
+ long register(Component<?> component);
+
+ <T> SortedSet<Component<? extends T>> find(Class<T> clss, Predicate<Map<String, Object>> filter);
+
+ default <T> Component.PublishedType<T> getSingleton(Class<T> type) {
+ SortedSet<Component<? extends T>> found = find(type, null);
+ if (found.size() == 0)
+ throw new IllegalStateException("No component found for " + type);
+ return found.first().getType(type);
+ }
+
+ default <T> T getObject(Class<T> clss) {
+ SortedSet<Component<? extends T>> found = find(clss, null);
+ if (found.size() == 0)
+ return null;
+ return found.first().get();
+ }
+
+ Component<?> get(Object instance);
+
+// default <T> PublishedType<T> getType(Class<T> clss) {
+// SortedSet<Component<? extends T>> components = find(clss, null);
+// if (components.size() == 0)
+// return null;
+// return components.first().getType(clss);
+// }
+
+ void activate();
+
+ void deactivate();
+
+ boolean isActive();
+
+ void clear();
+}
--- /dev/null
+package org.argeo.api.register;
+
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * Key used to classify and filter available components.
+ */
+public class RankingKey implements Comparable<RankingKey> {
+ public final static String SERVICE_PID = "service.pid";
+ public final static String SERVICE_ID = "service.id";
+ public final static String SERVICE_RANKING = "service.ranking";
+
+ private String pid;
+ private Integer ranking = 0;
+ private Long id = 0l;
+
+ public RankingKey(String pid, Integer ranking, Long id) {
+ super();
+ this.pid = pid;
+ this.ranking = ranking;
+ this.id = id;
+ }
+
+ public RankingKey(Map<String, Object> properties) {
+ this.pid = properties.containsKey(SERVICE_PID) ? properties.get(SERVICE_PID).toString() : null;
+ this.ranking = properties.containsKey(SERVICE_RANKING)
+ ? Integer.parseInt(properties.get(SERVICE_RANKING).toString())
+ : 0;
+ this.id = properties.containsKey(SERVICE_ID) ? (Long) properties.get(SERVICE_ID) : null;
+ }
+
+ @Override
+ public int hashCode() {
+ Integer result = 0;
+ if (pid != null)
+ result = +pid.hashCode();
+ if (ranking != null)
+ result = +ranking;
+ return result;
+ }
+
+ @Override
+ protected Object clone() throws CloneNotSupportedException {
+ return new RankingKey(pid, ranking, id);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder("");
+ if (pid != null)
+ sb.append(pid);
+ if (ranking != null && ranking != 0)
+ sb.append(' ').append(ranking);
+ return sb.toString();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof RankingKey))
+ return false;
+ RankingKey other = (RankingKey) obj;
+ return Objects.equals(pid, other.pid) && Objects.equals(ranking, other.ranking) && Objects.equals(id, other.id);
+ }
+
+ @Override
+ public int compareTo(RankingKey o) {
+ if (pid != null && o.pid != null) {
+ if (pid.equals(o.pid)) {
+ if (ranking.equals(o.ranking))
+ if (id != null && o.id != null)
+ return id.compareTo(o.id);
+ else
+ return 0;
+ else
+ return ranking.compareTo(o.ranking);
+ } else {
+ return pid.compareTo(o.pid);
+ }
+
+ } else {
+ }
+ return -1;
+ }
+
+ public String getPid() {
+ return pid;
+ }
+
+ public Integer getRanking() {
+ return ranking;
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public static RankingKey minPid(String pid) {
+ return new RankingKey(pid, Integer.MIN_VALUE, null);
+ }
+
+ public static RankingKey maxPid(String pid) {
+ return new RankingKey(pid, Integer.MAX_VALUE, null);
+ }
+}
--- /dev/null
+package org.argeo.api.register;
+
+import java.util.HashSet;
+import java.util.IdentityHashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.Predicate;
+
+/** A minimal component register. */
+public class SimpleRegister implements ComponentRegister {
+ private final AtomicBoolean started = new AtomicBoolean(false);
+ private final IdentityHashMap<Object, Component<?>> components = new IdentityHashMap<>();
+ private final AtomicLong nextServiceId = new AtomicLong(0l);
+
+ @Override
+ public long register(Component<?> component) {
+ return registerComponent(component);
+ }
+
+ @SuppressWarnings({ "unchecked" })
+ @Override
+ public synchronized <T> SortedSet<Component<? extends T>> find(Class<T> clss,
+ Predicate<Map<String, Object>> filter) {
+ SortedSet<Component<? extends T>> result = new TreeSet<>();
+ instances: for (Object instance : components.keySet()) {
+ if (!clss.isAssignableFrom(instance.getClass()))
+ continue instances;
+ Component<? extends T> component = (Component<? extends T>) components.get(instance);
+
+ if (component.isPublishedType(clss)) {
+ if (filter != null) {
+ filter.test(component.getProperties());
+ }
+ result.add(component);
+ }
+ }
+ if (result.isEmpty())
+ return null;
+ return result;
+
+ }
+
+ synchronized long registerComponent(Component<?> component) {
+ if (started.get()) // TODO make it really dynamic
+ throw new IllegalStateException("Already activated");
+ if (components.containsKey(component.get()))
+ throw new IllegalArgumentException("Already registered as component");
+ components.put(component.get(), component);
+ return nextServiceId.incrementAndGet();
+ }
+
+ @Override
+ public synchronized Component<?> get(Object instance) {
+ if (!components.containsKey(instance))
+ throw new IllegalArgumentException("Not registered as component");
+ return components.get(instance);
+ }
+
+ @Override
+ public synchronized void activate() {
+ if (started.get())
+ throw new IllegalStateException("Already activated");
+ Set<CompletableFuture<?>> constraints = new HashSet<>();
+ for (Component<?> component : components.values()) {
+ component.startActivating();
+ constraints.add(component.getActivated());
+ }
+
+ // wait
+ try {
+ CompletableFuture.allOf(constraints.toArray(new CompletableFuture[0])).thenRun(() -> started.set(true))
+ .get();
+ } catch (InterruptedException e) {
+ throw new RuntimeException("Register activation has been interrupted", e);
+ } catch (ExecutionException e) {
+ if (RuntimeException.class.isAssignableFrom(e.getCause().getClass())) {
+ throw (RuntimeException) e.getCause();
+ } else {
+ throw new IllegalStateException("Cannot activate register", e.getCause());
+ }
+ }
+ }
+
+ @Override
+ public synchronized void deactivate() {
+ if (!started.get())
+ throw new IllegalStateException("Not activated");
+ Set<CompletableFuture<?>> constraints = new HashSet<>();
+ for (Component<?> component : components.values()) {
+ component.startDeactivating();
+ constraints.add(component.getDeactivated());
+ }
+
+ // wait
+ try {
+ CompletableFuture.allOf(constraints.toArray(new CompletableFuture[0])).thenRun(() -> started.set(false))
+ .get();
+ } catch (InterruptedException e) {
+ throw new RuntimeException("Register deactivation has been interrupted", e);
+ } catch (ExecutionException e) {
+ if (RuntimeException.class.isAssignableFrom(e.getCause().getClass())) {
+ throw (RuntimeException) e.getCause();
+ } else {
+ throw new IllegalStateException("Cannot deactivate register", e.getCause());
+ }
+ }
+ }
+
+ @Override
+ public synchronized boolean isActive() {
+ return started.get();
+ }
+
+ @Override
+ public synchronized void clear() {
+ components.clear();
+ }
+}
<?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.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-17"/>
<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
<classpathentry kind="src" path="src"/>
<classpathentry kind="output" path="bin"/>
}
/**
- * If positive, only clock_hi is taken from the argument (range & 0x3F00), if
+ * If positive, only clock_hi is taken from the argument (range amp; 0x3F00), if
* negative, the full range of possible values is used.
*/
public void setCurrentClockSequenceRange(long range) {
/**
* Implementation of the basic RFC4122 algorithms.
*
- * @see https://datatracker.ietf.org/doc/html/rfc4122
+ * @see "https://datatracker.ietf.org/doc/html/rfc4122"
*/
public abstract class AbstractUuidFactory implements UuidFactory {
/**
* Force this node id to be identified as no MAC address.
*
- * @see https://datatracker.ietf.org/doc/html/rfc4122#section-4.5
+ * @see "https://datatracker.ietf.org/doc/html/rfc4122#section-4.5"
*/
protected static void forceToNoMacAddress(byte[] nodeId, int offset) {
assert nodeId != null && offset < nodeId.length;
}
/**
- * Always returns <code>true</true> since it is unknown from which values it was
+ * Always returns <code>true</code> since it is unknown from which values it was
* constructed..
*/
@Override
package org.argeo.api.uuid;
-import static java.lang.System.Logger.Level.DEBUG;
-import static java.lang.System.Logger.Level.WARNING;
-
-import java.lang.System.Logger;
import java.security.SecureRandom;
import java.time.Clock;
import java.time.Duration;
import java.util.Objects;
import java.util.Set;
import java.util.WeakHashMap;
+import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.atomic.AtomicLong;
import org.argeo.api.uuid.UuidFactory.TimeUuidState;
* sequences. If that limit is reached, the clock sequence which has not be used
* for the most time is reallocated to the new thread. It is assumed that the
* context where time uUIDs will be generated will often be using thread pools
- * (e.g. {@link ForkJoinPool#commonPool(), http server, database access, etc.)
+ * (e.g. {@link ForkJoinPool#commonPool()}, http server, database access, etc.)
* and that such reallocation won't have to happen too often.
*/
public class ConcurrentTimeUuidState implements UuidFactory.TimeUuidState {
- private final static Logger logger = System.getLogger(ConcurrentTimeUuidState.class.getName());
+// private final static Logger logger = System.getLogger(ConcurrentTimeUuidState.class.getName());
/** The maximum possible value of the clocksequence. */
private final static int MAX_CLOCKSEQUENCE = 0x3F00;
@Override
public long getMostSignificantBits() {
long timestamp = useTimestamp();
- long mostSig = UuidFactory.MOST_SIG_VERSION1 | ((timestamp & 0xFFFFFFFFL) << 32) // time_low
- | (((timestamp >> 32) & 0xFFFFL) << 16) // time_mid
- | ((timestamp >> 48) & 0x0FFFL);// time_hi_and_version
+ long mostSig = TimeUuid.toMostSignificantBits(timestamp);
return mostSig;
}
}
assert holderToRemove != null;
- long oldClockSequence = holderToRemove.clockSequence;
+// long oldClockSequence = holderToRemove.clockSequence;
holderToRemove.clockSequence = -1;
activeHolders.remove(holderToRemove);
- if (logger.isLoggable(WARNING))
- logger.log(WARNING, "Removed " + holderToRemove + ", oldClockSequence=" + oldClockSequence);
+// if (logger.isLoggable(WARNING))
+// logger.log(WARNING, "Removed " + holderToRemove + ", oldClockSequence=" + oldClockSequence);
}
long newClockSequence = -1;
// TODO use an iterator to check the values
holder.setClockSequence(newClockSequence);
activeHolders.put(holder, newClockSequence);
- if (logger.isLoggable(DEBUG)) {
- String clockDesc = range >= 0 ? Long.toHexString(newClockSequence & 0x00FF)
- : Long.toHexString(newClockSequence | 0x8000);
- String rangeDesc = Long.toHexString(min | 0x8000) + "-" + Long.toHexString(max | 0x8000);
- logger.log(DEBUG, "New clocksequence " + clockDesc + " for thread " + Thread.currentThread().getId()
- + " (in range " + rangeDesc + ")");
- }
+// if (logger.isLoggable(DEBUG)) {
+// String clockDesc = range >= 0 ? Long.toHexString(newClockSequence & 0x00FF)
+// : Long.toHexString(newClockSequence | 0x8000);
+// String rangeDesc = Long.toHexString(min | 0x8000) + "-" + Long.toHexString(max | 0x8000);
+// logger.log(DEBUG, "New clocksequence " + clockDesc + " for thread " + Thread.currentThread().getId()
+// + " (in range " + rangeDesc + ")");
+// }
}
private synchronized int getRangeSize() {
package org.argeo.api.uuid;
-import static java.lang.System.Logger.Level.DEBUG;
-import static java.lang.System.Logger.Level.WARNING;
-
-import java.lang.System.Logger;
import java.security.DrbgParameters;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
* A configurable implementation of an {@link AsyncUuidFactory}, which can be
* used as a base class for more optimised implementations.
*
- * @see https://datatracker.ietf.org/doc/html/rfc4122
+ * @see "https://datatracker.ietf.org/doc/html/rfc4122"
*/
public class ConcurrentUuidFactory extends AbstractAsyncUuidFactory implements TypedUuidFactory {
- private final static Logger logger = System.getLogger(ConcurrentUuidFactory.class.getName());
+// private final static Logger logger = System.getLogger(ConcurrentUuidFactory.class.getName());
public ConcurrentUuidFactory(long initialClockRange, byte[] nodeId) {
this(initialClockRange, nodeId, 0);
}
+ /** With a random node id. */
+ public ConcurrentUuidFactory(long initialClockRange) {
+ this(initialClockRange, NodeIdSupplier.randomNodeId());
+ }
+
public ConcurrentUuidFactory(long initialClockRange, byte[] nodeId, int offset) {
Objects.requireNonNull(nodeId);
if (offset + 6 > nodeId.length)
DrbgParameters.instantiation(256, DrbgParameters.Capability.PR_AND_RESEED, "UUID".getBytes()));
} catch (NoSuchAlgorithmException e) {
try {
- logger.log(DEBUG, "DRBG secure random not found, using strong");
+// logger.log(DEBUG, "DRBG secure random not found, using strong");
secureRandom = SecureRandom.getInstanceStrong();
} catch (NoSuchAlgorithmException e1) {
- logger.log(WARNING, "No strong secure random was found, using default");
+// logger.log(WARNING, "No strong secure random was found, using default");
secureRandom = new SecureRandom();
}
+ } catch (java.lang.NoClassDefFoundError e) {// Android
+ secureRandom = new SecureRandom();
}
return secureRandom;
}
/**
* A variant 6 {@link UUID}.
*
- * @see https://datatracker.ietf.org/doc/html/rfc4122#section-4.1.1
+ * @see "https://datatracker.ietf.org/doc/html/rfc4122#section-4.1.1"
*/
public class GUID extends TypedUuid {
private static final long serialVersionUID = APM.SERIAL;
* <li>P: (1db31359-bdd8-5a0f-b672-30c247d582c5)</li>
* </ul>
*
- * @see https://docs.microsoft.com/en-us/dotnet/api/system.guid.tostring
+ * @see "https://docs.microsoft.com/en-us/dotnet/api/system.guid.tostring"
*/
public static String toString(UUID uuid, char format, boolean upperCase) {
String str;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.UnknownHostException;
+import java.util.Enumeration;
import java.util.UUID;
/**
* An {@link UUID} factory whose node id (for time based UUIDs) is the hardware
* MAC address as specified in RFC4122.
*
- * @see https://datatracker.ietf.org/doc/html/rfc4122.html#section-4.1.6
+ * @see "https://datatracker.ietf.org/doc/html/rfc4122.html#section-4.1.6"
*/
public class MacAddressUuidFactory extends ConcurrentUuidFactory {
- public final static UuidFactory DEFAULT = new MacAddressUuidFactory();
-
public MacAddressUuidFactory() {
- this(0);
+ this(0, localHardwareAddressAsNodeId());
}
public MacAddressUuidFactory(long initialClockRange) {
- super(initialClockRange, localHardwareAddressAsNodeId());
+ this(initialClockRange, localHardwareAddressAsNodeId());
+ }
+
+ public MacAddressUuidFactory(byte[] hardwareAddress) {
+ this(0, hardwareAddress);
}
- public static byte[] localHardwareAddressAsNodeId() {
+ public MacAddressUuidFactory(long initialClockRange, byte[] hardwareAddress) {
+ super(initialClockRange, hardwareAddress);
+ }
+
+ private static byte[] localHardwareAddressAsNodeId() {
InetAddress localHost;
try {
localHost = InetAddress.getLocalHost();
NetworkInterface nic = NetworkInterface.getByInetAddress(localHost);
- return hardwareAddressToNodeId(nic);
+ if (nic != null)
+ return hardwareAddressToNodeId(nic);
+ Enumeration<NetworkInterface> netInterfaces = null;
+ try {
+ netInterfaces = NetworkInterface.getNetworkInterfaces();
+ } catch (SocketException e) {
+ throw new IllegalStateException(e);
+ }
+ if (netInterfaces == null || !netInterfaces.hasMoreElements())
+ throw new IllegalStateException("No interfaces");
+ return hardwareAddressToNodeId(netInterfaces.nextElement());
} catch (UnknownHostException | SocketException e) {
throw new IllegalStateException(e);
}
}
- public static byte[] hardwareAddressToNodeId(NetworkInterface nic) throws SocketException {
- byte[] hardwareAddress = nic.getHardwareAddress();
- final int length = 6;
- byte[] arr = new byte[length];
- for (int i = 0; i < length; i++) {
- arr[i] = hardwareAddress[length - 1 - i];
+ public static byte[] hardwareAddressToNodeId(NetworkInterface nic) {
+ try {
+ byte[] hardwareAddress = nic.getHardwareAddress();
+ final int length = 6;
+ byte[] arr = new byte[length];
+ for (int i = 0; i < length; i++) {
+ arr[i] = hardwareAddress[length - 1 - i];
+ }
+ return arr;
+ } catch (SocketException e) {
+ throw new IllegalStateException("Cannot retrieve hardware address from NIC", e);
}
- return arr;
}
}
package org.argeo.api.uuid;
+import java.security.SecureRandom;
import java.util.function.Supplier;
/** A factory for node id base */
public interface NodeIdSupplier extends Supplier<Long> {
static long toNodeIdBase(byte[] node) {
assert node.length == 6;
- return UuidFactory.LEAST_SIG_RFC4122_VARIANT
- | (node[0] & 0xFFL) //
+ return UuidFactory.LEAST_SIG_RFC4122_VARIANT | (node[0] & 0xFFL) //
| ((node[1] & 0xFFL) << 8) //
| ((node[2] & 0xFFL) << 16) //
| ((node[3] & 0xFFL) << 24) //
return (nodeId[0] & 1) != 0;
}
+ static byte[] randomNodeId() {
+ SecureRandom random = new SecureRandom();
+ byte[] nodeId = new byte[6];
+ random.nextBytes(nodeId);
+ return nodeId;
+ }
}
}
/**
- * Always returns <code>true</code> since random UUIDs are by definition not
- * opaque.
+ * Always returns <code>true</code> since random UUIDs are by definition opaque.
*/
@Override
public final boolean isOpaque() {
Duration duration = Duration.between(TimeUuid.TIMESTAMP_ZERO, instant);
return durationToTimestamp(duration);
}
+
+ /**
+ * Crate a time UUID with this instant as timestamp and clock and node id set to
+ * zero.
+ */
+ public static UUID fromInstant(Instant instant) {
+ long timestamp = instantToTimestamp(instant);
+ long mostSig = toMostSignificantBits(timestamp);
+ UUID uuid = new UUID(mostSig, UuidFactory.LEAST_SIG_RFC4122_VARIANT);
+ return uuid;
+ }
+
+ /** Convert timestamp in UUID format to most significant bits of a time UUID. */
+ static long toMostSignificantBits(long timestamp) {
+ long mostSig = UuidFactory.MOST_SIG_VERSION1 | ((timestamp & 0xFFFFFFFFL) << 32) // time_low
+ | (((timestamp >> 32) & 0xFFFFL) << 16) // time_mid
+ | ((timestamp >> 48) & 0x0FFFL);// time_hi_and_version
+ return mostSig;
+ }
}
* {@link Supplier#get()} method MUST be a v4 UUID (random).
*
* @see UUID
- * @see https://datatracker.ietf.org/doc/html/rfc4122
+ * @see "https://datatracker.ietf.org/doc/html/rfc4122"
*/
public interface UuidFactory extends Supplier<UUID> {
* Whether this UUID is time based but was not generated from an IEEE 802
* address, as per Section 4.5 of RFC4122.
*
- * @see https://datatracker.ietf.org/doc/html/rfc4122#section-4.5
+ * @see "https://datatracker.ietf.org/doc/html/rfc4122#section-4.5"
*/
static boolean isTimeBasedWithMacAddress(UUID uuid) {
if (uuid.version() == 1) {
* The state of a time based UUID generator, as described and discussed in
* section 4.2.1 of RFC4122.
*
- * @see https://datatracker.ietf.org/doc/html/rfc4122#section-4.2.1
+ * @see "https://datatracker.ietf.org/doc/html/rfc4122#section-4.2.1"
*/
interface TimeUuidState {
/** Current node id and clock sequence for this thread. */
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-17"/>
+ <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+ <classpathentry kind="src" path="src"/>
+ <classpathentry kind="output" path="bin"/>
+</classpath>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>org.argeo.cms.cli</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
+Main-Class: org.argeo.cms.cli.ArgeoCli
+
+Class-Path: \
+org.argeo.api.acr.2.3.jar \
+org.argeo.api.cli.2.3.jar \
+org.argeo.api.cms.2.3.jar \
+org.argeo.api.uuid.2.3.jar \
+org.argeo.cms.2.3.jar \
+org.argeo.cms.ee.2.3.jar \
+../osgi/equinox/org.argeo.cms/org.argeo.cms.lib.equinox.2.3.jar \
+org.argeo.cms.lib.jetty.2.3.jar \
+org.argeo.cms.lib.sshd.2.3.jar \
+org.argeo.cms.ux.2.3.jar \
+org.argeo.init.2.3.jar \
+org.argeo.util.2.3.jar \
+../org.argeo.tp/com.fasterxml.jackson.core.jackson-annotations.2.13.jar \
+../org.argeo.tp/com.fasterxml.jackson.core.jackson-core.2.13.jar \
+../org.argeo.tp/com.fasterxml.jackson.core.jackson-databind.2.13.jar \
+../org.argeo.tp/com.googlecode.javaewah.JavaEWAH.1.1.jar \
+../org.argeo.tp/de.thjom.java.systemd.2.1.jar \
+../org.argeo.tp/javax.servlet.4.0.jar \
+../org.argeo.tp/javax.websocket.1.1.jar \
+../org.argeo.tp/org.apache.batik.1.16.jar \
+../org.argeo.tp/org.apache.batik.constants.1.16.jar \
+../org.argeo.tp/org.apache.batik.css.1.16.jar \
+../org.argeo.tp/org.apache.batik.i18n.1.16.jar \
+../org.argeo.tp/org.apache.batik.util.1.16.jar \
+../org.argeo.tp/org.apache.commons.cli.1.5.jar \
+../org.argeo.tp/org.apache.commons.fileupload.1.4.jar \
+../org.argeo.tp/org.apache.commons.io.2.11.jar \
+../org.argeo.tp/org.apache.httpcomponents.httpclient.4.5.jar \
+../org.argeo.tp/org.apache.httpcomponents.httpcore.4.4.jar \
+../org.argeo.tp/org.apache.httpcomponents.httpmime.4.5.jar \
+../org.argeo.tp/org.apache.xalan.2.7.jar \
+../org.argeo.tp/org.apache.xerces.2.12.jar \
+../org.argeo.tp/org.apache.xmlgraphics.2.7.jar \
+../org.argeo.tp/org.apache.xml.resolver.1.2.jar \
+../org.argeo.tp/org.argeo.ext.slf4j.2.3.jar \
+../org.argeo.tp/org.eclipse.jgit.6.3.jar \
+../org.argeo.tp/org.freeedesktop.dbus.4.2.jar \
+../org.argeo.tp/org.slf4j.api.1.7.jar \
+../org.argeo.tp/org.slf4j.commons.logging.1.7.jar \
+../org.argeo.tp/org.w3c.css.sac.1.3.jar \
+../org.argeo.tp/org.w3c.dom.smil.1.0.jar \
+../org.argeo.tp/org.w3c.dom.svg.1.1.jar \
+../org.argeo.tp.crypto/bcmail.1.72.jar \
+../org.argeo.tp.crypto/bcpg.1.72.jar \
+../org.argeo.tp.crypto/bcpkix.1.72.jar \
+../org.argeo.tp.crypto/bcprov.1.72.jar \
+../org.argeo.tp.crypto/bcutil.1.72.jar \
+../org.argeo.tp.crypto/net.i2p.crypto.eddsa.0.3.jar \
+../org.argeo.tp.crypto/org.apache.sshd.2.9.jar \
+../org.argeo.tp.crypto/org.apache.sshd.cli.2.9.jar \
+../org.argeo.tp.crypto/org.apache.sshd.git.2.9.jar \
+../org.argeo.tp.crypto/org.apache.sshd.putty.2.9.jar \
+../org.argeo.tp.crypto/org.apache.sshd.scp.2.9.jar \
+../org.argeo.tp.crypto/org.apache.sshd.sftp.2.9.jar \
+../org.argeo.tp.crypto/org.apache.tomcat.jni.9.0.jar \
--- /dev/null
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+ .
--- /dev/null
+package org.argeo.cms.cli;
+
+import org.apache.commons.cli.Option;
+import org.argeo.api.cli.CommandsCli;
+import org.argeo.cms.cli.posix.PosixCommands;
+import org.argeo.cms.ssh.cli.SshCli;
+
+/** Argeo command line tools. */
+public class ArgeoCli extends CommandsCli {
+ public ArgeoCli(String commandName) {
+ super(commandName);
+ // Common options
+ options.addOption(Option.builder("v").hasArg().argName("verbose").desc("verbosity").build());
+ options.addOption(
+ Option.builder("D").hasArgs().argName("property=value").desc("use value for given property").build());
+
+ // common
+ addCommandsCli(new CmsCommands("cms"));
+ addCommandsCli(new SshCli("ssh"));
+ addCommandsCli(new PosixCommands("posix"));
+ addCommandsCli(new FsCommands("fs"));
+ }
+
+ @Override
+ public String getDescription() {
+ return "Argeo CMS utilities";
+ }
+
+ public static void main(String[] args) {
+ mainImpl(new ArgeoCli("argeo"), args);
+ }
+
+}
--- /dev/null
+package org.argeo.cms.cli;
+
+import org.argeo.api.cli.CommandsCli;
+
+public class CmsCli extends CommandsCli {
+
+ public CmsCli(String commandName) {
+ super(commandName);
+ addCommand("launch", new StaticCmsLaunch());
+ }
+
+ @Override
+ public String getDescription() {
+ return "Static CMS utilities.";
+ }
+
+ public static void main(String[] args) {
+ mainImpl(new CmsCli("cms"), args);
+ }
+
+}
--- /dev/null
+package org.argeo.cms.cli;
+
+import java.net.URI;
+import java.util.List;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.Options;
+import org.argeo.api.cli.CommandsCli;
+import org.argeo.api.cli.DescribedCommand;
+import org.argeo.cms.client.CmsClient;
+import org.argeo.cms.client.WebSocketPing;
+
+/** Commands dealing with CMS. */
+public class CmsCommands extends CommandsCli {
+ final static Option connectOption = Option.builder().option("c").longOpt("connect").desc("server to connect to")
+ .hasArg(true).build();
+
+ public CmsCommands(String commandName) {
+ super(commandName);
+ addCommand("ping", new Ping());
+ addCommand("get", new Get());
+ addCommand("status", new Status());
+ addCommand("event", new EventCommands("event"));
+ }
+
+ @Override
+ public String getDescription() {
+ return "Utilities related to an Argeo CMS";
+ }
+
+ class Ping implements DescribedCommand<Void> {
+ @Override
+ public Options getOptions() {
+ Options options = new Options();
+ options.addOption(connectOption);
+ return options;
+ }
+
+ @Override
+ public Void apply(List<String> t) {
+ CommandLine line = toCommandLine(t);
+ String uriArg = line.getOptionValue(connectOption);
+ // TODO make it more robust (trailing /, etc.)
+ URI uri = URI.create(uriArg);
+ if ("".equals(uri.getPath())) {
+ uri = URI.create(uri.toString() + "/cms/status/ping");
+ }
+ new WebSocketPing(uri).run();
+ return null;
+ }
+
+ @Override
+ public String getUsage() {
+ return "[ws|wss]://host:port/";
+ }
+
+ @Override
+ public String getDescription() {
+ return "Test whether an Argeo CMS is available, without auhtentication";
+ }
+
+ }
+
+ class Get implements DescribedCommand<String> {
+
+ @Override
+ public Options getOptions() {
+ Options options = new Options();
+ options.addOption(connectOption);
+ return options;
+ }
+
+ @Override
+ public String apply(List<String> t) {
+ CommandLine line = toCommandLine(t);
+ List<String> remaining = line.getArgList();
+ String additionalUri = null;
+ if (remaining.size() != 0) {
+ additionalUri = remaining.get(0);
+ }
+
+ String connectUri = line.getOptionValue(connectOption);
+ CmsClient cmsClient = new CmsClient(URI.create(connectUri));
+ return additionalUri != null ? cmsClient.getAsString(URI.create(additionalUri)) : cmsClient.getAsString();
+ }
+
+ @Override
+ public String getUsage() {
+ return "[URI]";
+ }
+
+ @Override
+ public String getDescription() {
+ return "Retrieve this URI as a string";
+ }
+
+ }
+
+ class Status implements DescribedCommand<String> {
+
+ @Override
+ public Options getOptions() {
+ Options options = new Options();
+ options.addOption(connectOption);
+ return options;
+ }
+
+ @Override
+ public String apply(List<String> t) {
+ CommandLine line = toCommandLine(t);
+ String connectUri = line.getOptionValue(connectOption);
+ CmsClient cmsClient = new CmsClient(URI.create(connectUri));
+ return cmsClient.getAsString(URI.create("/cms/status"));
+ }
+
+ @Override
+ public String getUsage() {
+ return "[URI]";
+ }
+
+ @Override
+ public String getDescription() {
+ return "Retrieve the CMS status as a string";
+ }
+
+ }
+}
--- /dev/null
+package org.argeo.cms.cli;
+
+import java.net.URI;
+import java.util.List;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Options;
+import org.argeo.api.cli.CommandArgsException;
+import org.argeo.api.cli.CommandsCli;
+import org.argeo.api.cli.DescribedCommand;
+import org.argeo.cms.client.WebSocketEventClient;
+
+/** Commands dealing with CMS events. */
+public class EventCommands extends CommandsCli {
+ public EventCommands(String commandName) {
+ super(commandName);
+ addCommand("listen", new EventListent());
+ }
+
+ @Override
+ public String getDescription() {
+ return "Utilities related to an Argeo CMS";
+ }
+
+ class EventListent implements DescribedCommand<Void> {
+
+ @Override
+ public Options getOptions() {
+ Options options = new Options();
+ options.addOption(CmsCommands.connectOption);
+ return options;
+ }
+
+ @Override
+ public Void apply(List<String> t) {
+ CommandLine line = toCommandLine(t);
+ List<String> remaining = line.getArgList();
+ if (remaining.size() == 0) {
+ throw new CommandArgsException("There must be at least one argument");
+ }
+ String topic = remaining.get(0);
+
+ String uriArg = line.getOptionValue(CmsCommands.connectOption);
+ // TODO make it more robust (trailing /, etc.)
+ URI uri = URI.create(uriArg);
+ if ("".equals(uri.getPath())) {
+ uri = URI.create(uri.toString() + "/cms/status/event/" + topic);
+ }
+ new WebSocketEventClient(uri).run();
+ return null;
+ }
+
+ @Override
+ public String getUsage() {
+ return "TOPIC";
+ }
+
+ @Override
+ public String getDescription() {
+ return "Listen to events on a topic";
+ }
+
+ }
+}
--- /dev/null
+package org.argeo.cms.cli;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.Options;
+import org.argeo.api.cli.CommandArgsException;
+import org.argeo.api.cli.DescribedCommand;
+import org.argeo.cms.file.PathSync;
+import org.argeo.cms.file.SyncResult;
+
+public class FileSync implements DescribedCommand<SyncResult<Path>> {
+ final static Option deleteOption = Option.builder().longOpt("delete").desc("delete from target").build();
+ final static Option recursiveOption = Option.builder("r").longOpt("recursive").desc("recurse into directories")
+ .build();
+ final static Option progressOption = Option.builder().longOpt("progress").hasArg(false).desc("show progress")
+ .build();
+
+ @Override
+ public SyncResult<Path> apply(List<String> t) {
+ try {
+ CommandLine line = toCommandLine(t);
+ List<String> remaining = line.getArgList();
+ if (remaining.size() == 0) {
+ throw new CommandArgsException("There must be at least one argument");
+ }
+ URI sourceUri = new URI(remaining.get(0));
+ URI targetUri;
+ if (remaining.size() == 1) {
+ targetUri = Paths.get(System.getProperty("user.dir")).toUri();
+ } else {
+ targetUri = new URI(remaining.get(1));
+ }
+ boolean delete = line.hasOption(deleteOption.getLongOpt());
+ boolean recursive = line.hasOption(recursiveOption.getLongOpt());
+ PathSync pathSync = new PathSync(sourceUri, targetUri, delete, recursive);
+ return pathSync.call();
+ } catch (URISyntaxException e) {
+ throw new CommandArgsException(e);
+ }
+ }
+
+ @Override
+ public Options getOptions() {
+ Options options = new Options();
+ options.addOption(recursiveOption);
+ options.addOption(deleteOption);
+ options.addOption(progressOption);
+ return options;
+ }
+
+ @Override
+ public String getUsage() {
+ return "[source URI] [target URI]";
+ }
+
+ public static void main(String[] args) {
+ DescribedCommand.mainImpl(new FileSync(), args);
+// Options options = new Options();
+// options.addOption("r", "recursive", false, "recurse into directories");
+// options.addOption(Option.builder().longOpt("progress").hasArg(false).desc("show progress").build());
+//
+// CommandLineParser parser = new DefaultParser();
+// try {
+// CommandLine line = parser.parse(options, args);
+// List<String> remaining = line.getArgList();
+// if (remaining.size() == 0) {
+// System.err.println("There must be at least one argument");
+// printHelp(options);
+// System.exit(1);
+// }
+// URI sourceUri = new URI(remaining.get(0));
+// URI targetUri;
+// if (remaining.size() == 1) {
+// targetUri = Paths.get(System.getProperty("user.dir")).toUri();
+// } else {
+// targetUri = new URI(remaining.get(1));
+// }
+// PathSync pathSync = new PathSync(sourceUri, targetUri);
+// pathSync.run();
+// } catch (Exception exp) {
+// exp.printStackTrace();
+// printHelp(options);
+// System.exit(1);
+// }
+ }
+
+// public static void printHelp(Options options) {
+// HelpFormatter formatter = new HelpFormatter();
+// formatter.printHelp("sync SRC [DEST]", options, true);
+// }
+
+ @Override
+ public String getDescription() {
+ return "Synchronises files";
+ }
+
+}
--- /dev/null
+package org.argeo.cms.cli;
+
+import org.argeo.api.cli.CommandsCli;
+
+/** File utilities. */
+public class FsCommands extends CommandsCli {
+
+ public FsCommands(String commandName) {
+ super(commandName);
+ addCommand("sync", new FileSync());
+ }
+
+ @Override
+ public String getDescription() {
+ return "Utilities around files and file systems";
+ }
+
+}
--- /dev/null
+package org.argeo.cms.cli;
+
+import java.lang.management.ManagementFactory;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.Options;
+import org.argeo.api.cli.DescribedCommand;
+import org.argeo.cms.runtime.StaticCms;
+
+public class StaticCmsLaunch implements DescribedCommand<String> {
+ private Option dataOption;
+
+ @Override
+ public Options getOptions() {
+ Options options = new Options();
+ dataOption = Option.builder().longOpt("data").hasArg().required()
+ .desc("path to the writable data area (mandatory)").build();
+ options.addOption(dataOption);
+ return options;
+ }
+
+ @Override
+ public String apply(List<String> args) {
+ CommandLine cl = toCommandLine(args);
+ String dataPath = cl.getOptionValue(dataOption);
+
+ Path instancePath = Paths.get(dataPath);
+ System.setProperty("osgi.instance.area", instancePath.toUri().toString());
+ System.setProperty("argeo.http.port", "0");
+
+ StaticCms staticCms = new StaticCms();
+ Runtime.getRuntime().addShutdownHook(new Thread(() -> staticCms.stop(), "Static CMS Shutdown"));
+ staticCms.start();
+
+ long jvmUptime = ManagementFactory.getRuntimeMXBean().getUptime();
+ System.out.println("Static CMS available in " + jvmUptime + " ms.");
+
+ staticCms.waitForStop();
+
+ return null;
+ }
+
+ @Override
+ public String getDescription() {
+ return "Launch a static CMS";
+ }
+
+}
--- /dev/null
+package org.argeo.cms.cli.posix;
+
+import java.util.List;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.Options;
+import org.argeo.api.cli.DescribedCommand;
+
+public class Echo implements DescribedCommand<String> {
+
+ @Override
+ public Options getOptions() {
+ Options options = new Options();
+ options.addOption(Option.builder("n").desc("do not output the trailing newline").build());
+ return options;
+ }
+
+ @Override
+ public String getDescription() {
+ return "Display a line of text";
+ }
+
+ @Override
+ public String getUsage() {
+ return "[STRING]...";
+ }
+
+ @Override
+ public String apply(List<String> args) {
+ CommandLine cl = toCommandLine(args);
+
+ StringBuffer sb = new StringBuffer();
+ for (String s : cl.getArgList()) {
+ sb.append(s).append(' ');
+ }
+
+ if (cl.hasOption('n')) {
+ sb.deleteCharAt(sb.length() - 1);
+ } else {
+ sb.setCharAt(sb.length() - 1, '\n');
+ }
+ return sb.toString();
+ }
+
+}
--- /dev/null
+package org.argeo.cms.cli.posix;
+
+import org.argeo.api.cli.CommandsCli;
+
+/** POSIX commands. */
+public class PosixCommands extends CommandsCli {
+
+ public PosixCommands(String commandName) {
+ super(commandName);
+ addCommand("echo", new Echo());
+ }
+
+ @Override
+ public String getDescription() {
+ return "Reimplementation of some POSIX commands in plain Java";
+ }
+
+ public static void main(String[] args) {
+ mainImpl(new PosixCommands("argeo-posix"), args);
+ }
+}
--- /dev/null
+/** Posix CLI commands. */
+package org.argeo.cms.cli.posix;
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-17"/>
+ <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+ <classpathentry kind="src" path="src"/>
+ <classpathentry kind="output" path="bin"/>
+</classpath>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>org.argeo.cms.ee</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" 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
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" immediate="true" immmediate="true" name="Status Handler">
+ <implementation class="org.argeo.cms.websocket.server.StatusHandler"/>
+ <service>
+ <provide interface="com.sun.net.httpserver.HttpHandler"/>
+ </service>
+ <property name="context.path" type="String" value="/cms/status/"/>
+ <reference bind="setCmsState" cardinality="1..1" interface="org.argeo.api.cms.CmsState" name="CmsState" policy="static"/>
+</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,\
+javax.servlet.*;version="[3,5)",\
+*
+
+Service-Component:\
+OSGI-INF/pkgServletContext.xml,\
+OSGI-INF/pkgServlet.xml,\
+OSGI-INF/statusHandler.xml,\
--- /dev/null
+bin.includes = META-INF/,\
+ .,\
+ OSGI-INF/jettyServiceFactory.xml,\
+ OSGI-INF/statusHandler.xml
+source.. = src/
+output.. = bin/
--- /dev/null
+package org.argeo.cms.integration;
+
+import java.io.IOException;
+import java.io.Writer;
+
+import javax.servlet.http.HttpServletResponse;
+
+import org.argeo.api.cms.CmsLog;
+import org.argeo.cms.util.ExceptionsChain;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+/** Serialisable wrapper of a {@link Throwable}. */
+public class CmsExceptionsChain extends ExceptionsChain {
+ public final static CmsLog log = CmsLog.getLog(CmsExceptionsChain.class);
+
+ public CmsExceptionsChain() {
+ super();
+ }
+
+ public CmsExceptionsChain(Throwable exception) {
+ super(exception);
+ if (log.isDebugEnabled())
+ log.error("Exception chain", exception);
+ }
+
+ public String toJsonString(ObjectMapper objectMapper) {
+ try {
+ return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(this);
+ } catch (JsonProcessingException e) {
+ throw new IllegalStateException("Cannot write system exceptions " + toString(), e);
+ }
+ }
+
+ public void writeAsJson(ObjectMapper objectMapper, Writer writer) {
+ try {
+ JsonGenerator jg = objectMapper.writerWithDefaultPrettyPrinter().getFactory().createGenerator(writer);
+ jg.writeObject(this);
+ } catch (IOException e) {
+ throw new IllegalStateException("Cannot write system exceptions " + toString(), e);
+ }
+ }
+
+ public void writeAsJson(ObjectMapper objectMapper, HttpServletResponse resp) {
+ try {
+ resp.setContentType("application/json");
+ resp.setStatus(500);
+ writeAsJson(objectMapper, resp.getWriter());
+ } catch (IOException e) {
+ throw new IllegalStateException("Cannot write system exceptions " + toString(), e);
+ }
+ }
+
+// public static void main(String[] args) throws Exception {
+// try {
+// try {
+// try {
+// testDeeper();
+// } catch (Exception e) {
+// throw new Exception("Less deep exception", e);
+// }
+// } catch (Exception e) {
+// throw new RuntimeException("Top exception", e);
+// }
+// } catch (Exception e) {
+// CmsExceptionsChain systemErrors = new CmsExceptionsChain(e);
+// ObjectMapper objectMapper = new ObjectMapper();
+// System.out.println(objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(systemErrors));
+// System.out.println(objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(e));
+// e.printStackTrace();
+// }
+// }
+//
+// static void testDeeper() throws Exception {
+// throw new IllegalStateException("Deep exception");
+// }
+
+}
--- /dev/null
+package org.argeo.cms.integration;
+
+import java.io.IOException;
+import java.util.Locale;
+import java.util.Set;
+
+import javax.security.auth.Subject;
+import javax.security.auth.callback.Callback;
+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 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.CmsSessionId;
+import org.argeo.cms.auth.RemoteAuthCallback;
+import org.argeo.cms.auth.RemoteAuthCallbackHandler;
+import org.argeo.cms.servlet.ServletHttpRequest;
+import org.argeo.cms.servlet.ServletHttpResponse;
+import org.osgi.service.useradmin.Authorization;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+/** Externally authenticate an http session. */
+public class CmsLoginServlet extends HttpServlet {
+ public final static String PARAM_USERNAME = "username";
+ public final static String PARAM_PASSWORD = "password";
+
+ private static final long serialVersionUID = 2478080654328751539L;
+ private ObjectMapper objectMapper = new ObjectMapper();
+
+ @Override
+ protected void doGet(HttpServletRequest request, HttpServletResponse response)
+ throws ServletException, IOException {
+ doPost(request, response);
+ }
+
+ @Override
+ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+ LoginContext lc = null;
+ String username = req.getParameter(PARAM_USERNAME);
+ String password = req.getParameter(PARAM_PASSWORD);
+ ServletHttpRequest request = new ServletHttpRequest(req);
+ ServletHttpResponse response = new ServletHttpResponse(resp);
+ try {
+ lc = new LoginContext(CmsAuth.LOGIN_CONTEXT_USER, new RemoteAuthCallbackHandler(request, response) {
+ public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
+ for (Callback callback : callbacks) {
+ if (callback instanceof NameCallback && username != null)
+ ((NameCallback) callback).setName(username);
+ else if (callback instanceof PasswordCallback && password != null)
+ ((PasswordCallback) callback).setPassword(password.toCharArray());
+ else if (callback instanceof RemoteAuthCallback) {
+ ((RemoteAuthCallback) callback).setRequest(request);
+ ((RemoteAuthCallback) callback).setResponse(response);
+ }
+ }
+ }
+ });
+ lc.login();
+
+ Subject subject = lc.getSubject();
+ CmsSessionId cmsSessionId = extractFrom(subject.getPrivateCredentials(CmsSessionId.class));
+ if (cmsSessionId == null) {
+ resp.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
+ return;
+ }
+ Authorization authorization = extractFrom(subject.getPrivateCredentials(Authorization.class));
+ Locale locale = extractFrom(subject.getPublicCredentials(Locale.class));
+
+ CmsSessionDescriptor cmsSessionDescriptor = new CmsSessionDescriptor(authorization.getName(),
+ cmsSessionId.getUuid().toString(), authorization.getRoles(), authorization.toString(),
+ locale != null ? locale.toString() : null);
+
+ resp.setContentType("application/json");
+ JsonGenerator jg = objectMapper.getFactory().createGenerator(resp.getWriter());
+ jg.writeObject(cmsSessionDescriptor);
+
+ String redirectTo = redirectTo(req);
+ if (redirectTo != null)
+ resp.sendRedirect(redirectTo);
+ } catch (LoginException e) {
+ resp.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
+ return;
+ }
+ }
+
+ protected <T> T extractFrom(Set<T> creds) {
+ if (creds.size() > 0)
+ return creds.iterator().next();
+ else
+ return null;
+ }
+
+ /**
+ * To be overridden in order to return a richer {@link CmsSessionDescriptor} to
+ * be serialized.
+ */
+ protected CmsSessionDescriptor enrichJson(CmsSessionDescriptor cmsSessionDescriptor) {
+ return cmsSessionDescriptor;
+ }
+
+ protected String redirectTo(HttpServletRequest request) {
+ return null;
+ }
+}
--- /dev/null
+package org.argeo.cms.integration;
+
+import java.io.IOException;
+import java.util.Set;
+
+import javax.security.auth.Subject;
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+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.CmsSessionId;
+import org.argeo.cms.CurrentUser;
+import org.argeo.cms.auth.RemoteAuthCallback;
+import org.argeo.cms.auth.RemoteAuthCallbackHandler;
+import org.argeo.cms.servlet.ServletHttpRequest;
+import org.argeo.cms.servlet.ServletHttpResponse;
+
+/** Externally authenticate an http session. */
+public class CmsLogoutServlet extends HttpServlet {
+ private static final long serialVersionUID = 2478080654328751539L;
+
+ @Override
+ protected void doGet(HttpServletRequest request, HttpServletResponse response)
+ throws ServletException, IOException {
+ doPost(request, response);
+ }
+
+ @Override
+ protected void doPost(HttpServletRequest request, HttpServletResponse response)
+ throws ServletException, IOException {
+ ServletHttpRequest httpRequest = new ServletHttpRequest(request);
+ ServletHttpResponse httpResponse = new ServletHttpResponse(response);
+ LoginContext lc = null;
+ try {
+ lc = new LoginContext(CmsAuth.LOGIN_CONTEXT_USER,
+ new RemoteAuthCallbackHandler(httpRequest, httpResponse) {
+ public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
+ for (Callback callback : callbacks) {
+ if (callback instanceof RemoteAuthCallback) {
+ ((RemoteAuthCallback) callback).setRequest(httpRequest);
+ ((RemoteAuthCallback) callback).setResponse(httpResponse);
+ }
+ }
+ }
+ });
+ lc.login();
+
+ Subject subject = lc.getSubject();
+ CmsSessionId cmsSessionId = extractFrom(subject.getPrivateCredentials(CmsSessionId.class));
+ if (cmsSessionId != null) {// logged in
+ CurrentUser.logoutCmsSession(subject);
+ }
+
+ } catch (LoginException e) {
+ // ignore
+ }
+
+ String redirectTo = redirectTo(request);
+ if (redirectTo != null)
+ response.sendRedirect(redirectTo);
+ }
+
+ protected <T> T extractFrom(Set<T> creds) {
+ if (creds.size() > 0)
+ return creds.iterator().next();
+ else
+ return null;
+ }
+
+ protected String redirectTo(HttpServletRequest request) {
+ return null;
+ }
+}
--- /dev/null
+package org.argeo.cms.integration;
+
+import java.io.IOException;
+import java.security.AccessControlContext;
+import java.util.Map;
+
+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.cms.auth.RemoteAuthCallbackHandler;
+import org.argeo.cms.auth.RemoteAuthUtils;
+import org.argeo.cms.servlet.ServletHttpRequest;
+import org.argeo.cms.servlet.ServletHttpResponse;
+import org.osgi.service.http.context.ServletContextHelper;
+
+/** Manages security access to servlets. */
+public class CmsPrivateServletContext extends ServletContextHelper {
+ public final static String LOGIN_PAGE = "argeo.cms.integration.loginPage";
+ public final static String LOGIN_SERVLET = "argeo.cms.integration.loginServlet";
+ private String loginPage;
+ private String loginServlet;
+
+ public void init(Map<String, String> properties) {
+ loginPage = properties.get(LOGIN_PAGE);
+ loginServlet = properties.get(LOGIN_SERVLET);
+ }
+
+ /**
+ * Add the {@link AccessControlContext} as a request attribute, or redirect to
+ * the login page.
+ */
+ @Override
+ public boolean handleSecurity(final HttpServletRequest req, HttpServletResponse resp) throws IOException {
+ LoginContext lc = null;
+ ServletHttpRequest request = new ServletHttpRequest(req);
+ ServletHttpResponse response = new ServletHttpResponse(resp);
+
+ String pathInfo = req.getPathInfo();
+ String servletPath = req.getServletPath();
+ if ((pathInfo != null && (servletPath + pathInfo).equals(loginPage)) || servletPath.contentEquals(loginServlet))
+ return true;
+ try {
+ lc = CmsAuth.USER.newLoginContext(new RemoteAuthCallbackHandler(request, response));
+ lc.login();
+ } catch (LoginException e) {
+ lc = processUnauthorized(req, resp);
+ if (lc == null)
+ return false;
+ }
+// Subject.doAs(lc.getSubject(), new PrivilegedAction<Void>() {
+//
+// @Override
+// public Void run() {
+// // TODO also set login context in order to log out ?
+// RemoteAuthUtils.configureRequestSecurity(request);
+// return null;
+// }
+//
+// });
+
+ return true;
+ }
+
+// @Override
+// public void finishSecurity(HttpServletRequest req, HttpServletResponse resp) {
+// RemoteAuthUtils.clearRequestSecurity(new ServletHttpRequest(req));
+// }
+
+ protected LoginContext processUnauthorized(HttpServletRequest request, HttpServletResponse response) {
+ try {
+ response.sendRedirect(loginPage);
+ } catch (IOException e) {
+ throw new RuntimeException("Cannot redirect to login page", e);
+ }
+ return null;
+ }
+}
--- /dev/null
+package org.argeo.cms.integration;
+
+import java.io.Serializable;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Set;
+import java.util.TreeSet;
+
+import org.argeo.api.cms.CmsSession;
+import org.osgi.service.useradmin.Authorization;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+
+/** A serializable descriptor of an internal {@link CmsSession}. */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class CmsSessionDescriptor implements Serializable, Authorization {
+ private static final long serialVersionUID = 8592162323372641462L;
+
+ private String name;
+ private String cmsSessionId;
+ private String displayName;
+ private String locale;
+ private Set<String> roles;
+
+ public CmsSessionDescriptor() {
+ }
+
+ public CmsSessionDescriptor(String name, String cmsSessionId, String[] roles, String displayName, String locale) {
+ this.name = name;
+ this.displayName = displayName;
+ this.cmsSessionId = cmsSessionId;
+ this.locale = locale;
+ this.roles = Collections.unmodifiableSortedSet(new TreeSet<>(Arrays.asList(roles)));
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getDisplayName() {
+ return displayName;
+ }
+
+ public void setDisplayName(String displayName) {
+ this.displayName = displayName;
+ }
+
+ public String getCmsSessionId() {
+ return cmsSessionId;
+ }
+
+ public void setCmsSessionId(String cmsSessionId) {
+ this.cmsSessionId = cmsSessionId;
+ }
+
+ public Boolean isAnonymous() {
+ return name == null;
+ }
+
+ public String getLocale() {
+ return locale;
+ }
+
+ public void setLocale(String locale) {
+ this.locale = locale;
+ }
+
+ @Override
+ public boolean hasRole(String name) {
+ return roles.contains(name);
+ }
+
+ @Override
+ public String[] getRoles() {
+ return roles.toArray(new String[roles.size()]);
+ }
+
+ public void setRoles(String[] roles) {
+ this.roles = Collections.unmodifiableSortedSet(new TreeSet<>(Arrays.asList(roles)));
+ }
+
+ @Override
+ public int hashCode() {
+ return cmsSessionId != null ? cmsSessionId.hashCode() : super.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return displayName != null ? displayName : name != null ? name : super.toString();
+ }
+
+}
--- /dev/null
+package org.argeo.cms.integration;
+
+import java.io.IOException;
+import java.time.ZonedDateTime;
+import java.util.Set;
+import java.util.UUID;
+
+import javax.security.auth.Subject;
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+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.acr.ldap.NamingUtils;
+import org.argeo.api.cms.CmsAuth;
+import org.argeo.api.cms.directory.CmsUserManager;
+import org.argeo.cms.auth.RemoteAuthCallback;
+import org.argeo.cms.auth.RemoteAuthCallbackHandler;
+import org.argeo.cms.servlet.ServletHttpRequest;
+import org.argeo.cms.servlet.ServletHttpResponse;
+import org.osgi.service.useradmin.Authorization;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+/** Provides access to tokens. */
+public class CmsTokenServlet extends HttpServlet {
+ private static final long serialVersionUID = 302918711430864140L;
+
+ public final static String PARAM_EXPIRY_DATE = "expiryDate";
+ public final static String PARAM_TOKEN = "token";
+
+ private final static int DEFAULT_HOURS = 24;
+
+ private CmsUserManager userManager;
+ private ObjectMapper objectMapper = new ObjectMapper();
+
+ @Override
+ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+ ServletHttpRequest request = new ServletHttpRequest(req);
+ ServletHttpResponse response = new ServletHttpResponse(resp);
+ LoginContext lc = null;
+ try {
+ lc = new LoginContext(CmsAuth.LOGIN_CONTEXT_USER, new RemoteAuthCallbackHandler(request, response) {
+ public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
+ for (Callback callback : callbacks) {
+ if (callback instanceof RemoteAuthCallback) {
+ ((RemoteAuthCallback) callback).setRequest(request);
+ ((RemoteAuthCallback) callback).setResponse(response);
+ }
+ }
+ }
+ });
+ lc.login();
+ } catch (LoginException e) {
+ // ignore
+ }
+
+ try {
+ Subject subject = lc.getSubject();
+ Authorization authorization = extractFrom(subject.getPrivateCredentials(Authorization.class));
+ String token = UUID.randomUUID().toString();
+ String expiryDateStr = req.getParameter(PARAM_EXPIRY_DATE);
+ ZonedDateTime expiryDate;
+ if (expiryDateStr != null) {
+ expiryDate = NamingUtils.ldapDateToZonedDateTime(expiryDateStr);
+ } else {
+ expiryDate = ZonedDateTime.now().plusHours(DEFAULT_HOURS);
+ expiryDateStr = NamingUtils.instantToLdapDate(expiryDate);
+ }
+ userManager.addAuthToken(authorization.getName(), token, expiryDate);
+
+ TokenDescriptor tokenDescriptor = new TokenDescriptor();
+ tokenDescriptor.setUsername(authorization.getName());
+ tokenDescriptor.setToken(token);
+ tokenDescriptor.setExpiryDate(expiryDateStr);
+// tokenDescriptor.setRoles(Collections.unmodifiableSortedSet(new TreeSet<>(Arrays.asList(roles))));
+
+ resp.setContentType("application/json");
+ JsonGenerator jg = objectMapper.getFactory().createGenerator(resp.getWriter());
+ jg.writeObject(tokenDescriptor);
+ } catch (Exception e) {
+ new CmsExceptionsChain(e).writeAsJson(objectMapper, resp);
+ }
+ }
+
+ @Override
+ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+ // temporarily wrap POST for ease of testing
+ doPost(req, resp);
+ }
+
+ @Override
+ protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+ try {
+ String token = req.getParameter(PARAM_TOKEN);
+ userManager.expireAuthToken(token);
+ } catch (Exception e) {
+ new CmsExceptionsChain(e).writeAsJson(objectMapper, resp);
+ }
+ }
+
+ protected <T> T extractFrom(Set<T> creds) {
+ if (creds.size() > 0)
+ return creds.iterator().next();
+ else
+ return null;
+ }
+
+ public void setUserManager(CmsUserManager userManager) {
+ this.userManager = userManager;
+ }
+}
--- /dev/null
+package org.argeo.cms.integration;
+
+import java.io.Serializable;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+
+/** A serializable descriptor of a token. */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class TokenDescriptor implements Serializable {
+ private static final long serialVersionUID = -6607393871416803324L;
+
+ private String token;
+ private String username;
+ private String expiryDate;
+// private Set<String> roles;
+
+ public String getToken() {
+ return token;
+ }
+
+ public void setToken(String token) {
+ this.token = token;
+ }
+
+ public String getUsername() {
+ return username;
+ }
+
+ public void setUsername(String username) {
+ this.username = username;
+ }
+
+// public Set<String> getRoles() {
+// return roles;
+// }
+//
+// public void setRoles(Set<String> roles) {
+// this.roles = roles;
+// }
+
+ public String getExpiryDate() {
+ return expiryDate;
+ }
+
+ public void setExpiryDate(String expiryDate) {
+ this.expiryDate = expiryDate;
+ }
+
+}
--- /dev/null
+/** Argeo CMS integration (JSON, web services). */
+package org.argeo.cms.integration;
\ No newline at end of file
--- /dev/null
+package org.argeo.cms.servlet;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.Map;
+
+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.RemoteAuthRequest;
+import org.argeo.cms.auth.RemoteAuthResponse;
+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());
+
+ private final String httpAuthRealm = "Argeo";
+ private final boolean forceBasic = false;
+
+ 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);
+ RemoteAuthRequest remoteAuthRequest = new ServletHttpRequest(request);
+ RemoteAuthResponse remoteAuthResponse = new ServletHttpResponse(response);
+ ClassLoader currentThreadContextClassLoader = Thread.currentThread().getContextClassLoader();
+ Thread.currentThread().setContextClassLoader(CmsServletContext.class.getClassLoader());
+ LoginContext lc;
+ try {
+ lc = CmsAuth.USER.newLoginContext(new RemoteAuthCallbackHandler(remoteAuthRequest, remoteAuthResponse));
+ lc.login();
+ } catch (LoginException e) {
+ if (authIsRequired(remoteAuthRequest, remoteAuthResponse)) {
+ int statusCode = RemoteAuthUtils.askForWwwAuth(remoteAuthRequest,
+ remoteAuthResponse, httpAuthRealm,
+ forceBasic);
+ response.setStatus(statusCode);
+ return false;
+
+ } else {
+ lc = RemoteAuthUtils.anonymousLogin(remoteAuthRequest, remoteAuthResponse);
+ }
+ if (lc == null)
+ return false;
+ } finally {
+ Thread.currentThread().setContextClassLoader(currentThreadContextClassLoader);
+ }
+
+// Subject subject = lc.getSubject();
+// Subject.doAs(subject, new PrivilegedAction<Void>() {
+//
+// @Override
+// public Void run() {
+// // TODO also set login context in order to log out ?
+// RemoteAuthUtils.configureRequestSecurity(remoteAuthRequest);
+// return null;
+// }
+//
+// });
+ return true;
+ }
+
+// @Override
+// public void finishSecurity(HttpServletRequest request, HttpServletResponse response) {
+// RemoteAuthUtils.clearRequestSecurity(new ServletHttpRequest(request));
+// }
+
+ protected boolean authIsRequired(RemoteAuthRequest remoteAuthRequest, RemoteAuthResponse remoteAuthResponse) {
+ return false;
+ }
+
+// protected LoginContext processUnauthorized(HttpServletRequest request, HttpServletResponse response) {
+// // anonymous
+// ClassLoader currentContextClassLoader = Thread.currentThread().getContextClassLoader();
+// try {
+// Thread.currentThread().setContextClassLoader(CmsServletContext.class.getClassLoader());
+// LoginContext lc = CmsAuth.ANONYMOUS.newLoginContext(
+// 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;
+// } finally {
+// Thread.currentThread().setContextClassLoader(currentContextClassLoader);
+// }
+// }
+
+ @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 org.argeo.cms.auth.RemoteAuthRequest;
+import org.argeo.cms.auth.RemoteAuthResponse;
+
+/** Servlet context forcing authentication. */
+public class PrivateWwwAuthServletContext extends CmsServletContext {
+ // TODO make it configurable
+// private final String httpAuthRealm = "Argeo";
+// private final boolean forceBasic = false;
+
+ protected boolean authIsRequired(RemoteAuthRequest remoteAuthRequest,
+ RemoteAuthResponse remoteAuthResponse) {
+ return true;
+ }
+
+
+// @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(HttpHeader.WWW_AUTHENTICATE.getName(), HttpHeader.NEGOTIATE);
+// else
+// response.setHeader(HttpHeader.WWW_AUTHENTICATE.getName(),
+// HttpHeader.BASIC + " " + HttpHeader.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 javax.servlet.http.HttpSession;
+
+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() {
+ HttpSession httpSession = request.getSession(false);
+ if (httpSession == null)
+ return null;
+ return new ServletHttpSession(httpSession);
+ }
+
+ @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 headerName, String value) {
+ response.setHeader(headerName, value);
+ }
+
+ @Override
+ public void addHeader(String headerName, String value) {
+ response.addHeader(headerName, 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;
+
+import static org.argeo.cms.http.HttpHeader.VIA;
+import static org.argeo.cms.http.HttpHeader.X_FORWARDED_HOST;
+
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+
+import javax.servlet.http.HttpServletRequest;
+
+/** Servlet utilities. */
+public class ServletUtils {
+
+ /**
+ * The base URL for this query (without any path component (not even an ending
+ * '/'), taking into account reverse proxies.
+ */
+ public static StringBuilder getRequestUrlBase(HttpServletRequest req) {
+ List<String> viaHosts = new ArrayList<>();
+ for (Enumeration<String> it = req.getHeaders(VIA.getHeaderName()); it.hasMoreElements();) {
+ String[] arr = it.nextElement().split(" ");
+ viaHosts.add(arr[1]);
+ }
+
+ String outerHost = viaHosts.isEmpty() ? null : viaHosts.get(0);
+ if (outerHost == null) {
+ // Try non-standard header
+ String forwardedHost = req.getHeader(X_FORWARDED_HOST.getHeaderName());
+ if (forwardedHost != null) {
+ String[] arr = forwardedHost.split(",");
+ outerHost = arr[0];
+ }
+ }
+
+ URI requestUrl = URI.create(req.getRequestURL().toString());
+
+ boolean isReverseProxy = outerHost != null && !outerHost.equals(requestUrl.getHost());
+ if (isReverseProxy) {
+ String protocol = req.isSecure() ? "https" : "http";
+ return new StringBuilder(protocol + "://" + outerHost);
+ } else {
+ return new StringBuilder(requestUrl.getScheme() + "://" + requestUrl.getHost()
+ + (requestUrl.getPort() > 0 ? ":" + requestUrl.getPort() : ""));
+ }
+ }
+
+ /** singleton */
+ private ServletUtils() {
+ }
+}
--- /dev/null
+package org.argeo.cms.servlet.httpserver;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.argeo.cms.auth.RemoteAuthSession;
+import org.argeo.cms.servlet.ServletHttpSession;
+
+import com.sun.net.httpserver.Authenticator;
+import com.sun.net.httpserver.HttpContext;
+import com.sun.net.httpserver.HttpHandler;
+import com.sun.net.httpserver.HttpPrincipal;
+
+/**
+ * An {@link HttpServlet} which integrates an {@link HttpContext} and its
+ * {@link Authenticator} in a servlet container.
+ */
+public class HttpContextServlet extends HttpServlet {
+ private static final long serialVersionUID = 2321612280413662738L;
+
+ private final HttpContext httpContext;
+
+ public HttpContextServlet(HttpContext httpContext) {
+ this.httpContext = httpContext;
+ }
+
+ @Override
+ protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+ try (ServletHttpExchange httpExchange = new ServletHttpExchange(httpContext, req, resp)) {
+ ServletHttpSession httpSession = new ServletHttpSession(req.getSession());
+ httpExchange.setAttribute(RemoteAuthSession.class.getName(), httpSession);
+ Authenticator authenticator = httpContext.getAuthenticator();
+ if (authenticator != null) {
+ Authenticator.Result authenticationResult = authenticator.authenticate(httpExchange);
+ if (authenticationResult instanceof Authenticator.Success) {
+ HttpPrincipal httpPrincipal = ((Authenticator.Success) authenticationResult).getPrincipal();
+ httpExchange.setPrincipal(httpPrincipal);
+ } else if (authenticationResult instanceof Authenticator.Retry) {
+ httpExchange.sendResponseHeaders((((Authenticator.Retry) authenticationResult).getResponseCode()),
+ -1);
+ resp.flushBuffer();
+ return;
+ } else if (authenticationResult instanceof Authenticator.Failure) {
+ httpExchange.sendResponseHeaders(((Authenticator.Failure) authenticationResult).getResponseCode(),
+ -1);
+ resp.flushBuffer();
+ return;
+ } else {
+ throw new UnsupportedOperationException(
+ "Authentication result " + authenticationResult.getClass().getName() + " is not supported");
+ }
+ }
+
+ HttpHandler httpHandler = httpContext.getHandler();
+ httpHandler.handle(httpExchange);
+ }
+ }
+}
--- /dev/null
+package org.argeo.cms.servlet.httpserver;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetSocketAddress;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+
+import javax.net.ssl.SSLSession;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import com.sun.net.httpserver.Headers;
+import com.sun.net.httpserver.HttpContext;
+import com.sun.net.httpserver.HttpPrincipal;
+import com.sun.net.httpserver.HttpsExchange;
+
+/** Integrates {@link HttpsExchange} in a servlet container. */
+class ServletHttpExchange extends HttpsExchange {
+ private final HttpContext httpContext;
+ private final HttpServletRequest httpServletRequest;
+ private final HttpServletResponse httpServletResponse;
+
+ private final Headers requestHeaders;
+ private final Headers responseHeaders;
+
+ private InputStream filteredIn;
+ private OutputStream filteredOut;
+
+ private HttpPrincipal principal;
+
+ public ServletHttpExchange(HttpContext httpContext, HttpServletRequest httpServletRequest,
+ HttpServletResponse httpServletResponse) {
+ this.httpContext = httpContext;
+ this.httpServletRequest = httpServletRequest;
+ this.httpServletResponse = httpServletResponse;
+
+ // request headers
+ requestHeaders = new Headers();
+ for (Enumeration<String> headerNames = httpServletRequest.getHeaderNames(); headerNames.hasMoreElements();) {
+ String headerName = headerNames.nextElement();
+ List<String> values = new ArrayList<>();
+ for (Enumeration<String> headerValues = httpServletRequest.getHeaders(headerName); headerValues
+ .hasMoreElements();)
+ values.add(headerValues.nextElement());
+ requestHeaders.put(headerName, values);
+ }
+
+ responseHeaders = new Headers();
+ }
+
+ @Override
+ public SSLSession getSSLSession() {
+ Object obj = httpServletRequest.getAttribute("javax.net.ssl.session");
+ if (obj == null || !(obj instanceof SSLSession))
+ throw new IllegalStateException("SSL session not found");
+ return (SSLSession) obj;
+ }
+
+ @Override
+ public Headers getRequestHeaders() {
+ return requestHeaders;
+ }
+
+ @Override
+ public Headers getResponseHeaders() {
+ return responseHeaders;
+ }
+
+ @Override
+ public URI getRequestURI() {
+ return URI.create(httpServletRequest.getRequestURI());
+ }
+
+ @Override
+ public String getRequestMethod() {
+ return httpServletRequest.getMethod();
+ }
+
+ @Override
+ public HttpContext getHttpContext() {
+ return httpContext;
+ }
+
+ @Override
+ public void close() {
+ try {
+ httpServletRequest.getInputStream().close();
+ } catch (IOException e) {
+ // TODO use proper logging
+ e.printStackTrace();
+ }
+ try {
+ httpServletResponse.getOutputStream().close();
+ } catch (IOException e) {
+ // TODO use proper logging
+ e.printStackTrace();
+ }
+
+ }
+
+ @Override
+ public InputStream getRequestBody() {
+ try {
+ if (filteredIn != null)
+ return filteredIn;
+ else
+ return httpServletRequest.getInputStream();
+ } catch (IOException e) {
+ throw new IllegalStateException("Cannot get request body", e);
+ }
+ }
+
+ @Override
+ public OutputStream getResponseBody() {
+ try {
+ if (filteredOut != null)
+ return filteredOut;
+ else
+ return httpServletResponse.getOutputStream();
+ } catch (IOException e) {
+ throw new IllegalStateException("Cannot get response body", e);
+ }
+ }
+
+ @Override
+ public void sendResponseHeaders(int rCode, long responseLength) throws IOException {
+ for (String headerName : responseHeaders.keySet()) {
+ for (String headerValue : responseHeaders.get(headerName)) {
+ httpServletResponse.addHeader(headerName, headerValue);
+ }
+ }
+ // TODO deal with content length etc.
+ httpServletResponse.setStatus(rCode);
+ }
+
+ @Override
+ public InetSocketAddress getRemoteAddress() {
+ return new InetSocketAddress(httpServletRequest.getRemoteHost(), httpServletRequest.getRemotePort());
+ }
+
+ @Override
+ public int getResponseCode() {
+ return httpServletResponse.getStatus();
+ }
+
+ @Override
+ public InetSocketAddress getLocalAddress() {
+ return new InetSocketAddress(httpServletRequest.getLocalName(), httpServletRequest.getLocalPort());
+ }
+
+ @Override
+ public String getProtocol() {
+ return httpServletRequest.getProtocol();
+ }
+
+ @Override
+ public Object getAttribute(String name) {
+ return httpServletRequest.getAttribute(name);
+ }
+
+ @Override
+ public void setAttribute(String name, Object value) {
+ httpServletRequest.setAttribute(name, value);
+ }
+
+ @Override
+ public void setStreams(InputStream i, OutputStream o) {
+ if (i != null)
+ filteredIn = i;
+ if (o != null)
+ filteredOut = o;
+
+ }
+
+ @Override
+ public HttpPrincipal getPrincipal() {
+ return principal;
+ }
+
+ void setPrincipal(HttpPrincipal principal) {
+ this.principal = principal;
+ }
+
+}
--- /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.argeo.cms.osgi.FilterRequirement;
+import org.argeo.cms.osgi.PublishNamespace;
+import org.argeo.cms.util.StreamUtils;
+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()) {
+ StreamUtils.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.websocket.server;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+import javax.websocket.Extension;
+import javax.websocket.HandshakeResponse;
+import javax.websocket.server.HandshakeRequest;
+import javax.websocket.server.ServerEndpointConfig;
+import javax.websocket.server.ServerEndpointConfig.Configurator;
+
+import org.argeo.api.cms.CmsAuth;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.cms.auth.RemoteAuthCallbackHandler;
+import org.argeo.cms.auth.RemoteAuthRequest;
+import org.argeo.cms.auth.RemoteAuthResponse;
+import org.argeo.cms.auth.RemoteAuthSession;
+import org.argeo.cms.auth.RemoteAuthUtils;
+import org.argeo.cms.servlet.CmsServletContext;
+
+/**
+ * <strong>Disabled until third party issues are solved.</strong>. Customises
+ * the initialisation of a new web socket.
+ */
+public class CmsWebSocketConfigurator extends Configurator {
+
+ private final static CmsLog log = CmsLog.getLog(CmsWebSocketConfigurator.class);
+
+ private final String httpAuthRealm = "Argeo";
+
+ @Override
+ public boolean checkOrigin(String originHeaderValue) {
+ return true;
+ }
+
+ @Override
+ public <T> T getEndpointInstance(Class<T> endpointClass) throws InstantiationException {
+ try {
+ return endpointClass.getDeclaredConstructor().newInstance();
+ } catch (Exception e) {
+ throw new IllegalArgumentException("Cannot get endpoint instance", e);
+ }
+ }
+
+ @Override
+ public List<Extension> getNegotiatedExtensions(List<Extension> installed, List<Extension> requested) {
+ return requested;
+ }
+
+ @Override
+ public String getNegotiatedSubprotocol(List<String> supported, List<String> requested) {
+ if ((requested == null) || (requested.size() == 0))
+ return "";
+ if ((supported == null) || (supported.isEmpty()))
+ return "";
+ for (String possible : requested) {
+ if (possible == null)
+ continue;
+ if (supported.contains(possible))
+ return possible;
+ }
+ return "";
+ }
+
+ @Override
+ public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {
+// if (true)
+// return;
+
+ WebSocketHandshakeRequest remoteAuthRequest = new WebSocketHandshakeRequest(request);
+ WebSocketHandshakeResponse remoteAuthResponse = new WebSocketHandshakeResponse(response);
+// RemoteAuthSession httpSession = new ServletHttpSession(
+// (javax.servlet.http.HttpSession) request.getHttpSession());
+ RemoteAuthSession remoteAuthSession = remoteAuthRequest.getSession();
+ if (log.isDebugEnabled() && remoteAuthSession != null)
+ log.debug("Web socket HTTP session id: " + remoteAuthSession.getId());
+
+// if (remoteAuthSession == null) {
+// rejectResponse(response, null);
+// }
+ ClassLoader currentThreadContextClassLoader = Thread.currentThread().getContextClassLoader();
+ Thread.currentThread().setContextClassLoader(CmsServletContext.class.getClassLoader());
+ LoginContext lc;
+ try {
+ lc = CmsAuth.USER.newLoginContext(new RemoteAuthCallbackHandler(remoteAuthRequest, remoteAuthResponse));
+ lc.login();
+ } catch (LoginException e) {
+ if (authIsRequired(remoteAuthRequest, remoteAuthResponse)) {
+ int statusCode = RemoteAuthUtils.askForWwwAuth(remoteAuthRequest, remoteAuthResponse, httpAuthRealm,
+ true);
+// remoteAuthResponse.setHeader("Status-Code", Integer.toString(statusCode));
+ return;
+ } else {
+ lc = RemoteAuthUtils.anonymousLogin(remoteAuthRequest, remoteAuthResponse);
+ }
+ if (lc == null) {
+ rejectResponse(response, e);
+ return;
+ }
+ } finally {
+ Thread.currentThread().setContextClassLoader(currentThreadContextClassLoader);
+ }
+
+// Subject subject = lc.getSubject();
+// Subject.doAs(subject, new PrivilegedAction<Void>() {
+//
+// @Override
+// public Void run() {
+// // TODO also set login context in order to log out ?
+// RemoteAuthUtils.configureRequestSecurity(remoteAuthRequest);
+// return null;
+// }
+//
+// });
+ }
+
+ protected boolean authIsRequired(RemoteAuthRequest remoteAuthRequest, RemoteAuthResponse remoteAuthResponse) {
+ return true;
+ }
+
+ /**
+ * Behaviour when the web socket could not be authenticated. Throws an
+ * {@link IllegalStateException} by default.
+ *
+ * @param e can be null
+ */
+ protected void rejectResponse(HandshakeResponse response, Exception e) {
+ response.getHeaders().put(HandshakeResponse.SEC_WEBSOCKET_ACCEPT, new ArrayList<String>());
+ // violent implementation, as suggested in
+ // https://stackoverflow.com/questions/21763829/jsr-356-how-to-abort-a-websocket-connection-during-the-handshake
+// throw new IllegalStateException("Web socket cannot be authenticated");
+ }
+}
--- /dev/null
+package org.argeo.cms.websocket.server;
+
+import java.io.IOException;
+import java.nio.channels.ClosedChannelException;
+import java.util.Map;
+
+import javax.websocket.OnClose;
+import javax.websocket.OnError;
+import javax.websocket.OnOpen;
+import javax.websocket.RemoteEndpoint;
+import javax.websocket.Session;
+import javax.websocket.server.PathParam;
+import javax.websocket.server.ServerEndpoint;
+
+import org.argeo.api.cms.CmsEventBus;
+import org.argeo.api.cms.CmsEventSubscriber;
+import org.argeo.api.cms.CmsLog;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
+
+@ServerEndpoint(value = "/cms/status/event/{topic}", configurator = CmsWebSocketConfigurator.class)
+public class EventEndpoint implements CmsEventSubscriber {
+ private final static CmsLog log = CmsLog.getLog(EventEndpoint.class);
+ private BundleContext bc = FrameworkUtil.getBundle(TestEndpoint.class).getBundleContext();
+
+ private RemoteEndpoint.Basic remote;
+ private CmsEventBus cmsEventBus;
+
+// private String topic = "cms";
+
+ @OnOpen
+ public void onOpen(Session session, @PathParam("topic") String topic) {
+ if (bc != null) {
+ cmsEventBus = bc.getService(bc.getServiceReference(CmsEventBus.class));
+ cmsEventBus.addEventSubscriber(topic, this);
+ }
+ remote = session.getBasicRemote();
+
+ }
+
+ @OnClose
+ public void onClose(@PathParam("topic") String topic) {
+ cmsEventBus.removeEventSubscriber(topic, this);
+ }
+
+ @Override
+ public void onEvent(String topic, Map<String, Object> properties) {
+ try {
+ remote.sendText(topic + ": " + properties);
+ } catch (IOException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ @OnError
+ public void onError(Throwable e) {
+ if (e instanceof ClosedChannelException) {
+ // ignore, as it probably means ping was closed on the other side
+ return;
+ }
+ log.error("Cannot process ping", e);
+ }
+}
--- /dev/null
+package org.argeo.cms.websocket.server;
+
+import java.nio.channels.ClosedChannelException;
+
+import javax.websocket.OnError;
+import javax.websocket.server.ServerEndpoint;
+
+import org.argeo.api.cms.CmsLog;
+
+@ServerEndpoint(value = "/cms/status/ping", configurator = PublicWebSocketConfigurator.class)
+public class PingEndpoint {
+ private final static CmsLog log = CmsLog.getLog(PingEndpoint.class);
+
+ @OnError
+ public void onError(Throwable e) {
+ if (e instanceof ClosedChannelException) {
+ // ignore, as it probably means ping was closed on the other side
+ return;
+ }
+ log.error("Cannot process ping", e);
+ }
+}
--- /dev/null
+package org.argeo.cms.websocket.server;
+
+import org.argeo.cms.auth.RemoteAuthRequest;
+import org.argeo.cms.auth.RemoteAuthResponse;
+
+public class PublicWebSocketConfigurator extends CmsWebSocketConfigurator {
+
+ @Override
+ protected boolean authIsRequired(RemoteAuthRequest remoteAuthRequest, RemoteAuthResponse remoteAuthResponse) {
+ return false;
+ }
+
+}
--- /dev/null
+package org.argeo.cms.websocket.server;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.StringJoiner;
+
+import org.argeo.api.cms.CmsState;
+import org.argeo.cms.CmsDeployProperty;
+
+import com.sun.net.httpserver.HttpExchange;
+import com.sun.net.httpserver.HttpHandler;
+
+public class StatusHandler implements WebsocketEndpoints, HttpHandler {
+ private CmsState cmsState;
+
+ @Override
+ public Set<Class<?>> getEndPoints() {
+ Set<Class<?>> res = new HashSet<>();
+ res.add(PingEndpoint.class);
+ res.add(EventEndpoint.class);
+ res.add(TestEndpoint.class);
+ return res;
+ }
+
+ @Override
+ public void handle(HttpExchange exchange) throws IOException {
+
+ StringJoiner sb = new StringJoiner("\n");
+ CmsDeployProperty[] deployProperties = CmsDeployProperty.values();
+ Arrays.sort(deployProperties, (o1, o2) -> o1.name().compareTo(o2.name()));
+ for (CmsDeployProperty deployProperty : deployProperties) {
+ List<String> values = cmsState.getDeployProperties(deployProperty.getProperty());
+ for (int i = 0; i < values.size(); i++) {
+ String value = values.get(i);
+ if (value != null) {
+ String line = deployProperty.getProperty() + (i == 0 ? "" : "." + i) + "=" + value;
+ sb.add(line);
+ }
+ }
+ }
+
+ byte[] msg = sb.toString().getBytes(StandardCharsets.UTF_8);
+ exchange.sendResponseHeaders(200, msg.length);
+ exchange.getResponseBody().write(msg);
+ }
+
+ public void setCmsState(CmsState cmsState) {
+ this.cmsState = cmsState;
+ }
+
+}
--- /dev/null
+package org.argeo.cms.websocket.server;
+
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+
+import javax.security.auth.Subject;
+import javax.websocket.CloseReason;
+import javax.websocket.EndpointConfig;
+import javax.websocket.OnClose;
+import javax.websocket.OnError;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+import javax.websocket.RemoteEndpoint;
+import javax.websocket.Session;
+import javax.websocket.server.PathParam;
+import javax.websocket.server.ServerEndpoint;
+
+import org.argeo.api.acr.ldap.NamingUtils;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.cms.integration.CmsExceptionsChain;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.event.Event;
+import org.osgi.service.event.EventConstants;
+import org.osgi.service.event.EventHandler;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+/** Provides WebSocket access. */
+@ServerEndpoint(value = "/cms/status/test/{topic}", configurator = CmsWebSocketConfigurator.class)
+public class TestEndpoint implements EventHandler {
+ private final static CmsLog log = CmsLog.getLog(TestEndpoint.class);
+
+ final static String TOPICS_BASE = "/test";
+ final static String INPUT = "input";
+ final static String TOPIC = "topic";
+ final static String VIEW_UID = "viewUid";
+ final static String COMPUTATION_UID = "computationUid";
+ final static String MESSAGES = "messages";
+ final static String ERRORS = "errors";
+
+ final static String EXCEPTION = "exception";
+ final static String MESSAGE = "message";
+
+ private BundleContext bc = FrameworkUtil.getBundle(TestEndpoint.class).getBundleContext();
+
+ private String wsSessionId;
+ private RemoteEndpoint.Basic remote;
+ private ServiceRegistration<EventHandler> eventHandlerSr;
+
+ // json
+ private ObjectMapper objectMapper = new ObjectMapper();
+
+ private WebSocketView view;
+
+ @OnOpen
+ public void onOpen(Session session, EndpointConfig endpointConfig) {
+ Map<String, List<String>> parameters = NamingUtils.queryToMap(session.getRequestURI());
+ String path = NamingUtils.getQueryValue(parameters, "path");
+ log.debug("WS Path: " + path);
+
+ wsSessionId = session.getId();
+
+ // 24h timeout
+ session.setMaxIdleTimeout(1000 * 60 * 60 * 24);
+
+ Map<String, Object> userProperties = session.getUserProperties();
+ Subject subject = null;
+// AccessControlContext accessControlContext = (AccessControlContext) userProperties
+// .get(ServletContextHelper.REMOTE_USER);
+// Subject subject = Subject.getSubject(accessControlContext);
+// // Deal with authentication failure
+// if (subject == null) {
+// try {
+// CloseReason.CloseCode closeCode = new CloseReason.CloseCode() {
+//
+// @Override
+// public int getCode() {
+// return 4001;
+// }
+// };
+// session.close(new CloseReason(closeCode, "Unauthorized"));
+// if (log.isTraceEnabled())
+// log.trace("Unauthorized web socket " + wsSessionId + ". Closing with code " + closeCode.getCode()
+// + ".");
+// return;
+// } catch (IOException e) {
+// // silent
+// }
+// return;// ignore
+// }
+
+ if (log.isDebugEnabled())
+ log.debug("WS#" + wsSessionId + " open for: " + subject);
+ remote = session.getBasicRemote();
+ view = new WebSocketView(subject);
+
+ // OSGi events
+ String[] topics = new String[] { TOPICS_BASE + "/*" };
+ Hashtable<String, Object> ht = new Hashtable<>();
+ ht.put(EventConstants.EVENT_TOPIC, topics);
+ ht.put(EventConstants.EVENT_FILTER, "(" + VIEW_UID + "=" + view.getUid() + ")");
+ eventHandlerSr = bc.registerService(EventHandler.class, this, ht);
+
+ if (log.isDebugEnabled())
+ log.debug("New view " + view.getUid() + " opened, via web socket.");
+ }
+
+ @OnMessage
+ public void onWebSocketText(@PathParam("topic") String topic, Session session, String message)
+ throws JsonMappingException, JsonProcessingException {
+ try {
+ if (log.isTraceEnabled())
+ log.trace("WS#" + view.getUid() + " received:\n" + message + "\n");
+// JsonNode jsonNode = objectMapper.readTree(message);
+// String topic = jsonNode.get(TOPIC).textValue();
+
+ final String computationUid = null;
+// if (MY_TOPIC.equals(topic)) {
+// view.checkRole(SPECIFIC_ROLE);
+// computationUid= process();
+// }
+ remote.sendText("ACK " + topic);
+ } catch (Exception e) {
+ log.error("Error when receiving web socket message", e);
+ sendSystemErrorMessage(e);
+ }
+ }
+
+ @OnClose
+ public void onWebSocketClose(CloseReason reason) {
+ if (eventHandlerSr != null)
+ eventHandlerSr.unregister();
+ if (view != null && log.isDebugEnabled())
+ log.debug("WS#" + view.getUid() + " closed: " + reason);
+ }
+
+ @OnError
+ public void onWebSocketError(Throwable cause) {
+ if (view != null) {
+ log.error("WS#" + view.getUid() + " ERROR", cause);
+ } else {
+ if (log.isTraceEnabled())
+ log.error("Error in web socket session " + wsSessionId, cause);
+ }
+ }
+
+ @Override
+ public void handleEvent(Event event) {
+ try {
+ Object uid = event.getProperty(COMPUTATION_UID);
+ Exception exception = (Exception) event.getProperty(EXCEPTION);
+ if (exception != null) {
+ CmsExceptionsChain systemErrors = new CmsExceptionsChain(exception);
+ String sent = systemErrors.toJsonString(objectMapper);
+ remote.sendText(sent);
+ return;
+ }
+ String topic = event.getTopic();
+ if (log.isTraceEnabled())
+ log.trace("WS#" + view.getUid() + " " + topic + ": notify event " + topic + "#" + uid + ", " + event);
+ } catch (Exception e) {
+ log.error("Error when handling event for WebSocket", e);
+ sendSystemErrorMessage(e);
+ }
+
+ }
+
+ /** Sends an error message in JSON format. */
+ protected void sendSystemErrorMessage(Exception e) {
+ CmsExceptionsChain systemErrors = new CmsExceptionsChain(e);
+ try {
+ if (remote != null)
+ remote.sendText(systemErrors.toJsonString(objectMapper));
+ } catch (Exception e1) {
+ log.error("Cannot send WebSocket system error messages " + systemErrors, e1);
+ }
+ }
+}
--- /dev/null
+package org.argeo.cms.websocket.server;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Objects;
+
+import javax.servlet.http.HttpSession;
+import javax.websocket.server.HandshakeRequest;
+
+import org.argeo.cms.auth.RemoteAuthRequest;
+import org.argeo.cms.auth.RemoteAuthSession;
+import org.argeo.cms.servlet.ServletHttpSession;
+
+public class WebSocketHandshakeRequest implements RemoteAuthRequest {
+ private final HandshakeRequest handshakeRequest;
+ private final HttpSession httpSession;
+
+ private Map<String, Object> attributes = new HashMap<>();
+
+ public WebSocketHandshakeRequest(HandshakeRequest handshakeRequest) {
+ Objects.requireNonNull(handshakeRequest);
+ this.handshakeRequest = handshakeRequest;
+ this.httpSession = (HttpSession) handshakeRequest.getHttpSession();
+// Objects.requireNonNull(this.httpSession);
+ }
+
+ @Override
+ public RemoteAuthSession getSession() {
+ if (httpSession == null)
+ return null;
+ return new ServletHttpSession(httpSession);
+ }
+
+ @Override
+ public RemoteAuthSession createSession() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Locale getLocale() {
+ // TODO check Accept-Language header
+ return Locale.getDefault();
+ }
+
+ @Override
+ public Object getAttribute(String key) {
+ return attributes.get(key);
+ }
+
+ @Override
+ public void setAttribute(String key, Object object) {
+ attributes.put(key, object);
+ }
+
+ @Override
+ public String getHeader(String key) {
+ List<String> values = handshakeRequest.getHeaders().get(key);
+ if (values.size() == 0)
+ return null;
+ if (values.size() > 1)
+ throw new IllegalStateException("More that one value for " + key + ": " + values);
+ return values.get(0);
+ }
+
+ @Override
+ public String getRemoteAddr() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int getLocalPort() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int getRemotePort() {
+ throw new UnsupportedOperationException();
+ }
+
+}
--- /dev/null
+package org.argeo.cms.websocket.server;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import javax.websocket.HandshakeResponse;
+
+import org.argeo.cms.auth.RemoteAuthResponse;
+
+public class WebSocketHandshakeResponse implements RemoteAuthResponse {
+ private final HandshakeResponse handshakeResponse;
+
+ public WebSocketHandshakeResponse(HandshakeResponse handshakeResponse) {
+ this.handshakeResponse = handshakeResponse;
+ }
+
+ @Override
+ public void setHeader(String headerName, String value) {
+ handshakeResponse.getHeaders().put(headerName, Collections.singletonList(value));
+ }
+
+ @Override
+ public void addHeader(String headerName, String value) {
+ List<String> values = handshakeResponse.getHeaders().getOrDefault(headerName, new ArrayList<>());
+ values.add(value);
+ }
+
+}
--- /dev/null
+package org.argeo.cms.websocket.server;
+
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.WebSocket;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.TimeUnit;
+
+/** Tests connectivity to the web socket server. */
+public class WebSocketTest {
+
+ public static void main(String[] args) throws Exception {
+ CompletableFuture<Boolean> received = new CompletableFuture<>();
+ WebSocket.Listener listener = new WebSocket.Listener() {
+
+ public CompletionStage<?> onText(WebSocket webSocket, CharSequence message, boolean last) {
+ System.out.println(message);
+ CompletionStage<String> res = CompletableFuture.completedStage(message.toString());
+ received.complete(true);
+ return res;
+ }
+ };
+
+ HttpClient client = HttpClient.newHttpClient();
+ CompletableFuture<WebSocket> ws = client.newWebSocketBuilder()
+ .buildAsync(URI.create("ws://localhost:7070/cms/status/test/my%20topic?path=my%2Frelative%2Fpath"), listener);
+ WebSocket webSocket = ws.get();
+ webSocket.sendText("TEST", true);
+
+ received.get(10, TimeUnit.SECONDS);
+ webSocket.sendClose(WebSocket.NORMAL_CLOSURE, "");
+ }
+
+}
--- /dev/null
+package org.argeo.cms.websocket.server;
+
+import java.security.Principal;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.UUID;
+
+import javax.security.auth.Subject;
+import javax.security.auth.x500.X500Principal;
+
+import org.osgi.service.useradmin.Role;
+
+/**
+ * Abstraction of a single Frontend view, that is a web browser page. There can
+ * be multiple views within one single authenticated HTTP session.
+ */
+public class WebSocketView {
+ private final String uid;
+ private Subject subject;
+
+ public WebSocketView(Subject subject) {
+ this.uid = UUID.randomUUID().toString();
+ this.subject = subject;
+ }
+
+ public String getUid() {
+ return uid;
+ }
+
+ public Set<String> getRoles() {
+ return roles(subject);
+ }
+
+ public boolean isInRole(String role) {
+ return getRoles().contains(role);
+ }
+
+ public void checkRole(String role) {
+ checkRole(subject, role);
+ }
+
+ public final static Set<String> roles(Subject subject) {
+ Set<String> roles = new HashSet<String>();
+ X500Principal principal = subject.getPrincipals(X500Principal.class).iterator().next();
+ String username = principal.getName();
+ roles.add(username);
+ for (Principal group : subject.getPrincipals()) {
+ if (group instanceof Role)
+ roles.add(group.getName());
+ }
+ return roles;
+ }
+
+ public static void checkRole(Subject subject, String role) {
+ Set<String> roles = roles(subject);
+ if (!roles.contains(role))
+ throw new IllegalStateException("User is not in role " + role);
+ }
+
+}
--- /dev/null
+package org.argeo.cms.websocket.server;
+
+import java.util.Set;
+
+/** Configure web socket in Jetty without hard dependency. */
+public interface WebsocketEndpoints {
+ Set<Class<?>> getEndPoints();
+
+}
--- /dev/null
+/** Argeo CMS websocket integration. */
+package org.argeo.cms.websocket.server;
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-17"/>
+ <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+ <classpathentry kind="src" path="src"/>
+ <classpathentry kind="output" path="bin"/>
+</classpath>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>org.argeo.cms.lib.jetty</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
+Import-Package: \
+javax.servlet.http,\
+org.eclipse.jetty.server.handler,\
+org.eclipse.jetty.util.component,\
+*
\ No newline at end of file
--- /dev/null
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+ .
+additional.bundles = org.eclipse.jetty.io,\
+ org.eclipse.jetty.security,\
+ org.argeo.ext.slf4j,\
+ org.slf4j.api
--- /dev/null
+package org.argeo.cms.jetty;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.concurrent.CompletableFuture;
+
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.websocket.DeploymentException;
+import javax.websocket.server.ServerContainer;
+
+import org.eclipse.jetty.server.session.SessionHandler;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.websocket.javax.server.config.JavaxWebSocketServletContainerInitializer;
+import org.eclipse.jetty.websocket.javax.server.config.JavaxWebSocketServletContainerInitializer.Configurator;
+
+/** A {@link JettyHttpServer} which is compatible with Equinox servlets. */
+public class CmsJettyServer extends JettyHttpServer {
+ private static final String CONTEXT_TEMPDIR = "javax.servlet.context.tempdir";
+ // Equinox compatibility
+ private static final String INTERNAL_CONTEXT_CLASSLOADER = "org.eclipse.equinox.http.jetty.internal.ContextClassLoader";
+ private Path tempDir;
+
+ private CompletableFuture<ServerContainer> serverContainer = new CompletableFuture<>();
+
+ protected void addServlets(ServletContextHandler servletContextHandler) throws ServletException {
+ }
+
+ @Override
+ public void start() {
+ try {
+ tempDir = Files.createTempDirectory("jetty");
+ } catch (IOException e) {
+ throw new IllegalStateException("Cannot create temp dir", e);
+ }
+ super.start();
+ }
+
+ @Override
+ protected ServletContextHandler createRootContextHandler() {
+ ServletContextHandler servletContextHandler = new ServletContextHandler();
+ servletContextHandler.setAttribute(INTERNAL_CONTEXT_CLASSLOADER,
+ Thread.currentThread().getContextClassLoader());
+ servletContextHandler.setClassLoader(this.getClass().getClassLoader());
+ servletContextHandler.setContextPath("/");
+
+ servletContextHandler.setAttribute(CONTEXT_TEMPDIR, tempDir.toAbsolutePath().toFile());
+ SessionHandler handler = new SessionHandler();
+ handler.setMaxInactiveInterval(-1);
+ servletContextHandler.setSessionHandler(handler);
+
+ JavaxWebSocketServletContainerInitializer.configure(servletContextHandler, new Configurator() {
+
+ @Override
+ public void accept(ServletContext servletContext, ServerContainer serverContainer)
+ throws DeploymentException {
+ CmsJettyServer.this.serverContainer.complete(serverContainer);
+ }
+ });
+
+ return servletContextHandler;
+ }
+
+ @Override
+ protected ServerContainer getRootServerContainer() {
+ return serverContainer.join();
+ }
+
+ @Override
+ protected void configureRootContextHandler(ServletContextHandler servletContextHandler) throws ServletException {
+ addServlets(servletContextHandler);
+ }
+
+ /*
+ * WEB SOCKET
+ */
+
+}
--- /dev/null
+package org.argeo.cms.jetty;
+
+import java.util.AbstractMap;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.eclipse.jetty.server.handler.ContextHandler;
+
+/**
+ * A {@link Map} implementation wrapping the attributes of a Jetty
+ * {@link ContextHandler}.
+ */
+class ContextHandlerAttributes extends AbstractMap<String, Object> {
+ private ContextHandler contextHandler;
+
+ public ContextHandlerAttributes(ContextHandler contextHandler) {
+ super();
+ this.contextHandler = contextHandler;
+ }
+
+ @Override
+ public Set<Entry<String, Object>> entrySet() {
+ Set<Entry<String, Object>> entries = new HashSet<>();
+ for (Enumeration<String> keys = contextHandler.getAttributeNames(); keys.hasMoreElements();) {
+ entries.add(new ContextAttributeEntry(keys.nextElement()));
+ }
+ return entries;
+ }
+
+ @Override
+ public Object put(String key, Object value) {
+ Object previousValue = get(key);
+ contextHandler.setAttribute(key, value);
+ return previousValue;
+ }
+
+ private class ContextAttributeEntry implements Map.Entry<String, Object> {
+ private final String key;
+
+ public ContextAttributeEntry(String key) {
+ this.key = key;
+ }
+
+ @Override
+ public String getKey() {
+ return key;
+ }
+
+ @Override
+ public Object getValue() {
+ return contextHandler.getAttribute(key);
+ }
+
+ @Override
+ public Object setValue(Object value) {
+ Object previousValue = getValue();
+ contextHandler.setAttribute(key, value);
+ return previousValue;
+ }
+
+ }
+}
--- /dev/null
+package org.argeo.cms.jetty;
+
+import java.util.AbstractMap;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import javax.servlet.ServletContext;
+import javax.websocket.DeploymentException;
+import javax.websocket.server.ServerContainer;
+
+import org.argeo.cms.servlet.httpserver.HttpContextServlet;
+import org.argeo.cms.websocket.server.WebsocketEndpoints;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.server.session.SessionHandler;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.eclipse.jetty.websocket.javax.server.config.JavaxWebSocketServletContainerInitializer;
+import org.eclipse.jetty.websocket.javax.server.config.JavaxWebSocketServletContainerInitializer.Configurator;
+
+import com.sun.net.httpserver.HttpHandler;
+
+/**
+ * An @{HttpContext} implementation based on a Jetty
+ * {@link ServletContextHandler}.
+ */
+class ContextHandlerHttpContext extends JettyHttpContext {
+ private final ServletContextHandler servletContextHandler;
+ private final ContextHandlerAttributes attributes;
+
+ public ContextHandlerHttpContext(JettyHttpServer httpServer, String path) {
+ super(httpServer, path);
+
+ // Jetty context handler
+ this.servletContextHandler = new ServletContextHandler();
+ servletContextHandler.setContextPath(path);
+ HttpContextServlet servlet = new HttpContextServlet(this);
+ servletContextHandler.addServlet(new ServletHolder(servlet), "/*");
+ SessionHandler sessionHandler = new SessionHandler();
+ // FIXME find a better default
+ sessionHandler.setMaxInactiveInterval(-1);
+ servletContextHandler.setSessionHandler(sessionHandler);
+
+ attributes = new ContextHandlerAttributes(servletContextHandler);
+ }
+
+ @Override
+ public void setHandler(HttpHandler handler) {
+ super.setHandler(handler);
+
+ // web socket
+ if (handler instanceof WebsocketEndpoints) {
+ JavaxWebSocketServletContainerInitializer.configure(servletContextHandler, new Configurator() {
+
+ @Override
+ public void accept(ServletContext servletContext, ServerContainer serverContainer)
+ throws DeploymentException {
+ for (Class<?> clss : ((WebsocketEndpoints) handler).getEndPoints()) {
+ serverContainer.addEndpoint(clss);
+ }
+ }
+ });
+ }
+
+ if (getJettyHttpServer().isStarted())
+ try {
+ servletContextHandler.start();
+ } catch (Exception e) {
+ throw new IllegalStateException("Cannot start context handler", e);
+ }
+ }
+
+ @Override
+ public Map<String, Object> getAttributes() {
+ return attributes;
+ }
+
+ @Override
+ protected ServletContextHandler getServletContextHandler() {
+ return servletContextHandler;
+ }
+
+}
--- /dev/null
+package org.argeo.cms.jetty;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+import javax.servlet.ServletContext;
+import javax.websocket.DeploymentException;
+import javax.websocket.server.ServerContainer;
+
+import org.argeo.cms.websocket.server.WebsocketEndpoints;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.websocket.javax.server.config.JavaxWebSocketServletContainerInitializer;
+import org.eclipse.jetty.websocket.javax.server.config.JavaxWebSocketServletContainerInitializer.Configurator;
+
+import com.sun.net.httpserver.Authenticator;
+import com.sun.net.httpserver.Filter;
+import com.sun.net.httpserver.HttpContext;
+import com.sun.net.httpserver.HttpHandler;
+import com.sun.net.httpserver.HttpServer;
+
+/**
+ * An @{HttpContext} implementation based on Jetty. It supports web sockets if
+ * the handler implements {@link WebsocketEndpoints}.
+ */
+abstract class JettyHttpContext extends HttpContext {
+ private final JettyHttpServer httpServer;
+ private final String path;
+ private final List<Filter> filters = new ArrayList<>();
+
+ private HttpHandler handler;
+ private Authenticator authenticator;
+
+ public JettyHttpContext(JettyHttpServer httpServer, String path) {
+ this.httpServer = httpServer;
+ if (!path.endsWith("/"))
+ throw new IllegalArgumentException("Path " + path + " should end with a /");
+ this.path = path;
+ }
+
+ protected abstract ServletContextHandler getServletContextHandler();
+
+ @Override
+ public HttpHandler getHandler() {
+ return handler;
+ }
+
+ @Override
+ public void setHandler(HttpHandler handler) {
+ if (this.handler != null)
+ throw new IllegalArgumentException("Handler is already set");
+ Objects.requireNonNull(handler);
+ this.handler = handler;
+ }
+
+ @Override
+ public String getPath() {
+ return path;
+ }
+
+ @Override
+ public HttpServer getServer() {
+ return getJettyHttpServer();
+ }
+
+ protected JettyHttpServer getJettyHttpServer() {
+ return httpServer;
+ }
+
+ @Override
+ public List<Filter> getFilters() {
+ return filters;
+ }
+
+ @Override
+ public Authenticator setAuthenticator(Authenticator auth) {
+ Authenticator previousAuthenticator = authenticator;
+ this.authenticator = auth;
+ return previousAuthenticator;
+ }
+
+ @Override
+ public Authenticator getAuthenticator() {
+ return authenticator;
+ }
+
+}
--- /dev/null
+package org.argeo.cms.jetty;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ThreadPoolExecutor;
+
+import javax.servlet.ServletException;
+import javax.websocket.server.ServerContainer;
+
+import org.argeo.api.cms.CmsLog;
+import org.argeo.api.cms.CmsState;
+import org.argeo.cms.CmsDeployProperty;
+import org.argeo.cms.http.server.HttpServerUtils;
+import org.eclipse.jetty.http.UriCompliance;
+import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.server.HttpConnectionFactory;
+import org.eclipse.jetty.server.SecureRequestCustomizer;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.SslConnectionFactory;
+import org.eclipse.jetty.server.handler.ContextHandlerCollection;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.eclipse.jetty.util.thread.ExecutorThreadPool;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+import org.eclipse.jetty.util.thread.ThreadPool;
+
+import com.sun.net.httpserver.HttpContext;
+import com.sun.net.httpserver.HttpHandler;
+import com.sun.net.httpserver.HttpsConfigurator;
+import com.sun.net.httpserver.HttpsServer;
+
+/** An {@link HttpServer} implementation based on Jetty. */
+public class JettyHttpServer extends HttpsServer {
+ private final static CmsLog log = CmsLog.getLog(JettyHttpServer.class);
+
+ private static final int DEFAULT_IDLE_TIMEOUT = 30000;
+
+ private Server server;
+
+ protected ServerConnector httpConnector;
+ protected ServerConnector httpsConnector;
+
+ private InetSocketAddress httpAddress;
+ private InetSocketAddress httpsAddress;
+
+ private ThreadPoolExecutor executor;
+
+ private HttpsConfigurator httpsConfigurator;
+
+ private final Map<String, JettyHttpContext> contexts = new TreeMap<>();
+
+ private ServletContextHandler rootContextHandler;
+ protected final ContextHandlerCollection contextHandlerCollection = new ContextHandlerCollection();
+
+ private boolean started;
+
+ private CmsState cmsState;
+
+ @Override
+ public void bind(InetSocketAddress addr, int backlog) throws IOException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void start() {
+ try {
+
+ ThreadPool threadPool = null;
+ if (executor != null) {
+ threadPool = new ExecutorThreadPool(executor);
+ } else {
+ // TODO make it configurable
+ threadPool = new QueuedThreadPool(10, 1);
+ }
+
+ server = new Server(threadPool);
+
+ configureConnectors();
+
+ if (httpConnector != null) {
+ httpConnector.open();
+ server.addConnector(httpConnector);
+ }
+
+ if (httpsConnector != null) {
+ httpsConnector.open();
+ server.addConnector(httpsConnector);
+ }
+
+ // holder
+
+ // context
+ rootContextHandler = createRootContextHandler();
+ // httpContext.addServlet(holder, "/*");
+ if (rootContextHandler != null)
+ configureRootContextHandler(rootContextHandler);
+
+ if (rootContextHandler != null && !contexts.containsKey("/"))
+ contextHandlerCollection.addHandler(rootContextHandler);
+
+ server.setHandler(contextHandlerCollection);
+
+ //
+ // START
+ server.start();
+ //
+
+ // Addresses
+ String httpHost = getDeployProperty(CmsDeployProperty.HOST);
+ String fallBackHostname = cmsState != null ? cmsState.getHostname() : "::1";
+ if (httpConnector != null) {
+ httpAddress = new InetSocketAddress(httpHost != null ? httpHost : fallBackHostname,
+ httpConnector.getLocalPort());
+ } else if (httpsConnector != null) {
+ httpsAddress = new InetSocketAddress(httpHost != null ? httpHost : fallBackHostname,
+ httpsConnector.getLocalPort());
+ }
+ // Clean up
+ Runtime.getRuntime().addShutdownHook(new Thread(() -> stop(), "Jetty shutdown"));
+
+ log.info(httpPortsMsg());
+ started = true;
+ } catch (Exception e) {
+ stop();
+ throw new IllegalStateException("Cannot start Jetty HTTP server", e);
+ }
+ }
+
+ protected void configureConnectors() {
+ String httpPortStr = getDeployProperty(CmsDeployProperty.HTTP_PORT);
+ String httpsPortStr = getDeployProperty(CmsDeployProperty.HTTPS_PORT);
+ if (httpPortStr != null && httpsPortStr != null)
+ throw new IllegalArgumentException("Either an HTTP or an HTTPS port should be configured, not both");
+ if (httpPortStr == null && httpsPortStr == null)
+ throw new IllegalArgumentException("Neither an HTTP or HTTPS port was configured");
+
+ /// TODO make it more generic
+ String httpHost = getDeployProperty(CmsDeployProperty.HOST);
+
+ // try {
+ if (httpPortStr != null || httpsPortStr != null) {
+ // TODO deal with hostname resolving taking too much time
+// String fallBackHostname = InetAddress.getLocalHost().getHostName();
+
+ boolean httpEnabled = httpPortStr != null;
+ boolean httpsEnabled = httpsPortStr != null;
+
+ if (httpEnabled) {
+ HttpConfiguration httpConfiguration = new HttpConfiguration();
+
+ if (httpsEnabled) {// not supported anymore to have both http and https, but it may change again
+ int httpsPort = Integer.parseInt(httpsPortStr);
+ httpConfiguration.setSecureScheme("https");
+ httpConfiguration.setSecurePort(httpsPort);
+ }
+
+ int httpPort = Integer.parseInt(httpPortStr);
+ httpConnector = new ServerConnector(server, new HttpConnectionFactory(httpConfiguration));
+ httpConnector.setPort(httpPort);
+ httpConnector.setHost(httpHost);
+ httpConnector.setIdleTimeout(DEFAULT_IDLE_TIMEOUT);
+
+ }
+
+ if (httpsEnabled) {
+ SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
+ // sslContextFactory.setKeyStore(KeyS)
+
+ sslContextFactory.setKeyStoreType(getDeployProperty(CmsDeployProperty.SSL_KEYSTORETYPE));
+ sslContextFactory.setKeyStorePath(getDeployProperty(CmsDeployProperty.SSL_KEYSTORE));
+ sslContextFactory.setKeyStorePassword(getDeployProperty(CmsDeployProperty.SSL_PASSWORD));
+ // sslContextFactory.setKeyManagerPassword(getFrameworkProp(CmsDeployProperty.SSL_KEYPASSWORD));
+ sslContextFactory.setProtocol("TLS");
+
+ sslContextFactory.setTrustStoreType(getDeployProperty(CmsDeployProperty.SSL_TRUSTSTORETYPE));
+ sslContextFactory.setTrustStorePath(getDeployProperty(CmsDeployProperty.SSL_TRUSTSTORE));
+ sslContextFactory.setTrustStorePassword(getDeployProperty(CmsDeployProperty.SSL_TRUSTSTOREPASSWORD));
+
+ String wantClientAuth = getDeployProperty(CmsDeployProperty.SSL_WANTCLIENTAUTH);
+ if (wantClientAuth != null && wantClientAuth.equals(Boolean.toString(true)))
+ sslContextFactory.setWantClientAuth(true);
+ String needClientAuth = getDeployProperty(CmsDeployProperty.SSL_NEEDCLIENTAUTH);
+ if (needClientAuth != null && needClientAuth.equals(Boolean.toString(true)))
+ sslContextFactory.setNeedClientAuth(true);
+
+ // HTTPS Configuration
+ HttpConfiguration httpsConfiguration = new HttpConfiguration();
+ httpsConfiguration.addCustomizer(new SecureRequestCustomizer());
+ httpsConfiguration.setUriCompliance(UriCompliance.LEGACY);
+
+ // HTTPS connector
+ httpsConnector = new ServerConnector(server, new SslConnectionFactory(sslContextFactory, "http/1.1"),
+ new HttpConnectionFactory(httpsConfiguration));
+ int httpsPort = Integer.parseInt(httpsPortStr);
+ httpsConnector.setPort(httpsPort);
+ httpsConnector.setHost(httpHost);
+ }
+ }
+ }
+
+ @Override
+ public void stop(int delay) {
+ // TODO wait for processing to complete
+ stop();
+
+ }
+
+ public void stop() {
+ try {
+ server.stop();
+ // TODO delete temp dir
+ started = false;
+ } catch (Exception e) {
+ log.error("Cannot stop Jetty HTTP server", e);
+ }
+
+ }
+
+ @Override
+ public void setExecutor(Executor executor) {
+ if (!(executor instanceof ThreadPoolExecutor))
+ throw new IllegalArgumentException("Only " + ThreadPoolExecutor.class.getName() + " are supported");
+ this.executor = (ThreadPoolExecutor) executor;
+ }
+
+ @Override
+ public Executor getExecutor() {
+ return executor;
+ }
+
+ @Override
+ public synchronized HttpContext createContext(String path, HttpHandler handler) {
+ HttpContext httpContext = createContext(path);
+ httpContext.setHandler(handler);
+ return httpContext;
+ }
+
+ @Override
+ public synchronized HttpContext createContext(String path) {
+ if (!path.endsWith("/"))
+ path = path + "/";
+ if (contexts.containsKey(path))
+ throw new IllegalArgumentException("Context " + path + " already exists");
+
+ JettyHttpContext httpContext = new ServletHttpContext(this, path);
+ contexts.put(path, httpContext);
+
+ contextHandlerCollection.addHandler(httpContext.getServletContextHandler());
+ return httpContext;
+ }
+
+ @Override
+ public synchronized void removeContext(String path) throws IllegalArgumentException {
+ if (!contexts.containsKey(path))
+ throw new IllegalArgumentException("Context " + path + " does not exist");
+ JettyHttpContext httpContext = contexts.remove(path);
+ if (httpContext instanceof ContextHandlerHttpContext contextHandlerHttpContext) {
+ // TODO stop handler first?
+ contextHandlerCollection.removeHandler(contextHandlerHttpContext.getServletContextHandler());
+ }
+ }
+
+ @Override
+ public synchronized void removeContext(HttpContext context) {
+ removeContext(context.getPath());
+ }
+
+ @Override
+ public InetSocketAddress getAddress() {
+ InetSocketAddress res = httpAddress != null ? httpAddress : httpsAddress;
+ if (res == null)
+ throw new IllegalStateException("Neither an HTTP nor and HTTPS address is available");
+ return res;
+ }
+
+ @Override
+ public void setHttpsConfigurator(HttpsConfigurator config) {
+ this.httpsConfigurator = config;
+ }
+
+ @Override
+ public HttpsConfigurator getHttpsConfigurator() {
+ return httpsConfigurator;
+ }
+
+ protected String getDeployProperty(CmsDeployProperty deployProperty) {
+ return cmsState != null ? cmsState.getDeployProperty(deployProperty.getProperty())
+ : System.getProperty(deployProperty.getProperty());
+ }
+
+ private String httpPortsMsg() {
+
+ return (httpConnector != null ? "HTTP " + getHttpPort() + " " : "")
+ + (httpsConnector != null ? "HTTPS " + getHttpsPort() : "");
+ }
+
+ public Integer getHttpPort() {
+ if (httpConnector == null)
+ return null;
+ return httpConnector.getLocalPort();
+ }
+
+ public Integer getHttpsPort() {
+ if (httpsConnector == null)
+ return null;
+ return httpsConnector.getLocalPort();
+ }
+
+ protected ServletContextHandler createRootContextHandler() {
+ return null;
+ }
+
+ protected void configureRootContextHandler(ServletContextHandler servletContextHandler) throws ServletException {
+
+ }
+
+ public void setCmsState(CmsState cmsState) {
+ this.cmsState = cmsState;
+ }
+
+ boolean isStarted() {
+ return started;
+ }
+
+ ServletContextHandler getRootContextHandler() {
+ return rootContextHandler;
+ }
+
+ ServerContainer getRootServerContainer() {
+ throw new UnsupportedOperationException();
+ }
+
+ public static void main(String... args) {
+ JettyHttpServer httpServer = new JettyHttpServer();
+ System.setProperty("argeo.http.port", "8080");
+ httpServer.createContext("/", (exchange) -> {
+ exchange.getResponseBody().write("Hello World!".getBytes());
+ });
+ httpServer.start();
+ httpServer.createContext("/sub/context", (exchange) -> {
+ final String key = "count";
+ Integer count = (Integer) exchange.getHttpContext().getAttributes().get(key);
+ if (count == null)
+ exchange.getHttpContext().getAttributes().put(key, 0);
+ else
+ exchange.getHttpContext().getAttributes().put(key, count + 1);
+ StringBuilder sb = new StringBuilder();
+ sb.append("Subcontext:");
+ sb.append(" " + key + "=" + exchange.getHttpContext().getAttributes().get(key));
+ sb.append(" relativePath=" + HttpServerUtils.relativize(exchange));
+ exchange.getResponseBody().write(sb.toString().getBytes());
+ });
+ }
+}
--- /dev/null
+package org.argeo.cms.jetty;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.websocket.DeploymentException;
+import javax.websocket.server.ServerContainer;
+import javax.websocket.server.ServerEndpointConfig;
+
+import org.argeo.api.cms.CmsLog;
+import org.argeo.cms.servlet.httpserver.HttpContextServlet;
+import org.argeo.cms.websocket.server.WebsocketEndpoints;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+
+import com.sun.net.httpserver.HttpHandler;
+
+/**
+ * A {@link JettyHttpContext} based on registering a servlet to the root handler
+ * of the {@link JettyHttpServer}, in order to integrate the sessions.
+ */
+public class ServletHttpContext extends JettyHttpContext {
+ private final static CmsLog log = CmsLog.getLog(ServletHttpContext.class);
+
+ private Map<String, Object> attributes = Collections.synchronizedMap(new HashMap<>());
+
+ public ServletHttpContext(JettyHttpServer httpServer, String path) {
+ super(httpServer, path);
+
+ ServletContextHandler rootContextHandler = httpServer.getRootContextHandler();
+ HttpContextServlet servlet = new HttpContextServlet(this);
+ rootContextHandler.addServlet(new ServletHolder(servlet), path + "*");
+ }
+
+ @Override
+ public void setHandler(HttpHandler handler) {
+ super.setHandler(handler);
+
+ // web socket
+ if (handler instanceof WebsocketEndpoints) {
+ ServerContainer serverContainer = getJettyHttpServer().getRootServerContainer();
+ for (Class<?> clss : ((WebsocketEndpoints) handler).getEndPoints()) {
+ try {
+ serverContainer.addEndpoint(clss);
+ log.debug(() -> "Added web socket " + clss + " to " + getPath());
+ } catch (DeploymentException e) {
+ log.error("Cannot deploy Web Socket " + clss, e);
+ }
+ }
+ }
+ }
+
+ @Override
+ public Map<String, Object> getAttributes() {
+ return attributes;
+ }
+
+ @Override
+ protected ServletContextHandler getServletContextHandler() {
+ return getJettyHttpServer().getRootContextHandler();
+ }
+
+}
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-17"/>
+ <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+ <classpathentry kind="src" path="src"/>
+ <classpathentry kind="output" path="bin"/>
+</classpath>
--- /dev/null
+/hostkey.ser
+/id_rsa
+/id_rsa.pub
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>org.argeo.cms.lib.sshd</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="CMS SSH Server" immediate="true">
+ <implementation class="org.argeo.cms.ssh.CmsSshServer"/>
+ <reference bind="setCmsState" cardinality="1..1" interface="org.argeo.api.cms.CmsState" name="CmsState" policy="static"/>
+ <service>
+ <provide interface="org.argeo.cms.CmsSshd"/>
+ </service>
+</scr:component>
--- /dev/null
+Import-Package: \
+org.apache.sshd.server.forward,\
+org.apache.sshd.common.forward,\
+org.apache.sshd.common.channel,\
+org.apache.sshd.common.helpers,\
+org.apache.sshd.common.file.util,\
+*
+
+Service-Component: \
+OSGI-INF/cmsSshServer.xml
--- /dev/null
+output.. = bin/
+bin.includes = META-INF/,\
+ .,\
+ OSGI-INF/
+source.. = src/
+additional.bundles = org.slf4j.api,\
+ org.argeo.ext.slf4j,\
+ org.apache.tomcat.jni
+
\ No newline at end of file
--- /dev/null
+package org.argeo.cms.bc;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Reader;
+import java.math.BigInteger;
+import java.net.InetAddress;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.KeyStore;
+import java.security.PrivateKey;
+import java.security.SecureRandom;
+import java.security.Security;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
+import java.util.Date;
+
+import javax.security.auth.x500.X500Principal;
+
+import org.argeo.api.cms.CmsLog;
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cert.X509v3CertificateBuilder;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
+import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.openssl.PEMParser;
+import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
+import org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8DecryptorProviderBuilder;
+import org.bouncycastle.operator.ContentSigner;
+import org.bouncycastle.operator.InputDecryptorProvider;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo;
+import org.bouncycastle.pkcs.PKCSException;
+
+/** Utilities around the BouncyCastle crypto library. */
+public class BcUtils {
+ private final static CmsLog log = CmsLog.getLog(BcUtils.class);
+
+ private final static String BC_SECURITY_PROVIDER;
+ static {
+ Security.addProvider(new BouncyCastleProvider());
+ BC_SECURITY_PROVIDER = "BC";
+ }
+
+ public static void createSelfSignedKeyStore(Path keyStorePath, char[] keyStorePassword, String keyStoreType) {
+ // for (Provider provider : Security.getProviders())
+ // System.out.println(provider.getName());
+ // File keyStoreFile = keyStorePath.toFile();
+ char[] keyPwd = Arrays.copyOf(keyStorePassword, keyStorePassword.length);
+ if (!Files.exists(keyStorePath)) {
+ try {
+ Files.createDirectories(keyStorePath.getParent());
+ KeyStore keyStore = getKeyStore(keyStorePath, keyStorePassword, keyStoreType);
+ generateSelfSignedCertificate(keyStore,
+ new X500Principal("CN=" + InetAddress.getLocalHost().getHostName() + ",OU=UNSECURE,O=UNSECURE"),
+ 1024, keyPwd);
+ saveKeyStore(keyStorePath, keyStorePassword, keyStore);
+ if (log.isDebugEnabled())
+ log.debug("Created self-signed unsecure keystore " + keyStorePath);
+ } catch (Exception e) {
+ try {
+ if (Files.size(keyStorePath) == 0)
+ Files.delete(keyStorePath);
+ } catch (IOException e1) {
+ // silent
+ }
+ log.error("Cannot create keystore " + keyStorePath, e);
+ }
+ } else {
+ throw new IllegalStateException("Keystore " + keyStorePath + " already exists");
+ }
+ }
+
+ public static X509Certificate generateSelfSignedCertificate(KeyStore keyStore, X500Principal x500Principal,
+ int keySize, char[] keyPassword) {
+ try {
+ KeyPairGenerator kpGen = KeyPairGenerator.getInstance("RSA", BC_SECURITY_PROVIDER);
+ kpGen.initialize(keySize, new SecureRandom());
+ KeyPair pair = kpGen.generateKeyPair();
+ Date notBefore = new Date(System.currentTimeMillis() - 10000);
+ Date notAfter = new Date(System.currentTimeMillis() + 365 * 24L * 3600 * 1000);
+ BigInteger serial = BigInteger.valueOf(System.currentTimeMillis());
+ X509v3CertificateBuilder certGen = new JcaX509v3CertificateBuilder(x500Principal, serial, notBefore,
+ notAfter, x500Principal, pair.getPublic());
+ ContentSigner sigGen = new JcaContentSignerBuilder("SHA256WithRSAEncryption")
+ .setProvider(BC_SECURITY_PROVIDER).build(pair.getPrivate());
+ X509Certificate cert = new JcaX509CertificateConverter().setProvider(BC_SECURITY_PROVIDER)
+ .getCertificate(certGen.build(sigGen));
+ cert.checkValidity(new Date());
+ cert.verify(cert.getPublicKey());
+
+ keyStore.setKeyEntry(x500Principal.getName(), pair.getPrivate(), keyPassword, new Certificate[] { cert });
+ return cert;
+ } catch (GeneralSecurityException | OperatorCreationException e) {
+ throw new RuntimeException("Cannot generate self-signed certificate", e);
+ }
+ }
+
+ public static PrivateKey loadPemPrivateKey(Reader reader, char[] keyPassword) {
+ try (PEMParser pemParser = new PEMParser(reader)) {
+ JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider(BC_SECURITY_PROVIDER);
+ Object object = pemParser.readObject();
+ PrivateKeyInfo privateKeyInfo;
+ if (object instanceof PKCS8EncryptedPrivateKeyInfo) {
+ if (keyPassword == null)
+ throw new IllegalArgumentException("A key password is required");
+ InputDecryptorProvider decProv = new JceOpenSSLPKCS8DecryptorProviderBuilder().build(keyPassword);
+ privateKeyInfo = ((PKCS8EncryptedPrivateKeyInfo) object).decryptPrivateKeyInfo(decProv);
+ } else if (object instanceof PrivateKeyInfo) {
+ privateKeyInfo = (PrivateKeyInfo) object;
+ } else {
+ throw new IllegalArgumentException("Unsupported format for private key");
+ }
+ return converter.getPrivateKey(privateKeyInfo);
+ } catch (IOException | OperatorCreationException | PKCSException e) {
+ throw new RuntimeException("Cannot read private key", e);
+ }
+ }
+
+ public static X509Certificate loadPemCertificate(Reader reader) {
+ try (PEMParser pemParser = new PEMParser(reader)) {
+ X509CertificateHolder certHolder = (X509CertificateHolder) pemParser.readObject();
+ X509Certificate cert = new JcaX509CertificateConverter().setProvider(BC_SECURITY_PROVIDER)
+ .getCertificate(certHolder);
+ return cert;
+ } catch (IOException | CertificateException e) {
+ throw new RuntimeException("Cannot read private key", e);
+ }
+ }
+
+ private static KeyStore getKeyStore(Path keyStoreFile, char[] keyStorePassword, String keyStoreType) {
+ try {
+ KeyStore store = KeyStore.getInstance(keyStoreType, BC_SECURITY_PROVIDER);
+ if (Files.exists(keyStoreFile)) {
+ try (InputStream fis = Files.newInputStream(keyStoreFile)) {
+ store.load(fis, keyStorePassword);
+ }
+ } else {
+ store.load(null);
+ }
+ return store;
+ } catch (GeneralSecurityException | IOException e) {
+ throw new RuntimeException("Cannot load keystore " + keyStoreFile, e);
+ }
+ }
+
+ private static void saveKeyStore(Path keyStoreFile, char[] keyStorePassword, KeyStore keyStore) {
+ try {
+ try (OutputStream fis = Files.newOutputStream(keyStoreFile)) {
+ keyStore.store(fis, keyStorePassword);
+ }
+ } catch (GeneralSecurityException | IOException e) {
+ throw new RuntimeException("Cannot save keystore " + keyStoreFile, e);
+ }
+ }
+
+ /** singleton */
+ private BcUtils() {
+ }
+}
--- /dev/null
+package org.argeo.cms.ssh;
+
+import java.io.Console;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Scanner;
+import java.util.Set;
+
+import org.apache.sshd.client.SshClient;
+import org.apache.sshd.client.channel.ClientChannel;
+import org.apache.sshd.client.channel.ClientChannelEvent;
+import org.apache.sshd.client.future.ConnectFuture;
+import org.apache.sshd.client.session.ClientSession;
+import org.apache.sshd.common.util.io.input.NoCloseInputStream;
+import org.apache.sshd.common.util.io.output.NoCloseOutputStream;
+import org.apache.sshd.sftp.client.fs.SftpFileSystemProvider;
+import org.argeo.api.cms.CmsLog;
+
+public abstract class AbstractSsh {
+ private final static CmsLog log = CmsLog.getLog(AbstractSsh.class);
+
+ private SshClient sshClient;
+ private SftpFileSystemProvider sftpFileSystemProvider;
+
+ private boolean passwordSet = false;
+ private ClientSession session;
+
+ private SshKeyPair sshKeyPair;
+
+ public synchronized SshClient getSshClient() {
+ if (sshClient == null) {
+ long begin = System.currentTimeMillis();
+ sshClient = SshClient.setUpDefaultClient();
+ sshClient.start();
+ long duration = System.currentTimeMillis() - begin;
+ if (log.isDebugEnabled())
+ log.debug("SSH client started in " + duration + " ms");
+ Runtime.getRuntime().addShutdownHook(new Thread(() -> sshClient.stop(), "Stop SSH client"));
+ }
+ return sshClient;
+ }
+
+ synchronized SftpFileSystemProvider getSftpFileSystemProvider() {
+ if (sftpFileSystemProvider == null) {
+ sftpFileSystemProvider = new SftpFileSystemProvider(sshClient);
+ }
+ return sftpFileSystemProvider;
+ }
+
+ public void authenticate() {
+ if (sshKeyPair != null) {
+ session.addPublicKeyIdentity(sshKeyPair.asKeyPair());
+ } else {
+
+ if (!passwordSet) {
+ String password;
+ Console console = System.console();
+ if (console == null) {// IDE
+ System.out.print("Password: ");
+ try (Scanner s = new Scanner(System.in)) {
+ password = s.next();
+ }
+ } else {
+ console.printf("Password: ");
+ char[] pwd = console.readPassword();
+ password = new String(pwd);
+ Arrays.fill(pwd, ' ');
+ }
+ session.addPasswordIdentity(password);
+ passwordSet = true;
+ }
+ }
+ verifyAuth();
+ }
+
+ public void verifyAuth() {
+ try {
+ session.auth().verify(1000l);
+ } catch (IOException e) {
+ throw new IllegalStateException("Cannot verify auth", e);
+ }
+ }
+
+ public static char[] readPassword() {
+ Console console = System.console();
+ if (console == null) {// IDE
+ System.out.print("Password: ");
+ try (Scanner s = new Scanner(System.in)) {
+ String password = s.next();
+ return password.toCharArray();
+ }
+ } else {
+ console.printf("Password: ");
+ char[] pwd = console.readPassword();
+ return pwd;
+ }
+ }
+
+ void addPassword(String password) {
+ session.addPasswordIdentity(password);
+ }
+
+ void loadKey(String password) {
+ loadKey(password, System.getProperty("user.home") + "/.ssh/id_rsa");
+ }
+
+ void loadKey(String password, String keyPath) {
+// try {
+// KeyPair keyPair = ClientIdentityLoader.DEFAULT.loadClientIdentity(keyPath,
+// FilePasswordProvider.of(password));
+// session.addPublicKeyIdentity(keyPair);
+// } catch (IOException | GeneralSecurityException e) {
+// throw new IllegalStateException(e);
+// }
+ }
+
+ void openSession(URI uri) {
+ openSession(uri.getUserInfo(), uri.getHost(), uri.getPort() > 0 ? uri.getPort() : null);
+ }
+
+ void openSession(String login, String host, Integer port) {
+ if (session != null)
+ throw new IllegalStateException("Session is already open");
+
+ if (host == null)
+ host = "localhost";
+ if (port == null)
+ port = 22;
+ if (login == null)
+ login = System.getProperty("user.name");
+ String password = null;
+ int sepIndex = login.indexOf(':');
+ if (sepIndex > 0)
+ if (sepIndex + 1 < login.length()) {
+ password = login.substring(sepIndex + 1);
+ login = login.substring(0, sepIndex);
+ } else {
+ throw new IllegalArgumentException("Illegal authority: " + login);
+ }
+ try {
+ ConnectFuture connectFuture = getSshClient().connect(login, host, port);
+ connectFuture.await();
+ ClientSession session = connectFuture.getSession();
+ if (password != null) {
+ session.addPasswordIdentity(password);
+ passwordSet = true;
+ }
+ this.session = session;
+ } catch (IOException e) {
+ throw new IllegalStateException("Cannot connect to " + host + ":" + port);
+ }
+ }
+
+ public void closeSession() {
+ if (session == null)
+ throw new IllegalStateException("No session is open");
+ try {
+ session.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ } finally {
+ session = null;
+ }
+ }
+
+ ClientSession getSession() {
+ return session;
+ }
+
+ public void setSshKeyPair(SshKeyPair sshKeyPair) {
+ this.sshKeyPair = sshKeyPair;
+ }
+
+ public static void openShell(AbstractSsh ssh) {
+ openShell(ssh.getSession());
+ }
+
+ public static void openShell(ClientSession session) {
+ try (ClientChannel channel = session.createChannel(ClientChannel.CHANNEL_SHELL)) {
+ channel.setIn(new NoCloseInputStream(System.in));
+ channel.setOut(new NoCloseOutputStream(System.out));
+ channel.setErr(new NoCloseOutputStream(System.err));
+ channel.open();
+
+ Set<ClientChannelEvent> events = new HashSet<>();
+ events.add(ClientChannelEvent.CLOSED);
+ channel.waitFor(events, 0);
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } finally {
+ session.close(false);
+ }
+ }
+
+ static URI toUri(String username, String host, int port) {
+ try {
+ if (username == null)
+ username = "root";
+ return new URI("ssh://" + username + "@" + host + ":" + port);
+ } catch (URISyntaxException e) {
+ throw new IllegalArgumentException("Cannot generate SSH URI to " + host + ":" + port + " for " + username,
+ e);
+ }
+ }
+
+}
--- /dev/null
+package org.argeo.cms.ssh;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import org.apache.sshd.scp.server.ScpCommandFactory;
+import org.apache.sshd.server.SshServer;
+import org.apache.sshd.server.config.keys.DefaultAuthorizedKeysAuthenticator;
+import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
+import org.apache.sshd.server.shell.ProcessShellFactory;
+import org.argeo.cms.util.OS;
+
+/** A simple SSH server with some defaults. Supports SCP. */
+public class BasicSshServer {
+ private Integer port;
+ private Path hostKeyPath;
+
+ private SshServer sshd = null;
+
+ public BasicSshServer(Integer port, Path hostKeyPath) {
+ this.port = port;
+ this.hostKeyPath = hostKeyPath;
+ }
+
+ public void init() {
+ try {
+ sshd = SshServer.setUpDefaultServer();
+ sshd.setPort(port);
+ if (hostKeyPath == null)
+ throw new IllegalStateException("An SSH server key must be set");
+ sshd.setKeyPairProvider(new SimpleGeneratorHostKeyProvider(hostKeyPath));
+ // sshd.setShellFactory(new ProcessShellFactory(new String[] { "/bin/sh", "-i",
+ // "-l" }));
+ String[] shellCommand = OS.LOCAL.getDefaultShellCommand();
+ // FIXME transfer args
+// sshd.setShellFactory(new ProcessShellFactory(shellCommand));
+ sshd.setShellFactory(new ProcessShellFactory(shellCommand[0], shellCommand));
+ sshd.setCommandFactory(new ScpCommandFactory());
+
+ sshd.setPublickeyAuthenticator(new DefaultAuthorizedKeysAuthenticator(true));
+ sshd.start();
+ } catch (Exception e) {
+ throw new RuntimeException("Cannot start SSH server on port " + port, e);
+ }
+ }
+
+ public void destroy() {
+ try {
+ sshd.stop();
+ } catch (IOException e) {
+ throw new RuntimeException("Cannot stop SSH server on port " + port, e);
+ }
+ }
+
+ public Integer getPort() {
+ return port;
+ }
+
+ public void setPort(Integer port) {
+ this.port = port;
+ }
+
+ public Path getHostKeyPath() {
+ return hostKeyPath;
+ }
+
+ public void setHostKeyPath(Path hostKeyPath) {
+ this.hostKeyPath = hostKeyPath;
+ }
+
+ public static void main(String[] args) {
+ int port = 2222;
+ Path hostKeyPath = Paths.get("hostkey.ser");
+ try {
+ if (args.length > 0)
+ port = Integer.parseInt(args[0]);
+ if (args.length > 1)
+ hostKeyPath = Paths.get(args[1]);
+ } catch (Exception e1) {
+ printUsage();
+ }
+
+ BasicSshServer sshServer = new BasicSshServer(port, hostKeyPath);
+ sshServer.init();
+ Runtime.getRuntime().addShutdownHook(new Thread("Shutdown SSH server") {
+
+ @Override
+ public void run() {
+ sshServer.destroy();
+ }
+ });
+ try {
+ synchronized (sshServer) {
+ sshServer.wait();
+ }
+ } catch (InterruptedException e) {
+ sshServer.destroy();
+ }
+
+ }
+
+ public static void printUsage() {
+ System.out.println("java " + BasicSshServer.class.getName() + " [port] [server key path]");
+ }
+
+}
--- /dev/null
+package org.argeo.cms.ssh;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Writer;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardOpenOption;
+import java.nio.file.attribute.PosixFilePermission;
+import java.security.KeyPair;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.PrivateKey;
+import java.security.UnrecoverableKeyException;
+import java.security.cert.CertificateException;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.sshd.common.config.keys.PublicKeyEntry;
+import org.apache.sshd.common.forward.PortForwardingEventListener;
+import org.apache.sshd.common.keyprovider.KeyPairProvider;
+import org.apache.sshd.common.session.Session;
+import org.apache.sshd.common.util.net.SshdSocketAddress;
+import org.apache.sshd.scp.server.ScpCommandFactory;
+import org.apache.sshd.server.SshServer;
+import org.apache.sshd.server.auth.gss.GSSAuthenticator;
+import org.apache.sshd.server.config.keys.AuthorizedKeysAuthenticator;
+import org.apache.sshd.server.config.keys.DefaultAuthorizedKeysAuthenticator;
+import org.apache.sshd.server.forward.AcceptAllForwardingFilter;
+import org.apache.sshd.server.jaas.JaasPasswordAuthenticator;
+import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
+import org.apache.sshd.server.shell.InteractiveProcessShellFactory;
+import org.apache.sshd.sftp.server.SftpSubsystemFactory;
+import org.argeo.api.cms.CmsAuth;
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.api.cms.CmsState;
+import org.argeo.cms.CmsDeployProperty;
+import org.argeo.cms.CmsSshd;
+
+public class CmsSshServer implements CmsSshd {
+ private final static CmsLog log = CmsLog.getLog(CmsSshServer.class);
+
+ private CmsState cmsState;
+ private SshServer sshd = null;
+
+ private int port;
+ private String host;
+
+ public void start() {
+ String portStr = cmsState.getDeployProperty(CmsDeployProperty.SSHD_PORT.getProperty());
+ if (portStr == null)
+ return; // ignore
+ port = Integer.parseInt(portStr);
+
+ host = cmsState.getDeployProperty(CmsDeployProperty.HOST.getProperty());
+
+ KeyPair nodeKeyPair = loadNodeKeyPair();
+
+ try {
+ // authorized keys
+ String authorizedKeysStr = cmsState.getDeployProperty(CmsDeployProperty.SSHD_AUTHORIZEDKEYS.getProperty());
+ Path authorizedKeysPath = authorizedKeysStr != null ? Paths.get(authorizedKeysStr)
+ : AuthorizedKeysAuthenticator.getDefaultAuthorizedKeysFile();
+ if (authorizedKeysStr != null && !Files.exists(authorizedKeysPath)) {
+ Files.createFile(authorizedKeysPath);
+ Set<PosixFilePermission> posixPermissions = new HashSet<>();
+ posixPermissions.add(PosixFilePermission.OWNER_READ);
+ posixPermissions.add(PosixFilePermission.OWNER_WRITE);
+ Files.setPosixFilePermissions(authorizedKeysPath, posixPermissions);
+
+ if (nodeKeyPair != null)
+ try {
+ String openSsshPublicKey = PublicKeyEntry.toString(nodeKeyPair.getPublic());
+ try (Writer writer = Files.newBufferedWriter(authorizedKeysPath, StandardCharsets.US_ASCII,
+ StandardOpenOption.APPEND)) {
+ writer.write(openSsshPublicKey);
+ }
+ } catch (IOException e) {
+ log.error("Cannot add node public key to SSH authorized keys", e);
+ }
+ }
+
+ // create server
+ sshd = SshServer.setUpDefaultServer();
+ sshd.setPort(port);
+ if (host != null)
+ sshd.setHost(host);
+
+ // host key
+ if (nodeKeyPair != null) {
+ sshd.setKeyPairProvider(KeyPairProvider.wrap(nodeKeyPair));
+ } else {
+ Path hostKeyPath = cmsState.getDataPath(DEFAULT_SSH_HOST_KEY_PATH);
+ if (hostKeyPath == null) // TODO deal with no data area?
+ throw new IllegalStateException("An SSH server key must be set");
+ sshd.setKeyPairProvider(new SimpleGeneratorHostKeyProvider(hostKeyPath));
+ }
+
+ // tunnels
+ sshd.setForwardingFilter(AcceptAllForwardingFilter.INSTANCE);
+ sshd.addPortForwardingEventListener(new PortForwardingEventListener() {
+
+ @Override
+ public void establishingExplicitTunnel(Session session, SshdSocketAddress local,
+ SshdSocketAddress remote, boolean localForwarding) throws IOException {
+ log.debug("Establishing tunnel " + local + ", " + remote);
+ }
+
+ @Override
+ public void establishedExplicitTunnel(Session session, SshdSocketAddress local,
+ SshdSocketAddress remote, boolean localForwarding, SshdSocketAddress boundAddress,
+ Throwable reason) throws IOException {
+ log.debug("Established tunnel " + local + ", " + remote + ", " + boundAddress);
+ }
+
+ @Override
+ public void establishingDynamicTunnel(Session session, SshdSocketAddress local) throws IOException {
+ log.debug("Establishing dynamic tunnel " + local);
+ }
+
+ @Override
+ public void establishedDynamicTunnel(Session session, SshdSocketAddress local,
+ SshdSocketAddress boundAddress, Throwable reason) throws IOException {
+ log.debug("Established dynamic tunnel " + local);
+ }
+
+ });
+
+ // Authentication
+ // FIXME use strict, set proper permissions, etc.
+ sshd.setPublickeyAuthenticator(
+ new DefaultAuthorizedKeysAuthenticator("user.name", authorizedKeysPath, true));
+ // sshd.setPublickeyAuthenticator(null);
+ // sshd.setKeyboardInteractiveAuthenticator(null);
+ JaasPasswordAuthenticator jaasPasswordAuthenticator = new JaasPasswordAuthenticator();
+ jaasPasswordAuthenticator.setDomain(CmsAuth.NODE.getLoginContextName());
+ sshd.setPasswordAuthenticator(jaasPasswordAuthenticator);
+
+ boolean gssApi = false;
+ if (gssApi) {
+ Path krb5keyTab = cmsState.getDataPath("private/krb5.keytab");
+ if (Files.exists(krb5keyTab)) {
+ // FIXME experimental
+ GSSAuthenticator gssAuthenticator = new GSSAuthenticator();
+ gssAuthenticator.setKeytabFile(krb5keyTab.toString());
+ gssAuthenticator.setServicePrincipalName("HTTP@" + host);
+ sshd.setGSSAuthenticator(gssAuthenticator);
+ }
+ }
+
+ // shell
+ // TODO make it configurable
+ sshd.setShellFactory(InteractiveProcessShellFactory.INSTANCE);
+// String[] shellCommand = OS.LOCAL.getDefaultShellCommand();
+// StringJoiner command = new StringJoiner(" ");
+// for (String str : shellCommand) {
+// command.add(str);
+// }
+// sshd.setShellFactory(new ProcessShellFactory(command.toString(), shellCommand));
+ sshd.setCommandFactory(new ScpCommandFactory());
+
+ // SFTP
+ sshd.setSubsystemFactories(Collections.singletonList(new SftpSubsystemFactory()));
+
+ // start
+ sshd.start();
+
+ log.debug(() -> "CMS SSH server started on port " + port + (host != null ? " of host " + host : ""));
+ } catch (IOException e) {
+ throw new RuntimeException("Cannot start SSH server on port " + port, e);
+ }
+
+ }
+
+ public void stop() {
+ if (sshd == null)
+ return;
+ try {
+ sshd.stop();
+ log.debug(() -> "CMS SSH server stopped on port " + port + (host != null ? " of host " + host : ""));
+ } catch (IOException e) {
+ throw new RuntimeException("Cannot stop SSH server", e);
+ }
+
+ }
+
+ protected KeyPair loadNodeKeyPair() {
+ try {
+ char[] keyStorePassword = cmsState.getDeployProperty(CmsDeployProperty.SSL_PASSWORD.getProperty())
+ .toCharArray();
+ Path keyStorePath = Paths.get(cmsState.getDeployProperty(CmsDeployProperty.SSL_KEYSTORE.getProperty()));
+ String keyStoreType = cmsState.getDeployProperty(CmsDeployProperty.SSL_KEYSTORETYPE.getProperty());
+
+ KeyStore store = KeyStore.getInstance(keyStoreType, "SunJSSE");
+ try (InputStream fis = Files.newInputStream(keyStorePath)) {
+ store.load(fis, keyStorePassword);
+ }
+ return new KeyPair(store.getCertificate(CmsConstants.NODE).getPublicKey(),
+ (PrivateKey) store.getKey(CmsConstants.NODE, keyStorePassword));
+ } catch (IOException | KeyStoreException | NoSuchProviderException | NoSuchAlgorithmException
+ | CertificateException | IllegalArgumentException | UnrecoverableKeyException e) {
+ log.error("Cannot add node public key to SSH authorized keys", e);
+ return null;
+ }
+
+ }
+
+ public void setCmsState(CmsState cmsState) {
+ this.cmsState = cmsState;
+ }
+
+}
--- /dev/null
+package org.argeo.cms.ssh;
+
+import java.io.IOException;
+import java.net.URI;
+import java.nio.file.FileSystem;
+import java.nio.file.Path;
+
+import org.apache.sshd.sftp.client.fs.SftpFileSystem;
+
+/** Create an SFTP {@link FileSystem}. */
+public class Sftp extends AbstractSsh {
+ private URI uri;
+
+ private SftpFileSystem fileSystem;
+
+ public Sftp(String username, String host, int port) {
+ this(AbstractSsh.toUri(username, host, port));
+ }
+
+ public Sftp(URI uri) {
+ this.uri = uri;
+ openSession(uri);
+ }
+
+ public FileSystem getFileSystem() {
+ if (fileSystem == null) {
+ try {
+ authenticate();
+ fileSystem = getSftpFileSystemProvider().newFileSystem(getSession());
+ } catch (IOException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+ return fileSystem;
+ }
+
+ public Path getBasePath() {
+ String p = uri.getPath() != null ? uri.getPath() : "/";
+ return getFileSystem().getPath(p);
+ }
+
+}
--- /dev/null
+package org.argeo.cms.ssh;
+
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.CommandLineParser;
+import org.apache.commons.cli.DefaultParser;
+import org.apache.commons.cli.HelpFormatter;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.Options;
+
+/** Create an SSH shell. */
+public class Ssh extends AbstractSsh {
+ private final URI uri;
+
+ public Ssh(String username, String host, int port) {
+ this(AbstractSsh.toUri(username, host, port));
+ }
+
+ public Ssh(URI uri) {
+ this.uri = uri;
+ openSession(uri);
+ }
+
+ public static void main(String[] args) {
+ Options options = getOptions();
+ CommandLineParser parser = new DefaultParser();
+ try {
+ CommandLine line = parser.parse(options, args);
+ List<String> remaining = line.getArgList();
+ if (remaining.size() == 0) {
+ System.err.println("There must be at least one argument");
+ printHelp(options);
+ System.exit(1);
+ }
+ URI uri = new URI("ssh://" + remaining.get(0));
+ List<String> command = new ArrayList<>();
+ if (remaining.size() > 1) {
+ for (int i = 1; i < remaining.size(); i++) {
+ command.add(remaining.get(i));
+ }
+ }
+
+ // auth
+ Ssh ssh = new Ssh(uri);
+ ssh.authenticate();
+
+ if (command.size() == 0) {// shell
+ AbstractSsh.openShell(ssh.getSession());
+ } else {// execute command
+
+ }
+ ssh.closeSession();
+ } catch (Exception exp) {
+ exp.printStackTrace();
+ printHelp(options);
+ System.exit(1);
+ } finally {
+
+ }
+ }
+
+ public URI getUri() {
+ return uri;
+ }
+
+ public static Options getOptions() {
+ Options options = new Options();
+// options.addOption("p", true, "port");
+ options.addOption(Option.builder("p").hasArg().argName("port").desc("port of the SSH server").build());
+
+ return options;
+ }
+
+ public static void printHelp(Options options) {
+ HelpFormatter formatter = new HelpFormatter();
+ formatter.printHelp("ssh [username@]hostname", options, true);
+ }
+}
--- /dev/null
+package org.argeo.cms.ssh;
+
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.security.GeneralSecurityException;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.interfaces.RSAPrivateCrtKey;
+import java.security.spec.RSAPublicKeySpec;
+
+import org.apache.sshd.common.config.keys.KeyUtils;
+import org.apache.sshd.common.config.keys.PublicKeyEntry;
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.openssl.PEMDecryptorProvider;
+import org.bouncycastle.openssl.PEMEncryptedKeyPair;
+import org.bouncycastle.openssl.PEMKeyPair;
+import org.bouncycastle.openssl.PEMParser;
+import org.bouncycastle.openssl.PKCS8Generator;
+import org.bouncycastle.openssl.bc.BcPEMDecryptorProvider;
+import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
+import org.bouncycastle.openssl.jcajce.JcaPEMWriter;
+import org.bouncycastle.openssl.jcajce.JcaPKCS8Generator;
+import org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8DecryptorProviderBuilder;
+import org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8EncryptorBuilder;
+import org.bouncycastle.operator.InputDecryptorProvider;
+import org.bouncycastle.operator.OutputEncryptor;
+import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo;
+
+@SuppressWarnings("restriction")
+public class SshKeyPair {
+ public final static String RSA_KEY_TYPE = "ssh-rsa";
+
+ private PublicKey publicKey;
+ private PrivateKey privateKey;
+ private KeyPair keyPair;
+
+ public SshKeyPair(KeyPair keyPair) {
+ super();
+ this.publicKey = keyPair.getPublic();
+ this.privateKey = keyPair.getPrivate();
+ this.keyPair = keyPair;
+ }
+
+ public SshKeyPair(PublicKey publicKey, PrivateKey privateKey) {
+ super();
+ this.publicKey = publicKey;
+ this.privateKey = privateKey;
+ this.keyPair = new KeyPair(publicKey, privateKey);
+ }
+
+ public KeyPair asKeyPair() {
+ return keyPair;
+ }
+
+ public String getPublicKeyAsOpenSshString() {
+ return PublicKeyEntry.toString(publicKey);
+ }
+
+ public String getPrivateKeyAsPemString(char[] password) {
+ try {
+ Object obj;
+
+ if (password != null) {
+ JceOpenSSLPKCS8EncryptorBuilder encryptorBuilder = new JceOpenSSLPKCS8EncryptorBuilder(
+ PKCS8Generator.PBE_SHA1_3DES);
+ encryptorBuilder.setPasssword(password);
+ OutputEncryptor oe = encryptorBuilder.build();
+ JcaPKCS8Generator gen = new JcaPKCS8Generator(privateKey, oe);
+ obj = gen.generate();
+ } else {
+ obj = privateKey;
+ }
+
+ StringWriter sw = new StringWriter();
+ JcaPEMWriter pemWrt = new JcaPEMWriter(sw);
+ pemWrt.writeObject(obj);
+ pemWrt.close();
+ return sw.toString();
+ } catch (Exception e) {
+ throw new RuntimeException("Cannot convert private key", e);
+ }
+ }
+
+ public static SshKeyPair loadOrGenerate(Path privateKeyPath, int size, char[] password) {
+ try {
+ SshKeyPair sshKeyPair;
+ if (Files.exists(privateKeyPath)) {
+// String privateKeyStr = new String(Files.readAllBytes(privateKeyPath), StandardCharsets.US_ASCII);
+ sshKeyPair = load(
+ new InputStreamReader(Files.newInputStream(privateKeyPath), StandardCharsets.US_ASCII),
+ password);
+ // TOD make sure public key is consistemt
+ } else {
+ sshKeyPair = generate(size);
+ Files.write(privateKeyPath,
+ sshKeyPair.getPrivateKeyAsPemString(password).getBytes(StandardCharsets.US_ASCII));
+ Path publicKeyPath = privateKeyPath.resolveSibling(privateKeyPath.getFileName() + ".pub");
+ Files.write(publicKeyPath,
+ sshKeyPair.getPublicKeyAsOpenSshString().getBytes(StandardCharsets.US_ASCII));
+ }
+ return sshKeyPair;
+ } catch (IOException e) {
+ throw new RuntimeException("Cannot read or write private key " + privateKeyPath, e);
+ }
+ }
+
+ public static SshKeyPair generate(int size) {
+ return generate(RSA_KEY_TYPE, size);
+ }
+
+ public static SshKeyPair generate(String keyType, int size) {
+ try {
+ KeyPair keyPair = KeyUtils.generateKeyPair(keyType, size);
+ PublicKey publicKey = keyPair.getPublic();
+ PrivateKey privateKey = keyPair.getPrivate();
+ return new SshKeyPair(publicKey, privateKey);
+ } catch (GeneralSecurityException e) {
+ throw new RuntimeException("Cannot generate SSH key", e);
+ }
+ }
+
+ public static SshKeyPair loadDefault(char[] password) {
+ Path privateKeyPath = Paths.get(System.getProperty("user.home") + "/.ssh/id_rsa");
+ // TODO try other formats
+ return load(privateKeyPath, password);
+ }
+
+ public static SshKeyPair load(Path privateKeyPath, char[] password) {
+ try (Reader reader = Files.newBufferedReader(privateKeyPath)) {
+ return load(reader, password);
+ } catch (IOException e) {
+ throw new IllegalStateException("Cannot load private key from " + privateKeyPath, e);
+ }
+
+ }
+
+ public static SshKeyPair load(Reader reader, char[] password) {
+ try (PEMParser pemParser = new PEMParser(reader)) {
+ Object object = pemParser.readObject();
+ JcaPEMKeyConverter converter = new JcaPEMKeyConverter();// .setProvider("BC");
+ KeyPair kp;
+ if (object instanceof PEMEncryptedKeyPair) {
+ PEMEncryptedKeyPair ekp = (PEMEncryptedKeyPair) object;
+ PEMDecryptorProvider decryptorProvider = new BcPEMDecryptorProvider(password);
+ PEMKeyPair pemKp = ekp.decryptKeyPair(decryptorProvider);
+ kp = converter.getKeyPair(pemKp);
+ } else if (object instanceof PKCS8EncryptedPrivateKeyInfo) {
+ // Encrypted key - we will use provided password
+ PKCS8EncryptedPrivateKeyInfo ckp = (PKCS8EncryptedPrivateKeyInfo) object;
+// PEMDecryptorProvider decProv = new JcePEMDecryptorProviderBuilder().build(password);
+ InputDecryptorProvider inputDecryptorProvider = new JceOpenSSLPKCS8DecryptorProviderBuilder()
+ .build(password);
+ PrivateKeyInfo pkInfo = ckp.decryptPrivateKeyInfo(inputDecryptorProvider);
+ PrivateKey privateKey = converter.getPrivateKey(pkInfo);
+
+ // generate public key
+ RSAPrivateCrtKey privk = (RSAPrivateCrtKey) privateKey;
+ RSAPublicKeySpec publicKeySpec = new java.security.spec.RSAPublicKeySpec(privk.getModulus(),
+ privk.getPublicExponent());
+ KeyFactory keyFactory = KeyFactory.getInstance("RSA");
+ PublicKey publicKey = keyFactory.generatePublic(publicKeySpec);
+
+ kp = new KeyPair(publicKey, privateKey);
+ } else {
+ // Unencrypted key - no password needed
+// PKCS8EncryptedPrivateKeyInfo ukp = (PKCS8EncryptedPrivateKeyInfo) object;
+ PEMKeyPair pemKp = (PEMKeyPair) object;
+ kp = converter.getKeyPair(pemKp);
+ }
+ return new SshKeyPair(kp);
+ } catch (Exception e) {
+ throw new RuntimeException("Cannot load private key", e);
+ }
+ }
+
+ public static void main(String args[]) {
+ Path privateKeyPath = Paths.get(System.getProperty("user.dir") + "/id_rsa");
+ SshKeyPair skp = SshKeyPair.loadOrGenerate(privateKeyPath, 1024, null);
+ System.out.println("Public:\n" + skp.getPublicKeyAsOpenSshString());
+ System.out.println("Private (plain):\n" + skp.getPrivateKeyAsPemString(null));
+ System.out.println("Private (encrypted):\n" + skp.getPrivateKeyAsPemString("demo".toCharArray()));
+
+ StringReader reader = new StringReader(skp.getPrivateKeyAsPemString(null));
+ skp = SshKeyPair.load(reader, null);
+ System.out.println("Public:\n" + skp.getPublicKeyAsOpenSshString());
+ System.out.println("Private (plain):\n" + skp.getPrivateKeyAsPemString(null));
+ System.out.println("Private (encrypted):\n" + skp.getPrivateKeyAsPemString("demo".toCharArray()));
+
+ reader = new StringReader(skp.getPrivateKeyAsPemString("demo".toCharArray()));
+ skp = SshKeyPair.load(reader, "demo".toCharArray());
+ System.out.println("Public:\n" + skp.getPublicKeyAsOpenSshString());
+ System.out.println("Private (plain):\n" + skp.getPrivateKeyAsPemString(null));
+ System.out.println("Private (encrypted):\n" + skp.getPrivateKeyAsPemString("demo".toCharArray()));
+ }
+
+}
--- /dev/null
+package org.argeo.cms.ssh;
+
+import java.io.Console;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.file.DirectoryStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.security.KeyPair;
+import java.util.Map;
+import java.util.Scanner;
+
+import org.apache.sshd.agent.SshAgent;
+import org.apache.sshd.agent.SshAgentFactory;
+import org.apache.sshd.agent.local.LocalAgentFactory;
+import org.apache.sshd.agent.unix.UnixAgentFactory;
+import org.apache.sshd.client.SshClient;
+import org.apache.sshd.client.config.keys.ClientIdentityLoader;
+import org.apache.sshd.client.future.ConnectFuture;
+import org.apache.sshd.client.session.ClientSession;
+import org.apache.sshd.common.NamedResource;
+import org.apache.sshd.common.config.keys.FilePasswordProvider;
+import org.apache.sshd.sftp.client.fs.SftpFileSystem;
+import org.apache.sshd.sftp.client.fs.SftpFileSystemProvider;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.cms.util.StreamUtils;
+
+public class SshSync {
+ private final static CmsLog log = CmsLog.getLog(SshSync.class);
+
+ public static void main(String[] args) {
+
+ try (SshClient client = SshClient.setUpDefaultClient()) {
+ client.start();
+ boolean osAgent = false;
+ SshAgentFactory agentFactory = osAgent ? new UnixAgentFactory() : new LocalAgentFactory();
+ // SshAgentFactory agentFactory = new LocalAgentFactory();
+ client.setAgentFactory(agentFactory);
+ SshAgent sshAgent = agentFactory.createClient(null, client);
+
+ String login = System.getProperty("user.name");
+ String host = "localhost";
+ int port = 22;
+
+ if (!osAgent) {
+ String keyPath = "/home/" + login + "/.ssh/id_rsa";
+
+ String password;
+ Console console = System.console();
+ if (console != null) {
+ password = new String(console.readPassword(keyPath + ": "));
+ } else {
+ System.out.print(keyPath + ": ");
+ try (Scanner s = new Scanner(System.in)) {
+ password = s.next();
+ }
+ }
+ NamedResource namedResource = new NamedResource() {
+
+ @Override
+ public String getName() {
+ return keyPath;
+ }
+ };
+ KeyPair keyPair = ClientIdentityLoader.DEFAULT
+ .loadClientIdentities(null, namedResource, FilePasswordProvider.of(password)).iterator().next();
+ sshAgent.addIdentity(keyPair, "NO COMMENT");
+ }
+
+// List<? extends Map.Entry<PublicKey, String>> identities = sshAgent.getIdentities();
+// for (Map.Entry<PublicKey, String> entry : identities) {
+// System.out.println(entry.getValue() + " : " + entry.getKey());
+// }
+
+ ConnectFuture connectFuture = client.connect(login, host, port);
+ connectFuture.await();
+ ClientSession session = connectFuture.getSession();
+
+ try {
+
+// session.addPasswordIdentity(new String(password));
+ session.auth().verify(1000l);
+
+ SftpFileSystemProvider fsProvider = new SftpFileSystemProvider(client);
+
+ SftpFileSystem fs = fsProvider.newFileSystem(session);
+ Path testPath = fs.getPath("/home/" + login + "/tmp");
+ Files.list(testPath).forEach(System.out::println);
+ test(testPath);
+
+ } finally {
+ client.stop();
+ }
+ } catch (Exception e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+
+ static void test(Path testBase) {
+ try {
+ Path testPath = testBase.resolve("ssh-test.txt");
+ Files.createFile(testPath);
+ log.debug("Created file " + testPath);
+ Files.delete(testPath);
+ log.debug("Deleted " + testPath);
+ String txt = "TEST\nTEST2\n";
+ byte[] arr = txt.getBytes();
+ Files.write(testPath, arr);
+ log.debug("Wrote " + testPath);
+ byte[] read = Files.readAllBytes(testPath);
+ log.debug("Read " + testPath);
+ Path testDir = testBase.resolve("testDir");
+ log.debug("Resolved " + testDir);
+ // Copy
+ Files.createDirectory(testDir);
+ log.debug("Created directory " + testDir);
+ Path subsubdir = Files.createDirectories(testDir.resolve("subdir/subsubdir"));
+ log.debug("Created sub directories " + subsubdir);
+ Path copiedFile = testDir.resolve("copiedFile.txt");
+ log.debug("Resolved " + copiedFile);
+ Path relativeCopiedFile = testDir.relativize(copiedFile);
+ log.debug("Relative copied file " + relativeCopiedFile);
+ try (OutputStream out = Files.newOutputStream(copiedFile);
+ InputStream in = Files.newInputStream(testPath)) {
+ StreamUtils.copy(in, out);
+ }
+ log.debug("Copied " + testPath + " to " + copiedFile);
+ Files.delete(testPath);
+ log.debug("Deleted " + testPath);
+ byte[] copiedRead = Files.readAllBytes(copiedFile);
+ log.debug("Read " + copiedFile);
+ // Browse directories
+ DirectoryStream<Path> files = Files.newDirectoryStream(testDir);
+ int fileCount = 0;
+ Path listedFile = null;
+ for (Path file : files) {
+ fileCount++;
+ if (!Files.isDirectory(file))
+ listedFile = file;
+ }
+ log.debug("Listed " + testDir);
+ // Generic attributes
+ Map<String, Object> attrs = Files.readAttributes(copiedFile, "*");
+ log.debug("Read attributes of " + copiedFile + ": " + attrs.keySet());
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+
+ }
+
+}
--- /dev/null
+package org.argeo.cms.ssh.cli;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+import java.util.Objects;
+
+import org.apache.sshd.client.config.keys.ClientIdentityLoader;
+import org.apache.sshd.common.NamedResource;
+import org.apache.sshd.common.config.keys.FilePasswordProvider;
+import org.apache.sshd.common.session.SessionContext;
+import org.apache.sshd.common.util.ValidateUtils;
+import org.apache.sshd.common.util.io.IoUtils;
+import org.apache.sshd.common.util.io.resource.PathResource;
+import org.apache.sshd.common.util.security.SecurityUtils;
+
+/** Separate class in order to avoit static field from Apache SSHD. */
+class DefaultClientIdentityLoader implements ClientIdentityLoader {
+ @Override
+ public boolean isValidLocation(NamedResource location) throws IOException {
+ Path path = toPath(location);
+ return Files.exists(path, IoUtils.EMPTY_LINK_OPTIONS);
+ }
+
+ @Override
+ public Iterable<KeyPair> loadClientIdentities(SessionContext session, NamedResource location,
+ FilePasswordProvider provider) throws IOException, GeneralSecurityException {
+ Path path = toPath(location);
+ PathResource resource = new PathResource(path);
+ try (InputStream inputStream = resource.openInputStream()) {
+ return SecurityUtils.loadKeyPairIdentities(session, resource, inputStream, provider);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "DEFAULT";
+ }
+
+ private Path toPath(NamedResource location) {
+ Objects.requireNonNull(location, "No location provided");
+
+ Path path = Paths
+ .get(ValidateUtils.checkNotNullAndNotEmpty(location.getName(), "No location value for %s", location));
+ path = path.toAbsolutePath();
+ path = path.normalize();
+ return path;
+ }
+
+}
--- /dev/null
+package org.argeo.cms.ssh.cli;
+
+import org.argeo.api.cli.CommandsCli;
+
+/** SSH command line interface. */
+public class SshCli extends CommandsCli {
+ public SshCli(String commandName) {
+ super(commandName);
+ addCommand("shell", new SshShell());
+ }
+
+ @Override
+ public String getDescription() {
+ return "SSH utilities.";
+ }
+
+ public static void main(String[] args) {
+ mainImpl(new SshCli("ssh"), args);
+ }
+
+}
--- /dev/null
+package org.argeo.cms.ssh.cli;
+
+import java.io.IOException;
+import java.lang.management.ManagementFactory;
+import java.net.URI;
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+import java.util.List;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.Options;
+import org.apache.sshd.agent.SshAgent;
+import org.apache.sshd.agent.SshAgentFactory;
+import org.apache.sshd.agent.local.LocalAgentFactory;
+import org.apache.sshd.agent.unix.UnixAgentFactory;
+import org.apache.sshd.common.NamedResource;
+import org.apache.sshd.common.config.keys.FilePasswordProvider;
+import org.argeo.api.cli.CommandArgsException;
+import org.argeo.api.cli.DescribedCommand;
+import org.argeo.cms.ssh.AbstractSsh;
+import org.argeo.cms.ssh.Ssh;
+
+public class SshShell implements DescribedCommand<String> {
+ private Option portOption;
+
+ @Override
+ public Options getOptions() {
+ Options options = new Options();
+ portOption = Option.builder().option("p").longOpt("port").hasArg().desc("port to connect to").build();
+ options.addOption(portOption);
+ return options;
+ }
+
+ @Override
+ public String apply(List<String> args) {
+ CommandLine cl = toCommandLine(args);
+ String portStr = cl.getOptionValue(portOption);
+ if (portStr == null)
+ portStr = "22";
+
+ if (cl.getArgList().size() == 0)
+ throw new CommandArgsException("Host must be provided");
+ String host = cl.getArgList().get(0);
+
+ String uriStr = "ssh://" + host + ":" + portStr + "/";
+ // System.out.println(uriStr);
+ URI uri = URI.create(uriStr);
+
+ Ssh ssh = null;
+ try {
+ ssh = new Ssh(uri);
+ boolean osAgent;
+ SshAgent sshAgent;
+ try {
+ String sshAuthSockentEnv = System.getenv(SshAgent.SSH_AUTHSOCKET_ENV_NAME);
+ if (sshAuthSockentEnv != null) {
+ ssh.getSshClient().getProperties().put(SshAgent.SSH_AUTHSOCKET_ENV_NAME, sshAuthSockentEnv);
+ SshAgentFactory agentFactory = new UnixAgentFactory();
+ ssh.getSshClient().setAgentFactory(agentFactory);
+ sshAgent = agentFactory.createClient(null, ssh.getSshClient());
+ osAgent = true;
+ } else {
+ osAgent = false;
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ osAgent = false;
+ }
+
+ if (!osAgent) {
+ SshAgentFactory agentFactory = new LocalAgentFactory();
+ ssh.getSshClient().setAgentFactory(agentFactory);
+ sshAgent = agentFactory.createClient(null, ssh.getSshClient());
+ String keyPath = System.getProperty("user.home") + "/.ssh/id_rsa";
+
+ char[] keyPassword = AbstractSsh.readPassword();
+ NamedResource namedResource = new NamedResource() {
+
+ @Override
+ public String getName() {
+ return keyPath;
+ }
+ };
+ KeyPair keyPair = new DefaultClientIdentityLoader()
+ .loadClientIdentities(null, namedResource, FilePasswordProvider.of(new String(keyPassword)))
+ .iterator().next();
+ sshAgent.addIdentity(keyPair, "NO COMMENT");
+ }
+
+// char[] keyPassword = AbstractSsh.readPassword();
+// SshKeyPair keyPair = SshKeyPair.loadDefault(keyPassword);
+// Arrays.fill(keyPassword, '*');
+// ssh.setSshKeyPair(keyPair);
+// ssh.authenticate();
+ ssh.verifyAuth();
+
+ long jvmUptime = ManagementFactory.getRuntimeMXBean().getUptime();
+ System.out.println("Ssh available in " + jvmUptime + " ms.");
+
+ AbstractSsh.openShell(ssh);
+ } catch (IOException | GeneralSecurityException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } finally {
+ if (ssh != null)
+ ssh.closeSession();
+ }
+ return null;
+ }
+
+ @Override
+ public String getUsage() {
+ return "<hostname>";
+ }
+
+ @Override
+ public String getDescription() {
+ return "Opens a remote shell";
+ }
+
+}
\ No newline at end of file
--- /dev/null
+/** SSH support. */
+package org.argeo.cms.ssh;
\ 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.pgsql</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
-Import-Package: org.postgresql;version="[42,43)"
+++ /dev/null
-source.. = src/
-output.. = bin/
-bin.includes = META-INF/,\
- .
+++ /dev/null
-package org.argeo.cms.pgsql.util;
-
-import java.sql.Connection;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.sql.Statement;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Properties;
-
-import org.postgresql.Driver;
-
-/** Simple PostgreSQL check. */
-public class CheckPg {
-
- public List<String> listTables() {
- String osUser = System.getProperty("user.name");
-
- String url = "jdbc:postgresql://localhost/" + osUser;
- Properties props = new Properties();
- props.setProperty("user", osUser);
- props.setProperty("password", "changeit");
- List<String> result = new ArrayList<>();
-
- Driver driver = new Driver();
- try (Connection conn = driver.connect(url, props); Statement s = conn.createStatement();) {
- s.execute("SELECT * FROM pg_catalog.pg_tables");
- ResultSet rs = s.getResultSet();
- while (rs.next()) {
- result.add(rs.getString("tablename"));
- }
- return result;
- } catch (SQLException e) {
- throw new IllegalStateException(e);
- }
- }
-
- public static void main(String[] args) {
- new CheckPg().listTables().forEach(System.out::println);
- }
-
-}
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-17"/>
+ <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+ <classpathentry kind="src" path="src"/>
+ <classpathentry kind="output" path="bin"/>
+</classpath>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>org.argeo.cms.ux</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
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+ .
--- /dev/null
+package org.argeo.cms.media;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.Reader;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import org.apache.batik.transcoder.TranscoderException;
+import org.apache.batik.transcoder.TranscoderInput;
+import org.apache.batik.transcoder.TranscoderOutput;
+import org.apache.batik.transcoder.image.ImageTranscoder;
+import org.apache.batik.transcoder.image.PNGTranscoder;
+
+public class SvgToPng {
+
+ public void convertSvgDir(Path sourceDir, Path targetDir, int width) {
+ System.out.println("##\n## " + width + "px - " + sourceDir + "\n##");
+ try {
+ if (targetDir == null)
+ targetDir = sourceDir.getParent().resolve(Integer.toString(width));
+ Files.createDirectories(targetDir);
+
+ PNGTranscoder transcoder = new PNGTranscoder();
+ // transcoder.addTranscodingHint(ImageTranscoder.KEY_BACKGROUND_COLOR,
+ // Color.WHITE);
+ transcoder.addTranscodingHint(PNGTranscoder.KEY_WIDTH, (float) width);
+ transcoder.addTranscodingHint(PNGTranscoder.KEY_HEIGHT, (float) width);
+
+ for (Path source : Files.newDirectoryStream(sourceDir, "*.svg")) {
+ // FIXME extract base name
+ String baseName = null; // = FilenameUtils.getBaseName(source.toString());
+ Path target = targetDir.resolve(baseName + ".png");
+ convertSvgFile(transcoder, source, target);
+ }
+ } catch (IOException | TranscoderException e) {
+ throw new IllegalStateException("Cannot convert from " + sourceDir + " to " + targetDir, e);
+ }
+
+ }
+
+ protected void convertSvgFile(ImageTranscoder transcoder, Path source, Path target)
+ throws IOException, TranscoderException {
+ try (Reader reader = Files.newBufferedReader(source); OutputStream out = Files.newOutputStream(target);) {
+ TranscoderInput input = new TranscoderInput(reader);
+// BufferedImage image = transcoder.createImage(32, 32);
+ TranscoderOutput output = new TranscoderOutput(out);
+ transcoder.transcode(input, output);
+ System.out.println(source.getFileName() + " -> " + target);
+ }
+ }
+
+ public static void main(String[] args) throws Exception {
+
+ Path path = Paths.get(args[0]);
+
+ SvgToPng svgToPng = new SvgToPng();
+ svgToPng.convertSvgDir(path, null, 16);
+ svgToPng.convertSvgDir(path, null, 32);
+ svgToPng.convertSvgDir(path, null, 64);
+ svgToPng.convertSvgDir(path, null, 96);
+ }
+}
--- /dev/null
+package org.argeo.cms.ux;
+
+import java.util.IdentityHashMap;
+
+import org.argeo.api.cms.ux.CmsEditable;
+import org.argeo.api.cms.ux.CmsEditionEvent;
+import org.argeo.api.cms.ux.CmsEditionListener;
+
+public abstract class AbstractCmsEditable implements CmsEditable {
+ private IdentityHashMap<CmsEditionListener, Object> listeners = new IdentityHashMap<>();
+
+ protected void notifyListeners(CmsEditionEvent e) {
+ if (CmsEditionEvent.START_EDITING == e.getType()) {
+ for (CmsEditionListener listener : listeners.keySet())
+ listener.editionStarted(e);
+ } else if (CmsEditionEvent.STOP_EDITING == e.getType()) {
+ for (CmsEditionListener listener : listeners.keySet())
+ listener.editionStopped(e);
+ } else {
+ throw new IllegalArgumentException("Unkown edition event type " + e.getType());
+ }
+ }
+
+ @Override
+ public void addCmsEditionListener(CmsEditionListener listener) {
+ listeners.put(listener, new Object());
+ }
+
+ @Override
+ public void removeCmsEditionListener(CmsEditionListener listener) {
+ listeners.remove(listener, new Object());
+ }
+
+}
--- /dev/null
+package org.argeo.cms.ux;
+
+import org.argeo.api.cms.ux.Cms2DSize;
+import org.argeo.api.cms.ux.CmsImageManager;
+
+/** Manages only public images so far. */
+public abstract class AbstractImageManager<V, M> implements CmsImageManager<V, M> {
+ 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;
+
+ protected 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);
+ }
+
+ protected int scale(int origDimension, int otherDimension, int otherConstraint) {
+ return Math.round(origDimension * divide(otherConstraint, otherDimension));
+ }
+
+ protected float divide(int a, int b) {
+ return ((float) a) / ((float) b);
+ }
+
+ /** @return null if not available */
+ @Override
+ public String getImageTag(M node) {
+ return getImageTag(node, getImageSize(node));
+ }
+
+ protected String getImageTag(M 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(M node, Cms2DSize size) {
+ return getImageTagBuilder(node, Integer.toString(size.getWidth()), Integer.toString(size.getHeight()));
+ }
+
+ /** @return null if not available */
+ protected StringBuilder getImageTagBuilder(M node, String width, String height) {
+ String url = getImageUrl(node);
+ if (url == null)
+ return null;
+ return CmsUxUtils.imgBuilder(url, width, height);
+ }
+
+}
--- /dev/null
+package org.argeo.cms.ux;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.acr.ContentRepository;
+import org.argeo.api.acr.ContentSession;
+import org.argeo.api.cms.ux.Cms2DSize;
+import org.argeo.api.cms.ux.CmsView;
+import org.argeo.cms.util.CurrentSubject;
+
+public class CmsUxUtils {
+ public static ContentSession getContentSession(ContentRepository contentRepository, CmsView cmsView) {
+ return CurrentSubject.callAs(cmsView.getCmsSession().getSubject(), () -> contentRepository.get());
+ }
+
+ public static String getTitle(Content content) {
+ return content.getName().getLocalPart();
+ }
+
+ /** singleton */
+ private CmsUxUtils() {
+
+ }
+
+ 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 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()));
+ }
+}
--- /dev/null
+package org.argeo.cms.ux.acr;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.argeo.api.acr.Content;
+import org.argeo.cms.ux.widgets.AbstractHierarchicalPart;
+import org.argeo.cms.ux.widgets.HierarchicalPart;
+
+public class ContentHierarchicalPart extends AbstractHierarchicalPart<Content> implements HierarchicalPart<Content> {
+ @Override
+ public List<Content> getChildren(Content content) {
+ List<Content> res = new ArrayList<>();
+ if (isLeaf(content))
+ return res;
+ if (content == null)
+ return res;
+ for (Iterator<Content> it = content.iterator(); it.hasNext();) {
+ res.add(it.next());
+ }
+
+ return res;
+ }
+
+ protected boolean isLeaf(Content content) {
+ return false;
+ }
+}
--- /dev/null
+package org.argeo.cms.ux.acr;
+
+import org.argeo.api.acr.Content;
+
+/** A part displaying or editing a content. */
+public interface ContentPart {
+ Content getContent();
+
+ @Deprecated
+ default Content getNode() {
+ return getContent();
+ }
+
+}
--- /dev/null
+package org.argeo.cms.ux.widgets;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public abstract class AbstractColumnsPart<INPUT, TYPE> extends AbstractDataPart<INPUT, TYPE> implements ColumnsPart<INPUT, TYPE> {
+
+ private List<Column<TYPE>> columns = new ArrayList<>();
+
+ @Override
+ public Column<TYPE> getColumn(int index) {
+ if (index >= columns.size())
+ throw new IllegalArgumentException("There a only " + columns.size());
+ return columns.get(index);
+ }
+
+ @Override
+ public void addColumn(Column<TYPE> column) {
+ columns.add(column);
+ }
+
+ @Override
+ public int getColumnCount() {
+ return columns.size();
+ }
+}
--- /dev/null
+package org.argeo.cms.ux.widgets;
+
+import java.util.IdentityHashMap;
+import java.util.function.Consumer;
+
+public abstract class AbstractDataPart<INPUT, TYPE> implements DataPart<INPUT, TYPE> {
+ private Consumer<TYPE> onSelected;
+ private Consumer<TYPE> onAction;
+
+ private IdentityHashMap<DataView<INPUT, TYPE>, Object> views = new IdentityHashMap<>();
+
+ private INPUT data;
+
+ @Override
+ public void setInput(INPUT data) {
+ this.data = data;
+ refresh();
+ }
+
+ @Override
+ public INPUT getInput() {
+ return data;
+ }
+
+ @Override
+ public void onSelected(Consumer<TYPE> onSelected) {
+ this.onSelected = onSelected;
+ }
+
+ @Override
+ public void onAction(Consumer<TYPE> onAction) {
+ this.onAction = onAction;
+ }
+
+ public Consumer<TYPE> getOnSelected() {
+ return onSelected;
+ }
+
+ public Consumer<TYPE> getOnAction() {
+ return onAction;
+ }
+
+ @Override
+ public void refresh() {
+ for (DataView<INPUT, TYPE> view : views.keySet()) {
+ view.refresh();
+ }
+ }
+
+ protected void notifyItemCountChange() {
+ for (DataView<INPUT, TYPE> view : views.keySet()) {
+ view.notifyItemCountChange();
+ }
+ }
+
+ @Override
+ public void addView(DataView<INPUT, TYPE> view) {
+ views.put(view, new Object());
+ }
+
+ @Override
+ public void removeView(DataView<INPUT, TYPE> view) {
+ views.remove(view);
+ }
+}
--- /dev/null
+package org.argeo.cms.ux.widgets;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public abstract class AbstractGuidedForm implements GuidedForm {
+ private String formTitle;
+ private List<Page> pages = new ArrayList<>();
+ private View view;
+
+ @Override
+ public abstract void addPages();
+
+ public void addPage(AbstractGuidedFormPage page) {
+ page.setView(view);
+ pages.add(page);
+ }
+
+ @Override
+ public boolean canFinish() {
+ return false;
+ }
+
+ @Override
+ public boolean performFinish() {
+ return false;
+ }
+
+ @Override
+ public boolean performCancel() {
+ return false;
+ }
+
+ @Override
+ public int getPageCount() {
+ return pages.size();
+ }
+
+ @Override
+ public List<Page> getPages() {
+ return Collections.unmodifiableList(pages);
+ }
+
+ @Override
+ public Page getStartingPage() {
+ if (pages.isEmpty())
+ throw new IllegalStateException("No page available");
+ return pages.get(0);
+ }
+
+ @Override
+ public Page getPreviousPage(Page page) {
+ int index = pages.indexOf(page);
+ if (index == 0 || index == -1) {
+ // first page or page not found
+ return null;
+ }
+ return pages.get(index - 1);
+ }
+
+ @Override
+ public Page getNextPage(Page page) {
+ int index = pages.indexOf(page);
+ if (index == pages.size() - 1 || index == -1) {
+ // last page or page not found
+ return null;
+ }
+ return pages.get(index + 1);
+ }
+
+ public void setFormTitle(String formTitle) {
+ this.formTitle = formTitle;
+ }
+
+ @Override
+ public String getFormTitle() {
+ return formTitle;
+ }
+
+ @Override
+ public void setView(View view) {
+ this.view = view;
+ }
+
+}
--- /dev/null
+package org.argeo.cms.ux.widgets;
+
+import org.argeo.cms.ux.widgets.GuidedForm.View;
+import org.argeo.cms.ux.widgets.GuidedForm.Page;
+
+public class AbstractGuidedFormPage implements Page {
+ private String pageName;
+ private String title;
+ private View view;
+
+ public AbstractGuidedFormPage(String pageName) {
+ super();
+ this.pageName = pageName;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ public void setView(View container) {
+ this.view = container;
+
+ }
+
+ public String getPageName() {
+ return pageName;
+ }
+
+ @Override
+ public String getTitle() {
+ return title;
+ }
+
+ public View getView() {
+ return view;
+ }
+
+}
--- /dev/null
+package org.argeo.cms.ux.widgets;
+
+public abstract class AbstractHierarchicalPart<T> extends AbstractColumnsPart<T, T> implements HierarchicalPart<T> {
+
+}
--- /dev/null
+package org.argeo.cms.ux.widgets;
+
+public abstract class AbstractTabularPart<INPUT, TYPE> extends AbstractColumnsPart<INPUT, TYPE>
+ implements TabularPart<INPUT, TYPE> {
+
+}
--- /dev/null
+package org.argeo.cms.ux.widgets;
+
+public interface CmsDialog {
+
+ // must be the same value as org.eclipse.jface.window.Window#OK
+ int OK = 0;
+ // must be the same value as org.eclipse.jface.window.Window#CANCEL
+ int CANCEL = 1;
+
+}
--- /dev/null
+package org.argeo.cms.ux.widgets;
+
+import org.argeo.api.cms.ux.CmsIcon;
+
+/** A column in a data representation. */
+@FunctionalInterface
+public interface Column<TYPE> {
+ String getText(TYPE model);
+
+ default int getWidth() {
+ return 200;
+ }
+
+ default CmsIcon getIcon(TYPE model) {
+ return null;
+ }
+
+}
--- /dev/null
+package org.argeo.cms.ux.widgets;
+
+/** A presentation of data in columns. */
+public interface ColumnsPart<INPUT, TYPE> extends DataPart<INPUT, TYPE> {
+
+ Column<TYPE> getColumn(int index);
+
+ void addColumn(Column<TYPE> column);
+
+ int getColumnCount();
+
+}
--- /dev/null
+package org.argeo.cms.ux.widgets;
+
+import java.util.function.Consumer;
+
+public interface DataPart<INPUT, TYPE> {
+ void setInput(INPUT data);
+
+ INPUT getInput();
+
+ void onSelected(Consumer<TYPE> onSelected);
+
+ Consumer<TYPE> getOnSelected();
+
+ void onAction(Consumer<TYPE> onAction);
+
+ Consumer<TYPE> getOnAction();
+
+ void refresh();
+
+ void addView(DataView<INPUT, TYPE> view);
+
+ void removeView(DataView<INPUT, TYPE> view);
+
+// void select(TYPE data);
+//
+// TYPE getSelected();
+
+}
--- /dev/null
+package org.argeo.cms.ux.widgets;
+
+public interface DataView<INPUT,TYPE> {
+ void refresh();
+
+ void notifyItemCountChange();
+}
--- /dev/null
+package org.argeo.cms.ux.widgets;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class DefaultTabularPart<INPUT, T> extends AbstractTabularPart<INPUT, T> implements TabularPart<INPUT, T> {
+ private List<T> content;
+
+ @Override
+ public int getItemCount() {
+ return content.size();
+ }
+
+ @Override
+ public T getData(int row) {
+ assert row < getItemCount();
+ return content.get(row);
+ }
+
+ @Override
+ public void refresh() {
+ INPUT input = getInput();
+ if (input == null) {
+ content = new ArrayList<>();
+ return;
+ }
+ content = asList(input);
+ super.refresh();
+ }
+
+ protected List<T> asList(INPUT input) {
+ List<T> res = new ArrayList<>();
+ content.clear();
+ if (input instanceof List) {
+ content = (List<T>) input;
+ } else if (input instanceof Iterable) {
+ for (T item : (Iterable<T>) input)
+ content.add(item);
+ } else {
+ throw new IllegalArgumentException(
+ "Unsupported class " + input.getClass() + ", method should be overridden.");
+ }
+ return res;
+ }
+
+}
--- /dev/null
+package org.argeo.cms.ux.widgets;
+
+/** Manages whether an editable or non editable control is shown. */
+public interface EditablePart {
+ public void startEditing();
+
+ public void stopEditing();
+}
--- /dev/null
+package org.argeo.cms.ux.widgets;
+
+import java.util.List;
+
+public interface GuidedForm {
+ String getFormTitle();
+
+ boolean canFinish();
+
+ boolean performFinish();
+
+ boolean performCancel();
+
+ void addPages();
+
+ int getPageCount();
+
+ List<Page> getPages();
+
+ Page getStartingPage();
+
+ Page getPreviousPage(Page page);
+
+ Page getNextPage(Page page);
+
+ void setView(View view);
+
+ interface Page {
+
+ default boolean canFlipToNextPage() {
+ return true;
+ }
+
+ default String getMessage() {
+ return null;
+ }
+
+ String getTitle();
+
+ }
+
+ interface View {
+ void updateButtons();
+ }
+}
--- /dev/null
+package org.argeo.cms.ux.widgets;
+
+import java.util.List;
+
+/** A hierarchical representation of data. */
+public interface HierarchicalPart<T> extends ColumnsPart<T, T> {
+ List<T> getChildren(T parent);
+}
--- /dev/null
+package org.argeo.cms.ux.widgets;
+
+/** A tabular presentation of data. */
+public interface TabularPart<INPUT, TYPE> extends ColumnsPart<INPUT, TYPE> {
+ int getItemCount();
+
+ TYPE getData(int row);
+}
--- /dev/null
+package org.argeo.cms.ux.widgets;
+
+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());
+ }
+
+}
<?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.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-17">
<attributes>
<attribute name="module" value="true"/>
</attributes>
--- /dev/null
+node/
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" immediate="true">
+ <implementation class="org.argeo.cms.internal.runtime.CmsAcrHttpHandler"/>
+ <service>
+ <provide interface="com.sun.net.httpserver.HttpHandler"/>
+ </service>
+ <property name="context.path" type="String" value="/api/acr/" />
+ <reference bind="setContentRepository" cardinality="1..1" interface="org.argeo.api.acr.spi.ProvidedRepository" name="ProvidedRepository" 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" immediate="true" name="org.argeo.cms">
+ <implementation class="org.argeo.cms.internal.http.CmsAuthenticator"/>
+ <service>
+ <provide interface="com.sun.net.httpserver.Authenticator"/>
+ </service>
+ <reference cardinality="1..1" interface="org.osgi.service.useradmin.UserAdmin" name="UserAdmin" 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="start" deactivate="stop" immediate="true" name="ACR Content Repository">
+ <implementation class="org.argeo.cms.internal.runtime.DeployedContentRepository"/>
+ <reference bind="addProvider" cardinality="0..n" interface="org.argeo.api.acr.spi.ContentProvider" name="ContentProvider" policy="dynamic" />
+ <service>
+ <provide interface="org.argeo.api.acr.ContentRepository"/>
+ <provide interface="org.argeo.api.acr.spi.ProvidedRepository"/>
+ </service>
+ <reference bind="setCmsState" cardinality="1..1" interface="org.argeo.api.cms.CmsState" name="CmsState" policy="static"/>
+ <reference bind="setUuidFactory" cardinality="1..1" interface="org.argeo.api.uuid.UuidFactory" name="UuidFactory" policy="static"/>
+ <reference bind="setUserManager" cardinality="1..1" interface="org.argeo.api.cms.directory.CmsUserManager" name="CmsUserManager" policy="static"/>
+</scr:component>
<provide interface="org.argeo.api.cms.CmsContext"/>
</service>
<reference bind="setCmsState" cardinality="1..1" interface="org.argeo.api.cms.CmsState" name="CmsState" policy="static"/>
+ <reference bind="setCmsEventBus" cardinality="1..1" interface="org.argeo.api.cms.CmsEventBus" name="CmsEventBus" policy="static"/>
<reference bind="setUserAdmin" cardinality="1..1" interface="org.osgi.service.useradmin.UserAdmin" name="UserAdmin" policy="static"/>
-</scr:component>
+ <reference bind="setUuidFactory" cardinality="1..1" interface="org.argeo.api.uuid.UuidFactory" name="UuidFactory" policy="static"/>
+ </scr:component>
<?xml version="1.0" encoding="UTF-8"?>
-<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="start" deactivate="stop" immediate="false" name="CMS Deployment">
- <reference bind="setDeployConfig" cardinality="1..1" interface="org.argeo.cms.internal.osgi.DeployConfig" name="DeployConfig" policy="static"/>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="start" deactivate="stop" immediate="true" name="CMS Deployment">
<implementation class="org.argeo.cms.internal.runtime.CmsDeploymentImpl"/>
<reference bind="setCmsState" cardinality="1..1" interface="org.argeo.api.cms.CmsState" name="CmsState" policy="static"/>
+ <reference bind="setCmsSshd" cardinality="0..1" interface="org.argeo.cms.CmsSshd" policy="dynamic"/>
+ <reference bind="setHttpServer" cardinality="0..1" interface="com.sun.net.httpserver.HttpServer" policy="dynamic"/>
+ <reference bind="addHttpHandler" unbind="removeHttpHandler" cardinality="0..n" interface="com.sun.net.httpserver.HttpHandler" policy="dynamic"/>
<service>
<provide interface="org.argeo.api.cms.CmsDeployment"/>
</service>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="CMS Event Bus">
+ <implementation class="org.argeo.cms.internal.runtime.CmsEventBusImpl"/>
+ <service>
+ <provide interface="org.argeo.api.cms.CmsEventBus"/>
+ </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" activate="start" deactivate="stop" immediate="true" name="CMS OSGi Logger">
+ <implementation class="org.argeo.cms.internal.osgi.CmsOsgiLogger"/>
+ <reference bind="setLogReaderService" cardinality="1..1" interface="org.osgi.service.log.LogReaderService" name="LogReaderService" policy="static"/>
+</scr:component>
<service>
<provide interface="org.argeo.api.cms.CmsState"/>
</service>
+ <reference bind="setUuidFactory" cardinality="1..1" interface="org.argeo.api.uuid.UuidFactory" name="UuidFactory" 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="start" deactivate="stop" immediate="false" name="Node User Admin">
+ <implementation class="org.argeo.cms.internal.runtime.CmsUserAdmin"/>
+ <property name="service.pid" type="String" value="org.argeo.api.userAdmin"/>
+ <reference bind="setTransactionManager" cardinality="1..1" interface="org.argeo.api.cms.transaction.WorkControl" name="WorkControl" policy="static"/>
+ <reference bind="setUserTransaction" cardinality="1..1" interface="org.argeo.api.cms.transaction.WorkTransaction" name="WorkTransaction" policy="static"/>
+ <reference bind="setCmsState" cardinality="1..1" interface="org.argeo.api.cms.CmsState" name="CmsState" policy="static"/>
+ <service>
+ <provide interface="org.osgi.service.useradmin.UserAdmin"/>
+ </service>
+</scr:component>
<?xml version="1.0" encoding="UTF-8"?>
-<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="User Admin Service">
- <implementation class="org.argeo.cms.internal.auth.CmsUserManagerImpl"/>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="start" deactivate="stop" immediate="true" name="User Admin Service">
+ <implementation class="org.argeo.cms.internal.runtime.CmsUserManagerImpl"/>
<service>
- <provide interface="org.argeo.cms.CmsUserManager"/>
+ <provide interface="org.argeo.api.cms.directory.CmsUserManager"/>
</service>
<reference bind="setUserAdmin" cardinality="1..1" interface="org.osgi.service.useradmin.UserAdmin" name="UserAdmin" policy="static"/>
- <reference bind="setUserTransaction" cardinality="1..1" interface="org.argeo.osgi.transaction.WorkTransaction" name="UserTransaction" policy="static"/>
- <reference bind="addUserDirectory" cardinality="0..n" interface="org.argeo.osgi.useradmin.UserDirectory" name="UserDirectory" policy="static" unbind="removeUserDirectory"/>
+ <reference bind="setUserTransaction" cardinality="1..1" interface="org.argeo.api.cms.transaction.WorkTransaction" name="UserTransaction" 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="start" deactivate="stop" name="Deploy Config">
- <implementation class="org.argeo.cms.internal.osgi.DeployConfig"/>
- <service>
- <provide interface="org.argeo.cms.internal.osgi.DeployConfig"/>
- </service>
- <reference bind="setConfigurationAdmin" cardinality="1..1" interface="org.osgi.service.cm.ConfigurationAdmin" name="ConfigurationAdmin" policy="static"/>
- <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="start" deactivate="stop" name="Node User Admin">
- <implementation class="org.argeo.cms.internal.osgi.NodeUserAdmin"/>
- <property name="service.pid" type="String" value="org.argeo.api.userAdmin"/>
- <service>
- <provide interface="org.osgi.service.cm.ManagedServiceFactory"/>
- </service>
- <reference bind="setTransactionManager" cardinality="1..1" interface="org.argeo.osgi.transaction.WorkControl" name="WorkControl" policy="static"/>
- <reference bind="setUserTransaction" cardinality="1..1" interface="org.argeo.osgi.transaction.WorkTransaction" name="WorkTransaction" policy="static"/>
- <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" name="Simple Transaction Manager">
- <implementation class="org.argeo.osgi.transaction.SimpleTransactionManager"/>
- <service>
- <provide interface="org.argeo.osgi.transaction.WorkControl"/>
- <provide interface="org.argeo.osgi.transaction.WorkTransaction"/>
- </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" name="Simple Transaction Manager">
+ <implementation class="org.argeo.api.cms.transaction.SimpleTransactionManager"/>
+ <service>
+ <provide interface="org.argeo.api.cms.transaction.WorkControl"/>
+ <provide interface="org.argeo.api.cms.transaction.WorkTransaction"/>
+ </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" name="UUID Factory">
+ <implementation class="org.argeo.cms.acr.CmsUuidFactory"/>
+ <service>
+ <provide interface="org.argeo.api.uuid.UuidFactory"/>
+ </service>
+</scr:component>
Bundle-Activator: org.argeo.cms.internal.osgi.CmsActivator
Import-Package: \
-org.argeo.osgi.transaction, \
-org.apache.commons.httpclient.cookie;resolution:=optional,\
-!com.sun.security.jgss,\
org.osgi.*;version=0.0.0,\
*
Service-Component:\
+OSGI-INF/cmsOsgiLogger.xml,\
+OSGI-INF/uuidFactory.xml,\
+OSGI-INF/cmsEventBus.xml,\
OSGI-INF/cmsState.xml,\
-OSGI-INF/simpleTransactionManager.xml,\
-OSGI-INF/nodeUserAdmin.xml,\
+OSGI-INF/transactionManager.xml,\
+OSGI-INF/cmsUserAdmin.xml,\
OSGI-INF/cmsUserManager.xml,\
-OSGI-INF/deployConfig.xml,\
+OSGI-INF/cmsContentRepository.xml,\
+OSGI-INF/cmsAcrHttpHandler.xml,\
OSGI-INF/cmsDeployment.xml,\
OSGI-INF/cmsContext.xml,\
-output.. = bin/
bin.includes = META-INF/,\
.,\
bin/,\
OSGI-INF/,\
- OSGI-INF/simpleTransactionManager.xml,\
- OSGI-INF/cmsState.xml,\
- OSGI-INF/nodeUserAdmin.xml,\
- OSGI-INF/deployConfig.xml,\
- OSGI-INF/cmsDeployment.xml,\
- OSGI-INF/cmsContext.xml
+ OSGI-INF/cmsEventBus.xml
source.. = src/
+output.. = bin/
import org.argeo.api.cms.CmsApp;
import org.argeo.api.cms.CmsAppListener;
-import org.argeo.api.cms.CmsTheme;
+import org.argeo.api.cms.CmsContext;
+import org.argeo.api.cms.ux.CmsTheme;
/** Base class for {@link CmsApp}s. */
public abstract class AbstractCmsApp implements CmsApp {
+ private CmsContext cmsContext;
+
private Map<String, CmsTheme> themes = Collections.synchronizedMap(new HashMap<>());
private List<CmsAppListener> cmsAppListeners = new ArrayList<>();
- protected abstract String getThemeId(String uiName);
+ /** To be overridden in order to provide themes. */
+ protected String getThemeId(String uiName) {
+ return null;
+ }
@Override
public CmsTheme getTheme(String uiName) {
String themeId = getThemeId(uiName);
if ("org.eclipse.rap.rwt.theme.Default".equals(themeId))
continue uiNames;
- if (!themes.containsKey(themeId)) {
+ if (themeId != null && !themes.containsKey(themeId)) {
themeMissing = true;
break uiNames;
}
cmsAppListeners.remove(listener);
}
+ @Override
+ public CmsContext getCmsContext() {
+ return cmsContext;
+ }
+
+ public void setCmsContext(CmsContext cmsContext) {
+ this.cmsContext = cmsContext;
+ }
+
+
+
}
--- /dev/null
+package org.argeo.cms;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.CharArrayWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.io.Reader;
+import java.io.Writer;
+import java.security.Provider;
+import java.security.Security;
+import java.util.Arrays;
+import java.util.Iterator;
+
+import javax.crypto.SecretKey;
+import javax.security.auth.Subject;
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.callback.TextOutputCallback;
+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.keyring.PBEKeySpecCallback;
+import org.argeo.cms.util.CurrentSubject;
+import org.argeo.cms.util.StreamUtils;
+import org.argeo.api.cms.keyring.CryptoKeyring;
+import org.argeo.api.cms.keyring.Keyring;
+
+/** username / password based keyring. TODO internationalize */
+public abstract class AbstractKeyring implements Keyring, CryptoKeyring {
+ // public final static String DEFAULT_KEYRING_LOGIN_CONTEXT = "KEYRING";
+
+ // private String loginContextName = DEFAULT_KEYRING_LOGIN_CONTEXT;
+ private CallbackHandler defaultCallbackHandler;
+
+ private String charset = "UTF-8";
+
+ /**
+ * Default provider is bouncy castle, in order to have consistent behaviour
+ * across implementations
+ */
+ private String securityProviderName = "BC";
+
+ /**
+ * Whether the keyring has already been created in the past with a master
+ * password
+ */
+ protected abstract Boolean isSetup();
+
+ /**
+ * Setup the keyring persistently, {@link #isSetup()} must return true
+ * afterwards
+ */
+ protected abstract void setup(char[] password);
+
+ /** Populates the key spec callback */
+ protected abstract void handleKeySpecCallback(PBEKeySpecCallback pbeCallback);
+
+ protected abstract void encrypt(String path, InputStream unencrypted);
+
+ protected abstract InputStream decrypt(String path);
+
+ /** Triggers lazy initialization */
+ protected SecretKey getSecretKey(char[] password) {
+ Subject subject = CurrentSubject.current();
+ if (subject == null)
+ throw new IllegalStateException("Current subject cannot be null");
+ // we assume only one secrete key is available
+ Iterator<SecretKey> iterator = subject.getPrivateCredentials(SecretKey.class).iterator();
+ if (!iterator.hasNext() || password != null) {// not initialized
+ CallbackHandler callbackHandler = password == null ? new KeyringCallbackHandler()
+ : new PasswordProvidedCallBackHandler(password);
+ ClassLoader currentContextClassLoader = Thread.currentThread().getContextClassLoader();
+ Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
+ try {
+ LoginContext loginContext = new LoginContext(CmsAuth.LOGIN_CONTEXT_KEYRING, subject, callbackHandler);
+ loginContext.login();
+ // FIXME will login even if password is wrong
+ iterator = subject.getPrivateCredentials(SecretKey.class).iterator();
+ return iterator.next();
+ } catch (LoginException e) {
+ throw new IllegalStateException("Keyring login failed", e);
+ } finally {
+ Thread.currentThread().setContextClassLoader(currentContextClassLoader);
+ }
+
+ } else {
+ SecretKey secretKey = iterator.next();
+ if (iterator.hasNext())
+ throw new IllegalStateException("More than one secret key in private credentials");
+ return secretKey;
+ }
+ }
+
+ public InputStream getAsStream(String path) {
+ return decrypt(path);
+ }
+
+ public void set(String path, InputStream in) {
+ encrypt(path, in);
+ }
+
+ public char[] getAsChars(String path) {
+ // InputStream in = getAsStream(path);
+ // CharArrayWriter writer = null;
+ // Reader reader = null;
+ try (InputStream in = getAsStream(path);
+ CharArrayWriter writer = new CharArrayWriter();
+ Reader reader = new InputStreamReader(in, charset);) {
+ StreamUtils.copy(reader, writer);
+ return writer.toCharArray();
+ } catch (IOException e) {
+ throw new IllegalStateException("Cannot decrypt to char array", e);
+ } finally {
+ // IOUtils.closeQuietly(reader);
+ // IOUtils.closeQuietly(in);
+ // IOUtils.closeQuietly(writer);
+ }
+ }
+
+ public void set(String path, char[] arr) {
+ // ByteArrayOutputStream out = new ByteArrayOutputStream();
+ // ByteArrayInputStream in = null;
+ // Writer writer = null;
+ try (ByteArrayOutputStream out = new ByteArrayOutputStream();
+ Writer writer = new OutputStreamWriter(out, charset);) {
+ // writer = new OutputStreamWriter(out, charset);
+ writer.write(arr);
+ writer.flush();
+ // in = new ByteArrayInputStream(out.toByteArray());
+ try (ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());) {
+ set(path, in);
+ }
+ } catch (IOException e) {
+ throw new IllegalStateException("Cannot encrypt to char array", e);
+ } finally {
+ // IOUtils.closeQuietly(writer);
+ // IOUtils.closeQuietly(out);
+ // IOUtils.closeQuietly(in);
+ }
+ }
+
+ public void unlock(char[] password) {
+ if (!isSetup())
+ setup(password);
+ SecretKey secretKey = getSecretKey(password);
+ if (secretKey == null)
+ throw new IllegalStateException("Could not unlock keyring");
+ }
+
+ protected Provider getSecurityProvider() {
+ return Security.getProvider(securityProviderName);
+ }
+
+ public void setDefaultCallbackHandler(CallbackHandler defaultCallbackHandler) {
+ this.defaultCallbackHandler = defaultCallbackHandler;
+ }
+
+ public void setCharset(String charset) {
+ this.charset = charset;
+ }
+
+ public void setSecurityProviderName(String securityProviderName) {
+ this.securityProviderName = securityProviderName;
+ }
+
+ // @Deprecated
+ // protected static byte[] hash(char[] password, byte[] salt, Integer
+ // iterationCount) {
+ // ByteArrayOutputStream out = null;
+ // OutputStreamWriter writer = null;
+ // try {
+ // out = new ByteArrayOutputStream();
+ // writer = new OutputStreamWriter(out, "UTF-8");
+ // writer.write(password);
+ // MessageDigest pwDigest = MessageDigest.getInstance("SHA-256");
+ // pwDigest.reset();
+ // pwDigest.update(salt);
+ // byte[] btPass = pwDigest.digest(out.toByteArray());
+ // for (int i = 0; i < iterationCount; i++) {
+ // pwDigest.reset();
+ // btPass = pwDigest.digest(btPass);
+ // }
+ // return btPass;
+ // } catch (Exception e) {
+ // throw new CmsException("Cannot hash", e);
+ // } finally {
+ // IOUtils.closeQuietly(out);
+ // IOUtils.closeQuietly(writer);
+ // }
+ //
+ // }
+
+ /**
+ * Convenience method using the underlying callback to ask for a password
+ * (typically used when the password is not saved in the keyring)
+ */
+ protected char[] ask() {
+ PasswordCallback passwordCb = new PasswordCallback("Password", false);
+ Callback[] dialogCbs = new Callback[] { passwordCb };
+ try {
+ defaultCallbackHandler.handle(dialogCbs);
+ char[] password = passwordCb.getPassword();
+ return password;
+ } catch (Exception e) {
+ throw new IllegalStateException("Cannot ask for a password", e);
+ }
+
+ }
+
+ class KeyringCallbackHandler implements CallbackHandler {
+ public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
+ // checks
+ if (callbacks.length != 2)
+ throw new IllegalArgumentException(
+ "Keyring requires 2 and only 2 callbacks: {PasswordCallback,PBEKeySpecCallback}");
+ if (!(callbacks[0] instanceof PasswordCallback))
+ throw new UnsupportedCallbackException(callbacks[0]);
+ if (!(callbacks[1] instanceof PBEKeySpecCallback))
+ throw new UnsupportedCallbackException(callbacks[0]);
+
+ PasswordCallback passwordCb = (PasswordCallback) callbacks[0];
+ PBEKeySpecCallback pbeCb = (PBEKeySpecCallback) callbacks[1];
+
+ if (isSetup()) {
+ Callback[] dialogCbs = new Callback[] { passwordCb };
+ defaultCallbackHandler.handle(dialogCbs);
+ } else {// setup keyring
+ TextOutputCallback textCb1 = new TextOutputCallback(TextOutputCallback.INFORMATION,
+ "Enter a master password which will protect your private data");
+ TextOutputCallback textCb2 = new TextOutputCallback(TextOutputCallback.INFORMATION,
+ "(for example your credentials to third-party services)");
+ TextOutputCallback textCb3 = new TextOutputCallback(TextOutputCallback.INFORMATION,
+ "Don't forget this password since the data cannot be read without it");
+ PasswordCallback confirmPasswordCb = new PasswordCallback("Confirm password", false);
+ // first try
+ Callback[] dialogCbs = new Callback[] { textCb1, textCb2, textCb3, passwordCb, confirmPasswordCb };
+ defaultCallbackHandler.handle(dialogCbs);
+
+ // if passwords different, retry (except if cancelled)
+ while (passwordCb.getPassword() != null
+ && !Arrays.equals(passwordCb.getPassword(), confirmPasswordCb.getPassword())) {
+ TextOutputCallback textCb = new TextOutputCallback(TextOutputCallback.ERROR,
+ "The passwords do not match");
+ dialogCbs = new Callback[] { textCb, passwordCb, confirmPasswordCb };
+ defaultCallbackHandler.handle(dialogCbs);
+ }
+
+ if (passwordCb.getPassword() != null) {// not cancelled
+ setup(passwordCb.getPassword());
+ }
+ }
+
+ if (passwordCb.getPassword() != null)
+ handleKeySpecCallback(pbeCb);
+ }
+
+ }
+
+ class PasswordProvidedCallBackHandler implements CallbackHandler {
+ private final char[] password;
+
+ public PasswordProvidedCallBackHandler(char[] password) {
+ this.password = password;
+ }
+
+ @Override
+ public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
+ // checks
+ if (callbacks.length != 2)
+ throw new IllegalArgumentException(
+ "Keyring requires 2 and only 2 callbacks: {PasswordCallback,PBEKeySpecCallback}");
+ if (!(callbacks[0] instanceof PasswordCallback))
+ throw new UnsupportedCallbackException(callbacks[0]);
+ if (!(callbacks[1] instanceof PBEKeySpecCallback))
+ throw new UnsupportedCallbackException(callbacks[0]);
+
+ PasswordCallback passwordCb = (PasswordCallback) callbacks[0];
+ passwordCb.setPassword(password);
+ PBEKeySpecCallback pbeCb = (PBEKeySpecCallback) callbacks[1];
+ handleKeySpecCallback(pbeCb);
+ }
+
+ }
+}
+++ /dev/null
-package org.argeo.cms;
-
-/** Framework agnostic interface for log notifications */
-@Deprecated
-public interface ArgeoLogListener {
- /**
- * Appends a log
- *
- * @param username
- * authentified user, null for anonymous
- * @param level
- * INFO, DEBUG, WARN, etc. (logging framework specific)
- * @param category
- * hierarchy (logging framework specific)
- * @param thread
- * name of the thread which logged this message
- * @param msg
- * any object as long as its toString() method returns the
- * message
- * @param exception
- * exception in log4j ThrowableStrRep format
- */
- public void appendLog(String username, Long timestamp, String level,
- String category, String thread, Object msg, String[] exception);
-}
+++ /dev/null
-package org.argeo.cms;
-
-/**
- * Logging framework agnostic identifying a logging service, to which one can
- * register
- */
-@Deprecated
-public interface ArgeoLogger {
- /**
- * Register for events by threads with the same authentication (or all
- * threads if admin)
- */
- public void register(ArgeoLogListener listener,
- Integer numberOfPreviousEvents);
-
- /**
- * For admin use only: register for all users
- *
- * @param listener
- * the log listener
- * @param numberOfPreviousEvents
- * the number of previous events to notify
- * @param everything
- * if true even anonymous is logged
- */
- public void registerForAll(ArgeoLogListener listener,
- Integer numberOfPreviousEvents, boolean everything);
-
- public void unregister(ArgeoLogListener listener);
-
- public void unregisterForAll(ArgeoLogListener listener);
-}
package org.argeo.cms;
/** JCR names in the http://www.argeo.org/argeo namespace */
+@Deprecated
public interface ArgeoNames {
public final static String ARGEO_NAMESPACE = "http://www.argeo.org/ns/argeo";
package org.argeo.cms;
/** JCR types in the http://www.argeo.org/argeo namespace */
+@Deprecated
public interface ArgeoTypes {
public final static String ARGEO_REMOTE_REPOSITORY = "argeo:remoteRepository";
-
+
// tabular
public final static String ARGEO_TABLE = "argeo:table";
public final static String ARGEO_COLUMN = "argeo:column";
--- /dev/null
+package org.argeo.cms;
+
+import java.util.Objects;
+
+/** A property that can be used to configure a CMS node deployment. */
+public enum CmsDeployProperty {
+ //
+ // DIRECTORY
+ //
+ DIRECTORY("argeo.directory", 64),
+ //
+ // DATABASE
+ //
+ /** URL of the database backend. */
+ DB_URL("argeo.db.url"),
+ /** DB user of the database backend. */
+ DB_USER("argeo.db.user"),
+ /** DB user password of the database backend. */
+ DB_PASSWORD("argeo.db.password"),
+ //
+ // NETWORK
+ //
+ /** Either a host or an IP address. Restricts all servers to it. */
+ HOST("argeo.host"),
+ /** Either a host or an IP address. Restricts all servers to it. */
+ DNS("argeo.dns", 16),
+ //
+ // HTTP
+ //
+ /** Request an HTTP server on this port. */
+ HTTP_PORT("argeo.http.port"),
+ /** Request an HTTPS server on this port. */
+ HTTPS_PORT("argeo.https.port"),
+ /**
+ * The HTTP header used to convey the DN of a client verified by a reverse
+ * proxy. Typically SSL_CLIENT_S_DN for Apache.
+ */
+ HTTP_PROXY_SSL_HEADER_DN("argeo.http.proxy.ssl.header.dn"),
+ //
+ // SSL
+ //
+ /** SSL keystore for the system. */
+ SSL_KEYSTORE("argeo.ssl.keystore"),
+ /** SSL keystore password for the system. */
+ SSL_PASSWORD("argeo.ssl.password"),
+ /** SSL keystore type password for the system. */
+ SSL_KEYSTORETYPE("argeo.ssl.keystoretype"),
+ /** SSL password for the private key. */
+ SSL_KEYPASSWORD("argeo.ssl.keypassword"),
+ /** Whether a client certificate is required. */
+ SSL_NEEDCLIENTAUTH("argeo.ssl.needclientauth"),
+ /** Whether a client certificate can be used. */
+ SSL_WANTCLIENTAUTH("argeo.ssl.wantclientauth"),
+ /** SSL protocol to use. */
+ SSL_PROTOCOL("argeo.ssl.protocol"),
+ /** SSL algorithm to use. */
+ SSL_ALGORITHM("argeo.ssl.algorithm"),
+ /** Custom SSL trust store. */
+ SSL_TRUSTSTORE("argeo.ssl.truststore"),
+ /** Custom SSL trust store type. */
+ SSL_TRUSTSTORETYPE("argeo.ssl.truststoretype"),
+ /** Custom SSL trust store type. */
+ SSL_TRUSTSTOREPASSWORD("argeo.ssl.truststorepassword"),
+ //
+ // WEBSOCKET
+ //
+ /** Whether web socket should be enables in web server. */
+ WEBSOCKET_ENABLED("argeo.websocket.enabled"),
+ //
+ // SSH
+ //
+ /** Request an HTTP server on this port. */
+ SSHD_PORT("argeo.sshd.port"),
+ /** Path to admin authorized keys file. */
+ SSHD_AUTHORIZEDKEYS("argeo.sshd.authorizedkeys"),
+ //
+ // INTERNATIONALIZATION
+ //
+ /** Locales enabled for this system, the first one is considered the default. */
+ LOCALE("argeo.locale", 256),
+ //
+ // NODE
+ //
+ /** Directories to copy to the data area during the first initialisation. */
+ NODE_INIT("argeo.node.init", 64),
+ //
+ // JAVA
+ //
+ /** Custom JAAS config. */
+ JAVA_LOGIN_CONFIG("java.security.auth.login.config", true),
+ //
+ // OSGi
+ //
+ /** OSGi writable data area. */
+ OSGI_INSTANCE_AREA("osgi.instance.area"),
+ /** OSGi writable configuration area. */
+ OSGI_CONFIGURATION_AREA("osgi.configuration.area"),
+ //
+ ;
+
+ private String property;
+ private boolean systemPropertyOnly = false;
+
+ private int maxCount = 1;
+
+ CmsDeployProperty(String property) {
+ this(property, 1, false);
+ }
+
+ CmsDeployProperty(String property, int maxCount) {
+ this(property, maxCount, false);
+ }
+
+ CmsDeployProperty(String property, boolean systemPropertyOnly) {
+ this.property = property;
+ }
+
+ CmsDeployProperty(String property, int maxCount, boolean systemPropertyOnly) {
+ this.property = property;
+ this.systemPropertyOnly = systemPropertyOnly;
+ this.maxCount = maxCount;
+ }
+
+ public String getProperty() {
+ return property;
+ }
+
+ public boolean isSystemPropertyOnly() {
+ return systemPropertyOnly;
+ }
+
+ public int getMaxCount() {
+ return maxCount;
+ }
+
+ public static CmsDeployProperty find(String property) {
+ int index = getPropertyIndex(property);
+ String propertyName = index == 0 ? property : property.substring(0, property.lastIndexOf('.'));
+ for (CmsDeployProperty deployProperty : values()) {
+ if (deployProperty.getProperty().equals(propertyName))
+ return deployProperty;
+ }
+ return null;
+ }
+
+ public static int getPropertyIndex(String property) {
+ Objects.requireNonNull(property);
+ int lastDot = property.lastIndexOf('.');
+ if (lastDot <= 0 || lastDot == (property.length() - 1)) {
+ throw new IllegalArgumentException("Property " + property + " is not qualified (must contain a dot).");
+ }
+ String lastSegment = property.substring(lastDot + 1);
+ int index;
+ try {
+ index = Integer.parseInt(lastSegment);
+ } catch (NumberFormatException e) {
+ index = 0;
+ }
+ return index;
+ }
+
+}
+++ /dev/null
-package org.argeo.cms;
-
-/** @deprecated Use standard Java {@link RuntimeException} instead. */
-@Deprecated
-public class CmsException extends RuntimeException {
- private static final long serialVersionUID = -5341764743356771313L;
-
- public CmsException(String message) {
- super(message);
- }
-
- public CmsException(String message, Throwable e) {
- super(message, e);
- }
-
-}
--- /dev/null
+package org.argeo.cms;
+
+import org.argeo.api.cms.CmsConstants;
+
+/** Just a marker interface for the time being. */
+public interface CmsSshd {
+ final static String NODE_USERNAME_ALIAS = "user.name";
+ final static String DEFAULT_SSH_HOST_KEY_PATH = "private/" + CmsConstants.NODE + ".ser";
+}
+++ /dev/null
-package org.argeo.cms;
-
-import java.time.ZonedDateTime;
-import java.util.List;
-import java.util.Set;
-
-import javax.security.auth.Subject;
-
-import org.osgi.framework.InvalidSyntaxException;
-import org.osgi.service.useradmin.Role;
-import org.osgi.service.useradmin.User;
-
-/**
- * Provide method interfaces to manage user concepts without accessing directly
- * the userAdmin.
- */
-public interface CmsUserManager {
-
- // CurrentUser
- /** Returns the e-mail of the current logged in user */
- public String getMyMail();
-
- // Other users
- /** Returns a {@link User} given a username */
- public User getUser(String username);
-
- /** Can be a group or a user */
- public String getUserDisplayName(String dn);
-
- /** Can be a group or a user */
- public String getUserMail(String dn);
-
- /** Lists all roles of the given user */
- public String[] getUserRoles(String dn);
-
- /** Checks if the passed user belongs to the passed role */
- public boolean isUserInRole(String userDn, String roleDn);
-
- // Search
- /** Returns a filtered list of roles */
- public Role[] getRoles(String filter) throws InvalidSyntaxException;
-
- /** Recursively lists users in a given group. */
- public Set<User> listUsersInGroup(String groupDn, String filter);
-
- /** Search among groups including system roles and users if needed */
- public List<User> listGroups(String filter, boolean includeUsers, boolean includeSystemRoles);
-
- /* MISCELLANEOUS */
- /** Returns the dn of a role given its local ID */
- public String buildDefaultDN(String localId, int type);
-
- /** Exposes the main default domain name for this instance */
- public String getDefaultDomainName();
-
- /**
- * Search for a {@link User} (might also be a group) whose uid or cn is equals
- * to localId within the various user repositories defined in the current
- * context.
- */
- public User getUserFromLocalId(String localId);
-
- void changeOwnPassword(char[] oldPassword, char[] newPassword);
-
- void resetPassword(String username, char[] newPassword);
-
- @Deprecated
- String addSharedSecret(String username, int hours);
-
-// String addSharedSecret(String username, String authInfo, String authToken);
-
- void addAuthToken(String userDn, String token, Integer hours, String... roles);
-
- void addAuthToken(String userDn, String token, ZonedDateTime expiryDate, String... roles);
-
- void expireAuthToken(String token);
-
- void expireAuthTokens(Subject subject);
-
-// User createUserFromPerson(Node person);
-
-// @Deprecated
-// public UserAdmin getUserAdmin();
-//
-// @Deprecated
-// public UserTransaction getUserTransaction();
-}
\ No newline at end of file
--- /dev/null
+package org.argeo.cms;
+
+import java.security.Principal;
+import java.security.PrivilegedAction;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.Set;
+import java.util.UUID;
+
+import javax.security.auth.Subject;
+import javax.security.auth.x500.X500Principal;
+
+import org.argeo.api.acr.NamespaceUtils;
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.api.cms.CmsSession;
+import org.argeo.api.cms.CmsSessionId;
+import org.argeo.cms.internal.auth.CmsSessionImpl;
+import org.argeo.cms.internal.auth.ImpliedByPrincipal;
+import org.argeo.cms.internal.runtime.CmsContextImpl;
+import org.argeo.cms.util.CurrentSubject;
+import org.osgi.service.useradmin.Authorization;
+
+/**
+ * Programmatic access to the currently authenticated user, within a CMS
+ * context.
+ */
+public final class CurrentUser {
+ /**
+ * Technical username of the currently authenticated user.
+ *
+ * @return the authenticated username or null if not authenticated / anonymous
+ */
+ public static String getUsername() {
+ return getUsername(currentSubject());
+ }
+
+ /**
+ * Human readable name of the currently authenticated user (typically first name
+ * and last name).
+ */
+ public static String getDisplayName() {
+ return getDisplayName(currentSubject());
+ }
+
+ /** Whether a user is currently authenticated. */
+ public static boolean isAnonymous() {
+ return isAnonymous(currentSubject());
+ }
+
+ /** Locale of the current user */
+ public static Locale locale() {
+ return locale(currentSubject());
+ }
+
+ /** Roles of the currently logged-in user */
+ public static Set<String> roles() {
+ return roles(currentSubject());
+ }
+
+ /** Returns true if the current user is in the specified role */
+ public static boolean isInRole(String role) {
+ Set<String> roles = roles();
+ return roles.contains(role);
+ }
+
+ /** Implies this {@link SystemRole} in this context. */
+ public static boolean implies(SystemRole role, String context) {
+ return role.implied(currentSubject(), context);
+ }
+
+ /** Implies this role name, also independently of the context. */
+ public static boolean implies(String role, String context) {
+ return SystemRole.implied(NamespaceUtils.parsePrefixedName(role), currentSubject(), context);
+ }
+
+ /** Get the primary context this user belongs to. */
+ public static boolean isUserContext(String context) {
+ // TODO have the role context as a separated credential in the Subjecto?
+ return RoleNameUtils.getContext(getUsername()).equalsIgnoreCase(context);
+ }
+
+ /** Executes as the current user */
+ public static <T> T doAs(PrivilegedAction<T> action) {
+ return Subject.doAs(currentSubject(), action);
+ }
+
+ /** Executes as the current user */
+ public static <T> T tryAs(PrivilegedExceptionAction<T> action) throws PrivilegedActionException {
+ return Subject.doAs(currentSubject(), action);
+ }
+
+ /*
+ * WRAPPERS
+ */
+
+ public static String getUsername(Subject subject) {
+ if (subject == null)
+ throw new IllegalArgumentException("Subject cannot be null");
+ if (subject.getPrincipals(X500Principal.class).size() != 1)
+ return CmsConstants.ROLE_ANONYMOUS;
+ Principal principal = subject.getPrincipals(X500Principal.class).iterator().next();
+ return principal.getName();
+ }
+
+ public static String getDisplayName(Subject subject) {
+ return getAuthorization(subject).toString();
+ }
+
+ public static Set<String> roles(Subject subject) {
+ Set<String> roles = new HashSet<String>();
+ roles.add(getUsername(subject));
+ for (Principal group : subject.getPrincipals(ImpliedByPrincipal.class)) {
+ roles.add(group.getName());
+ }
+ return roles;
+ }
+
+ public static Locale locale(Subject subject) {
+ Set<Locale> locales = subject.getPublicCredentials(Locale.class);
+ if (locales.isEmpty()) {
+ Locale defaultLocale = CmsContextImpl.getCmsContext().getDefaultLocale();
+ return defaultLocale;
+ } else
+ return locales.iterator().next();
+ }
+
+ /** Whether this user is currently authenticated. */
+ public static boolean isAnonymous(Subject subject) {
+ if (subject == null)
+ return true;
+ String username = getUsername(subject);
+ return username == null || username.equalsIgnoreCase(CmsConstants.ROLE_ANONYMOUS);
+ }
+
+ public static CmsSession getCmsSession() {
+ Subject subject = currentSubject();
+ Iterator<CmsSessionId> it = subject.getPrivateCredentials(CmsSessionId.class).iterator();
+ if (!it.hasNext())
+ throw new IllegalStateException("No CMS session id available for " + subject);
+ CmsSessionId cmsSessionId = it.next();
+ if (it.hasNext())
+ throw new IllegalStateException("More than one CMS session id available for " + subject);
+ return CmsContextImpl.getCmsContext().getCmsSessionByUuid(cmsSessionId.getUuid());
+ }
+
+ public static boolean isAvailable() {
+ return CurrentSubject.current() != null;
+ }
+
+ /*
+ * HELPERS
+ */
+ private static Subject currentSubject() {
+ Subject subject = CurrentSubject.current();
+ if (subject == null)
+ throw new IllegalStateException("Cannot find related subject");
+ return subject;
+ }
+
+ private static Authorization getAuthorization(Subject subject) {
+ return subject.getPrivateCredentials(Authorization.class).iterator().next();
+ }
+
+ public static boolean logoutCmsSession(Subject subject) {
+ UUID nodeSessionId;
+ if (subject.getPrivateCredentials(CmsSessionId.class).size() == 1)
+ nodeSessionId = subject.getPrivateCredentials(CmsSessionId.class).iterator().next().getUuid();
+ else
+ return false;
+ CmsSessionImpl cmsSession = CmsContextImpl.getCmsContext().getCmsSessionByUuid(nodeSessionId);
+
+ // FIXME logout all views
+ // TODO check why it is sometimes null
+ if (cmsSession != null)
+ cmsSession.close();
+ // if (log.isDebugEnabled())
+ // log.debug("Logged out CMS session " + cmsSession.getUuid());
+ return true;
+ }
+
+ /** singleton */
+ private CurrentUser() {
+ }
+}
package org.argeo.cms;
-import java.security.AccessController;
-import java.util.ArrayList;
-import java.util.List;
import java.util.Locale;
import java.util.ResourceBundle;
-import javax.security.auth.Subject;
-
import org.argeo.api.cms.CmsLog;
-import org.argeo.cms.auth.CurrentUser;
/** Utilities simplifying the development of localization enums. */
public class LocaleUtils {
/** Where the search for a message is actually performed. */
public static String local(String key, Locale locale, String resource, ClassLoader classLoader) {
ResourceBundle rb = ResourceBundle.getBundle(resource, locale, classLoader);
- assert key.length() > 2;
- if (isLocaleKey(key))
+ if (isLocaleKey(key)) {
+ assert key.length() > 1;
key = key.substring(1);
+ }
if (rb.containsKey(key))
return rb.getString(key);
else // for simple cases, the key will actually be the English word
static Locale getCurrentLocale() {
Locale currentLocale = null;
- if (Subject.getSubject(AccessController.getContext()) != null)
+ if (CurrentUser.isAvailable())
currentLocale = CurrentUser.locale();
else if (threadLocale.get() != null) {
currentLocale = threadLocale.get();
// return Locale.getDefault();
}
- /** Returns null if argument is null. */
- public static List<Locale> asLocaleList(Object locales) {
- if (locales == null)
- return null;
- ArrayList<Locale> availableLocales = new ArrayList<Locale>();
- String[] codes = locales.toString().split(",");
- for (int i = 0; i < codes.length; i++) {
- String code = codes[i];
- // variant not supported
- int indexUnd = code.indexOf("_");
- Locale locale;
- if (indexUnd > 0) {
- String language = code.substring(0, indexUnd);
- String country = code.substring(indexUnd + 1);
- locale = new Locale(language, country);
- } else {
- locale = new Locale(code);
- }
- availableLocales.add(locale);
- }
- return availableLocales;
- }
}
--- /dev/null
+package org.argeo.cms;
+
+import static org.argeo.api.acr.RuntimeNamespaceContext.getNamespaceContext;
+
+import javax.xml.namespace.QName;
+
+import org.argeo.api.acr.ArgeoNamespace;
+import org.argeo.api.acr.NamespaceUtils;
+import org.argeo.cms.directory.ldap.LdapNameUtils;
+
+/** Simplifies analysis of system roles. */
+public class RoleNameUtils {
+ public static String getLastRdnValue(String dn) {
+ return LdapNameUtils.getLastRdnValue(dn);
+// // we don't use LdapName for portability with Android
+// // TODO make it more robust
+// String[] parts = dn.split(",");
+// String[] rdn = parts[0].split("=");
+// return rdn[1];
+ }
+
+ public static QName getLastRdnAsName(String dn) {
+ String cn = getLastRdnValue(dn);
+ QName roleName = NamespaceUtils.parsePrefixedName(getNamespaceContext(), cn);
+ return roleName;
+ }
+
+ public static boolean isSystemRole(QName roleName) {
+ return roleName.getNamespaceURI().equals(ArgeoNamespace.ROLE_NAMESPACE_URI);
+ }
+
+ public static String getParent(String dn) {
+ int index = dn.indexOf(',');
+ return dn.substring(index + 1);
+ }
+
+ /** Up two levels. */
+ public static String getContext(String dn) {
+ return getParent(getParent(dn));
+ }
+}
--- /dev/null
+package org.argeo.cms;
+
+import java.util.Set;
+
+import javax.security.auth.Subject;
+import javax.xml.namespace.QName;
+
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.cms.internal.auth.ImpliedByPrincipal;
+
+/** A programmatic role. */
+public interface SystemRole {
+ QName qName();
+
+ /** Whether this role is implied for this authenticated user. */
+ default boolean implied(Subject subject, String context) {
+ return implied(qName(), subject, context);
+ }
+
+ /** Whether this role is implied for this distinguished name. */
+ default boolean implied(String dn, String context) {
+ String roleContext = RoleNameUtils.getContext(dn);
+ QName roleName = RoleNameUtils.getLastRdnAsName(dn);
+ return roleContext.equalsIgnoreCase(context) && qName().equals(roleName);
+ }
+
+ /**
+ * Whether this role is implied for this authenticated subject. If context is
+ * <code>null</code>, it is not considered; this should be used to build user
+ * interfaces, but not to authorise.
+ */
+ static boolean implied(QName name, Subject subject, String context) {
+ Set<ImpliedByPrincipal> roles = subject.getPrincipals(ImpliedByPrincipal.class);
+ for (ImpliedByPrincipal role : roles) {
+ if (role.isSystemRole()) {
+ if (role.getRoleName().equals(name)) {
+ // !! if context is not specified, it is considered irrelevant
+ if (context == null)
+ return true;
+ if (role.getContext().equalsIgnoreCase(context)
+ || role.getContext().equals(CmsConstants.NODE_BASEDN))
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+}
--- /dev/null
+package org.argeo.cms.acr;
+
+import java.util.AbstractMap;
+import java.util.AbstractSet;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import javax.xml.namespace.QName;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.acr.CrName;
+import org.argeo.api.acr.NamespaceUtils;
+import org.argeo.api.acr.spi.ProvidedContent;
+import org.argeo.api.acr.spi.ProvidedSession;
+import org.argeo.cms.util.LangUtils;
+
+/** Partial reference implementation of a {@link ProvidedContent}. */
+public abstract class AbstractContent extends AbstractMap<QName, Object> implements ProvidedContent {
+ private final ProvidedSession session;
+
+ // cache
+// private String _path = null;
+
+ public AbstractContent(ProvidedSession session) {
+ this.session = session;
+ }
+
+ /*
+ * ATTRIBUTES OPERATIONS
+ */
+// protected abstract Iterable<QName> keys();
+//
+// protected abstract void removeAttr(QName key);
+
+ @Override
+ public Set<Entry<QName, Object>> entrySet() {
+ Set<Entry<QName, Object>> result = new AttrSet();
+ return result;
+ }
+
+ @Override
+ public Class<?> getType(QName key) {
+ return String.class;
+ }
+
+ @Override
+ public boolean isMultiple(QName key) {
+ return false;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public <A> List<A> getMultiple(QName key, Class<A> clss) {
+ Object value = get(key);
+ if (value == null)
+ return new ArrayList<>();
+ if (value instanceof List) {
+ if (isDefaultAttrTypeRequested(clss))
+ return (List<A>) value;
+ List<A> res = new ArrayList<>();
+ List<?> lst = (List<?>) value;
+ for (Object o : lst) {
+ A item = clss.isAssignableFrom(String.class) ? (A) o.toString() : (A) o;
+ res.add(item);
+ }
+ return res;
+ } else {// singleton
+// try {
+ A res = (A) value;
+ return Collections.singletonList(res);
+// } catch (ClassCastException e) {
+// return Optional.empty();
+// }
+ }
+ }
+
+ /*
+ * CONTENT OPERATIONS
+ */
+
+ @Override
+ public String getPath() {
+// if (_path != null)
+// return _path;
+ List<Content> ancestors = new ArrayList<>();
+ collectAncestors(ancestors, this);
+ StringBuilder path = new StringBuilder();
+ ancestors: for (Content c : ancestors) {
+ QName name = c.getName();
+ if (CrName.root.qName().equals(name))
+ continue ancestors;
+
+ path.append('/');
+ path.append(NamespaceUtils.toPrefixedName(name));
+ int siblingIndex = c.getSiblingIndex();
+ if (siblingIndex != 1)
+ path.append('[').append(siblingIndex).append(']');
+ }
+// _path = path.toString();
+// return _path;
+ return path.toString();
+ }
+
+ private void collectAncestors(List<Content> ancestors, Content content) {
+ if (content == null)
+ return;
+ ancestors.add(0, content);
+ collectAncestors(ancestors, content.getParent());
+ }
+
+ @Override
+ public int getDepth() {
+ List<Content> ancestors = new ArrayList<>();
+ collectAncestors(ancestors, this);
+ return ancestors.size();
+ }
+
+ @Override
+ public String getSessionLocalId() {
+ return getPath();
+ }
+
+ /*
+ * SESSION
+ */
+
+ @Override
+ public ProvidedSession getSession() {
+ return session;
+ }
+
+ /*
+ * TYPING
+ */
+
+ @Override
+ public List<QName> getContentClasses() {
+ return new ArrayList<>();
+ }
+
+ /*
+ * UTILITIES
+ */
+ protected boolean isDefaultAttrTypeRequested(Class<?> clss) {
+ // check whether clss is Object.class
+ return clss.isAssignableFrom(Object.class);
+ }
+
+// @Override
+// public String toString() {
+// return "content " + getPath();
+// }
+
+ /*
+ * DEFAULTS
+ */
+ // - no children
+ // - no attributes
+ // - cannot be modified
+ @Override
+ public Iterator<Content> iterator() {
+ return Collections.emptyIterator();
+ }
+
+ @Override
+ public Content add(QName name, QName... classes) {
+ throw new UnsupportedOperationException("Content cannot be added.");
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException("Content cannot be removed.");
+ }
+
+ protected Iterable<QName> keys() {
+ return Collections.emptySet();
+ }
+
+ @Override
+ public <A> Optional<A> get(QName key, Class<A> clss) {
+ return null;
+ }
+
+ protected void removeAttr(QName key) {
+ throw new UnsupportedOperationException("Attributes cannot be removed.");
+ }
+
+ /*
+ * SUB CLASSES
+ */
+
+ class AttrSet extends AbstractSet<Entry<QName, Object>> {
+
+ @Override
+ public Iterator<Entry<QName, Object>> iterator() {
+ final Iterator<QName> keys = keys().iterator();
+ Iterator<Entry<QName, Object>> it = new Iterator<Map.Entry<QName, Object>>() {
+
+ QName key = null;
+
+ @Override
+ public boolean hasNext() {
+ return keys.hasNext();
+ }
+
+ @Override
+ public Entry<QName, Object> next() {
+ key = keys.next();
+ // TODO check type
+ Optional<?> value = get(key, Object.class);
+ assert !value.isEmpty();
+ AbstractMap.SimpleEntry<QName, Object> entry = new SimpleEntry<>(key, value.get());
+ return entry;
+ }
+
+ @Override
+ public void remove() {
+ if (key != null) {
+ AbstractContent.this.removeAttr(key);
+ } else {
+ throw new IllegalStateException("Iteration has not started");
+ }
+ }
+
+ };
+ return it;
+ }
+
+ @Override
+ public int size() {
+ return LangUtils.size(keys());
+ }
+
+ }
+
+ /*
+ * OBJECT METHODS
+ */
+ @Override
+ public String toString() {
+ return "content " + getPath();
+ }
+}
--- /dev/null
+package org.argeo.cms.acr;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.xml.namespace.QName;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+
+import org.argeo.api.acr.ArgeoNamespace;
+import org.argeo.api.acr.Content;
+import org.argeo.api.acr.CrName;
+import org.argeo.api.acr.NamespaceUtils;
+import org.argeo.api.acr.RuntimeNamespaceContext;
+import org.argeo.api.acr.spi.ContentNamespace;
+import org.argeo.api.acr.spi.ContentProvider;
+import org.argeo.api.acr.spi.ProvidedContent;
+import org.argeo.api.acr.spi.ProvidedRepository;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.cms.acr.xml.DomContentProvider;
+import org.argeo.cms.acr.xml.DomUtils;
+import org.w3c.dom.DOMException;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.xml.sax.SAXException;
+
+/**
+ * Base implementation of a {@link ProvidedRepository}.
+ */
+public abstract class AbstractContentRepository implements ProvidedRepository {
+ private final static CmsLog log = CmsLog.getLog(AbstractContentRepository.class);
+
+ private MountManager mountManager;
+ private TypesManager typesManager;
+
+ private CmsContentSession systemSession;
+
+ private Set<ContentProvider> providersToAdd = new HashSet<>();
+
+ // utilities
+ /** Should be used only to copy source and results. */
+ private TransformerFactory identityTransformerFactory = TransformerFactory.newInstance();
+
+// public final static String ACR_MOUNT_PATH_PROPERTY = "acr.mount.path";
+
+ public AbstractContentRepository() {
+ long begin = System.currentTimeMillis();
+ // types
+ typesManager = new TypesManager();
+ typesManager.init();
+ Set<QName> types = typesManager.listTypes();
+ if (log.isTraceEnabled())
+ for (QName type : types) {
+ log.trace(type + " - " + typesManager.getAttributeTypes(type));
+ }
+ long duration = System.currentTimeMillis() - begin;
+ log.debug(() -> "CMS content types available (initialisation took " + duration + " ms)");
+ }
+
+ protected abstract CmsContentSession newSystemSession();
+
+ public void start() {
+ systemSession = newSystemSession();
+ // mounts
+ mountManager = new MountManager(systemSession);
+ }
+
+ public void stop() {
+ systemSession.close();
+ systemSession = null;
+ }
+
+ /*
+ * REPOSITORY
+ */
+ @Override
+ public void addProvider(ContentProvider provider) {
+ if (mountManager == null) {
+ providersToAdd.add(provider);
+ log.debug(
+ () -> "Will add provider " + provider.getMountPath() + " (" + provider.getClass().getName() + ")");
+ } else {
+ mountManager.addStructuralContentProvider(provider);
+ log.debug(() -> "Added provider " + provider.getMountPath() + " (" + provider.getClass().getName() + ")");
+ }
+ }
+
+ @Override
+ public void registerTypes(ContentNamespace... namespaces) {
+ typesManager.registerTypes(namespaces);
+ }
+
+ /*
+ * FACTORIES
+ */
+ public void initRootContentProvider(Path path) {
+ try {
+// DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+// factory.setNamespaceAware(true);
+// factory.setXIncludeAware(true);
+// factory.setSchema(contentTypesManager.getSchema());
+//
+ DocumentBuilder dBuilder = typesManager.newDocumentBuilder();
+
+ Document document;
+// if (path != null && Files.exists(path)) {
+// InputSource inputSource = new InputSource(path.toAbsolutePath().toUri().toString());
+// inputSource.setEncoding(StandardCharsets.UTF_8.name());
+// // TODO public id as well?
+// document = dBuilder.parse(inputSource);
+// } else {
+ document = dBuilder.newDocument();
+ Element root = document.createElementNS(ArgeoNamespace.CR_NAMESPACE_URI,
+ NamespaceUtils.toPrefixedName(CrName.root.qName()));
+
+ for (String prefix : RuntimeNamespaceContext.getPrefixes().keySet()) {
+// root.setAttributeNS(XMLConstants.XMLNS_ATTRIBUTE_NS_URI, XMLConstants.XMLNS_ATTRIBUTE + ":" + prefix,
+// contentTypesManager.getPrefixes().get(prefix));
+ DomUtils.addNamespace(root, prefix,
+ RuntimeNamespaceContext.getNamespaceContext().getNamespaceURI(prefix));
+ }
+
+ document.appendChild(root);
+
+ // write it
+ if (path != null) {
+ try (OutputStream out = Files.newOutputStream(path)) {
+ writeDom(document, out);
+ }
+ }
+// }
+
+ String mountPath = "/";
+ DomContentProvider contentProvider = new DomContentProvider(mountPath, document);
+ addProvider(contentProvider);
+ } catch (DOMException | IOException e) {
+ throw new IllegalStateException("Cannot init ACR root " + path, e);
+ }
+
+ // add content providers already notified
+ for (ContentProvider contentProvider : providersToAdd)
+ addProvider(contentProvider);
+ providersToAdd.clear();
+ }
+
+ public void writeDom(Document document, OutputStream out) throws IOException {
+ try {
+ Transformer transformer = identityTransformerFactory.newTransformer();
+ transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
+ transformer.setOutputProperty(OutputKeys.INDENT, "yes");
+
+ DOMSource source = new DOMSource(document);
+ typesManager.validate(source);
+ StreamResult result = new StreamResult(out);
+ transformer.transform(source, result);
+ } catch (TransformerException e) {
+ throw new IOException("Cannot write dom", e);
+ }
+ }
+
+ /*
+ * MOUNT MANAGEMENT
+ */
+
+ @Override
+ public ContentProvider getMountContentProvider(Content mountPoint, boolean initialize, QName... types) {
+ String mountPath = mountPoint.getPath();
+ // TODO check consistency with types
+
+ return mountManager.getOrAddMountedProvider(mountPath, (path) -> {
+ DocumentBuilder dBuilder = typesManager.newDocumentBuilder();
+ Document document;
+ if (initialize) {
+ QName firstType = types[0];
+ document = dBuilder.newDocument();
+ String prefix = ((ProvidedContent) mountPoint).getSession().getPrefix(firstType.getNamespaceURI());
+ Element root = document.createElementNS(firstType.getNamespaceURI(),
+ prefix + ":" + firstType.getLocalPart());
+ DomUtils.addNamespace(root, prefix, firstType.getNamespaceURI());
+ document.appendChild(root);
+ } else {
+ try (InputStream in = mountPoint.open(InputStream.class)) {
+ document = dBuilder.parse(in);
+ // TODO check consistency with types
+ } catch (IOException | SAXException e) {
+ throw new IllegalStateException("Cannot load mount from " + mountPoint, e);
+ }
+ }
+ DomContentProvider contentProvider = new DomContentProvider(path, document);
+ return contentProvider;
+ });
+ }
+
+ @Override
+ public boolean shouldMount(QName... types) {
+ if (types.length == 0)
+ return false;
+ QName firstType = types[0];
+ Set<QName> registeredTypes = typesManager.listTypes();
+ if (registeredTypes.contains(firstType))
+ return true;
+ return false;
+ }
+
+ MountManager getMountManager() {
+ return mountManager;
+ }
+
+ TypesManager getTypesManager() {
+ return typesManager;
+ }
+
+ CmsContentSession getSystemSession() {
+ return systemSession;
+ }
+
+
+}
--- /dev/null
+package org.argeo.cms.acr;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Objects;
+
+import org.argeo.api.acr.ArgeoNamespace;
+import org.argeo.api.acr.spi.ContentNamespace;
+
+/** Content namespaces supported by CMS. */
+public enum CmsContentNamespace implements ContentNamespace {
+ //
+ // ARGEO
+ //
+ CR(ArgeoNamespace.CR_DEFAULT_PREFIX, ArgeoNamespace.CR_NAMESPACE_URI, "cr.xsd", null),
+ //
+ SLC("slc", "http://www.argeo.org/ns/slc", null, null),
+ //
+ ARGEO("argeo", "http://www.argeo.org/ns/argeo", null, null),
+ //
+ // EXTERNAL
+ //
+ XSD("xs", "http://www.w3.org/2001/XMLSchema", "XMLSchema.xsd", "http://www.w3.org/2001/XMLSchema.xsd"),
+ //
+ XML("xml", "http://www.w3.org/XML/1998/namespace", "xml.xsd", "http://www.w3.org/2001/xml.xsd"),
+ //
+ XLINK("xlink", "http://www.w3.org/1999/xlink", "xlink.xsd", "https://www.w3.org/1999/xlink.xsd"),
+ //
+ WEBDAV("D", "DAV:", null, "https://raw.githubusercontent.com/lookfirst/sardine/master/webdav.xsd"),
+ //
+ XSLT("xsl", "http://www.w3.org/1999/XSL/Transform", "schema-for-xslt20.xsd",
+ "https://www.w3.org/2007/schema-for-xslt20.xsd"),
+ //
+ SVG("svg", "http://www.w3.org/2000/svg", "SVG.xsd",
+ "https://raw.githubusercontent.com/oreillymedia/HTMLBook/master/schema/svg/SVG.xsd"),
+ //
+ DSML("dsml", "urn:oasis:names:tc:DSML:2:0:core", "DSMLv2.xsd",
+ "https://www.oasis-open.org/committees/dsml/docs/DSMLv2.xsd"),
+ //
+ ;
+
+ private final static String RESOURCE_BASE = "/org/argeo/cms/acr/schemas/";
+
+ private String defaultPrefix;
+ private String namespace;
+ private URL resource;
+ private URL publicUrl;
+
+ CmsContentNamespace(String defaultPrefix, String namespace, String resourceFileName, String publicUrl) {
+ Objects.requireNonNull(namespace);
+ this.defaultPrefix = defaultPrefix;
+ Objects.requireNonNull(namespace);
+ this.namespace = namespace;
+ if (resourceFileName != null) {
+ resource = getClass().getResource(RESOURCE_BASE + resourceFileName);
+ Objects.requireNonNull(resource);
+ }
+ if (publicUrl != null)
+ try {
+ this.publicUrl = new URL(publicUrl);
+ } catch (MalformedURLException e) {
+ throw new IllegalArgumentException("Cannot interpret public URL", e);
+ }
+ }
+
+ @Override
+ public String getDefaultPrefix() {
+ return defaultPrefix;
+ }
+
+ @Override
+ public String getNamespaceURI() {
+ return namespace;
+ }
+
+ @Override
+ public URL getSchemaResource() {
+ return resource;
+ }
+
+ public URL getPublicUrl() {
+ return publicUrl;
+ }
+
+}
package org.argeo.cms.acr;
-import java.security.AccessController;
+import java.util.Collections;
+import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
-import java.util.NavigableMap;
-import java.util.Set;
-import java.util.TreeMap;
-import java.util.stream.Collectors;
-import javax.security.auth.Subject;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
-import org.argeo.api.acr.Content;
import org.argeo.api.acr.ContentSession;
-import org.argeo.api.acr.CrName;
-import org.argeo.api.acr.spi.ContentProvider;
import org.argeo.api.acr.spi.ProvidedRepository;
-import org.argeo.api.acr.spi.ProvidedSession;
+import org.argeo.api.cms.CmsAuth;
+import org.argeo.api.cms.CmsSession;
+import org.argeo.api.cms.CmsState;
+import org.argeo.api.cms.DataAdminPrincipal;
+import org.argeo.api.uuid.UuidFactory;
+import org.argeo.cms.CurrentUser;
import org.argeo.cms.internal.runtime.CmsContextImpl;
+import org.argeo.cms.util.CurrentSubject;
-public class CmsContentRepository implements ProvidedRepository {
- private NavigableMap<String, ContentProvider> partitions = new TreeMap<>();
+/**
+ * Multi-session {@link ProvidedRepository}, integrated with a CMS.
+ */
+public class CmsContentRepository extends AbstractContentRepository {
+ public final static String RUN_BASE = "/run";
+ public final static String DIRECTORY_BASE = "/directory";
- // TODO synchronize ?
- private NavigableMap<String, String> prefixes = new TreeMap<>();
+ private Map<CmsSession, CmsContentSession> userSessions = Collections.synchronizedMap(new HashMap<>());
- public CmsContentRepository() {
- prefixes.put(CrName.CR_DEFAULT_PREFIX, CrName.CR_NAMESPACE_URI);
- prefixes.put("basic", CrName.CR_NAMESPACE_URI);
- prefixes.put("owner", CrName.CR_NAMESPACE_URI);
- prefixes.put("posix", CrName.CR_NAMESPACE_URI);
- }
-
- public void start() {
-
- }
-
- public void stop() {
-
- }
+ private CmsState cmsState;
+ private UuidFactory uuidFactory;
/*
* REPOSITORY
@Override
public ContentSession get(Locale locale) {
- Subject subject = Subject.getSubject(AccessController.getContext());
- return new CmsContentSession(subject, locale);
- }
-
- public void addProvider(String base, ContentProvider provider) {
- partitions.put(base, provider);
- }
-
- public void registerPrefix(String prefix, String namespaceURI) {
- String registeredUri = prefixes.get(prefix);
- if (registeredUri == null) {
- prefixes.put(prefix, namespaceURI);
- return;
- }
- if (!registeredUri.equals(namespaceURI))
- throw new IllegalStateException("Prefix " + prefix + " is already registred for " + registeredUri);
- // do nothing if same namespace is already registered
- }
-
- /*
- * NAMESPACE CONTEXT
- */
-
- /*
- * SESSION
- */
-
- class CmsContentSession implements ProvidedSession {
- private Subject subject;
- private Locale locale;
-
- public CmsContentSession(Subject subject, Locale locale) {
- this.subject = subject;
- this.locale = locale;
- }
-
- @Override
- public Content get(String path) {
- Map.Entry<String, ContentProvider> entry = partitions.floorEntry(path);
- String mountPath = entry.getKey();
- ContentProvider provider = entry.getValue();
- String relativePath = path.substring(mountPath.length());
- return provider.get(CmsContentSession.this, mountPath, relativePath);
- }
-
- @Override
- public Subject getSubject() {
- return subject;
- }
-
- @Override
- public Locale getLocale() {
- return locale;
+ if (!CmsSession.hasCmsSession(CurrentSubject.current())) {
+ if (DataAdminPrincipal.isDataAdmin(CurrentSubject.current())) {
+ // TODO open multiple data admin sessions?
+ return getSystemSession();
+ }
+ throw new IllegalStateException("Caller must be authenticated");
}
- @Override
- public ProvidedRepository getRepository() {
- return CmsContentRepository.this;
+ CmsSession cmsSession = CurrentUser.getCmsSession();
+ CmsContentSession contentSession = userSessions.get(cmsSession);
+ if (contentSession == null) {
+ final CmsContentSession newContentSession = new CmsContentSession(this, cmsSession.getUuid(),
+ cmsSession.getSubject(), locale, uuidFactory);
+ cmsSession.addOnCloseCallback((c) -> {
+ newContentSession.close();
+ userSessions.remove(cmsSession);
+ });
+ contentSession = newContentSession;
}
+ return contentSession;
+ }
- /*
- * NAMESPACE CONTEXT
- */
-
- @Override
- public String findNamespace(String prefix) {
- return prefixes.get(prefix);
+ @Override
+ protected CmsContentSession newSystemSession() {
+ LoginContext loginContext;
+ try {
+ loginContext = new LoginContext(CmsAuth.DATA_ADMIN.getLoginContextName());
+ loginContext.login();
+ } catch (LoginException e1) {
+ throw new RuntimeException("Could not login as data admin", e1);
+ } finally {
}
+ return new CmsContentSession(this, getCmsState().getUuid(), loginContext.getSubject(), Locale.getDefault(),
+ uuidFactory);
+ }
- @Override
- public Set<String> findPrefixes(String namespaceURI) {
- Set<String> res = prefixes.entrySet().stream().filter(e -> e.getValue().equals(namespaceURI))
- .map(Map.Entry::getKey).collect(Collectors.toUnmodifiableSet());
-
- return res;
- }
+ protected CmsState getCmsState() {
+ return cmsState;
+ }
- @Override
- public String findPrefix(String namespaceURI) {
- if (CrName.CR_NAMESPACE_URI.equals(namespaceURI) && prefixes.containsKey(CrName.CR_DEFAULT_PREFIX))
- return CrName.CR_DEFAULT_PREFIX;
- return ProvidedSession.super.findPrefix(namespaceURI);
- }
+ public void setCmsState(CmsState cmsState) {
+ this.cmsState = cmsState;
+ }
+ public void setUuidFactory(UuidFactory uuidFactory) {
+ this.uuidFactory = uuidFactory;
}
}
--- /dev/null
+package org.argeo.cms.acr;
+
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+import java.util.function.Consumer;
+
+import javax.security.auth.Subject;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.acr.ContentSession;
+import org.argeo.api.acr.DName;
+import org.argeo.api.acr.spi.ContentProvider;
+import org.argeo.api.acr.spi.ProvidedContent;
+import org.argeo.api.acr.spi.ProvidedRepository;
+import org.argeo.api.acr.spi.ProvidedSession;
+import org.argeo.api.uuid.UuidFactory;
+import org.argeo.cms.acr.xml.DomContentProvider;
+
+/** Implements {@link ProvidedSession}. */
+class CmsContentSession implements ProvidedSession {
+ final private AbstractContentRepository contentRepository;
+
+ private final UUID uuid;
+ private Subject subject;
+ private Locale locale;
+
+ private UuidFactory uuidFactory;
+
+ private CompletableFuture<ProvidedSession> closed = new CompletableFuture<>();
+
+ private CompletableFuture<ContentSession> edition;
+
+ private Set<ContentProvider> modifiedProviders = new HashSet<>();
+
+ private Content sessionRunDir;
+
+ public CmsContentSession(AbstractContentRepository contentRepository, UUID uuid, Subject subject, Locale locale,
+ UuidFactory uuidFactory) {
+ this.contentRepository = contentRepository;
+ this.subject = subject;
+ this.locale = locale;
+ this.uuid = uuid;
+ this.uuidFactory = uuidFactory;
+ }
+
+ public void close() {
+ closed.complete(this);
+
+ if (sessionRunDir != null)
+ sessionRunDir.remove();
+ }
+
+ @Override
+ public CompletionStage<ProvidedSession> onClose() {
+ return closed.minimalCompletionStage();
+ }
+
+ @Override
+ public Content get(String path) {
+ if (!path.startsWith(ContentUtils.ROOT_SLASH))
+ throw new IllegalArgumentException(path + " is not an absolute path");
+ ContentProvider contentProvider = contentRepository.getMountManager().findContentProvider(path);
+ String mountPath = contentProvider.getMountPath();
+ String relativePath = extractRelativePath(mountPath, path);
+ ProvidedContent content = contentProvider.get(CmsContentSession.this, relativePath);
+ return content;
+ }
+
+ @Override
+ public boolean exists(String path) {
+ if (!path.startsWith(ContentUtils.ROOT_SLASH))
+ throw new IllegalArgumentException(path + " is not an absolute path");
+ ContentProvider contentProvider = contentRepository.getMountManager().findContentProvider(path);
+ String mountPath = contentProvider.getMountPath();
+ String relativePath = extractRelativePath(mountPath, path);
+ return contentProvider.exists(this, relativePath);
+ }
+
+ private String extractRelativePath(String mountPath, String path) {
+ String relativePath = path.substring(mountPath.length());
+ if (relativePath.length() > 0 && relativePath.charAt(0) == '/')
+ relativePath = relativePath.substring(1);
+ return relativePath;
+ }
+
+ @Override
+ public Subject getSubject() {
+ return subject;
+ }
+
+ @Override
+ public Locale getLocale() {
+ return locale;
+ }
+
+ @Override
+ public ProvidedRepository getRepository() {
+ return contentRepository;
+ }
+
+ public UuidFactory getUuidFactory() {
+ return uuidFactory;
+ }
+
+ /*
+ * MOUNT MANAGEMENT
+ */
+ @Override
+ public Content getMountPoint(String path) {
+ String[] parent = ContentUtils.getParentPath(path);
+ ProvidedContent mountParent = (ProvidedContent) get(parent[0]);
+// Content mountPoint = mountParent.getProvider().get(CmsContentSession.this, null, path);
+ return mountParent.getMountPoint(parent[1]);
+ }
+
+ /*
+ * EDITION
+ */
+ @Override
+ public CompletionStage<ContentSession> edit(Consumer<ContentSession> work) {
+ edition = CompletableFuture.supplyAsync(() -> {
+ work.accept(this);
+ return this;
+ }).thenApply((s) -> {
+ synchronized (CmsContentSession.this) {
+ // TODO optimise
+ for (ContentProvider provider : modifiedProviders) {
+ if (provider instanceof DomContentProvider) {
+ ((DomContentProvider) provider).persist(s);
+ }
+ }
+ modifiedProviders.clear();
+ return s;
+ }
+ });
+ return edition.minimalCompletionStage();
+ }
+
+ @Override
+ public boolean isEditing() {
+ return edition != null && !edition.isDone();
+ }
+
+ @Override
+ public synchronized void notifyModification(ProvidedContent content) {
+ ContentProvider contentProvider = content.getProvider();
+ modifiedProviders.add(contentProvider);
+ }
+
+ @Override
+ public UUID getUuid() {
+ return uuid;
+ }
+
+// @Override
+ public Content getSessionRunDir() {
+ if (sessionRunDir == null) {
+ String runDirPath = CmsContentRepository.RUN_BASE + '/' + uuid.toString();
+ if (exists(runDirPath))
+ sessionRunDir = get(runDirPath);
+ else {
+ Content runDir = get(CmsContentRepository.RUN_BASE);
+ // TODO deal with no run dir available?
+ sessionRunDir = runDir.add(uuid.toString(), DName.collection.qName());
+ }
+ }
+ return sessionRunDir;
+ }
+}
\ No newline at end of file
--- /dev/null
+package org.argeo.cms.acr;
+
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.StringJoiner;
+import java.util.function.BiConsumer;
+
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+import javax.xml.namespace.QName;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.acr.ContentRepository;
+import org.argeo.api.acr.ContentSession;
+import org.argeo.api.acr.DName;
+import org.argeo.api.cms.CmsAuth;
+import org.argeo.api.cms.directory.CmsDirectory;
+import org.argeo.api.cms.directory.CmsUserManager;
+import org.argeo.api.cms.directory.HierarchyUnit;
+import org.argeo.api.cms.directory.UserDirectory;
+import org.argeo.cms.util.CurrentSubject;
+import org.osgi.service.useradmin.Role;
+
+/** Utilities and routines around {@link Content}. */
+public class ContentUtils {
+ public static void traverse(Content content, BiConsumer<Content, Integer> doIt) {
+ traverse(content, doIt, (Integer) null);
+ }
+
+ public static void traverse(Content content, BiConsumer<Content, Integer> doIt, Integer maxDepth) {
+ doTraverse(content, doIt, 0, maxDepth);
+ }
+
+ private static void doTraverse(Content content, BiConsumer<Content, Integer> doIt, int currentDepth,
+ Integer maxDepth) {
+ doIt.accept(content, currentDepth);
+ if (maxDepth != null && currentDepth == maxDepth)
+ return;
+ int nextDepth = currentDepth + 1;
+ for (Content child : content) {
+ doTraverse(child, doIt, nextDepth, maxDepth);
+ }
+ }
+
+ public static void print(Content content, PrintStream out, int depth, boolean printText) {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < depth; i++) {
+ sb.append(" ");
+ }
+ String prefix = sb.toString();
+ out.println(prefix + content.getName());
+ for (QName key : content.keySet()) {
+ out.println(prefix + " " + key + "=" + content.get(key));
+ }
+ if (printText) {
+ if (content.hasText()) {
+ out.println("<![CDATA[" + content.getText().trim() + "]]>");
+ }
+ }
+ }
+
+// public static <T> boolean isString(T t) {
+// return t instanceof String;
+// }
+
+ public static final char SLASH = '/';
+ public static final String SLASH_STRING = Character.toString(SLASH);
+ public static final String ROOT_SLASH = "" + SLASH;
+ public static final String EMPTY = "";
+
+ /**
+ * Split a path (with '/' separator) in an array of length 2, the first part
+ * being the parent path (which could be either absolute or relative), the
+ * second one being the last segment, (guaranteed to be without a '/').
+ */
+ public static String[] getParentPath(String path) {
+ if (path == null)
+ throw new IllegalArgumentException("Path cannot be null");
+ if (path.length() == 0)
+ throw new IllegalArgumentException("Path cannot be empty");
+ checkDoubleSlash(path);
+ int parentIndex = path.lastIndexOf(SLASH);
+ if (parentIndex == path.length() - 1) {// trailing '/'
+ path = path.substring(0, path.length() - 1);
+ parentIndex = path.lastIndexOf(SLASH);
+ }
+
+ if (parentIndex == -1) // no '/'
+ return new String[] { EMPTY, path };
+
+ return new String[] { parentIndex != 0 ? path.substring(0, parentIndex) : "" + SLASH,
+ path.substring(parentIndex + 1) };
+ }
+
+ public static String toPath(List<String> segments) {
+ // TODO checks
+ StringJoiner sj = new StringJoiner("/");
+ segments.forEach((s) -> sj.add(s));
+ return sj.toString();
+ }
+
+ public static List<String> toPathSegments(String path) {
+ List<String> res = new ArrayList<>();
+ if (EMPTY.equals(path) || ROOT_SLASH.equals(path))
+ return res;
+ collectPathSegments(path, res);
+ return res;
+ }
+
+ private static void collectPathSegments(String path, List<String> segments) {
+ String[] parent = getParentPath(path);
+ if (EMPTY.equals(parent[1])) // root
+ return;
+ segments.add(0, parent[1]);
+ if (EMPTY.equals(parent[0])) // end
+ return;
+ collectPathSegments(parent[0], segments);
+ }
+
+ public static void checkDoubleSlash(String path) {
+ if (path.contains(SLASH + "" + SLASH))
+ throw new IllegalArgumentException("Path " + path + " contains //");
+ }
+
+ /*
+ * DIRECTORY
+ */
+
+ public static Content roleToContent(CmsUserManager userManager, ContentSession contentSession, Role role) {
+ UserDirectory userDirectory = userManager.getDirectory(role);
+ String path = directoryPath(userDirectory) + userDirectory.getRolePath(role);
+ Content content = contentSession.get(path);
+ return content;
+ }
+
+ public static Content hierarchyUnitToContent(ContentSession contentSession, HierarchyUnit hierarchyUnit) {
+ CmsDirectory directory = hierarchyUnit.getDirectory();
+ StringJoiner relativePath = new StringJoiner(SLASH_STRING);
+ buildHierarchyUnitPath(hierarchyUnit, relativePath);
+ String path = directoryPath(directory) + relativePath.toString();
+ Content content = contentSession.get(path);
+ return content;
+ }
+
+ /** The path to this {@link CmsDirectory}. Ends with a /. */
+ private static String directoryPath(CmsDirectory directory) {
+ return CmsContentRepository.DIRECTORY_BASE + SLASH + directory.getName() + SLASH;
+ }
+
+ /** Recursively build a relative path of a {@link HierarchyUnit}. */
+ private static void buildHierarchyUnitPath(HierarchyUnit current, StringJoiner relativePath) {
+ if (current.getParent() == null) // directory
+ return;
+ buildHierarchyUnitPath(current.getParent(), relativePath);
+ relativePath.add(current.getHierarchyUnitName());
+ }
+
+ /*
+ * CONSUMER UTILS
+ */
+
+ public static Content createCollections(ContentSession session, String path) {
+ if (session.exists(path)) {
+ Content content = session.get(path);
+ if (!content.isContentClass(DName.collection.qName())) {
+ throw new IllegalStateException("Content " + path + " already exists, but is not a collection");
+ } else {
+ return content;
+ }
+ } else {
+ String[] parentPath = getParentPath(path);
+ Content parent = createCollections(session, parentPath[0]);
+ Content content = parent.add(parentPath[1], DName.collection.qName());
+ return content;
+ }
+ }
+
+ public static ContentSession openDataAdminSession(ContentRepository repository) {
+ LoginContext loginContext;
+ try {
+ loginContext = CmsAuth.DATA_ADMIN.newLoginContext();
+ loginContext.login();
+ } catch (LoginException e1) {
+ throw new RuntimeException("Could not login as data admin", e1);
+ } finally {
+ }
+
+ ClassLoader currentCl = Thread.currentThread().getContextClassLoader();
+ try {
+ Thread.currentThread().setContextClassLoader(ContentUtils.class.getClassLoader());
+ return CurrentSubject.callAs(loginContext.getSubject(), () -> repository.get());
+ } finally {
+ Thread.currentThread().setContextClassLoader(currentCl);
+ }
+ }
+
+ /** Singleton. */
+ private ContentUtils() {
+
+ }
+
+}
--- /dev/null
+package org.argeo.cms.acr;
+
+import java.util.Map;
+import java.util.NavigableMap;
+import java.util.Objects;
+import java.util.TreeMap;
+import java.util.function.Function;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.acr.CrName;
+import org.argeo.api.acr.spi.ContentProvider;
+
+/** Manages the structural and dynamic mounts within the content repository. */
+class MountManager {
+ private final NavigableMap<String, ContentProvider> partitions = new TreeMap<>();
+
+ private final CmsContentSession systemSession;
+
+ public MountManager(CmsContentSession systemSession) {
+ this.systemSession = systemSession;
+ }
+
+ synchronized void addStructuralContentProvider(ContentProvider contentProvider) {
+ String mountPath = contentProvider.getMountPath();
+ Objects.requireNonNull(mountPath);
+ if (partitions.containsKey(mountPath))
+ throw new IllegalStateException("A provider is already registered for " + mountPath);
+ partitions.put(mountPath, contentProvider);
+ if ("/".equals(mountPath))// root
+ return;
+ String[] parentPath = ContentUtils.getParentPath(mountPath);
+ Content parent = systemSession.get(parentPath[0]);
+ Content mount = parent.add(parentPath[1]);
+ mount.put(CrName.mount.qName(), "true");
+
+ }
+
+ synchronized ContentProvider getOrAddMountedProvider(String mountPath, Function<String, ContentProvider> factory) {
+ Objects.requireNonNull(factory);
+ if (!partitions.containsKey(mountPath)) {
+ ContentProvider contentProvider = factory.apply(mountPath);
+ if (!mountPath.equals(contentProvider.getMountPath()))
+ throw new IllegalArgumentException("Mount path " + mountPath + " is inconsistent with content provider "
+ + contentProvider.getMountPath());
+ partitions.put(mountPath, contentProvider);
+ }
+ return partitions.get(mountPath);
+ }
+
+ synchronized ContentProvider findContentProvider(String path) {
+// if (ContentUtils.EMPTY.equals(path))
+// return partitions.firstEntry().getValue();
+ Map.Entry<String, ContentProvider> entry = partitions.floorEntry(path);
+ if (entry == null)
+ throw new IllegalArgumentException("No entry provider found for path '" + path + "'");
+ String mountPath = entry.getKey();
+ if (!path.startsWith(mountPath)) {
+ // FIXME make it more robust and find when there is no content provider
+ String[] parent = ContentUtils.getParentPath(path);
+ return findContentProvider(parent[0]);
+ // throw new IllegalArgumentException("Path " + path + " doesn't have a content
+ // provider");
+ }
+ ContentProvider contentProvider = entry.getValue();
+ assert mountPath.equals(contentProvider.getMountPath());
+ return contentProvider;
+ }
+}
--- /dev/null
+package org.argeo.cms.acr;
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Locale;
+import java.util.Objects;
+import java.util.UUID;
+
+import javax.security.auth.Subject;
+import javax.security.auth.x500.X500Principal;
+
+import org.argeo.api.acr.ContentSession;
+import org.argeo.api.acr.ldap.LdapAttr;
+import org.argeo.api.acr.spi.ProvidedRepository;
+import org.argeo.api.uuid.MacAddressUuidFactory;
+import org.argeo.api.uuid.UuidFactory;
+import org.argeo.cms.acr.fs.FsContentProvider;
+
+/**
+ * A standalone {@link ProvidedRepository} with a single {@link Subject} (which
+ * also provides the system session).
+ */
+public class SingleUserContentRepository extends AbstractContentRepository {
+ private final Subject subject;
+ private final Locale locale;
+
+ private UUID uuid;
+
+ private UuidFactory uuidFactory = new MacAddressUuidFactory();
+
+ // the single session
+ private CmsContentSession contentSession;
+
+ public SingleUserContentRepository(Subject subject) {
+ this(subject, Locale.getDefault());
+ }
+
+ public SingleUserContentRepository(Subject subject, Locale locale) {
+ Objects.requireNonNull(subject);
+ Objects.requireNonNull(locale);
+
+ this.subject = subject;
+ this.locale = locale;
+
+ // TODO use an UUID factory
+ this.uuid = UUID.randomUUID();
+ }
+
+ @Override
+ public void start() {
+ Objects.requireNonNull(subject);
+ Objects.requireNonNull(locale);
+
+ super.start();
+ initRootContentProvider(null);
+ if (contentSession != null)
+ throw new IllegalStateException("Repository is already started, stop it first.");
+ contentSession = new CmsContentSession(this, uuid, subject, locale, uuidFactory);
+ }
+
+ @Override
+ public void stop() {
+ if (contentSession != null)
+ contentSession.close();
+ contentSession = null;
+ super.stop();
+ }
+
+ @Override
+ public ContentSession get(Locale locale) {
+ if (!this.locale.equals(locale))
+ throw new UnsupportedOperationException("This repository does not support multi-locale sessions");
+ return contentSession;
+ }
+
+ @Override
+ public ContentSession get() {
+ return contentSession;
+ }
+
+ @Override
+ protected CmsContentSession newSystemSession() {
+ return new CmsContentSession(this, uuid, subject, Locale.getDefault(), uuidFactory);
+ }
+
+ public static void main(String... args) {
+ Path homePath = Paths.get(System.getProperty("user.home"));
+ String username = System.getProperty("user.name");
+ X500Principal principal = new X500Principal(LdapAttr.uid + "=" + username + ",dc=localhost");
+ Subject subject = new Subject();
+ subject.getPrincipals().add(principal);
+
+ SingleUserContentRepository contentRepository = new SingleUserContentRepository(subject);
+ contentRepository.start();
+ FsContentProvider homeContentProvider = new FsContentProvider("/home", homePath);
+ contentRepository.addProvider(homeContentProvider);
+ Runtime.getRuntime().addShutdownHook(new Thread(() -> contentRepository.stop(), "Shutdown content repository"));
+
+ ContentSession contentSession = contentRepository.get();
+ ContentUtils.traverse(contentSession.get("/"), (c, depth) -> ContentUtils.print(c, System.out, depth, false),
+ 2);
+
+ }
+}
--- /dev/null
+package org.argeo.cms.acr;
+
+import org.argeo.api.acr.QNamed;
+
+public enum SvgAttrs implements QNamed {
+ /** */
+ width,
+ /** */
+ height,
+ /** */
+ length,
+ /** */
+ unit,
+ /** */
+ dur,
+ /** */
+ direction,
+ //
+ ;
+
+ @Override
+ public String getNamespace() {
+ return CmsContentNamespace.SVG.getNamespaceURI();
+ }
+
+ @Override
+ public String getDefaultPrefix() {
+ return CmsContentNamespace.SVG.getDefaultPrefix();
+ }
+
+}
--- /dev/null
+package org.argeo.cms.acr;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+import javax.xml.XMLConstants;
+import javax.xml.namespace.QName;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.Source;
+import javax.xml.transform.stream.StreamSource;
+import javax.xml.validation.Schema;
+import javax.xml.validation.SchemaFactory;
+import javax.xml.validation.Validator;
+
+import org.apache.xerces.impl.xs.XSImplementationImpl;
+import org.apache.xerces.impl.xs.util.StringListImpl;
+import org.apache.xerces.jaxp.DocumentBuilderFactoryImpl;
+import org.apache.xerces.xs.StringList;
+import org.apache.xerces.xs.XSAttributeDeclaration;
+import org.apache.xerces.xs.XSAttributeUse;
+import org.apache.xerces.xs.XSComplexTypeDefinition;
+import org.apache.xerces.xs.XSConstants;
+import org.apache.xerces.xs.XSElementDeclaration;
+import org.apache.xerces.xs.XSException;
+import org.apache.xerces.xs.XSImplementation;
+import org.apache.xerces.xs.XSLoader;
+import org.apache.xerces.xs.XSModel;
+import org.apache.xerces.xs.XSModelGroup;
+import org.apache.xerces.xs.XSNamedMap;
+import org.apache.xerces.xs.XSObjectList;
+import org.apache.xerces.xs.XSParticle;
+import org.apache.xerces.xs.XSSimpleTypeDefinition;
+import org.apache.xerces.xs.XSTerm;
+import org.apache.xerces.xs.XSTypeDefinition;
+import org.argeo.api.acr.CrAttributeType;
+import org.argeo.api.acr.NamespaceUtils;
+import org.argeo.api.acr.RuntimeNamespaceContext;
+import org.argeo.api.acr.spi.ContentNamespace;
+import org.argeo.api.cms.CmsLog;
+import org.xml.sax.ErrorHandler;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+
+/** Register content types. */
+class TypesManager {
+ private final static CmsLog log = CmsLog.getLog(TypesManager.class);
+// private Map<String, String> prefixes = new TreeMap<>();
+
+ // immutable factories
+ private SchemaFactory schemaFactory;
+
+ /** Schema sources. */
+ private List<URL> sources = new ArrayList<>();
+
+ // cached
+ private Schema schema;
+ private DocumentBuilderFactory documentBuilderFactory;
+ private XSModel xsModel;
+ private SortedMap<QName, Map<QName, CrAttributeType>> types;
+
+ private boolean validating = false;
+ private boolean creatingXsModel = false;
+
+ private final static boolean limited = false;
+
+ public TypesManager() {
+ schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
+
+ // types
+ types = new TreeMap<>(NamespaceUtils.QNAME_COMPARATOR);
+
+ }
+
+ public void init() {
+ registerTypes(CmsContentNamespace.values());
+ }
+
+ public void registerTypes(ContentNamespace... namespaces) {
+// if (prefixes.containsKey(defaultPrefix))
+// throw new IllegalStateException(
+// "Prefix " + defaultPrefix + " is already mapped with " + prefixes.get(defaultPrefix));
+// prefixes.put(defaultPrefix, namespace);
+ for (ContentNamespace contentNamespace : namespaces) {
+ RuntimeNamespaceContext.register(contentNamespace.getNamespaceURI(), contentNamespace.getDefaultPrefix());
+
+ if (contentNamespace.getSchemaResource() != null) {
+ sources.add(contentNamespace.getSchemaResource());
+ log.debug(() -> "Registered types " + contentNamespace.getNamespaceURI() + " from "
+ + contentNamespace.getSchemaResource().toExternalForm());
+ }
+ }
+ reload();
+ }
+
+ public Set<QName> listTypes() {
+ return types.keySet();
+ }
+
+ public Map<QName, CrAttributeType> getAttributeTypes(QName type) {
+ if (!types.containsKey(type))
+ throw new IllegalArgumentException("Unkown type");
+ return types.get(type);
+ }
+
+ private synchronized void reload() {
+ try {
+ // schema
+ if (validating) {
+ List<StreamSource> sourcesToUse = new ArrayList<>();
+ for (URL sourceUrl : sources) {
+ sourcesToUse.add(new StreamSource(sourceUrl.toExternalForm()));
+ }
+ schema = schemaFactory.newSchema(sourcesToUse.toArray(new Source[sourcesToUse.size()]));
+// for (StreamSource source : sourcesToUse) {
+// try {
+// source.getInputStream().close();
+// } catch (IOException e) {
+// // TODO Auto-generated catch block
+// e.printStackTrace();
+// }
+// }
+ }
+
+ // document builder factory
+ // we force usage of Xerces for predictability
+ documentBuilderFactory = limited ? DocumentBuilderFactory.newInstance() : new DocumentBuilderFactoryImpl();
+ documentBuilderFactory.setNamespaceAware(true);
+ if (!limited) {
+ documentBuilderFactory.setXIncludeAware(true);
+ if (validating) {
+ documentBuilderFactory.setSchema(getSchema());
+ documentBuilderFactory.setValidating(validating);
+ }
+ }
+
+ if (creatingXsModel) {
+ // XS model
+ // TODO use JVM implementation?
+// DOMImplementationRegistry registry = DOMImplementationRegistry.newInstance();
+// XSImplementation implementation = (XSImplementation) registry.getDOMImplementation("XS-Loader");
+ XSImplementation xsImplementation = new XSImplementationImpl();
+ XSLoader xsLoader = xsImplementation.createXSLoader(null);
+ List<String> systemIds = new ArrayList<>();
+ for (URL sourceUrl : sources) {
+ systemIds.add(sourceUrl.toExternalForm());
+ }
+ StringList sl = new StringListImpl(systemIds.toArray(new String[systemIds.size()]), systemIds.size());
+ xsModel = xsLoader.loadURIList(sl);
+
+ // types
+// XSNamedMap map = xsModel.getComponents(XSConstants.ELEMENT_DECLARATION);
+// for (int i = 0; i < map.getLength(); i++) {
+// XSElementDeclaration eDec = (XSElementDeclaration) map.item(i);
+// QName type = new QName(eDec.getNamespace(), eDec.getName());
+// types.add(type);
+// }
+ collectTypes();
+
+ log.debug("Created XS model");
+ }
+ } catch (XSException | SAXException e) {
+ throw new IllegalStateException("Cannot reload types", e);
+ }
+ }
+
+ private void collectTypes() {
+ types.clear();
+ // elements
+ XSNamedMap topLevelElements = xsModel.getComponents(XSConstants.ELEMENT_DECLARATION);
+ for (int i = 0; i < topLevelElements.getLength(); i++) {
+ XSElementDeclaration eDec = (XSElementDeclaration) topLevelElements.item(i);
+ collectElementDeclaration("", eDec);
+ }
+
+ // types
+ XSNamedMap topLevelTypes = xsModel.getComponents(XSConstants.TYPE_DEFINITION);
+ for (int i = 0; i < topLevelTypes.getLength(); i++) {
+ XSTypeDefinition tDef = (XSTypeDefinition) topLevelTypes.item(i);
+ collectType(tDef, null, null);
+ }
+
+ }
+
+ private void collectType(XSTypeDefinition tDef, String namespace, String nameHint) {
+ if (tDef.getTypeCategory() == XSTypeDefinition.COMPLEX_TYPE) {
+ XSComplexTypeDefinition ctDef = (XSComplexTypeDefinition) tDef;
+ if (ctDef.getContentType() != XSComplexTypeDefinition.CONTENTTYPE_SIMPLE
+ || ctDef.getAttributeUses().getLength() > 0 || ctDef.getAttributeWildcard() != null) {
+ collectComplexType("", null, ctDef);
+ } else {
+ throw new IllegalArgumentException("Unsupported type " + tDef.getTypeCategory());
+ }
+ }
+ }
+
+ private void collectComplexType(String prefix, QName parent, XSComplexTypeDefinition ctDef) {
+ if (ctDef.getContentType() == XSComplexTypeDefinition.CONTENTTYPE_SIMPLE) {
+
+ // content with attributes and a string value
+
+ XSSimpleTypeDefinition stDef = ctDef.getSimpleType();
+ // QName name = new QName(stDef.getNamespace(), stDef.getName());
+ // log.warn(prefix + "Simple " + ctDef + " - " + attributes);
+// System.err.println(prefix + "Simple from " + parent + " - " + attributes);
+//
+// if (parentAttributes != null) {
+// for (QName attr : attributes.keySet()) {
+// if (!parentAttributes.containsKey(attr))
+// System.err.println(prefix + " - " + attr + " not available in parent");
+//
+// }
+// }
+
+ } else if (ctDef.getContentType() == XSComplexTypeDefinition.CONTENTTYPE_ELEMENT
+ || ctDef.getContentType() == XSComplexTypeDefinition.CONTENTTYPE_MIXED) {
+ XSParticle p = ctDef.getParticle();
+
+ collectParticle(prefix, p, false);
+ } else if (ctDef.getContentType() == XSComplexTypeDefinition.CONTENTTYPE_EMPTY) {
+ // Parent only contains attributes
+// if (parent != null)
+// System.err.println(prefix + "Empty from " + parent + " - " + attributes);
+// if (parentAttributes != null) {
+// for (QName attr : attributes.keySet()) {
+// if (!parentAttributes.containsKey(attr))
+// System.err.println(prefix + " - " + attr + " not available in parent");
+//
+// }
+// }
+// log.debug(prefix + "Empty " + ctDef.getNamespace() + ":" + ctDef.getName() + " - " + attributes);
+ } else {
+ throw new IllegalArgumentException("Unsupported type " + ctDef.getTypeCategory());
+ }
+ }
+
+ private void collectParticle(String prefix, XSParticle particle, boolean multipleFromAbove) {
+ boolean orderable = false;
+
+ XSTerm term = particle.getTerm();
+
+ if (particle.getMaxOccurs() == 0) {
+ return;
+ }
+
+ boolean mandatory = false;
+ if (particle.getMinOccurs() > 0) {
+ mandatory = true;
+ }
+
+ boolean multiple = false;
+ if (particle.getMaxOccurs() > 1 || particle.getMaxOccursUnbounded()) {
+ multiple = true;
+ }
+ if (!multiple && multipleFromAbove)
+ multiple = true;
+
+ if (term.getType() == XSConstants.ELEMENT_DECLARATION) {
+ XSElementDeclaration eDec = (XSElementDeclaration) term;
+
+ collectElementDeclaration(prefix, eDec);
+ // If this particle is a wildcard (an <xs:any> )then it
+ // is converted into a node def.
+ } else if (term.getType() == XSConstants.WILDCARD) {
+ // TODO can be anything
+
+ // If this particle is a model group (one of
+ // <xs:sequence>, <xs:choice> or <xs:all>) then
+ // it subparticles must be processed.
+ } else if (term.getType() == XSConstants.MODEL_GROUP) {
+ XSModelGroup mg = (XSModelGroup) term;
+
+ if (mg.getCompositor() == XSModelGroup.COMPOSITOR_SEQUENCE) {
+ orderable = true;
+ }
+ XSObjectList list = mg.getParticles();
+ for (int i = 0; i < list.getLength(); i++) {
+ XSParticle pp = (XSParticle) list.item(i);
+ collectParticle(prefix + " ", pp, multiple);
+ }
+ }
+ }
+
+ private void collectElementDeclaration(String prefix, XSElementDeclaration eDec) {
+ QName name = new QName(eDec.getNamespace(), eDec.getName());
+ XSTypeDefinition tDef = eDec.getTypeDefinition();
+
+ XSComplexTypeDefinition ctDef = null;
+ Map<QName, CrAttributeType> attributes = new HashMap<>();
+ if (tDef.getTypeCategory() == XSTypeDefinition.SIMPLE_TYPE) {
+ XSSimpleTypeDefinition stDef = (XSSimpleTypeDefinition) tDef;
+// System.err.println(prefix + "Simple element " + name);
+ } else if (tDef.getTypeCategory() == XSTypeDefinition.COMPLEX_TYPE) {
+ ctDef = (XSComplexTypeDefinition) tDef;
+ if (ctDef.getContentType() == XSComplexTypeDefinition.CONTENTTYPE_SIMPLE
+ && ctDef.getAttributeUses().getLength() == 0 && ctDef.getAttributeWildcard() == null) {
+ XSSimpleTypeDefinition stDef = ctDef.getSimpleType();
+// System.err.println(prefix + "Simplified element " + name);
+ } else {
+ if (!types.containsKey(name)) {
+// System.out.println(prefix + "Element " + name);
+
+ XSObjectList list = ctDef.getAttributeUses();
+ for (int i = 0; i < list.getLength(); i++) {
+ XSAttributeUse au = (XSAttributeUse) list.item(i);
+ XSAttributeDeclaration ad = au.getAttrDeclaration();
+ QName attrName = new QName(ad.getNamespace(), ad.getName());
+ // Get the simple type def for this attribute
+ XSSimpleTypeDefinition std = ad.getTypeDefinition();
+ attributes.put(attrName, xsToCrType(std.getBuiltInKind()));
+// System.out.println(prefix + " - " + attrName + " = " + attributes.get(attrName));
+ }
+ // REGISTER
+ types.put(name, attributes);
+ if (ctDef != null)
+ collectComplexType(prefix + " ", name, ctDef);
+ }
+ }
+ }
+
+ }
+
+ private CrAttributeType xsToCrType(short kind) {
+ CrAttributeType propertyType;
+ switch (kind) {
+ case XSConstants.ANYSIMPLETYPE_DT:
+ case XSConstants.STRING_DT:
+ case XSConstants.ID_DT:
+ case XSConstants.ENTITY_DT:
+ case XSConstants.NOTATION_DT:
+ case XSConstants.NORMALIZEDSTRING_DT:
+ case XSConstants.TOKEN_DT:
+ case XSConstants.LANGUAGE_DT:
+ case XSConstants.NMTOKEN_DT:
+ propertyType = CrAttributeType.STRING;
+ break;
+ case XSConstants.BOOLEAN_DT:
+ propertyType = CrAttributeType.BOOLEAN;
+ break;
+ case XSConstants.DECIMAL_DT:
+ case XSConstants.FLOAT_DT:
+ case XSConstants.DOUBLE_DT:
+ propertyType = CrAttributeType.DOUBLE;
+ break;
+ case XSConstants.DURATION_DT:
+ case XSConstants.DATETIME_DT:
+ case XSConstants.TIME_DT:
+ case XSConstants.DATE_DT:
+ case XSConstants.GYEARMONTH_DT:
+ case XSConstants.GYEAR_DT:
+ case XSConstants.GMONTHDAY_DT:
+ case XSConstants.GDAY_DT:
+ case XSConstants.GMONTH_DT:
+ propertyType = CrAttributeType.DATE_TIME;
+ break;
+ case XSConstants.HEXBINARY_DT:
+ case XSConstants.BASE64BINARY_DT:
+ case XSConstants.ANYURI_DT:
+ propertyType = CrAttributeType.ANY_URI;
+ break;
+ case XSConstants.QNAME_DT:
+ case XSConstants.NAME_DT:
+ case XSConstants.NCNAME_DT:
+ // TODO support QName?
+ propertyType = CrAttributeType.STRING;
+ break;
+ case XSConstants.IDREF_DT:
+ // TODO support references?
+ propertyType = CrAttributeType.STRING;
+ break;
+ case XSConstants.INTEGER_DT:
+ case XSConstants.NONPOSITIVEINTEGER_DT:
+ case XSConstants.NEGATIVEINTEGER_DT:
+ case XSConstants.LONG_DT:
+ case XSConstants.INT_DT:
+ case XSConstants.SHORT_DT:
+ case XSConstants.BYTE_DT:
+ case XSConstants.NONNEGATIVEINTEGER_DT:
+ case XSConstants.UNSIGNEDLONG_DT:
+ case XSConstants.UNSIGNEDINT_DT:
+ case XSConstants.UNSIGNEDSHORT_DT:
+ case XSConstants.UNSIGNEDBYTE_DT:
+ case XSConstants.POSITIVEINTEGER_DT:
+ propertyType = CrAttributeType.LONG;
+ break;
+ case XSConstants.LISTOFUNION_DT:
+ case XSConstants.LIST_DT:
+ case XSConstants.UNAVAILABLE_DT:
+ propertyType = CrAttributeType.STRING;
+ break;
+ default:
+ propertyType = CrAttributeType.STRING;
+ break;
+ }
+ return propertyType;
+ }
+
+ public DocumentBuilder newDocumentBuilder() {
+ try {
+ DocumentBuilder dBuilder = documentBuilderFactory.newDocumentBuilder();
+ dBuilder.setErrorHandler(new ErrorHandler() {
+
+ @Override
+ public void warning(SAXParseException exception) throws SAXException {
+ log.warn(exception);
+ }
+
+ @Override
+ public void fatalError(SAXParseException exception) throws SAXException {
+ log.error(exception);
+ }
+
+ @Override
+ public void error(SAXParseException exception) throws SAXException {
+ log.error(exception);
+ }
+ });
+ return dBuilder;
+ } catch (ParserConfigurationException e) {
+ throw new IllegalStateException("Cannot create document builder", e);
+ }
+ }
+
+ public void printTypes() {
+ if (xsModel != null)
+ try {
+
+ // Convert top level complex type definitions to node types
+ log.debug("\n## TYPES");
+ XSNamedMap map = xsModel.getComponents(XSConstants.TYPE_DEFINITION);
+ for (int i = 0; i < map.getLength(); i++) {
+ XSTypeDefinition tDef = (XSTypeDefinition) map.item(i);
+ log.debug(tDef);
+ }
+ // Convert local (anonymous) complex type defs found in top level
+ // element declarations
+ log.debug("\n## ELEMENTS");
+ map = xsModel.getComponents(XSConstants.ELEMENT_DECLARATION);
+ for (int i = 0; i < map.getLength(); i++) {
+ XSElementDeclaration eDec = (XSElementDeclaration) map.item(i);
+ XSTypeDefinition tDef = eDec.getTypeDefinition();
+ log.debug(eDec + ", " + tDef);
+ }
+ log.debug("\n## ATTRIBUTES");
+ map = xsModel.getComponents(XSConstants.ATTRIBUTE_DECLARATION);
+ for (int i = 0; i < map.getLength(); i++) {
+ XSAttributeDeclaration eDec = (XSAttributeDeclaration) map.item(i);
+ XSTypeDefinition tDef = eDec.getTypeDefinition();
+ log.debug(eDec.getNamespace() + ":" + eDec.getName() + ", " + tDef);
+ }
+ } catch (ClassCastException | XSException e) {
+ throw new RuntimeException(e);
+ }
+
+ }
+
+ public void validate(Source source) throws IOException {
+ if (!validating)
+ return;
+ Validator validator;
+ synchronized (this) {
+ validator = schema.newValidator();
+ }
+ try {
+ validator.validate(source);
+ } catch (SAXException e) {
+ log.error(source + " is not valid " + e);
+ // throw new IllegalArgumentException("Provided source is not valid", e);
+ }
+ }
+
+// public Map<String, String> getPrefixes() {
+// return prefixes;
+// }
+
+// public List<Source> getSources() {
+// return sources;
+// }
+
+ public Schema getSchema() {
+ return schema;
+ }
+
+}
--- /dev/null
+package org.argeo.cms.acr.dav;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import javax.xml.namespace.QName;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.acr.ContentName;
+import org.argeo.api.acr.NamespaceUtils;
+import org.argeo.api.acr.spi.ContentProvider;
+import org.argeo.api.acr.spi.ProvidedSession;
+import org.argeo.cms.acr.AbstractContent;
+import org.argeo.cms.acr.ContentUtils;
+import org.argeo.cms.dav.DavResponse;
+import org.argeo.cms.http.HttpStatus;
+
+public class DavContent extends AbstractContent {
+ private final DavContentProvider provider;
+ private final URI uri;
+
+ private Set<QName> keyNames;
+ private Optional<Map<QName, String>> values;
+
+ public DavContent(ProvidedSession session, DavContentProvider provider, URI uri, Set<QName> keyNames) {
+ this(session, provider, uri, keyNames, Optional.empty());
+ }
+
+ public DavContent(ProvidedSession session, DavContentProvider provider, URI uri, Set<QName> keyNames,
+ Optional<Map<QName, String>> values) {
+ super(session);
+ this.provider = provider;
+ this.uri = uri;
+ this.keyNames = keyNames;
+ this.values = values;
+ }
+
+ @Override
+ public QName getName() {
+ String fileName = ContentUtils.getParentPath(uri.getPath())[1];
+ ContentName name = NamespaceUtils.parsePrefixedName(provider, fileName);
+ return name;
+ }
+
+ @Override
+ public Content getParent() {
+ try {
+ String parentPath = ContentUtils.getParentPath(uri.getPath())[0];
+ URI parentUri = new URI(uri.getScheme(), uri.getHost(), parentPath, null);
+ return provider.getDavContent(getSession(), parentUri);
+ } catch (URISyntaxException e) {
+ throw new IllegalStateException("Cannot create parent", e);
+ }
+ }
+
+ @Override
+ public Iterator<Content> iterator() {
+ Iterator<DavResponse> responses = provider.getDavClient().listChildren(uri);
+ return new DavResponseIterator(responses);
+ }
+
+ @Override
+ protected Iterable<QName> keys() {
+ return keyNames;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public <A> Optional<A> get(QName key, Class<A> clss) {
+ if (values.isEmpty()) {
+ DavResponse response = provider.getDavClient().get(uri);
+ values = Optional.of(response.getProperties());
+ }
+ String valueStr = values.get().get(key);
+ if (valueStr == null)
+ return Optional.empty();
+ // TODO convert
+ return Optional.of((A) valueStr);
+ }
+
+ @Override
+ public ContentProvider getProvider() {
+ return provider;
+ }
+
+ class DavResponseIterator implements Iterator<Content> {
+ private final Iterator<DavResponse> responses;
+
+ public DavResponseIterator(Iterator<DavResponse> responses) {
+ this.responses = responses;
+ }
+
+ @Override
+ public boolean hasNext() {
+ return responses.hasNext();
+ }
+
+ @Override
+ public Content next() {
+ DavResponse response = responses.next();
+ String relativePath = response.getHref();
+ URI contentUri = provider.relativePathToUri(relativePath);
+ return new DavContent(getSession(), provider, contentUri, response.getPropertyNames(HttpStatus.OK));
+ }
+
+ }
+}
--- /dev/null
+package org.argeo.cms.acr.dav;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Iterator;
+
+import org.argeo.api.acr.RuntimeNamespaceContext;
+import org.argeo.api.acr.spi.ContentProvider;
+import org.argeo.api.acr.spi.ProvidedContent;
+import org.argeo.api.acr.spi.ProvidedSession;
+import org.argeo.cms.dav.DavClient;
+import org.argeo.cms.dav.DavResponse;
+import org.argeo.cms.http.HttpStatus;
+
+public class DavContentProvider implements ContentProvider {
+ private String mountPath;
+ private URI baseUri;
+
+ private DavClient davClient;
+
+ public DavContentProvider(String mountPath, URI baseUri) {
+ this.mountPath = mountPath;
+ this.baseUri = baseUri;
+ if (!baseUri.getPath().endsWith("/"))
+ throw new IllegalArgumentException("Base URI " + baseUri + " path does not end with /");
+ this.davClient = new DavClient();
+ }
+
+ @Override
+ public String getNamespaceURI(String prefix) {
+ // FIXME retrieve mappings from WebDav
+ return RuntimeNamespaceContext.getNamespaceContext().getNamespaceURI(prefix);
+ }
+
+ @Override
+ public Iterator<String> getPrefixes(String namespaceURI) {
+ // FIXME retrieve mappings from WebDav
+ return RuntimeNamespaceContext.getNamespaceContext().getPrefixes(namespaceURI);
+ }
+
+ @Override
+ public ProvidedContent get(ProvidedSession session, String relativePath) {
+ URI contentUri = relativePathToUri(relativePath);
+ return getDavContent(session, contentUri);
+ }
+
+ DavContent getDavContent(ProvidedSession session, URI uri) {
+ DavResponse response = davClient.get(uri);
+ return new DavContent(session, this, uri, response.getPropertyNames(HttpStatus.OK));
+ }
+
+ @Override
+ public boolean exists(ProvidedSession session, String relativePath) {
+ URI contentUri = relativePathToUri(relativePath);
+ return davClient.exists(contentUri);
+ }
+
+ @Override
+ public String getMountPath() {
+ return mountPath;
+ }
+
+ DavClient getDavClient() {
+ return davClient;
+ }
+
+ URI relativePathToUri(String relativePath) {
+ try {
+ // TODO check last slash
+ String path = relativePath.startsWith("/") ? relativePath : baseUri.getPath() + relativePath;
+ URI uri = new URI(baseUri.getScheme(), baseUri.getHost(), path, baseUri.getFragment());
+ return uri;
+ } catch (URISyntaxException e) {
+ throw new IllegalArgumentException("Cannot build URI for " + relativePath + " relatively to " + baseUri, e);
+ }
+ }
+}
--- /dev/null
+package org.argeo.cms.acr.directory;
+
+import java.util.ArrayList;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import java.util.TreeSet;
+
+import javax.xml.namespace.QName;
+
+import org.argeo.api.acr.ArgeoNamespace;
+import org.argeo.api.acr.ContentName;
+import org.argeo.api.acr.CrAttributeType;
+import org.argeo.api.acr.NamespaceUtils;
+import org.argeo.api.acr.ldap.LdapAttr;
+import org.argeo.api.acr.ldap.LdapObj;
+import org.argeo.api.acr.spi.ContentProvider;
+import org.argeo.api.acr.spi.ProvidedSession;
+import org.argeo.cms.acr.AbstractContent;
+
+abstract class AbstractDirectoryContent extends AbstractContent {
+ protected final DirectoryContentProvider provider;
+
+ public AbstractDirectoryContent(ProvidedSession session, DirectoryContentProvider provider) {
+ super(session);
+ this.provider = provider;
+ }
+
+ abstract Dictionary<String, Object> doGetProperties();
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public <A> Optional<A> get(QName key, Class<A> clss) {
+ String attrName = key.getLocalPart();
+ Object value = doGetProperties().get(attrName);
+ if (value == null)
+ return Optional.empty();
+ Optional<A> res = CrAttributeType.cast(clss, value);
+ if (res.isEmpty())
+ return Optional.of((A) value);
+ else
+ return res;
+ }
+
+ @Override
+ protected Iterable<QName> keys() {
+ Dictionary<String, Object> properties = doGetProperties();
+ Set<QName> keys = new TreeSet<>(NamespaceUtils.QNAME_COMPARATOR);
+ keys: for (Enumeration<String> it = properties.keys(); it.hasMoreElements();) {
+ String key = it.nextElement();
+ if (key.equalsIgnoreCase(LdapAttr.objectClass.name()))
+ continue keys;
+ if (key.equalsIgnoreCase(LdapAttr.objectClasses.name()))
+ continue keys;
+ ContentName name = new ContentName(ArgeoNamespace.LDAP_NAMESPACE_URI, key, provider);
+ keys.add(name);
+ }
+ return keys;
+ }
+
+ @Override
+ public List<QName> getContentClasses() {
+ Dictionary<String, Object> properties = doGetProperties();
+ List<QName> contentClasses = new ArrayList<>();
+ String objectClass = properties.get(LdapAttr.objectClass.name()).toString();
+ contentClasses.add(new ContentName(ArgeoNamespace.LDAP_NAMESPACE_URI, objectClass, provider));
+
+ String[] objectClasses = properties.get(LdapAttr.objectClasses.name()).toString().split("\\n");
+ objectClasses: for (String oc : objectClasses) {
+ if (LdapObj.top.name().equalsIgnoreCase(oc))
+ continue objectClasses;
+ if (objectClass.equalsIgnoreCase(oc))
+ continue objectClasses;
+ contentClasses.add(new ContentName(ArgeoNamespace.LDAP_NAMESPACE_URI, oc, provider));
+ }
+ return contentClasses;
+ }
+
+ @Override
+ public Object put(QName key, Object value) {
+ Object previous = get(key);
+ provider.getUserManager().edit(() -> doGetProperties().put(key.getLocalPart(), value));
+ return previous;
+ }
+
+ @Override
+ protected void removeAttr(QName key) {
+ doGetProperties().remove(key.getLocalPart());
+ }
+
+ @Override
+ public ContentProvider getProvider() {
+ return provider;
+ }
+
+}
--- /dev/null
+package org.argeo.cms.acr.directory;
+
+import java.util.ArrayList;
+import java.util.Dictionary;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.xml.namespace.QName;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.acr.ContentName;
+import org.argeo.api.acr.spi.ProvidedSession;
+import org.argeo.api.cms.directory.CmsDirectory;
+import org.argeo.api.cms.directory.HierarchyUnit;
+
+class DirectoryContent extends AbstractDirectoryContent {
+ private CmsDirectory directory;
+
+ public DirectoryContent(ProvidedSession session, DirectoryContentProvider provider, CmsDirectory directory) {
+ super(session, provider);
+ this.directory = directory;
+ }
+
+ @Override
+ Dictionary<String, Object> doGetProperties() {
+ return directory.getProperties();
+ }
+
+ @Override
+ public Iterator<Content> iterator() {
+ List<Content> res = new ArrayList<>();
+ for (Iterator<HierarchyUnit> it = directory.getDirectHierarchyUnits(false).iterator(); it.hasNext();) {
+ res.add(new HierarchyUnitContent(getSession(), provider, it.next()));
+ }
+ return res.iterator();
+ }
+
+ @Override
+ public QName getName() {
+ return new ContentName(directory.getName());
+ }
+
+ @Override
+ public Content getParent() {
+ return provider.getRootContent(getSession());
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public <A> A adapt(Class<A> clss) {
+ if (clss.equals(HierarchyUnit.class))
+ return (A) directory;
+ if (clss.equals(CmsDirectory.class))
+ return (A) directory;
+ return super.adapt(clss);
+ }
+
+}
--- /dev/null
+package org.argeo.cms.acr.directory;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.xml.namespace.QName;
+
+import org.argeo.api.acr.ArgeoNamespace;
+import org.argeo.api.acr.Content;
+import org.argeo.api.acr.ContentName;
+import org.argeo.api.acr.ContentNotFoundException;
+import org.argeo.api.acr.spi.ContentProvider;
+import org.argeo.api.acr.spi.ProvidedContent;
+import org.argeo.api.acr.spi.ProvidedSession;
+import org.argeo.api.cms.directory.CmsUserManager;
+import org.argeo.api.cms.directory.HierarchyUnit;
+import org.argeo.api.cms.directory.UserDirectory;
+import org.argeo.cms.acr.AbstractContent;
+import org.argeo.cms.acr.ContentUtils;
+import org.osgi.service.useradmin.User;
+
+public class DirectoryContentProvider implements ContentProvider {
+ private String mountPath;
+ private String mountName;
+
+ private CmsUserManager userManager;
+
+ public DirectoryContentProvider(String mountPath, CmsUserManager userManager) {
+ this.mountPath = mountPath;
+ List<String> mountSegments = ContentUtils.toPathSegments(mountPath);
+ this.mountName = mountSegments.get(mountSegments.size() - 1);
+ this.userManager = userManager;
+ }
+
+ @Override
+ public ProvidedContent get(ProvidedSession session, String relativePath) {
+ List<String> segments = ContentUtils.toPathSegments(relativePath);
+ if (segments.size() == 0)
+ return new UserManagerContent(session);
+ String userDirectoryName = segments.get(0);
+ UserDirectory userDirectory = null;
+ userDirectories: for (UserDirectory ud : userManager.getUserDirectories()) {
+ if (userDirectoryName.equals(ud.getName())) {
+ userDirectory = ud;
+ break userDirectories;
+ }
+ }
+ if (userDirectory == null)
+ throw new ContentNotFoundException(session, mountPath + "/" + relativePath,
+ "Cannot find user directory " + userDirectoryName);
+ if (segments.size() == 1) {
+ return new DirectoryContent(session, this, userDirectory);
+ } else {
+ List<String> relSegments = new ArrayList<>(segments);
+ relSegments.remove(0);
+ String pathWithinUserDirectory = ContentUtils.toPath(relSegments);
+// LdapName dn;
+// try {
+// dn = LdapNameUtils.toLdapName(userDirectoryDn);
+// for (int i = 1; i < segments.size(); i++) {
+// dn.add(segments.get(i));
+// }
+// } catch (InvalidNameException e) {
+// throw new IllegalStateException("Cannot interpret " + segments + " as DN", e);
+// }
+ User user = (User) userDirectory.getRoleByPath(pathWithinUserDirectory);
+ if (user != null) {
+ HierarchyUnit parent = userDirectory.getHierarchyUnit(user);
+ return new RoleContent(session, this, new HierarchyUnitContent(session, this, parent), user);
+ }
+ HierarchyUnit hierarchyUnit = userDirectory.getHierarchyUnit(pathWithinUserDirectory);
+ if (hierarchyUnit == null)
+ throw new ContentNotFoundException(session,
+ mountPath + "/" + relativePath + "/" + pathWithinUserDirectory,
+ "Cannot find " + pathWithinUserDirectory + " within " + userDirectoryName);
+ return new HierarchyUnitContent(session, this, hierarchyUnit);
+ }
+ }
+
+ @Override
+ public boolean exists(ProvidedSession session, String relativePath) {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ @Override
+ public String getMountPath() {
+ return mountPath;
+ }
+
+ @Override
+ public String getNamespaceURI(String prefix) {
+ if (ArgeoNamespace.LDAP_DEFAULT_PREFIX.equals(prefix))
+ return ArgeoNamespace.LDAP_NAMESPACE_URI;
+ throw new IllegalArgumentException("Only prefix " + ArgeoNamespace.LDAP_DEFAULT_PREFIX + " is supported");
+ }
+
+ @Override
+ public Iterator<String> getPrefixes(String namespaceURI) {
+ if (ArgeoNamespace.LDAP_NAMESPACE_URI.equals(namespaceURI))
+ return Collections.singletonList(ArgeoNamespace.LDAP_DEFAULT_PREFIX).iterator();
+ throw new IllegalArgumentException("Only namespace URI " + ArgeoNamespace.LDAP_NAMESPACE_URI + " is supported");
+ }
+
+ public void setUserManager(CmsUserManager userManager) {
+ this.userManager = userManager;
+ }
+
+ public CmsUserManager getUserManager() {
+ return userManager;
+ }
+
+ UserManagerContent getRootContent(ProvidedSession session) {
+ return new UserManagerContent(session);
+ }
+
+ /*
+ * COMMON UTILITIES
+ */
+ class UserManagerContent extends AbstractContent {
+
+ public UserManagerContent(ProvidedSession session) {
+ super(session);
+ }
+
+ @Override
+ public ContentProvider getProvider() {
+ return DirectoryContentProvider.this;
+ }
+
+ @Override
+ public QName getName() {
+ return new ContentName(mountName);
+ }
+
+ @Override
+ public Content getParent() {
+ return null;
+ }
+
+ @Override
+ public Iterator<Content> iterator() {
+ List<Content> res = new ArrayList<>();
+ for (UserDirectory userDirectory : userManager.getUserDirectories()) {
+ DirectoryContent content = new DirectoryContent(getSession(), DirectoryContentProvider.this,
+ userDirectory);
+ res.add(content);
+ }
+ return res.iterator();
+ }
+
+ }
+}
--- /dev/null
+package org.argeo.cms.acr.directory;
+
+import java.util.ArrayList;
+import java.util.Dictionary;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Objects;
+
+import javax.xml.namespace.QName;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.acr.ContentName;
+import org.argeo.api.acr.CrName;
+import org.argeo.api.acr.DName;
+import org.argeo.api.acr.spi.ProvidedSession;
+import org.argeo.api.cms.directory.CmsDirectory;
+import org.argeo.api.cms.directory.HierarchyUnit;
+import org.argeo.api.cms.directory.UserDirectory;
+import org.osgi.service.useradmin.Role;
+
+class HierarchyUnitContent extends AbstractDirectoryContent {
+ private HierarchyUnit hierarchyUnit;
+
+ public HierarchyUnitContent(ProvidedSession session, DirectoryContentProvider provider,
+ HierarchyUnit hierarchyUnit) {
+ super(session, provider);
+ Objects.requireNonNull(hierarchyUnit);
+ this.hierarchyUnit = hierarchyUnit;
+ }
+
+ @Override
+ Dictionary<String, Object> doGetProperties() {
+ return hierarchyUnit.getProperties();
+ }
+
+ @Override
+ public QName getName() {
+// if (hierarchyUnit.getParent() == null) {// base DN
+// String baseDn = hierarchyUnit.getBasePath();
+// return new ContentName(baseDn);
+// }
+ String name = hierarchyUnit.getHierarchyUnitName();
+ return new ContentName(name);
+ }
+
+ @Override
+ public Content getParent() {
+ HierarchyUnit parentHu = hierarchyUnit.getParent();
+ if (parentHu instanceof CmsDirectory) {
+ return new DirectoryContent(getSession(), provider, hierarchyUnit.getDirectory());
+ }
+ return new HierarchyUnitContent(getSession(), provider, parentHu);
+ }
+
+ @Override
+ public Iterator<Content> iterator() {
+ List<Content> lst = new ArrayList<>();
+ for (HierarchyUnit hu : hierarchyUnit.getDirectHierarchyUnits(false))
+ lst.add(new HierarchyUnitContent(getSession(), provider, hu));
+
+ for (Role role : ((UserDirectory) hierarchyUnit.getDirectory()).getHierarchyUnitRoles(hierarchyUnit, null,
+ false))
+ lst.add(new RoleContent(getSession(), provider, this, role));
+ return lst.iterator();
+ }
+
+ /*
+ * TYPING
+ */
+ @Override
+ public List<QName> getContentClasses() {
+ List<QName> contentClasses = super.getContentClasses();
+ contentClasses.add(DName.collection.qName());
+ return contentClasses;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public <A> A adapt(Class<A> clss) {
+ if (clss.equals(HierarchyUnit.class))
+ return (A) hierarchyUnit;
+ return super.adapt(clss);
+ }
+
+ /*
+ * ACCESSOR
+ */
+ HierarchyUnit getHierarchyUnit() {
+ return hierarchyUnit;
+ }
+
+}
--- /dev/null
+package org.argeo.cms.acr.directory;
+
+import java.util.Dictionary;
+
+import javax.xml.namespace.QName;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.acr.ContentName;
+import org.argeo.api.acr.spi.ProvidedSession;
+import org.argeo.api.cms.directory.UserDirectory;
+import org.osgi.service.useradmin.Role;
+
+class RoleContent extends AbstractDirectoryContent {
+
+ private HierarchyUnitContent parent;
+ private Role role;
+
+ public RoleContent(ProvidedSession session, DirectoryContentProvider provider, HierarchyUnitContent parent,
+ Role role) {
+ super(session, provider);
+ this.parent = parent;
+ this.role = role;
+ }
+
+ @Override
+ Dictionary<String, Object> doGetProperties() {
+ return role.getProperties();
+ }
+
+ @Override
+ public QName getName() {
+ String name = ((UserDirectory) parent.getHierarchyUnit().getDirectory()).getRoleSimpleName(role);
+ return new ContentName(name);
+ }
+
+ @Override
+ public Content getParent() {
+ return parent;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public <A> A adapt(Class<A> clss) {
+ if (Role.class.isAssignableFrom(clss))
+ return (A) role;
+ return super.adapt(clss);
+ }
+
+}
package org.argeo.cms.acr.fs;
+import java.io.Closeable;
import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
import java.nio.file.attribute.FileTime;
import java.nio.file.attribute.UserDefinedFileAttributeView;
import java.time.Instant;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
+import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
+import java.util.StringJoiner;
+import java.util.concurrent.CompletableFuture;
+import javax.xml.XMLConstants;
import javax.xml.namespace.QName;
+import javax.xml.transform.Source;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.stream.StreamResult;
import org.argeo.api.acr.Content;
import org.argeo.api.acr.ContentName;
import org.argeo.api.acr.ContentResourceException;
+import org.argeo.api.acr.CrAttributeType;
import org.argeo.api.acr.CrName;
-import org.argeo.api.acr.spi.AbstractContent;
+import org.argeo.api.acr.DName;
+import org.argeo.api.acr.NamespaceUtils;
+import org.argeo.api.acr.spi.ContentProvider;
import org.argeo.api.acr.spi.ProvidedContent;
import org.argeo.api.acr.spi.ProvidedSession;
-import org.argeo.util.FsUtils;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.cms.acr.AbstractContent;
+import org.argeo.cms.acr.ContentUtils;
+import org.argeo.cms.util.FsUtils;
+/** Content persisted as a filesystem {@link Path}. */
public class FsContent extends AbstractContent implements ProvidedContent {
- private final static String USER_ = "user:";
+ private CmsLog log = CmsLog.getLog(FsContent.class);
+
+ final static String USER_ = "user:";
private static final Map<QName, String> BASIC_KEYS;
private static final Map<QName, String> POSIX_KEYS;
static {
BASIC_KEYS = new HashMap<>();
- BASIC_KEYS.put(CrName.CREATION_TIME.get(), "basic:creationTime");
- BASIC_KEYS.put(CrName.LAST_MODIFIED_TIME.get(), "basic:lastModifiedTime");
- BASIC_KEYS.put(CrName.SIZE.get(), "basic:size");
- BASIC_KEYS.put(CrName.FILE_KEY.get(), "basic:fileKey");
+ BASIC_KEYS.put(DName.creationdate.qName(), "basic:creationTime");
+ BASIC_KEYS.put(DName.getlastmodified.qName(), "basic:lastModifiedTime");
+ BASIC_KEYS.put(DName.getcontentlength.qName(), "basic:size");
+
+ BASIC_KEYS.put(CrName.fileKey.qName(), "basic:fileKey");
POSIX_KEYS = new HashMap<>(BASIC_KEYS);
- POSIX_KEYS.put(CrName.OWNER.get(), "owner:owner");
- POSIX_KEYS.put(CrName.GROUP.get(), "posix:group");
- POSIX_KEYS.put(CrName.PERMISSIONS.get(), "posix:permissions");
+ POSIX_KEYS.put(DName.owner.qName(), "owner:owner");
+ POSIX_KEYS.put(DName.group.qName(), "posix:group");
+ POSIX_KEYS.put(CrName.permissions.qName(), "posix:permissions");
}
- private final ProvidedSession session;
private final FsContentProvider provider;
private final Path path;
- private final boolean isRoot;
+ private final boolean isMountBase;
private final QName name;
protected FsContent(ProvidedSession session, FsContentProvider contentProvider, Path path) {
- this.session = session;
+ super(session);
this.provider = contentProvider;
this.path = path;
- this.isRoot = contentProvider.isRoot(path);
+ this.isMountBase = contentProvider.isMountBase(path);
// TODO check file names with ':' ?
- if (isRoot)
- this.name = CrName.ROOT.get();
- else
- this.name = session.parsePrefixedName(path.getFileName().toString());
+ if (isMountBase) {
+ String mountPath = provider.getMountPath();
+ if (mountPath != null && !mountPath.equals(ContentUtils.ROOT_SLASH)) {
+ Content mountPoint = session.getMountPoint(mountPath);
+ this.name = mountPoint.getName();
+ } else {
+ this.name = CrName.root.qName();
+ }
+ } else {
+
+ // TODO should we support prefixed name for known types?
+ QName providerName = NamespaceUtils.parsePrefixedName(provider, path.getFileName().toString());
+// QName providerName = new QName(path.getFileName().toString());
+ // TODO remove extension if mounted?
+ this.name = new ContentName(providerName, session);
+ }
}
protected FsContent(FsContent context, Path path) {
* ATTRIBUTES
*/
+ @SuppressWarnings("unchecked")
@Override
public <A> Optional<A> get(QName key, Class<A> clss) {
Object value;
try {
// We need to add user: when accessing via Files#getAttribute
- value = Files.getAttribute(path, toFsAttributeKey(key));
+
+ if (POSIX_KEYS.containsKey(key)) {
+ value = Files.getAttribute(path, toFsAttributeKey(key));
+ } else {
+ UserDefinedFileAttributeView udfav = Files.getFileAttributeView(path,
+ UserDefinedFileAttributeView.class);
+ String prefixedName = NamespaceUtils.toPrefixedName(provider, key);
+ if (!udfav.list().contains(prefixedName))
+ return Optional.empty();
+ ByteBuffer buf = ByteBuffer.allocate(udfav.size(prefixedName));
+ udfav.read(prefixedName, buf);
+ buf.flip();
+ if (buf.hasArray())
+ value = buf.array();
+ else {
+ byte[] arr = new byte[buf.remaining()];
+ buf.get(arr);
+ value = arr;
+ }
+ }
} catch (IOException e) {
throw new ContentResourceException("Cannot retrieve attribute " + key + " for " + path, e);
}
}
// TODO perform trivial file conversion to other formats
}
+
+ // TODO better deal with multiple types
if (value instanceof byte[]) {
- res = (A) new String((byte[]) value, StandardCharsets.UTF_8);
- }
- if (res == null)
- try {
- res = (A) value;
- } catch (ClassCastException e) {
- return Optional.empty();
+ String str = new String((byte[]) value, StandardCharsets.UTF_8);
+ String[] arr = str.split("\n");
+
+ if (arr.length == 1) {
+ if (clss.isAssignableFrom(String.class)) {
+ res = (A) arr[0];
+ } else {
+ res = (A) CrAttributeType.parse(arr[0]);
+ }
+ } else {
+ List<Object> lst = new ArrayList<>();
+ for (String s : arr) {
+ lst.add(CrAttributeType.parse(s));
+ }
+ res = (A) lst;
}
+ }
+ if (res == null) {
+ if (isDefaultAttrTypeRequested(clss))
+ return Optional.of((A) CrAttributeType.parse(value.toString()));
+ if (clss.isAssignableFrom(value.getClass()))
+ return Optional.of((A) value);
+ if (clss.isAssignableFrom(String.class))
+ return Optional.of((A) value.toString());
+ log.warn("Cannot interpret " + key + " in " + this);
+ return Optional.empty();
+// try {
+// res = (A) value;
+// } catch (ClassCastException e) {
+// return Optional.empty();
+// }
+ }
return Optional.of(res);
}
if (udfav != null) {
try {
for (String name : udfav.list()) {
- result.add(session.parsePrefixedName(name));
+ QName providerName = NamespaceUtils.parsePrefixedName(provider, name);
+ if (providerName.getNamespaceURI().equals(XMLConstants.XMLNS_ATTRIBUTE_NS_URI))
+ continue; // skip prefix mapping
+ QName sessionName = new ContentName(providerName, getSession());
+ result.add(sessionName);
}
} catch (IOException e) {
throw new ContentResourceException("Cannot list attributes for " + path, e);
protected void removeAttr(QName key) {
UserDefinedFileAttributeView udfav = Files.getFileAttributeView(path, UserDefinedFileAttributeView.class);
try {
- udfav.delete(session.toPrefixedName(key));
+ udfav.delete(NamespaceUtils.toPrefixedName(provider, key));
} catch (IOException e) {
throw new ContentResourceException("Cannot delete attribute " + key + " for " + path, e);
}
@Override
public Object put(QName key, Object value) {
Object previous = get(key);
+
+ String toWrite;
+ if (value instanceof List) {
+ StringJoiner sj = new StringJoiner("\n");
+ for (Object obj : (List<?>) value) {
+ sj.add(obj.toString());
+ }
+ toWrite = sj.toString();
+ } else {
+ toWrite = value.toString();
+ }
+
UserDefinedFileAttributeView udfav = Files.getFileAttributeView(path, UserDefinedFileAttributeView.class);
- ByteBuffer bb = ByteBuffer.wrap(value.toString().getBytes(StandardCharsets.UTF_8));
+ ByteBuffer bb = ByteBuffer.wrap(toWrite.getBytes(StandardCharsets.UTF_8));
try {
- int size = udfav.write(session.toPrefixedName(key), bb);
+ udfav.write(NamespaceUtils.toPrefixedName(provider, key), bb);
} catch (IOException e) {
throw new ContentResourceException("Cannot delete attribute " + key + " for " + path, e);
}
if (POSIX_KEYS.containsKey(key))
return POSIX_KEYS.get(key);
else
- return USER_ + session.toPrefixedName(key);
+ return USER_ + NamespaceUtils.toPrefixedName(provider, key);
}
/*
public Iterator<Content> iterator() {
if (Files.isDirectory(path)) {
try {
- return Files.list(path).map((p) -> (Content) new FsContent(this, p)).iterator();
+ return Files.list(path).map((p) -> {
+ FsContent fsContent = new FsContent(this, p);
+ Optional<String> isMount = fsContent.get(CrName.mount.qName(), String.class);
+ if (isMount.orElse("false").equals("true")) {
+ QName[] classes = null;
+ ContentProvider contentProvider = getSession().getRepository()
+ .getMountContentProvider(fsContent, false, classes);
+ Content mountedContent = contentProvider.get(getSession(), "");
+ return mountedContent;
+ } else {
+ return (Content) fsContent;
+ }
+ }).iterator();
} catch (IOException e) {
throw new ContentResourceException("Cannot list " + path, e);
}
@Override
public Content add(QName name, QName... classes) {
+ FsContent fsContent;
try {
- Path newPath = path.resolve(session.toPrefixedName(name));
- if (ContentName.contains(classes, CrName.COLLECTION.get()))
+ Path newPath = path.resolve(NamespaceUtils.toPrefixedName(provider, name));
+ if (ContentName.contains(classes, DName.collection.qName()))
Files.createDirectory(newPath);
else
Files.createFile(newPath);
// for(ContentClass clss:classes) {
// Files.setAttribute(newPath, name, newPath, null)
// }
- return new FsContent(this, newPath);
+ fsContent = new FsContent(this, newPath);
} catch (IOException e) {
throw new ContentResourceException("Cannot create new content", e);
}
+
+ if (classes.length > 0)
+ fsContent.addContentClasses(classes);
+ if (getSession().getRepository().shouldMount(classes)) {
+ ContentProvider contentProvider = getSession().getRepository().getMountContentProvider(fsContent, true,
+ classes);
+ Content mountedContent = contentProvider.get(getSession(), "");
+ fsContent.put(CrName.mount.qName(), "true");
+ return mountedContent;
+
+ } else {
+ return fsContent;
+ }
}
@Override
@Override
public Content getParent() {
- if (isRoot)
- return null;// TODO deal with mounts
+ if (isMountBase) {
+ String mountPath = provider.getMountPath();
+ if (mountPath == null || mountPath.equals("/"))
+ return null;
+ String[] parent = ContentUtils.getParentPath(mountPath);
+ return getSession().get(parent[0]);
+ }
return new FsContent(this, path.getParent());
}
+ @SuppressWarnings("unchecked")
+ @Override
+ public <C extends Closeable> C open(Class<C> clss) throws IOException, IllegalArgumentException {
+ if (InputStream.class.isAssignableFrom(clss)) {
+ if (Files.isDirectory(path))
+ throw new UnsupportedOperationException("Cannot open " + path + " as stream, since it is a directory");
+ return (C) Files.newInputStream(path);
+ } else if (OutputStream.class.isAssignableFrom(clss)) {
+ if (Files.isDirectory(path))
+ throw new UnsupportedOperationException("Cannot open " + path + " as stream, since it is a directory");
+ return (C) Files.newOutputStream(path);
+ }
+ return super.open(clss);
+ }
+
/*
- * ACCESSORS
+ * MOUNT MANAGEMENT
*/
@Override
- public ProvidedSession getSession() {
- return session;
+ public ProvidedContent getMountPoint(String relativePath) {
+ Path childPath = path.resolve(relativePath);
+ // TODO check that it is a mount
+ return new FsContent(this, childPath);
}
+ /*
+ * TYPING
+ */
+
+ @Override
+ public List<QName> getContentClasses() {
+ List<QName> res = new ArrayList<>();
+ List<String> value = getMultiple(DName.resourcetype.qName(), String.class);
+ for (String s : value) {
+ QName name = NamespaceUtils.parsePrefixedName(provider, s);
+ res.add(name);
+ }
+ if (Files.isDirectory(path))
+ res.add(DName.collection.qName());
+ return res;
+ }
+
+ @Override
+ public void addContentClasses(QName... contentClass) {
+ List<String> toWrite = new ArrayList<>();
+ for (QName cc : getContentClasses()) {
+ if (cc.equals(DName.collection.qName()))
+ continue; // skip
+ toWrite.add(NamespaceUtils.toPrefixedName(provider, cc));
+ }
+ for (QName cc : contentClass) {
+ toWrite.add(NamespaceUtils.toPrefixedName(provider, cc));
+ }
+ put(DName.resourcetype.qName(), toWrite);
+ }
+
+ /*
+ * ACCESSORS
+ */
+
@Override
public FsContentProvider getProvider() {
return provider;
}
+ /*
+ * READ / WRITE
+ */
+ @SuppressWarnings("unchecked")
+ public <A> CompletableFuture<A> write(Class<A> clss) {
+ if (isContentClass(DName.collection.qName())) {
+ throw new IllegalStateException("Cannot directly write to a collection");
+ }
+ if (InputStream.class.isAssignableFrom(clss)) {
+ CompletableFuture<InputStream> res = new CompletableFuture<>();
+ res.thenAccept((in) -> {
+ try {
+ Files.copy(in, path, StandardCopyOption.REPLACE_EXISTING);
+ } catch (IOException e) {
+ throw new RuntimeException("Cannot write to " + path, e);
+ }
+ });
+ return (CompletableFuture<A>) res;
+ } else if (Source.class.isAssignableFrom(clss)) {
+ CompletableFuture<Source> res = new CompletableFuture<Source>();
+ res.thenAccept((source) -> {
+// Path targetPath = path.getParent().resolve(path.getFileName()+".xml");
+ Path targetPath = path;
+ try (OutputStream out = Files.newOutputStream(targetPath)) {
+ StreamResult result = new StreamResult(out);
+ TransformerFactory.newDefaultInstance().newTransformer().transform(source, result);
+ } catch (IOException | TransformerException e) {
+ throw new RuntimeException("Cannot write to " + path, e);
+ }
+ });
+ return (CompletableFuture<A>) res;
+ } else {
+ return super.write(clss);
+ }
+ }
}
package org.argeo.cms.acr.fs;
import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.nio.file.attribute.UserDefinedFileAttributeView;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.NavigableMap;
+import java.util.Objects;
+import java.util.TreeMap;
+import java.util.stream.Collectors;
-import org.argeo.api.acr.Content;
+import org.argeo.api.acr.ArgeoNamespace;
import org.argeo.api.acr.ContentResourceException;
+import org.argeo.api.acr.NamespaceUtils;
+import org.argeo.api.acr.RuntimeNamespaceContext;
import org.argeo.api.acr.spi.ContentProvider;
+import org.argeo.api.acr.spi.ProvidedContent;
import org.argeo.api.acr.spi.ProvidedSession;
+/** Access a file system as a {@link ContentProvider}. */
public class FsContentProvider implements ContentProvider {
- private final Path rootPath;
+ final static String XMLNS_ = "xmlns:";
- public FsContentProvider(Path rootPath) {
- super();
+ protected String mountPath;
+ protected Path rootPath;
+
+ private NavigableMap<String, String> prefixes = new TreeMap<>();
+
+ public FsContentProvider(String mountPath, Path rootPath) {
+ Objects.requireNonNull(mountPath);
+ Objects.requireNonNull(rootPath);
+
+ this.mountPath = mountPath;
this.rootPath = rootPath;
+ // FIXME make it more robust
+ initNamespaces();
+ }
+
+ protected FsContentProvider() {
+
}
- boolean isRoot(Path path) {
+ protected void initNamespaces() {
+ try {
+ UserDefinedFileAttributeView udfav = Files.getFileAttributeView(rootPath,
+ UserDefinedFileAttributeView.class);
+ if (udfav == null)
+ return;
+ for (String name : udfav.list()) {
+ if (name.startsWith(XMLNS_)) {
+ ByteBuffer buf = ByteBuffer.allocate(udfav.size(name));
+ udfav.read(name, buf);
+ buf.flip();
+ String namespace = StandardCharsets.UTF_8.decode(buf).toString();
+ String prefix = name.substring(XMLNS_.length());
+ prefixes.put(prefix, namespace);
+ }
+ }
+
+ // defaults
+ addDefaultNamespace(udfav, ArgeoNamespace.CR_DEFAULT_PREFIX, ArgeoNamespace.CR_NAMESPACE_URI);
+ addDefaultNamespace(udfav, "basic", ArgeoNamespace.CR_NAMESPACE_URI);
+ addDefaultNamespace(udfav, "owner", ArgeoNamespace.CR_NAMESPACE_URI);
+ addDefaultNamespace(udfav, "posix", ArgeoNamespace.CR_NAMESPACE_URI);
+ } catch (IOException e) {
+ throw new RuntimeException("Cannot read namespaces from " + rootPath, e);
+ }
+
+ }
+
+ protected void addDefaultNamespace(UserDefinedFileAttributeView udfav, String prefix, String namespace)
+ throws IOException {
+ if (!prefixes.containsKey(prefix)) {
+ ByteBuffer bb = ByteBuffer.wrap(namespace.getBytes(StandardCharsets.UTF_8));
+ udfav.write(XMLNS_ + prefix, bb);
+ prefixes.put(prefix, namespace);
+ }
+ }
+
+ public void registerPrefix(String prefix, String namespace) {
+ if (prefixes.containsKey(prefix))
+ prefixes.remove(prefix);
+ try {
+ UserDefinedFileAttributeView udfav = Files.getFileAttributeView(rootPath,
+ UserDefinedFileAttributeView.class);
+ addDefaultNamespace(udfav, prefix, namespace);
+ } catch (IOException e) {
+ throw new RuntimeException("Cannot register namespace " + prefix + " " + namespace + " on " + rootPath, e);
+ }
+
+ }
+
+ @Override
+ public String getMountPath() {
+ return mountPath;
+ }
+
+ boolean isMountBase(Path path) {
try {
return Files.isSameFile(rootPath, path);
} catch (IOException e) {
}
@Override
- public Content get(ProvidedSession session, String mountPath, String relativePath) {
+ public ProvidedContent get(ProvidedSession session, String relativePath) {
return new FsContent(session, this, rootPath.resolve(relativePath));
}
+
+ @Override
+ public boolean exists(ProvidedSession session, String relativePath) {
+ return Files.exists(rootPath.resolve(relativePath));
+ }
+
+ /*
+ * NAMESPACE CONTEXT
+ */
+
+ @Override
+ public String getNamespaceURI(String prefix) {
+ return NamespaceUtils.getNamespaceURI((p) -> prefixes.get(p), prefix);
+ }
+
+ @Override
+ public Iterator<String> getPrefixes(String namespaceURI) {
+ Iterator<String> res = NamespaceUtils.getPrefixes((ns) -> prefixes.entrySet().stream()
+ .filter(e -> e.getValue().equals(ns)).map(Map.Entry::getKey).collect(Collectors.toUnmodifiableSet()),
+ namespaceURI);
+ if (!res.hasNext()) {
+ String prefix = RuntimeNamespaceContext.getNamespaceContext().getPrefix(namespaceURI);
+ if (prefix != null) {
+ registerPrefix(prefix, namespaceURI);
+ return getPrefixes(namespaceURI);
+ } else {
+ throw new IllegalArgumentException("Unknown namespace " + namespaceURI);
+ }
+ }
+ return res;
+ }
+
}
--- /dev/null
+package org.argeo.cms.acr.fs;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.util.Map;
+import java.util.Objects;
+
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.api.cms.CmsState;
+
+public class FsContentProviderService extends FsContentProvider {
+ private CmsState cmsState;
+
+ public void start(Map<String, String> properties) {
+ mountPath = properties.get(CmsConstants.ACR_MOUNT_PATH);
+ Objects.requireNonNull(mountPath);
+ if (!mountPath.startsWith("/"))
+ throw new IllegalArgumentException("Mount path must start with /");
+
+ String relPath = mountPath.substring(1);
+ rootPath = cmsState.getDataPath(relPath);
+ try {
+ Files.createDirectories(rootPath);
+ } catch (IOException e) {
+ throw new IllegalStateException(
+ "Cannot initialize FS content provider " + mountPath + " with base" + rootPath, e);
+ }
+
+ initNamespaces();
+ }
+
+ public void stop() {
+ }
+
+ public void setCmsState(CmsState cmsState) {
+ this.cmsState = cmsState;
+ }
+
+}
--- /dev/null
+<xsd:schema targetNamespace="urn:oasis:names:tc:DSML:2:0:core" \r
+ xmlns="urn:oasis:names:tc:DSML:2:0:core" \r
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">\r
+<!-- Copyright (C) The Organization for the Advancement of Structured Information Standards [OASIS] 2001. All Rights Reserved. -->\r
+ <!-- DSML Requests -->\r
+ <xsd:group name="DSMLRequests">\r
+ <xsd:choice>\r
+ <xsd:element name="authRequest" type="AuthRequest"/>\r
+ <xsd:group ref="BatchRequests"/>\r
+ </xsd:choice>\r
+ </xsd:group>\r
+ <xsd:group name="BatchRequests">\r
+ <xsd:choice>\r
+ <xsd:element name="searchRequest" type="SearchRequest"/>\r
+ <xsd:element name="modifyRequest" type="ModifyRequest"/>\r
+ <xsd:element name="addRequest" type="AddRequest"/>\r
+ <xsd:element name="delRequest" type="DelRequest"/>\r
+ <xsd:element name="modDNRequest" type="ModifyDNRequest"/>\r
+ <xsd:element name="compareRequest" type="CompareRequest"/>\r
+ <xsd:element name="abandonRequest" type="AbandonRequest"/>\r
+ <xsd:element name="extendedRequest" type="ExtendedRequest"/>\r
+ </xsd:choice>\r
+ </xsd:group>\r
+ <!-- DSML Responses -->\r
+ <xsd:group name="DSMLResponses">\r
+ <xsd:choice>\r
+ <xsd:element name="authResponse" type="LDAPResult"/>\r
+ <xsd:element name="searchResultEntry" type="SearchResultEntry"/>\r
+ <xsd:element name="searchResultReference" type="SearchResultReference"/>\r
+ <xsd:element name="searchResultDone" type="LDAPResult"/>\r
+ <xsd:element name="modifyResponse" type="LDAPResult"/>\r
+ <xsd:element name="addResponse" type="LDAPResult"/>\r
+ <xsd:element name="delResponse" type="LDAPResult"/>\r
+ <xsd:element name="modDNResponse" type="LDAPResult"/>\r
+ <xsd:element name="compareResponse" type="LDAPResult"/>\r
+ <xsd:element name="extendedResponse" type="ExtendedResponse"/>\r
+ <xsd:element name="errorResponse" type="ErrorResponse"/>\r
+ </xsd:choice>\r
+ </xsd:group>\r
+ <!-- *************** Batch Envelopes ********************* -->\r
+ <xsd:element name="batchRequest" type="BatchRequest"/>\r
+ <xsd:element name="batchResponse" type="BatchResponse"/>\r
+ <!-- **** Batch Request Envelope **** -->\r
+ <xsd:complexType name="BatchRequest">\r
+ <xsd:sequence>\r
+ <xsd:element name="authRequest" type="AuthRequest" minOccurs="0" maxOccurs="1"/>\r
+ <xsd:group ref="BatchRequests" minOccurs="0" maxOccurs="unbounded"/>\r
+ </xsd:sequence>\r
+ <xsd:attribute name="requestID" type="RequestID" use="optional"/>\r
+ <xsd:attribute name="processing" use="optional" default="sequential">\r
+ <xsd:simpleType>\r
+ <xsd:restriction base="xsd:string">\r
+ <xsd:enumeration value="sequential"/>\r
+ <xsd:enumeration value="parallel"/>\r
+ </xsd:restriction>\r
+ </xsd:simpleType>\r
+ </xsd:attribute>\r
+ <xsd:attribute name="responseOrder" use="optional" default="sequential">\r
+ <xsd:simpleType>\r
+ <xsd:restriction base="xsd:string">\r
+ <xsd:enumeration value="sequential"/>\r
+ <xsd:enumeration value="unordered"/>\r
+ </xsd:restriction>\r
+ </xsd:simpleType>\r
+ </xsd:attribute>\r
+ <xsd:attribute name="onError" use="optional" default="exit">\r
+ <xsd:simpleType>\r
+ <xsd:restriction base="xsd:string">\r
+ <xsd:enumeration value="resume"/>\r
+ <xsd:enumeration value="exit"/>\r
+ </xsd:restriction>\r
+ </xsd:simpleType>\r
+ </xsd:attribute>\r
+ </xsd:complexType>\r
+ <!-- **** Batch Response Envelope **** -->\r
+ <xsd:complexType name="BatchResponse">\r
+ <xsd:sequence>\r
+ <xsd:group ref="BatchResponses" minOccurs="0" maxOccurs="unbounded"/>\r
+ </xsd:sequence>\r
+ <xsd:attribute name="requestID" type="RequestID" use="optional"/>\r
+ </xsd:complexType>\r
+ <!-- **** Batch Responses **** -->\r
+ <xsd:group name="BatchResponses">\r
+ <xsd:choice>\r
+ <xsd:element name="searchResponse" type="SearchResponse"/>\r
+ <xsd:element name="authResponse" type="LDAPResult"/>\r
+ <xsd:element name="modifyResponse" type="LDAPResult"/>\r
+ <xsd:element name="addResponse" type="LDAPResult"/>\r
+ <xsd:element name="delResponse" type="LDAPResult"/>\r
+ <xsd:element name="modDNResponse" type="LDAPResult"/>\r
+ <xsd:element name="compareResponse" type="LDAPResult"/>\r
+ <xsd:element name="extendedResponse" type="ExtendedResponse"/>\r
+ <xsd:element name="errorResponse" type="ErrorResponse"/>\r
+ </xsd:choice>\r
+ </xsd:group>\r
+ <!-- **** Search Response **** -->\r
+ <xsd:complexType name="SearchResponse">\r
+ <xsd:sequence>\r
+ <xsd:element name="searchResultEntry" type="SearchResultEntry"\r
+ minOccurs="0" maxOccurs="unbounded"/>\r
+ <xsd:element name="searchResultReference" type="SearchResultReference"\r
+ minOccurs="0" maxOccurs="unbounded"/>\r
+ <xsd:element name="searchResultDone" type="LDAPResult"/>\r
+ </xsd:sequence>\r
+ <xsd:attribute name="requestID" type="RequestID" use="optional"/>\r
+ </xsd:complexType>\r
+ <!-- ***** DsmlDN ***** -->\r
+ <xsd:simpleType name="DsmlDN">\r
+ <xsd:restriction base="xsd:string"/>\r
+ </xsd:simpleType>\r
+ <!-- ***** DsmlRDN ***** -->\r
+ <xsd:simpleType name="DsmlRDN">\r
+ <xsd:restriction base="xsd:string"/>\r
+ </xsd:simpleType>\r
+ <!-- ***** Request ID ***** -->\r
+ <xsd:simpleType name="RequestID">\r
+ <xsd:restriction base="xsd:string"/>\r
+ </xsd:simpleType>\r
+ <!-- ***** AttributeDescriptionValue ***** -->\r
+ <xsd:simpleType name="AttributeDescriptionValue">\r
+ <xsd:restriction base="xsd:string">\r
+ <xsd:pattern value="((([0-2](\.[0-9]+)+)|([a-zA-Z]+([a-zA-Z0-9]|[-])*))(;([a-zA-Z0-9]|[-])+)*)"/>\r
+ </xsd:restriction>\r
+ </xsd:simpleType>\r
+ <xsd:simpleType name="NumericOID">\r
+ <xsd:restriction base="xsd:string">\r
+ <xsd:pattern value="[0-2]\.[0-9]+(\.[0-9]+)*"/>\r
+ </xsd:restriction>\r
+ </xsd:simpleType>\r
+ <!-- ***** MAX Integer ***** -->\r
+ <xsd:simpleType name="MAXINT">\r
+ <xsd:restriction base="xsd:unsignedInt">\r
+ <xsd:maxInclusive value="2147483647"/>\r
+ </xsd:restriction>\r
+ </xsd:simpleType>\r
+ <!-- **** DSML Value **** -->\r
+ <xsd:simpleType name="DsmlValue">\r
+ <xsd:union memberTypes="xsd:string xsd:base64Binary xsd:anyURI"/>\r
+ </xsd:simpleType>\r
+ <!-- **** DSML Control **** -->\r
+ <xsd:complexType name="Control">\r
+ <xsd:sequence>\r
+ <xsd:element name="controlValue" type="xsd:anyType" minOccurs="0"/>\r
+ </xsd:sequence>\r
+ <xsd:attribute name="type" type="NumericOID" use="required"/>\r
+ <xsd:attribute name="criticality" type="xsd:boolean" use="optional" default="false"/>\r
+ </xsd:complexType>\r
+ <!-- **** DSML Filter **** -->\r
+ <xsd:complexType name="Filter">\r
+ <xsd:group ref="FilterGroup"/>\r
+ </xsd:complexType>\r
+ <xsd:group name="FilterGroup">\r
+ <xsd:sequence>\r
+ <xsd:choice>\r
+ <xsd:element name="and" type="FilterSet"/>\r
+ <xsd:element name="or" type="FilterSet"/>\r
+ <xsd:element name="not" type="Filter"/>\r
+ <xsd:element name="equalityMatch" type="AttributeValueAssertion"/>\r
+ <xsd:element name="substrings" type="SubstringFilter"/>\r
+ <xsd:element name="greaterOrEqual" type="AttributeValueAssertion"/>\r
+ <xsd:element name="lessOrEqual" type="AttributeValueAssertion"/>\r
+ <xsd:element name="present" type="AttributeDescription"/>\r
+ <xsd:element name="approxMatch" type="AttributeValueAssertion"/>\r
+ <xsd:element name="extensibleMatch" type="MatchingRuleAssertion"/>\r
+ </xsd:choice>\r
+ </xsd:sequence>\r
+ </xsd:group>\r
+ <xsd:complexType name="FilterSet">\r
+ <xsd:sequence>\r
+ <xsd:group ref="FilterGroup" minOccurs="0" maxOccurs="unbounded"/>\r
+ </xsd:sequence>\r
+ </xsd:complexType>\r
+ <xsd:complexType name="AttributeValueAssertion">\r
+ <xsd:sequence>\r
+ <xsd:element name="value" type="DsmlValue"/>\r
+ </xsd:sequence>\r
+ <xsd:attribute name="name" type="AttributeDescriptionValue" use="required"/>\r
+ </xsd:complexType>\r
+ <xsd:complexType name="AttributeDescription">\r
+ <xsd:attribute name="name" type="AttributeDescriptionValue" use="required"/>\r
+ </xsd:complexType>\r
+ <xsd:complexType name="SubstringFilter">\r
+ <xsd:sequence>\r
+ <xsd:element name="initial" type="DsmlValue" minOccurs="0"/>\r
+ <xsd:element name="any" type="DsmlValue" minOccurs="0" maxOccurs="unbounded"/>\r
+ <xsd:element name="final" type="DsmlValue" minOccurs="0"/>\r
+ </xsd:sequence>\r
+ <xsd:attribute name="name" type="AttributeDescriptionValue" use="required"/>\r
+ </xsd:complexType>\r
+ <xsd:complexType name="MatchingRuleAssertion">\r
+ <xsd:sequence>\r
+ <xsd:element name="value" type="DsmlValue"/>\r
+ </xsd:sequence>\r
+ <xsd:attribute name="dnAttributes" type="xsd:boolean" use="optional" default="false"/>\r
+ <xsd:attribute name="matchingRule" type="xsd:string" use="optional"/>\r
+ <xsd:attribute name="name" type="AttributeDescriptionValue" use="optional"/>\r
+ </xsd:complexType>\r
+ <!-- *************** DSML MESSAGE ******************** -->\r
+ <xsd:complexType name="DsmlMessage">\r
+ <xsd:sequence>\r
+ <xsd:element name="control" type="Control" minOccurs="0" maxOccurs="unbounded"/>\r
+ </xsd:sequence>\r
+ <xsd:attribute name="requestID" type="RequestID" use="optional"/>\r
+ </xsd:complexType>\r
+ <!-- *************** LDAP RESULT ********************* -->\r
+ <xsd:simpleType name="LDAPResultCode">\r
+ <xsd:restriction base="xsd:string">\r
+ <xsd:enumeration value="success"/>\r
+ <xsd:enumeration value="operationsError"/>\r
+ <xsd:enumeration value="protocolError"/>\r
+ <xsd:enumeration value="timeLimitExceeded"/>\r
+ <xsd:enumeration value="sizeLimitExceeded"/>\r
+ <xsd:enumeration value="compareFalse"/>\r
+ <xsd:enumeration value="compareTrue"/>\r
+ <xsd:enumeration value="authMethodNotSupported"/>\r
+ <xsd:enumeration value="strongAuthRequired"/>\r
+ <xsd:enumeration value="referral"/>\r
+ <xsd:enumeration value="adminLimitExceeded"/>\r
+ <xsd:enumeration value="unavailableCriticalExtension"/>\r
+ <xsd:enumeration value="confidentialityRequired"/>\r
+ <xsd:enumeration value="saslBindInProgress"/>\r
+ <xsd:enumeration value="noSuchAttribute"/>\r
+ <xsd:enumeration value="undefinedAttributeType"/>\r
+ <xsd:enumeration value="inappropriateMatching"/>\r
+ <xsd:enumeration value="constraintViolation"/>\r
+ <xsd:enumeration value="attributeOrValueExists"/>\r
+ <xsd:enumeration value="invalidAttributeSyntax"/>\r
+ <xsd:enumeration value="noSuchObject"/>\r
+ <xsd:enumeration value="aliasProblem"/>\r
+ <xsd:enumeration value="invalidDNSyntax"/>\r
+ <xsd:enumeration value="aliasDerefencingProblem"/>\r
+ <xsd:enumeration value="inappropriateAuthentication"/>\r
+ <xsd:enumeration value="invalidCredentials"/>\r
+ <xsd:enumeration value="insufficientAccessRights"/>\r
+ <xsd:enumeration value="busy"/>\r
+ <xsd:enumeration value="unavailable"/>\r
+ <xsd:enumeration value="unwillingToPerform"/>\r
+ <xsd:enumeration value="loopDetect"/>\r
+ <xsd:enumeration value="namingViolation"/>\r
+ <xsd:enumeration value="objectClassViolation"/>\r
+ <xsd:enumeration value="notAllowedOnNonLeaf"/>\r
+ <xsd:enumeration value="notAllowedOnRDN"/>\r
+ <xsd:enumeration value="entryAlreadyExists"/>\r
+ <xsd:enumeration value="objectClassModsProhibited"/>\r
+ <xsd:enumeration value="affectMultipleDSAs"/>\r
+ <xsd:enumeration value="other"/>\r
+ </xsd:restriction>\r
+ </xsd:simpleType>\r
+ <xsd:complexType name="ResultCode">\r
+ <xsd:attribute name="code" type="xsd:int" use="required"/>\r
+ <xsd:attribute name="descr" type="LDAPResultCode" use="optional"/>\r
+ </xsd:complexType>\r
+ <xsd:complexType name="LDAPResult">\r
+ <xsd:complexContent>\r
+ <xsd:extension base="DsmlMessage">\r
+ <xsd:sequence>\r
+ <xsd:element name="resultCode" type="ResultCode"/>\r
+ <xsd:element name="errorMessage" type="xsd:string" minOccurs="0"/>\r
+ <xsd:element name="referral" type="xsd:anyURI" minOccurs="0" maxOccurs="unbounded"/>\r
+ </xsd:sequence>\r
+ <xsd:attribute name="matchedDN" type="DsmlDN" use="optional"/>\r
+ </xsd:extension>\r
+ </xsd:complexContent>\r
+ </xsd:complexType>\r
+ <xsd:complexType name="ErrorResponse">\r
+ <xsd:sequence>\r
+ <xsd:element name="message" type="xsd:string" minOccurs="0"/>\r
+ <xsd:element name="detail" minOccurs="0">\r
+ <xsd:complexType>\r
+ <xsd:sequence>\r
+ <xsd:any/>\r
+ </xsd:sequence>\r
+ </xsd:complexType>\r
+ </xsd:element>\r
+ </xsd:sequence>\r
+ <xsd:attribute name="requestID" type="RequestID" use="optional"/>\r
+ <xsd:attribute name="type">\r
+ <xsd:simpleType>\r
+ <xsd:restriction base="xsd:string">\r
+ <xsd:enumeration value="notAttempted"/>\r
+ <xsd:enumeration value="couldNotConnect"/>\r
+ <xsd:enumeration value="connectionClosed"/>\r
+ <xsd:enumeration value="malformedRequest"/>\r
+ <xsd:enumeration value="gatewayInternalError"/>\r
+ <xsd:enumeration value="authenticationFailed"/>\r
+ <xsd:enumeration value="unresolvableURI"/>\r
+ <xsd:enumeration value="other"/>\r
+ </xsd:restriction>\r
+ </xsd:simpleType>\r
+ </xsd:attribute>\r
+ </xsd:complexType>\r
+ <!-- *************** Auth ********************* -->\r
+ <xsd:complexType name="AuthRequest">\r
+ <xsd:complexContent>\r
+ <xsd:extension base="DsmlMessage">\r
+ <xsd:attribute name="principal" type="xsd:string" use="required"/>\r
+ </xsd:extension>\r
+ </xsd:complexContent>\r
+ </xsd:complexType>\r
+ <!-- *************** Search ********************* -->\r
+ <xsd:complexType name="AttributeDescriptions">\r
+ <xsd:sequence minOccurs="0" maxOccurs="unbounded">\r
+ <xsd:element name="attribute" type="AttributeDescription"/>\r
+ </xsd:sequence>\r
+ </xsd:complexType>\r
+ <xsd:complexType name="SearchRequest">\r
+ <xsd:complexContent>\r
+ <xsd:extension base="DsmlMessage">\r
+ <xsd:sequence>\r
+ <xsd:element name="filter" type="Filter"/>\r
+ <xsd:element name="attributes" type="AttributeDescriptions" minOccurs="0"/>\r
+ </xsd:sequence>\r
+ <xsd:attribute name="dn" type="DsmlDN" use="required"/>\r
+ <xsd:attribute name="scope" use="required">\r
+ <xsd:simpleType>\r
+ <xsd:restriction base="xsd:string">\r
+ <xsd:enumeration value="baseObject"/>\r
+ <xsd:enumeration value="singleLevel"/>\r
+ <xsd:enumeration value="wholeSubtree"/>\r
+ </xsd:restriction>\r
+ </xsd:simpleType>\r
+ </xsd:attribute>\r
+ <xsd:attribute name="derefAliases" use="required">\r
+ <xsd:simpleType>\r
+ <xsd:restriction base="xsd:string">\r
+ <xsd:enumeration value="neverDerefAliases"/>\r
+ <xsd:enumeration value="derefInSearching"/>\r
+ <xsd:enumeration value="derefFindingBaseObj"/>\r
+ <xsd:enumeration value="derefAlways"/>\r
+ </xsd:restriction>\r
+ </xsd:simpleType>\r
+ </xsd:attribute>\r
+ <xsd:attribute name="sizeLimit" type="MAXINT" use="optional" default="0"/>\r
+ <xsd:attribute name="timeLimit" type="MAXINT" use="optional" default="0"/>\r
+ <xsd:attribute name="typesOnly" type="xsd:boolean" use="optional" default="false"/>\r
+ </xsd:extension>\r
+ </xsd:complexContent>\r
+ </xsd:complexType>\r
+ <!-- ***** Search Result Entry ***** -->\r
+ <xsd:complexType name="SearchResultEntry">\r
+ <xsd:complexContent>\r
+ <xsd:extension base="DsmlMessage">\r
+ <xsd:sequence>\r
+ <xsd:element name="attr" type="DsmlAttr" minOccurs="0" maxOccurs="unbounded"/>\r
+ </xsd:sequence>\r
+ <xsd:attribute name="dn" type="DsmlDN" use="required"/>\r
+ </xsd:extension>\r
+ </xsd:complexContent>\r
+ </xsd:complexType>\r
+ <xsd:complexType name="DsmlAttr">\r
+ <xsd:sequence>\r
+ <xsd:element name="value" type="DsmlValue" minOccurs="0" maxOccurs="unbounded"/>\r
+ </xsd:sequence>\r
+ <xsd:attribute name="name" type="AttributeDescriptionValue" use="required"/>\r
+ </xsd:complexType>\r
+ <xsd:complexType name="DsmlModification">\r
+ <xsd:sequence>\r
+ <xsd:element name="value" type="DsmlValue" minOccurs="0" maxOccurs="unbounded"/>\r
+ </xsd:sequence>\r
+ <xsd:attribute name="name" type="AttributeDescriptionValue" use="required"/>\r
+ <xsd:attribute name="operation" use="required">\r
+ <xsd:simpleType>\r
+ <xsd:restriction base="xsd:string">\r
+ <xsd:enumeration value="add"/>\r
+ <xsd:enumeration value="delete"/>\r
+ <xsd:enumeration value="replace"/>\r
+ </xsd:restriction>\r
+ </xsd:simpleType>\r
+ </xsd:attribute>\r
+ </xsd:complexType>\r
+ <!-- ***** Search Result Reference ***** -->\r
+ <xsd:complexType name="SearchResultReference">\r
+ <xsd:complexContent>\r
+ <xsd:extension base="DsmlMessage">\r
+ <xsd:sequence>\r
+ <xsd:element name="ref" type="xsd:anyURI" maxOccurs="unbounded"/>\r
+ </xsd:sequence>\r
+ </xsd:extension>\r
+ </xsd:complexContent>\r
+ </xsd:complexType>\r
+ <!-- ************* MODIFY ******************** -->\r
+ <xsd:complexType name="ModifyRequest">\r
+ <xsd:complexContent>\r
+ <xsd:extension base="DsmlMessage">\r
+ <xsd:sequence>\r
+ <xsd:element name="modification" type="DsmlModification" minOccurs="0" maxOccurs="unbounded"/>\r
+ </xsd:sequence>\r
+ <xsd:attribute name="dn" type="DsmlDN" use="required"/>\r
+ </xsd:extension>\r
+ </xsd:complexContent>\r
+ </xsd:complexType>\r
+ <!-- *************** ADD ********************* -->\r
+ <xsd:complexType name="AddRequest">\r
+ <xsd:complexContent>\r
+ <xsd:extension base="DsmlMessage">\r
+ <xsd:sequence>\r
+ <xsd:element name="attr" type="DsmlAttr" minOccurs="0" maxOccurs="unbounded"/>\r
+ </xsd:sequence>\r
+ <xsd:attribute name="dn" type="DsmlDN" use="required"/>\r
+ </xsd:extension>\r
+ </xsd:complexContent>\r
+ </xsd:complexType>\r
+ <!-- *************** DELETE ********************* -->\r
+ <xsd:complexType name="DelRequest">\r
+ <xsd:complexContent>\r
+ <xsd:extension base="DsmlMessage">\r
+ <xsd:attribute name="dn" type="DsmlDN" use="required"/>\r
+ </xsd:extension>\r
+ </xsd:complexContent>\r
+ </xsd:complexType>\r
+ <!-- *************** MODIFY DN ********************* -->\r
+ <xsd:complexType name="ModifyDNRequest">\r
+ <xsd:complexContent>\r
+ <xsd:extension base="DsmlMessage">\r
+ <xsd:attribute name="dn" type="DsmlDN" use="required"/>\r
+ <xsd:attribute name="newrdn" type="DsmlRDN" use="required"/>\r
+ <xsd:attribute name="deleteoldrdn" type="xsd:boolean" use="optional" default="true"/>\r
+ <xsd:attribute name="newSuperior" type="DsmlDN" use="optional"/>\r
+ </xsd:extension>\r
+ </xsd:complexContent>\r
+ </xsd:complexType>\r
+ <!-- ************* COMPARE ******************** -->\r
+ <xsd:complexType name="CompareRequest">\r
+ <xsd:complexContent>\r
+ <xsd:extension base="DsmlMessage">\r
+ <xsd:sequence>\r
+ <xsd:element name="assertion" type="AttributeValueAssertion"/>\r
+ </xsd:sequence>\r
+ <xsd:attribute name="dn" type="DsmlDN" use="required"/>\r
+ </xsd:extension>\r
+ </xsd:complexContent>\r
+ </xsd:complexType>\r
+ <!-- ***** ABANDON ***** -->\r
+ <xsd:complexType name="AbandonRequest">\r
+ <xsd:complexContent>\r
+ <xsd:extension base="DsmlMessage">\r
+ <xsd:attribute name="abandonID" type="RequestID" use="required"/>\r
+ </xsd:extension>\r
+ </xsd:complexContent>\r
+ </xsd:complexType>\r
+ <!-- ************* EXTENDED OPERATION ******************** -->\r
+ <xsd:complexType name="ExtendedRequest">\r
+ <xsd:complexContent>\r
+ <xsd:extension base="DsmlMessage">\r
+ <xsd:sequence>\r
+ <xsd:element name="requestName" type="NumericOID"/>\r
+ <xsd:element name="requestValue" type="xsd:anyType" minOccurs="0"/>\r
+ </xsd:sequence>\r
+ </xsd:extension>\r
+ </xsd:complexContent>\r
+ </xsd:complexType>\r
+ <xsd:complexType name="ExtendedResponse">\r
+ <xsd:complexContent>\r
+ <xsd:extension base="LDAPResult">\r
+ <xsd:sequence>\r
+ <xsd:element name="responseName" type="NumericOID" minOccurs="0"/>\r
+ <xsd:element name="response" type="xsd:anyType" minOccurs="0"/>\r
+ </xsd:sequence>\r
+ </xsd:extension>\r
+ </xsd:complexContent>\r
+ </xsd:complexType>\r
+ <!-- ********************END base SCHEMA ********************* -->\r
+</xsd:schema>\r
--- /dev/null
+<?xml version="1.0"?>\r
+<!-- edited with XML Spy v4.0.1 U (http://www.xmlspy.com) by Chris Lilley (W3C Staff) -->\r
+<schema targetNamespace="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2001/XMLSchema" xmlns:svg="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" elementFormDefault="unqualified" attributeFormDefault="unqualified" xml:lang="en">\r
+ <!-- don't declare the XML namespace; it is predeclared and redeclaring it upsets some software -->\r
+ <import namespace="http://www.w3.org/1999/xlink" schemaLocation="xlink.xsd"/>\r
+ <import namespace="http://www.w3.org/XML/1998/namespace" schemaLocation="xml.xsd"/>\r
+ <!-- simpleTypes -->\r
+ <simpleType name="BaselineShiftValueType">\r
+ <annotation>\r
+ <documentation>The actual definition is\r
+ baseline | sub | super | <percentage> | <length> | inherit \r
+ not sure that union can do this\r
+ </documentation>\r
+ </annotation>\r
+ <restriction base="string"/>\r
+ </simpleType>\r
+ <!-- SVG BooleanType not needed, already defined by XML Schema -->\r
+ <simpleType name="ClassListType">\r
+ <annotation>\r
+ <documentation>Space-separated list of classes</documentation>\r
+ </annotation>\r
+ <list itemType="string"/>\r
+ </simpleType>\r
+ <simpleType name="ClipValueType">\r
+ <annotation>\r
+ <documentation> <shape> | auto | inherit </documentation>\r
+ </annotation>\r
+ <restriction base="string"/>\r
+ </simpleType>\r
+ <simpleType name="ClipPathValueType">\r
+ <annotation>\r
+ <documentation><uri> | none | inherit</documentation>\r
+ </annotation>\r
+ <restriction base="string"/>\r
+ </simpleType>\r
+ <simpleType name="ClipFillRuleType">\r
+ <annotation>\r
+ <documentation>'clip-rule' or fill-rule property/attribute value </documentation>\r
+ </annotation>\r
+ <restriction base="string">\r
+ <enumeration value="evenodd"/>\r
+ <enumeration value="nonzero"/>\r
+ <enumeration value="inherit"/>\r
+ </restriction>\r
+ </simpleType>\r
+ <simpleType name="ContentTypeType">\r
+ <annotation>\r
+ <documentation source="http://www.ietf.org/rfc/rfc2045.txt">media type, as per [RFC2045]</documentation>\r
+ <documentation>media type, as per [RFC2045] </documentation>\r
+ </annotation>\r
+ <restriction base="string"/>\r
+ </simpleType>\r
+ <simpleType name="CoordinateType">\r
+ <annotation>\r
+ <documentation source="http://www.w3.org/TR/SVG/types.html#DataTypeCoordinate">a <co-ordinate></documentation>\r
+ <documentation>a coordinate, which is a number optionally followed immediately by a unit identifier. Perhaps it is possible to represent this as a union by declaring unit idenifiers as a type?</documentation>\r
+ </annotation>\r
+ <restriction base="string">\r
+ <pattern value="((((\+|\-)?((\d+)))|((\+|\-)?(((((\d+)?\.(\d+))|((\d+)\.))([eE](\+|\-)?(\d+))?)|((\d+)([eE](\+|\-)?(\d+))))))(em|ex|px|pt|pc|cm|mm|in|%)?)"/>\r
+ </restriction>\r
+ </simpleType>\r
+ <simpleType name="CoordinatesType">\r
+ <annotation>\r
+ <documentation>a space separated list of CoordinateType. Punt to 'string' for now</documentation>\r
+ </annotation>\r
+ <restriction base="string"/>\r
+ </simpleType>\r
+ <simpleType name="ColorType">\r
+ <annotation>\r
+ <documentation source="http://www.w3.org/TR/SVG/types.html#DataTypeColor">a CSS2 Color </documentation>\r
+ <documentation>Color as defined in CSS2 and XSL 1.0 plus additional recognised color keyword names (the 'X11 colors')</documentation>\r
+ </annotation>\r
+ <restriction base="string"/>\r
+ </simpleType>\r
+ <simpleType name="CursorValueType">\r
+ <annotation>\r
+ <documentation>Value is an optional comma-separated list orf uri references followed by one token from an enumerated list.\r
+</documentation>\r
+ <documentation> [ [<uri> ,]* [ auto | crosshair | default | pointer | move | e-resize | ne-resize | nw-resize | n-resize | se-resize | sw-resize | s-resize | w-resize| text | wait | help ] ] | inherit </documentation>\r
+ </annotation>\r
+ <restriction base="string"/>\r
+ </simpleType>\r
+ <simpleType name="EnableBackgroundValueType">\r
+ <annotation>\r
+ <documentation>accumulate | new [ <x> <y> <width> <height> ] | inherit</documentation>\r
+ </annotation>\r
+ <restriction base="string"/>\r
+ </simpleType>\r
+ <simpleType name="ExtensionListType">\r
+ <annotation>\r
+ <documentation>extension list specification </documentation>\r
+ </annotation>\r
+ <restriction base="string"/>\r
+ </simpleType>\r
+ <simpleType name="FeatureListType">\r
+ <annotation>\r
+ <documentation>feature list specification </documentation>\r
+ </annotation>\r
+ <restriction base="string"/>\r
+ </simpleType>\r
+ <simpleType name="FilterValueType">\r
+ <annotation>\r
+ <documentation><uri> | none | inherit\r
+</documentation>\r
+ </annotation>\r
+ <restriction base="string"/>\r
+ </simpleType>\r
+ <simpleType name="FontFamilyValueType">\r
+ <annotation>\r
+ <documentation>[[ <family-name> | <generic-family> ],]* [<family-name> | <generic-family>] | inherit</documentation>\r
+ <documentation>'font-family' property/attribute value (i.e., list of fonts) </documentation>\r
+ </annotation>\r
+ <restriction base="string"/>\r
+ </simpleType>\r
+ <simpleType name="FontSizeValueType">\r
+ <annotation>\r
+ <documentation>'font-size' property/attribute value </documentation>\r
+ <documentation><absolute-size> | <relative-size> | <length> | <percentage> | inherit</documentation>\r
+ </annotation>\r
+ <restriction base="string"/>\r
+ </simpleType>\r
+ <simpleType name="FontSizeAdjustValueType">\r
+ <annotation>\r
+ <documentation>'font-size-adjust' property/attribute value </documentation>\r
+ <documentation><number> | none | inherit </documentation>\r
+ </annotation>\r
+ <restriction base="string"/>\r
+ </simpleType>\r
+ <simpleType name="GlyphOrientationHorizontalValueType">\r
+ <annotation>\r
+ <documentation>'glyph-orientation-horizontal' property/attribute value (e.g., <angle>)</documentation>\r
+ <documentation><angle> | inherit</documentation>\r
+ </annotation>\r
+ <restriction base="string"/>\r
+ </simpleType>\r
+ <simpleType name="GlyphOrientationVerticalValueType">\r
+ <annotation>\r
+ <documentation>'glyph-orientation-vertical' property/attribute value (e.g., 'auto', <angle>)</documentation>\r
+ <documentation>auto | <angle> | inherit</documentation>\r
+ </annotation>\r
+ <restriction base="string"/>\r
+ </simpleType>\r
+ <!-- no need to declare IntegerType as XML Schema defines integers -->\r
+ <simpleType name="KerningValue">\r
+ <annotation>\r
+ <documentation>'kerning' property/attribute value (e.g., auto | <length>)</documentation>\r
+ <documentation>auto | <length> | inherit </documentation>\r
+ </annotation>\r
+ <restriction base="string"/>\r
+ </simpleType>\r
+ <simpleType name="LanguageCodeType">\r
+ <annotation>\r
+ <documentation>a language code, as per [RFC3066]</documentation>\r
+ <documentation source="http://www.ietf.org/rfc/rfc3066.txt"/>\r
+ </annotation>\r
+ <restriction base="string"/>\r
+ </simpleType>\r
+ <simpleType name="LanguageCodesType">\r
+ <annotation>\r
+ <documentation>a comma-separated list of language codes, as per [RFC3066]</documentation>\r
+ <documentation source="http://www.ietf.org/rfc/rfc3066.txt"/>\r
+ </annotation>\r
+ <restriction base="string"/>\r
+ </simpleType>\r
+ <simpleType name="LengthType">\r
+ <annotation>\r
+ <documentation>a <length></documentation>\r
+ </annotation>\r
+ <restriction base="string">\r
+ <pattern value="((((\+|\-)?((\d+)))|((\+|\-)?(((((\d+)?\.(\d+))|((\d+)\.))([eE](\+|\-)?(\d+))?)|((\d+)([eE](\+|\-)?(\d+))))))(em|ex|px|pt|pc|cm|mm|in|%)?)"/>\r
+ </restriction>\r
+ </simpleType>\r
+ <simpleType name="LengthsType">\r
+ <annotation>\r
+ <documentation>a list of <length>s</documentation>\r
+ </annotation>\r
+ <restriction base="string"/>\r
+ <!-- make a regexp for this one -->\r
+ </simpleType>\r
+ <simpleType name="LinkTargetType">\r
+ <annotation>\r
+ <documentation>link to this target</documentation>\r
+ </annotation>\r
+ <restriction base="string"/>\r
+ </simpleType>\r
+ <simpleType name="MarkerValueType">\r
+ <annotation>\r
+ <documentation>'marker' property/attribute value (e.g., 'none', %URI;)</documentation>\r
+ </annotation>\r
+ <restriction base="string"/>\r
+ <!-- need to check this one, its a shorthand value -->\r
+ </simpleType>\r
+ <simpleType name="MaskValueType">\r
+ <annotation>\r
+ <documentation>'mask' property/attribute value (e.g., 'none', %URI;)</documentation>\r
+ <documentation><uri> | none | inherit</documentation>\r
+ </annotation>\r
+ <restriction base="string"/>\r
+ </simpleType>\r
+ <simpleType name="MediaDescType">\r
+ <annotation>\r
+ <documentation>comma-separated list of media descriptors.</documentation>\r
+ </annotation>\r
+ <restriction base="string"/>\r
+ </simpleType>\r
+ <!-- no need to define NumberType as XML Schema has double -->\r
+ <simpleType name="NumberOptionalNumberType">\r
+ <annotation>\r
+ <documentation>list of <number>s, but at least one and at most two</documentation>\r
+ </annotation>\r
+ <restriction base="string"/>\r
+ </simpleType>\r
+ <simpleType name="NumberOrPercentageType">\r
+ <annotation>\r
+ <documentation>a <number> or a <percentage> </documentation>\r
+ </annotation>\r
+ <restriction base="string"/>\r
+ </simpleType>\r
+ <simpleType name="NumbersType">\r
+ <annotation>\r
+ <documentation>list of <number>s</documentation>\r
+ </annotation>\r
+ <restriction base="string"/>\r
+ </simpleType>\r
+ <simpleType name="OpacityValueType">\r
+ <annotation>\r
+ <documentation>opacity value (e.g., <number>) </documentation>\r
+ <documentation><alphavalue> | inherit</documentation>\r
+ </annotation>\r
+ <restriction base="string"/>\r
+ </simpleType>\r
+ <simpleType name="PaintType">\r
+ <annotation>\r
+ <documentation>a 'fill' or 'stroke' property/attribute value</documentation>\r
+ </annotation>\r
+ <restriction base="string"/>\r
+ </simpleType>\r
+ <simpleType name="PathDataType">\r
+ <annotation>\r
+ <documentation>a path data specification</documentation>\r
+ <documentation source="http://www.w3.org/TR/SVG/paths.html"/>\r
+ <documentation>Yes, of course this was generated by a program!</documentation>\r
+ </annotation>\r
+ <restriction base="string"/>\r
+ </simpleType>\r
+ <simpleType name="PointsType">\r
+ <annotation>\r
+ <documentation>a list of points</documentation>\r
+ </annotation>\r
+ <restriction base="string"/>\r
+ </simpleType>\r
+ <simpleType name="PreserveAspectRatioSpecType">\r
+ <annotation>\r
+ <documentation>'preserveAspectRatio' attribute specification</documentation>\r
+ </annotation>\r
+ <restriction base="string">\r
+ <!-- HTMLBook Note: Revised pattern to be more accurate and match default values -->\r
+ <pattern value="\s*x(Min|Mid|Max)Y(Min|Mid|Max)(\s+(meet|slice)\s*)?"/>\r
+ </restriction>\r
+ </simpleType>\r
+ <simpleType name="ScriptType">\r
+ <annotation>\r
+ <documentation>script expression</documentation>\r
+ </annotation>\r
+ <restriction base="string"/>\r
+ </simpleType>\r
+ <simpleType name="SpacingValueType">\r
+ <annotation>\r
+ <documentation>'letter-spacing' or 'word-spacing' property/attribute value (e.g., normal | <length>)</documentation>\r
+ </annotation>\r
+ <restriction base="string"/>\r
+ </simpleType>\r
+ <simpleType name="StrokeDashArrayValueType">\r
+ <annotation>\r
+ <documentation>'stroke-dasharray' property/attribute value (e.g., 'none', list of <number>s)</documentation>\r
+ </annotation>\r
+ <restriction base="string"/>\r
+ </simpleType>\r
+ <simpleType name="StrokeDashOffsetValueType">\r
+ <annotation>\r
+ <documentation>'stroke-dashoffset' property/attribute value (e.g., 'none', >length>)</documentation>\r
+ </annotation>\r
+ <restriction base="string"/>\r
+ </simpleType>\r
+ <simpleType name="StrokeMiterLimitValueType">\r
+ <annotation>\r
+ <documentation>'stroke-miterlimit' property/attribute value (e.g., <number>)</documentation>\r
+ </annotation>\r
+ <restriction base="string"/>\r
+ </simpleType>\r
+ <simpleType name="StrokeWidthValueType">\r
+ <annotation>\r
+ <documentation>'stroke-width' property/attribute value (e.g., <length>)</documentation>\r
+ </annotation>\r
+ <restriction base="string"/>\r
+ </simpleType>\r
+ <!-- <simpleType name="StructuredTextType" base="string"/> expanded -->\r
+ <simpleType name="StyleSheetType">\r
+ <annotation>\r
+ <documentation>style sheet data</documentation>\r
+ </annotation>\r
+ <restriction base="string"/>\r
+ </simpleType>\r
+ <simpleType name="SVGColorType">\r
+ <annotation>\r
+ <documentation>An SVG color value (sRGB plus optional ICC)</documentation>\r
+ </annotation>\r
+ <restriction base="string"/>\r
+ </simpleType>\r
+ <!-- <simpleType name="TextType" base="string"/> not necessary (string) -->\r
+ <simpleType name="TextDecorationValueType">\r
+ <annotation>\r
+ <documentation>'text-decoration' property/attribute value (e.g., 'none', 'underline')</documentation>\r
+ </annotation>\r
+ <restriction base="string"/>\r
+ </simpleType>\r
+ <simpleType name="TransformListType">\r
+ <annotation>\r
+ <documentation>Yes, of course this was generated by a program!</documentation>\r
+ <documentation>list of transforms</documentation>\r
+ </annotation>\r
+ <restriction base="string"/>\r
+ </simpleType>\r
+ <!-- <simpleType name="URIType" base="string"/> not necessary (use AnyURI) -->\r
+ <simpleType name="ViewBoxSpecType">\r
+ <annotation>\r
+ <documentation>'viewBox' attribute specification</documentation>\r
+ </annotation>\r
+ <restriction base="string"/>\r
+ </simpleType>\r
+ <attributeGroup name="stdAttrs">\r
+ <annotation>\r
+ <documentation>All elements have an ID</documentation>\r
+ </annotation>\r
+ <attribute name="id" type="ID" use="optional"/>\r
+ <!-- HTMLBook Note: dropped invalid type attr on below element -->\r
+ <attribute ref="xml:base" use="optional"/>\r
+ </attributeGroup>\r
+ <attributeGroup name="langSpaceAttrs">\r
+ <annotation>\r
+ <documentation>Common attributes for elements that might contain character data content</documentation>\r
+ </annotation>\r
+ <attribute ref="xml:lang" use="optional"/>\r
+ <attribute ref="xml:space" use="optional"/>\r
+ </attributeGroup>\r
+ <attributeGroup name="testAttrs">\r
+ <annotation>\r
+ <documentation>Common attributes to check for system capabilities</documentation>\r
+ </annotation>\r
+ <attribute name="requiredFeatures" type="svg:FeatureListType" use="optional"/>\r
+ <attribute name="requiredExtensions" type="svg:ExtensionListType" use="optional"/>\r
+ <attribute name="systemLanguage" type="svg:LanguageCodesType" use="optional"/>\r
+ </attributeGroup>\r
+ <attributeGroup name="xlinkRefAttrs">\r
+ <annotation>\r
+ <documentation>For most uses of URI referencing: standard XLink attributes other than xlink:href</documentation>\r
+ </annotation>\r
+ <attribute ref="xlink:type" fixed="simple"/>\r
+ <attribute ref="xlink:role"/>\r
+ <attribute ref="xlink:arcrole"/>\r
+ <attribute ref="xlink:title"/>\r
+ <attribute ref="xlink:show" default="other"/>\r
+ <attribute ref="xlink:actuate"/>\r
+ </attributeGroup>\r
+ <attributeGroup name="xlinkRefAttrsEmbed">\r
+ <annotation>\r
+ <documentation>Standard XLink attributes for uses of URI referencing where xlink:show is 'embed'</documentation>\r
+ </annotation>\r
+ <attribute ref="xlink:type" fixed="simple"/>\r
+ <attribute ref="xlink:role"/>\r
+ <attribute ref="xlink:arcrole"/>\r
+ <attribute ref="xlink:title"/>\r
+ <attribute ref="xlink:show"/>\r
+ <attribute ref="xlink:actuate"/>\r
+ </attributeGroup>\r
+ <attributeGroup name="graphicsElementEvents">\r
+ <attribute name="onfocusin" type="svg:ScriptType" use="optional"/>\r
+ <attribute name="onfocusout" type="svg:ScriptType" use="optional"/>\r
+ <attribute name="onactivate" type="svg:ScriptType" use="optional"/>\r
+ <attribute name="onclick" type="svg:ScriptType" use="optional"/>\r
+ <attribute name="onmousedown" type="svg:ScriptType" use="optional"/>\r
+ <attribute name="onmouseup" type="svg:ScriptType" use="optional"/>\r
+ <attribute name="onmouseover" type="svg:ScriptType" use="optional"/>\r
+ <attribute name="onmousemove" type="svg:ScriptType" use="optional"/>\r
+ <attribute name="onmouseout" type="svg:ScriptType" use="optional"/>\r
+ <attribute name="onload" type="svg:ScriptType" use="optional"/>\r
+ </attributeGroup>\r
+ <attributeGroup name="documentEvents">\r
+ <attribute name="onunload" type="svg:ScriptType" use="optional"/>\r
+ <attribute name="onabort" type="svg:ScriptType" use="optional"/>\r
+ <attribute name="onerror" type="svg:ScriptType" use="optional"/>\r
+ <attribute name="onresize" type="svg:ScriptType" use="optional"/>\r
+ <attribute name="onscroll" type="svg:ScriptType" use="optional"/>\r
+ <attribute name="onzoom" type="svg:ScriptType" use="optional"/>\r
+ </attributeGroup>\r
+ <attributeGroup name="animationEvents">\r
+ <attribute name="onbegin" type="svg:ScriptType" use="optional"/>\r
+ <attribute name="onend" type="svg:ScriptType" use="optional"/>\r
+ <attribute name="onrepeat" type="svg:ScriptType" use="optional"/>\r
+ </attributeGroup>\r
+ <attributeGroup name="PresentationAttributes-Color">\r
+ <annotation>\r
+ <documentation>The following presentation attributes have to do with specifying color.</documentation>\r
+ </annotation>\r
+ <attribute name="color" type="svg:ColorType" use="optional"/>\r
+ <attribute name="color-interpolation" use="optional">\r
+ <simpleType>\r
+ <restriction base="string">\r
+ <enumeration value="auto"/>\r
+ <enumeration value="sRGB"/>\r
+ <enumeration value="linearRGB"/>\r
+ <enumeration value="inherit"/>\r
+ </restriction>\r
+ </simpleType>\r
+ </attribute>\r
+ <attribute name="color-rendering" use="optional">\r
+ <simpleType>\r
+ <restriction base="string">\r
+ <enumeration value="auto"/>\r
+ <enumeration value="optimizeSpeed"/>\r
+ <enumeration value="optimizeQuality"/>\r
+ <enumeration value="inherit"/>\r
+ </restriction>\r
+ </simpleType>\r
+ </attribute>\r
+ </attributeGroup>\r
+ <attributeGroup name="PresentationAttributes-Containers">\r
+ <annotation>\r
+ <documentation>The following presentation attributes apply to container elements</documentation>\r
+ </annotation>\r
+ <attribute name="enable-background" type="svg:EnableBackgroundValueType" use="optional"/>\r
+ </attributeGroup>\r
+ <attributeGroup name="PresentationAttributes-feFlood">\r
+ <annotation>\r
+ <documentation>The following presentation attributes apply to 'feFlood' elements</documentation>\r
+ </annotation>\r
+ <attribute name="flood-color" type="svg:SVGColorType" use="optional"/>\r
+ <attribute name="flood-opacity" type="svg:OpacityValueType" use="optional"/>\r
+ </attributeGroup>\r
+ <attributeGroup name="PresentationAttributes-FilterPrimitives">\r
+ <annotation>\r
+ <documentation>The following presentation attributes apply to filter primitives</documentation>\r
+ </annotation>\r
+ <attribute name="color-interpolation-filters" use="optional">\r
+ <simpleType>\r
+ <restriction base="string">\r
+ <enumeration value="auto"/>\r
+ <enumeration value="sRGB"/>\r
+ <enumeration value="linearRGB"/>\r
+ <enumeration value="inherit"/>\r
+ </restriction>\r
+ </simpleType>\r
+ </attribute>\r
+ </attributeGroup>\r
+ <attributeGroup name="PresentationAttributes-FillStroke">\r
+ <annotation>\r
+ <documentation>The following presentation attributes apply to filling and stroking operations</documentation>\r
+ </annotation>\r
+ <attribute name="fill" type="svg:PaintType" use="optional"/>\r
+ <attribute name="fill-opacity" type="svg:OpacityValueType" use="optional"/>\r
+ <attribute name="fill-rule" type="svg:ClipFillRuleType" use="optional"/>\r
+ <attribute name="stroke" type="svg:PaintType" use="optional"/>\r
+ <attribute name="stroke-dasharray" type="svg:StrokeDashArrayValueType" use="optional"/>\r
+ <attribute name="stroke-dashoffset" type="svg:StrokeDashOffsetValueType" use="optional"/>\r
+ <attribute name="stroke-linecap" use="optional">\r
+ <simpleType>\r
+ <restriction base="string">\r
+ <enumeration value="butt"/>\r
+ <enumeration value="round"/>\r
+ <enumeration value="square"/>\r
+ <enumeration value="inherit"/>\r
+ </restriction>\r
+ </simpleType>\r
+ </attribute>\r
+ <attribute name="stroke-linejoin" use="optional">\r
+ <simpleType>\r
+ <restriction base="string">\r
+ <enumeration value="miter"/>\r
+ <enumeration value="round"/>\r
+ <enumeration value="bevel"/>\r
+ <enumeration value="inherit"/>\r
+ </restriction>\r
+ </simpleType>\r
+ </attribute>\r
+ <attribute name="stroke-miterlimit" type="svg:StrokeMiterLimitValueType" use="optional"/>\r
+ <attribute name="stroke-opacity" type="svg:OpacityValueType" use="optional"/>\r
+ <attribute name="stroke-width" type="svg:StrokeWidthValueType" use="optional"/>\r
+ </attributeGroup>\r
+ <attributeGroup name="PresentationAttributes-FontSpecification">\r
+ <annotation>\r
+ <documentation>The following presentation attributes have to do with selecting a font to use</documentation>\r
+ </annotation>\r
+ <attribute name="font-family" type="svg:FontFamilyValueType" use="optional"/>\r
+ <attribute name="font-size" type="svg:FontSizeValueType" use="optional"/>\r
+ <attribute name="font-size-adjust" type="svg:FontSizeAdjustValueType" use="optional"/>\r
+ <attribute name="font-stretch" use="optional">\r
+ <simpleType>\r
+ <restriction base="string">\r
+ <enumeration value="normal"/>\r
+ <enumeration value="wider"/>\r
+ <enumeration value="narrower"/>\r
+ <enumeration value="ultra-condensed"/>\r
+ <enumeration value="extra-condensed"/>\r
+ <enumeration value="condensed"/>\r
+ <enumeration value="semi-condensed"/>\r
+ <enumeration value="semi-expanded"/>\r
+ <enumeration value="expanded"/>\r
+ <enumeration value="extra-expanded"/>\r
+ <enumeration value="ultra-expanded"/>\r
+ <enumeration value="inherit"/>\r
+ </restriction>\r
+ </simpleType>\r
+ </attribute>\r
+ <attribute name="font-style" use="optional">\r
+ <simpleType>\r
+ <restriction base="string">\r
+ <enumeration value="normal"/>\r
+ <enumeration value="italic"/>\r
+ <enumeration value="oblique"/>\r
+ <enumeration value="inherit"/>\r
+ </restriction>\r
+ </simpleType>\r
+ </attribute>\r
+ <attribute name="font-variant" use="optional">\r
+ <simpleType>\r
+ <restriction base="string">\r
+ <enumeration value="normal"/>\r
+ <enumeration value="small-caps"/>\r
+ <enumeration value="inherit"/>\r
+ </restriction>\r
+ </simpleType>\r
+ </attribute>\r
+ <attribute name="font-weight" use="optional">\r
+ <simpleType>\r
+ <restriction base="string">\r
+ <enumeration value="normal"/>\r
+ <enumeration value="bold"/>\r
+ <enumeration value="bolder"/>\r
+ <enumeration value="lighter"/>\r
+ <enumeration value="100"/>\r
+ <enumeration value="200"/>\r
+ <enumeration value="300"/>\r
+ <enumeration value="400"/>\r
+ <enumeration value="500"/>\r
+ <enumeration value="600"/>\r
+ <enumeration value="700"/>\r
+ <enumeration value="800"/>\r
+ <enumeration value="900"/>\r
+ <enumeration value="inherit"/>\r
+ </restriction>\r
+ </simpleType>\r
+ </attribute>\r
+ </attributeGroup>\r
+ <attributeGroup name="PresentationAttributes-Gradients">\r
+ <annotation>\r
+ <documentation>The following presentation attributes apply to gradient 'stop' elements</documentation>\r
+ </annotation>\r
+ <attribute name="stop-color" type="svg:SVGColorType" use="optional"/>\r
+ <attribute name="stop-opacity" type="svg:OpacityValueType" use="optional"/>\r
+ </attributeGroup>\r
+ <attributeGroup name="PresentationAttributes-Graphics">\r
+ <annotation>\r
+ <documentation>The following presentation attributes apply to graphics elements</documentation>\r
+ </annotation>\r
+ <attribute name="clip-path" type="svg:ClipPathValueType" use="optional"/>\r
+ <attribute name="clip-rule" type="svg:ClipFillRuleType" use="optional"/>\r
+ <attribute name="cursor" type="svg:CursorValueType" use="optional"/>\r
+ <attribute name="display" use="optional">\r
+ <simpleType>\r
+ <restriction base="string">\r
+ <enumeration value="inline"/>\r
+ <enumeration value="block"/>\r
+ <enumeration value="list-item"/>\r
+ <enumeration value="run-in"/>\r
+ <enumeration value="compact"/>\r
+ <enumeration value="marker"/>\r
+ <enumeration value="table"/>\r
+ <enumeration value="inline-table"/>\r
+ <enumeration value="table-row-group"/>\r
+ <enumeration value="table-header-group"/>\r
+ <enumeration value="table-footer-group"/>\r
+ <enumeration value="table-row"/>\r
+ <enumeration value="table-column-group"/>\r
+ <enumeration value="table-column"/>\r
+ <enumeration value="table-cell"/>\r
+ <enumeration value="table-caption"/>\r
+ <enumeration value="none"/>\r
+ <enumeration value="inherit"/>\r
+ </restriction>\r
+ </simpleType>\r
+ </attribute>\r
+ <attribute name="filter" type="svg:FilterValueType" use="optional"/>\r
+ <attribute name="image-rendering" use="optional">\r
+ <simpleType>\r
+ <restriction base="string">\r
+ <enumeration value="auto"/>\r
+ <enumeration value="optimizeSpeed"/>\r
+ <enumeration value="optimizeQuality"/>\r
+ <enumeration value="inherit"/>\r
+ </restriction>\r
+ </simpleType>\r
+ </attribute>\r
+ <attribute name="mask" type="svg:MaskValueType" use="optional"/>\r
+ <attribute name="opacity" type="svg:OpacityValueType" use="optional"/>\r
+ <attribute name="pointer-events" use="optional">\r
+ <simpleType>\r
+ <restriction base="string">\r
+ <enumeration value="visiblePainted"/>\r
+ <enumeration value="visibleFill"/>\r
+ <enumeration value="visibleStroke"/>\r
+ <enumeration value="visibleFillStroke"/>\r
+ <enumeration value="visible"/>\r
+ <enumeration value="painted"/>\r
+ <enumeration value="fill"/>\r
+ <enumeration value="stroke"/>\r
+ <enumeration value="fillstroke"/>\r
+ <enumeration value="all"/>\r
+ <enumeration value="none"/>\r
+ <enumeration value="inherit"/>\r
+ </restriction>\r
+ </simpleType>\r
+ </attribute>\r
+ <attribute name="shape-rendering" use="optional">\r
+ <simpleType>\r
+ <restriction base="string">\r
+ <enumeration value="auto"/>\r
+ <enumeration value="optimizeSpeed"/>\r
+ <enumeration value="crispEdges"/>\r
+ <enumeration value="geometricPrecision"/>\r
+ <enumeration value="inherit"/>\r
+ </restriction>\r
+ </simpleType>\r
+ </attribute>\r
+ <attribute name="text-rendering" use="optional">\r
+ <simpleType>\r
+ <restriction base="string">\r
+ <enumeration value="auto"/>\r
+ <enumeration value="optimizeSpeed"/>\r
+ <enumeration value="optimizeLegibility"/>\r
+ <enumeration value="geometricPrecision"/>\r
+ <enumeration value="inherit"/>\r
+ </restriction>\r
+ </simpleType>\r
+ </attribute>\r
+ <attribute name="visibility" use="optional">\r
+ <simpleType>\r
+ <restriction base="string">\r
+ <enumeration value="visible"/>\r
+ <enumeration value="hidden"/>\r
+ <enumeration value="inherit"/>\r
+ </restriction>\r
+ </simpleType>\r
+ </attribute>\r
+ </attributeGroup>\r
+ <attributeGroup name="PresentationAttributes-Images">\r
+ <annotation>\r
+ <documentation>The following presentation attributes apply to 'image' elements</documentation>\r
+ </annotation>\r
+ <attribute name="color-profile" use="optional"/>\r
+ </attributeGroup>\r
+ <attributeGroup name="PresentationAttributes-LightingEffects">\r
+ <annotation>\r
+ <documentation>The following presentation attributes apply to 'feDiffuseLighting' and 'feSpecularLighting' elements</documentation>\r
+ </annotation>\r
+ <attribute name="lighting-color" type="svg:SVGColorType" use="optional"/>\r
+ </attributeGroup>\r
+ <attributeGroup name="PresentationAttributes-Markers">\r
+ <annotation>\r
+ <documentation>The following presentation attributes apply to marker operations</documentation>\r
+ </annotation>\r
+ <attribute name="marker-start" type="svg:MarkerValueType" use="optional"/>\r
+ <attribute name="marker-mid" type="svg:MarkerValueType" use="optional"/>\r
+ <attribute name="marker-end" type="svg:MarkerValueType" use="optional"/>\r
+ </attributeGroup>\r
+ <attributeGroup name="PresentationAttributes-TextContentElements">\r
+ <annotation>\r
+ <documentation>The following presentation attributes apply to text content elements</documentation>\r
+ </annotation>\r
+ <attribute name="alignment-baseline" use="optional">\r
+ <simpleType>\r
+ <restriction base="string">\r
+ <enumeration value="baseline"/>\r
+ <enumeration value="top"/>\r
+ <enumeration value="before-edge"/>\r
+ <enumeration value="text-top"/>\r
+ <enumeration value="text-before-edge"/>\r
+ <enumeration value="middle"/>\r
+ <enumeration value="bottom"/>\r
+ <enumeration value="after-edge"/>\r
+ <enumeration value="text-bottom"/>\r
+ <enumeration value="text-after-edge"/>\r
+ <enumeration value="ideographic"/>\r
+ <enumeration value="lower"/>\r
+ <enumeration value="hanging"/>\r
+ <enumeration value="mathematical"/>\r
+ <enumeration value="inherit"/>\r
+ </restriction>\r
+ </simpleType>\r
+ </attribute>\r
+ <attribute name="baseline-shift" type="svg:BaselineShiftValueType" use="optional"/>\r
+ <attribute name="direction" use="optional">\r
+ <simpleType>\r
+ <restriction base="string">\r
+ <enumeration value="ltr"/>\r
+ <enumeration value="rtl"/>\r
+ <enumeration value="inherit"/>\r
+ </restriction>\r
+ </simpleType>\r
+ </attribute>\r
+ <attribute name="dominant-baseline" use="optional">\r
+ <simpleType>\r
+ <restriction base="string">\r
+ <enumeration value="auto"/>\r
+ <enumeration value="autosense-script"/>\r
+ <enumeration value="no-change"/>\r
+ <enumeration value="reset"/>\r
+ <enumeration value="ideographic"/>\r
+ <enumeration value="lower"/>\r
+ <enumeration value="hanging"/>\r
+ <enumeration value="mathematical"/>\r
+ <enumeration value="inherit"/>\r
+ </restriction>\r
+ </simpleType>\r
+ </attribute>\r
+ <attribute name="glyph-orientation-horizontal" type="svg:GlyphOrientationHorizontalValueType" use="optional"/>\r
+ <attribute name="glyph-orientation-vertical" type="svg:GlyphOrientationVerticalValueType" use="optional"/>\r
+ <attribute name="letter-spacing" type="svg:SpacingValueType" use="optional"/>\r
+ <attribute name="text-anchor" use="optional">\r
+ <simpleType>\r
+ <restriction base="string">\r
+ <enumeration value="start"/>\r
+ <enumeration value="middle"/>\r
+ <enumeration value="end"/>\r
+ <enumeration value="inherit"/>\r
+ </restriction>\r
+ </simpleType>\r
+ </attribute>\r
+ <attribute name="text-decoration" type="svg:TextDecorationValueType" use="optional"/>\r
+ <attribute name="unicode-bidi" use="optional">\r
+ <simpleType>\r
+ <restriction base="string">\r
+ <enumeration value="normal"/>\r
+ <enumeration value="embed"/>\r
+ <enumeration value="bidi-override"/>\r
+ <enumeration value="inherit"/>\r
+ </restriction>\r
+ </simpleType>\r
+ </attribute>\r
+ <attribute name="word-spacing" type="svg:SpacingValueType" use="optional"/>\r
+ </attributeGroup>\r
+ <attributeGroup name="PresentationAttributes-TextElements">\r
+ <annotation>\r
+ <documentation>The following presentation attributes apply to 'text' elements</documentation>\r
+ </annotation>\r
+ <attribute name="writing-mode" use="optional">\r
+ <simpleType>\r
+ <restriction base="string">\r
+ <enumeration value="lr-tb"/>\r
+ <enumeration value="rl-tb"/>\r
+ <enumeration value="tb-rl"/>\r
+ <enumeration value="lr"/>\r
+ <enumeration value="rl"/>\r
+ <enumeration value="tb"/>\r
+ <enumeration value="inherit"/>\r
+ </restriction>\r
+ </simpleType>\r
+ </attribute>\r
+ </attributeGroup>\r
+ <attributeGroup name="PresentationAttributes-Viewports">\r
+ <annotation>\r
+ <documentation>The following presentation attributes apply to elements that establish viewports</documentation>\r
+ </annotation>\r
+ <attribute name="clip" type="svg:ClipValueType" use="optional"/>\r
+ <attribute name="overflow" use="optional">\r
+ <simpleType>\r
+ <restriction base="string">\r
+ <enumeration value="visible"/>\r
+ <enumeration value="hidden"/>\r
+ <enumeration value="scroll"/>\r
+ <enumeration value="auto"/>\r
+ <enumeration value="inherit"/>\r
+ </restriction>\r
+ </simpleType>\r
+ </attribute>\r
+ </attributeGroup>\r
+ <attributeGroup name="PresentationAttributes-All">\r
+ <annotation>\r
+ <documentation>The following represents the complete list of presentation attributes</documentation>\r
+ </annotation>\r
+ <attributeGroup ref="svg:PresentationAttributes-Color"/>\r
+ <attributeGroup ref="svg:PresentationAttributes-Containers"/>\r
+ <attributeGroup ref="svg:PresentationAttributes-feFlood"/>\r
+ <attributeGroup ref="svg:PresentationAttributes-FillStroke"/>\r
+ <attributeGroup ref="svg:PresentationAttributes-FilterPrimitives"/>\r
+ <attributeGroup ref="svg:PresentationAttributes-FontSpecification"/>\r
+ <attributeGroup ref="svg:PresentationAttributes-Gradients"/>\r
+ <attributeGroup ref="svg:PresentationAttributes-Graphics"/>\r
+ <attributeGroup ref="svg:PresentationAttributes-Images"/>\r
+ <attributeGroup ref="svg:PresentationAttributes-LightingEffects"/>\r
+ <attributeGroup ref="svg:PresentationAttributes-Markers"/>\r
+ <attributeGroup ref="svg:PresentationAttributes-TextContentElements"/>\r
+ <attributeGroup ref="svg:PresentationAttributes-TextElements"/>\r
+ <attributeGroup ref="svg:PresentationAttributes-Viewports"/>\r
+ </attributeGroup>\r
+ <attributeGroup name="filter_primitive_attributes">\r
+ <attribute name="x" type="svg:CoordinateType" use="optional"/>\r
+ <attribute name="y" type="svg:CoordinateType" use="optional"/>\r
+ <attribute name="width" type="svg:LengthType" use="optional"/>\r
+ <attribute name="height" type="svg:LengthType" use="optional"/>\r
+ <attribute name="result" type="string" use="optional"/>\r
+ </attributeGroup>\r
+ <attributeGroup name="filter_primitive_attributes_with_in">\r
+ <attributeGroup ref="svg:filter_primitive_attributes"/>\r
+ <attribute name="in" type="string" use="optional"/>\r
+ </attributeGroup>\r
+ <attributeGroup name="component_transfer_function_attributes">\r
+ <attribute name="type" use="required">\r
+ <simpleType>\r
+ <restriction base="string">\r
+ <enumeration value="identity"/>\r
+ <enumeration value="table"/>\r
+ <enumeration value="discrete"/>\r
+ <enumeration value="linear"/>\r
+ <enumeration value="gamma"/>\r
+ </restriction>\r
+ </simpleType>\r
+ </attribute>\r
+ <attribute name="tableValues" type="string" use="optional"/>\r
+ <attribute name="slope" type="double" use="optional"/>\r
+ <attribute name="intercept" type="double" use="optional"/>\r
+ <attribute name="amplitude" type="double" use="optional"/>\r
+ <attribute name="exponent" type="double" use="optional"/>\r
+ <attribute name="offset" type="double" use="optional"/>\r
+ <!-- here -->\r
+ </attributeGroup>\r
+ <attributeGroup name="animElementAttrs">\r
+ <attributeGroup ref="svg:xlinkRefAttrs"/>\r
+ <!-- HTMLBook Note: Dropped invalid type attr on below element -->\r
+ <attribute ref="xlink:href" use="optional"/>\r
+ </attributeGroup>\r
+ <attributeGroup name="animAttributeAttrs">\r
+ <attribute name="attributeName" type="string" use="required"/>\r
+ <attribute name="attributeType" type="string" use="optional"/>\r
+ </attributeGroup>\r
+ <attributeGroup name="animTargetAttrs">\r
+ <attributeGroup ref="svg:animElementAttrs"/>\r
+ <attributeGroup ref="svg:animAttributeAttrs"/>\r
+ </attributeGroup>\r
+ <attributeGroup name="animTimingAttrs">\r
+ <attribute name="begin" type="string" use="optional"/>\r
+ <attribute name="dur" type="string" use="optional"/>\r
+ <attribute name="end" type="string" use="optional"/>\r
+ <attribute name="min" type="string" use="optional"/>\r
+ <attribute name="max" type="string" use="optional"/>\r
+ <attribute name="restart" default="always">\r
+ <simpleType>\r
+ <restriction base="string">\r
+ <enumeration value="always"/>\r
+ <enumeration value="never"/>\r
+ <enumeration value="whenNotActive"/>\r
+ </restriction>\r
+ </simpleType>\r
+ </attribute>\r
+ <attribute name="repeatCount" type="string" use="optional"/>\r
+ <attribute name="repeatDur" type="string" use="optional"/>\r
+ <attribute name="fill" default="remove">\r
+ <simpleType>\r
+ <restriction base="string">\r
+ <enumeration value="remove"/>\r
+ <enumeration value="freeze"/>\r
+ </restriction>\r
+ </simpleType>\r
+ </attribute>\r
+ </attributeGroup>\r
+ <attributeGroup name="animValueAttrs">\r
+ <attribute name="calcMode" default="linear">\r
+ <simpleType>\r
+ <restriction base="string">\r
+ <enumeration value="discrete"/>\r
+ <enumeration value="linear"/>\r
+ <enumeration value="paced"/>\r
+ <enumeration value="spline"/>\r
+ </restriction>\r
+ </simpleType>\r
+ </attribute>\r
+ <attribute name="values" type="string" use="optional"/>\r
+ <attribute name="keyTimes" type="string" use="optional"/>\r
+ <attribute name="keySplines" type="string" use="optional"/>\r
+ <attribute name="from" type="string" use="optional"/>\r
+ <attribute name="to" type="string" use="optional"/>\r
+ <attribute name="by" type="string" use="optional"/>\r
+ <!-- could add a pattern facet here -->\r
+ </attributeGroup>\r
+ <attributeGroup name="animAdditionAttrs">\r
+ <attribute name="additive" default="replace">\r
+ <simpleType>\r
+ <restriction base="string">\r
+ <enumeration value="replace"/>\r
+ <enumeration value="sum"/>\r
+ </restriction>\r
+ </simpleType>\r
+ </attribute>\r
+ <attribute name="accumulate" default="none">\r
+ <simpleType>\r
+ <restriction base="string">\r
+ <enumeration value="none"/>\r
+ <enumeration value="sum"/>\r
+ </restriction>\r
+ </simpleType>\r
+ </attribute>\r
+ </attributeGroup>\r
+ <group name="descTitleMetadata">\r
+ <annotation>\r
+ <!-- HTMLBook Note: switched here from invalid xs:all based content model (which can't be used for particles as was being done)\r
+ to xs:choice-based content model, which allows for one or more desc/title/metadata elements at the beginning of all content models in which \r
+ descTitleMetadata is referenced -->\r
+ <documentation>A bit simpler than the DTD, but see commented-out alternative</documentation>\r
+ </annotation>\r
+ <choice>\r
+ <element name="desc" type="svg:descType"/>\r
+ <element name="title" type="svg:titleType"/>\r
+ <element name="metadata" type="svg:metadataType"/>\r
+ </choice>\r
+ </group>\r
+ <!--\r
+ <group name="descTitleMetadata">\r
+ <annotation>\r
+ <documentation>Captures the ordering restrictions of the DTD, but suffers from over complexity. No easy way to express this without a wrapper element.</documentation>\r
+ </annotation>\r
+ <choice minOccurs="0" maxOccurs="1">\r
+ <sequence>\r
+ <element name="desc" type="svg:descType"/>\r
+ <choice minOccurs="0" maxOccurs="1">\r
+ <sequence>\r
+ <element name="title" type="svg:titleType"/>\r
+ <element name="metadata" type="svg:metadataType" minOccurs="0" maxOccurs="1"/>\r
+ </sequence>\r
+ <sequence>\r
+ <element name="metadata" type="svg:metadataType"/>\r
+ <element name="title" type="svg:titleType" minOccurs="0" maxOccurs="1"/>\r
+ </sequence>\r
+ </choice>\r
+ </sequence>\r
+ <sequence>\r
+ <element name="title" type="svg:titleType"/>\r
+ <choice minOccurs="0" maxOccurs="1">\r
+ <sequence>\r
+ <element name="desc" type="svg:descType"/>\r
+ <element name="metadata" type="svg:metadataType" minOccurs="0" maxOccurs="1"/>\r
+ </sequence>\r
+ <sequence>\r
+ <element name="metadata" type="svg:metadataType"/>\r
+ <element name="desc" type="svg:descType" minOccurs="0" maxOccurs="1"/>\r
+ </sequence>\r
+ </choice>\r
+ </sequence>\r
+ <sequence>\r
+ <element name="metadata" type="svg:metadataType"/>\r
+ <choice minOccurs="0" maxOccurs="1">\r
+ <sequence>\r
+ <element name="desc" type="svg:descType"/>\r
+ <element name="title" type="svg:titleType" minOccurs="0" maxOccurs="1"/>\r
+ </sequence>\r
+ <sequence>\r
+ <element name="title" type="svg:titleType"/>\r
+ <element name="desc" type="svg:descType" minOccurs="0" maxOccurs="1"/>\r
+ </sequence>\r
+ </choice>\r
+ </sequence>\r
+ </choice>\r
+ </group>\r
+-->\r
+ <element name="svg" type="svg:svgType"/>\r
+ <element name="g" type="svg:gType"/>\r
+ <element name="defs" type="svg:defsType"/>\r
+ <element name="desc" type="svg:descType"/>\r
+ <element name="title" type="svg:titleType"/>\r
+ <element name="symbol" type="svg:symbolType"/>\r
+ <element name="use" type="svg:useType"/>\r
+ <element name="image" type="svg:imageType"/>\r
+ <element name="switch" type="svg:switchType"/>\r
+ <element name="style" type="svg:styleType"/>\r
+ <element name="path" type="svg:pathType"/>\r
+ <element name="rect" type="svg:rectType"/>\r
+ <element name="circle" type="svg:circleType"/>\r
+ <element name="ellipse" type="svg:ellipseType"/>\r
+ <element name="line" type="svg:lineType"/>\r
+ <element name="polyline" type="svg:polylineType"/>\r
+ <element name="polygon" type="svg:polygonType"/>\r
+ <element name="text" type="svg:textType"/>\r
+ <element name="tspan" type="svg:tspanType"/>\r
+ <element name="tref" type="svg:trefType"/>\r
+ <element name="textPath" type="svg:textPathType"/>\r
+ <element name="altGlyph" type="svg:altGlyphType"/>\r
+ <element name="altGlyphDef" type="svg:altGlyphDefType"/>\r
+ <element name="altGlyphItem" type="svg:altGlyphItemType"/>\r
+ <element name="glyphRef" type="svg:glyphRefType"/>\r
+ <element name="marker" type="svg:markerType"/>\r
+ <element name="color-profile" type="svg:color-profileType"/>\r
+ <element name="linearGradient" type="svg:linearGradientType"/>\r
+ <element name="radialGradient" type="svg:radialGradientType"/>\r
+ <element name="stop" type="svg:stopType"/>\r
+ <element name="pattern" type="svg:patternType"/>\r
+ <element name="clipPath" type="svg:clipPathType"/>\r
+ <element name="mask" type="svg:maskType"/>\r
+ <element name="filter" type="svg:filterType"/>\r
+ <element name="feDistantLight" type="svg:feDistantLightType"/>\r
+ <element name="fePointLight" type="svg:fePointLightType"/>\r
+ <element name="feSpotLight" type="svg:feSpotLightType"/>\r
+ <element name="feBlend" type="svg:feBlendType"/>\r
+ <element name="feColorMatrix" type="svg:feColorMatrixType"/>\r
+ <element name="feComponentTransfer" type="svg:feComponentTransferType"/>\r
+ <element name="feFuncR" type="svg:feFuncRType"/>\r
+ <element name="feFuncG" type="svg:feFuncGType"/>\r
+ <element name="feFuncB" type="svg:feFuncBType"/>\r
+ <element name="feFuncA" type="svg:feFuncAType"/>\r
+ <element name="feComposite" type="svg:feCompositeType"/>\r
+ <element name="feConvolveMatrix" type="svg:feConvolveMatrixType"/>\r
+ <element name="feDiffuseLighting" type="svg:feDiffuseLightingType"/>\r
+ <element name="feDisplacementMap" type="svg:feDisplacementMapType"/>\r
+ <element name="feFlood" type="svg:feFloodType"/>\r
+ <element name="feGaussianBlur" type="svg:feGaussianBlurType"/>\r
+ <element name="feImage" type="svg:feImageType"/>\r
+ <element name="feMerge" type="svg:feMergeType"/>\r
+ <element name="feMergeNode" type="svg:feMergeNodeType"/>\r
+ <element name="feMorphology" type="svg:feMorphologyType"/>\r
+ <element name="feOffset" type="svg:feOffsetType"/>\r
+ <element name="feSpecularLighting" type="svg:feSpecularLightingType"/>\r
+ <element name="feTile" type="svg:feTileType"/>\r
+ <element name="feTurbulence" type="svg:feTurbulenceType"/>\r
+ <element name="cursor" type="svg:cursorType"/>\r
+ <element name="a" type="svg:aType"/>\r
+ <element name="view" type="svg:viewType"/>\r
+ <element name="script" type="svg:scriptType"/>\r
+ <element name="animate" type="svg:animateType"/>\r
+ <element name="set" type="svg:setType"/>\r
+ <element name="animateMotion" type="svg:animateMotionType"/>\r
+ <element name="mpath" type="svg:mpathType"/>\r
+ <element name="animateColor" type="svg:animateColorType"/>\r
+ <element name="animateTransform" type="svg:animateTransformType"/>\r
+ <element name="font" type="svg:fontType"/>\r
+ <element name="glyph" type="svg:glyphType"/>\r
+ <element name="missing-glyph" type="svg:missing-glyphType"/>\r
+ <element name="hkern" type="svg:hkernType"/>\r
+ <element name="vkern" type="svg:vkernType"/>\r
+ <element name="font-face" type="svg:font-faceType"/>\r
+ <element name="font-face-src" type="svg:font-face-srcType"/>\r
+ <element name="font-face-uri" type="svg:font-face-uriType"/>\r
+ <element name="font-face-format" type="svg:font-face-formatType"/>\r
+ <element name="font-face-name" type="svg:font-face-nameType"/>\r
+ <element name="definition-src" type="svg:definition-srcType"/>\r
+ <element name="metadata" type="svg:metadataType"/>\r
+ <element name="foreignObject" type="svg:foreignObjectType"/>\r
+ <complexType name="svgType">\r
+ <choice minOccurs="0" maxOccurs="unbounded">\r
+ <element ref="svg:desc"/>\r
+ <element ref="svg:title"/>\r
+ <element ref="svg:metadata"/>\r
+ <element ref="svg:defs"/>\r
+ <element ref="svg:path"/>\r
+ <element ref="svg:text"/>\r
+ <element ref="svg:rect"/>\r
+ <element ref="svg:circle"/>\r
+ <element ref="svg:ellipse"/>\r
+ <element ref="svg:line"/>\r
+ <element ref="svg:polyline"/>\r
+ <element ref="svg:polygon"/>\r
+ <element ref="svg:use"/>\r
+ <element ref="svg:image"/>\r
+ <element ref="svg:svg"/>\r
+ <element ref="svg:g"/>\r
+ <element ref="svg:view"/>\r
+ <element ref="svg:switch"/>\r
+ <element ref="svg:a"/>\r
+ <element ref="svg:altGlyphDef"/>\r
+ <element ref="svg:script"/>\r
+ <element ref="svg:style"/>\r
+ <element ref="svg:symbol"/>\r
+ <element ref="svg:marker"/>\r
+ <element ref="svg:clipPath"/>\r
+ <element ref="svg:mask"/>\r
+ <element ref="svg:linearGradient"/>\r
+ <element ref="svg:radialGradient"/>\r
+ <element ref="svg:pattern"/>\r
+ <element ref="svg:filter"/>\r
+ <element ref="svg:cursor"/>\r
+ <element ref="svg:font"/>\r
+ <element ref="svg:animate"/>\r
+ <element ref="svg:set"/>\r
+ <element ref="svg:animateMotion"/>\r
+ <element ref="svg:animateColor"/>\r
+ <element ref="svg:animateTransform"/>\r
+ <element ref="svg:color-profile"/>\r
+ <element ref="svg:font-face"/>\r
+ <!-- should this be done with named child element collections? Especially for modularisation. -->\r
+ </choice>\r
+ <attributeGroup ref="svg:stdAttrs"/>\r
+ <attributeGroup ref="svg:testAttrs"/>\r
+ <attributeGroup ref="svg:langSpaceAttrs"/>\r
+ <attribute name="externalResourcesRequired" type="boolean" use="optional"/>\r
+ <attribute name="class" type="svg:ClassListType" use="optional"/>\r
+ <attribute name="style" type="svg:StyleSheetType" use="optional"/>\r
+ <attributeGroup ref="svg:PresentationAttributes-All"/>\r
+ <attribute name="viewBox" type="svg:ViewBoxSpecType" use="optional"/>\r
+ <attribute name="version" type="double" use="optional"/>\r
+ <attribute name="preserveAspectRatio" type="svg:PreserveAspectRatioSpecType" default="xMidYMid meet"/>\r
+ <attribute name="zoomAndPan" default="magnify">\r
+ <simpleType>\r
+ <restriction base="string">\r
+ <enumeration value="disable"/>\r
+ <enumeration value="magnify"/>\r
+ <enumeration value="zoom"/>\r
+ </restriction>\r
+ </simpleType>\r
+ </attribute>\r
+ <attributeGroup ref="svg:graphicsElementEvents"/>\r
+ <attributeGroup ref="svg:documentEvents"/>\r
+ <attribute name="x" type="svg:CoordinateType" use="optional"/>\r
+ <attribute name="y" type="svg:CoordinateType" use="optional"/>\r
+ <attribute name="width" type="svg:LengthType" use="required"/>\r
+ <attribute name="height" type="svg:LengthType" use="required"/>\r
+ <attribute name="contentScriptType" type="svg:ContentTypeType" default="text/ecmascript"/>\r
+ <attribute name="contentStyleType" type="svg:ContentTypeType" default="text/css"/>\r
+ </complexType>\r
+ <complexType name="gType">\r
+ <choice minOccurs="0" maxOccurs="unbounded">\r
+ <element ref="svg:desc"/>\r
+ <element ref="svg:title"/>\r
+ <element ref="svg:metadata"/>\r
+ <element ref="svg:defs"/>\r
+ <element ref="svg:path"/>\r
+ <element ref="svg:text"/>\r
+ <element ref="svg:rect"/>\r
+ <element ref="svg:circle"/>\r
+ <element ref="svg:ellipse"/>\r
+ <element ref="svg:line"/>\r
+ <element ref="svg:polyline"/>\r
+ <element ref="svg:polygon"/>\r
+ <element ref="svg:use"/>\r
+ <element ref="svg:image"/>\r
+ <element ref="svg:svg"/>\r
+ <element ref="svg:g"/>\r
+ <element ref="svg:view"/>\r
+ <element ref="svg:switch"/>\r
+ <element ref="svg:a"/>\r
+ <element ref="svg:altGlyphDef"/>\r
+ <element ref="svg:script"/>\r
+ <element ref="svg:style"/>\r
+ <element ref="svg:symbol"/>\r
+ <element ref="svg:marker"/>\r
+ <element ref="svg:clipPath"/>\r
+ <element ref="svg:mask"/>\r
+ <element ref="svg:linearGradient"/>\r
+ <element ref="svg:radialGradient"/>\r
+ <element ref="svg:pattern"/>\r
+ <element ref="svg:filter"/>\r
+ <element ref="svg:cursor"/>\r
+ <element ref="svg:font"/>\r
+ <element ref="svg:animate"/>\r
+ <element ref="svg:set"/>\r
+ <element ref="svg:animateMotion"/>\r
+ <element ref="svg:animateColor"/>\r
+ <element ref="svg:animateTransform"/>\r
+ <element ref="svg:color-profile"/>\r
+ <element ref="svg:font-face"/>\r
+ </choice>\r
+ <attributeGroup ref="svg:stdAttrs"/>\r
+ <attributeGroup ref="svg:testAttrs"/>\r
+ <attributeGroup ref="svg:langSpaceAttrs"/>\r
+ <attribute name="externalResourcesRequired" type="boolean" use="optional"/>\r
+ <attribute name="class" type="svg:ClassListType" use="optional"/>\r
+ <attribute name="style" type="svg:StyleSheetType" use="optional"/>\r
+ <attributeGroup ref="svg:PresentationAttributes-All"/>\r
+ <attribute name="transform" type="svg:TransformListType" use="optional"/>\r
+ <attributeGroup ref="svg:graphicsElementEvents"/>\r
+ </complexType>\r
+ <complexType name="defsType">\r
+ <choice minOccurs="0" maxOccurs="unbounded">\r
+ <element ref="svg:desc"/>\r
+ <element ref="svg:title"/>\r
+ <element ref="svg:metadata"/>\r
+ <element ref="svg:defs"/>\r
+ <element ref="svg:path"/>\r
+ <element ref="svg:text"/>\r
+ <element ref="svg:rect"/>\r
+ <element ref="svg:circle"/>\r
+ <element ref="svg:ellipse"/>\r
+ <element ref="svg:line"/>\r
+ <element ref="svg:polyline"/>\r
+ <element ref="svg:polygon"/>\r
+ <element ref="svg:use"/>\r
+ <element ref="svg:image"/>\r
+ <element ref="svg:svg"/>\r
+ <element ref="svg:g"/>\r
+ <element ref="svg:view"/>\r
+ <element ref="svg:switch"/>\r
+ <element ref="svg:a"/>\r
+ <element ref="svg:altGlyphDef"/>\r
+ <element ref="svg:script"/>\r
+ <element ref="svg:style"/>\r
+ <element ref="svg:symbol"/>\r
+ <element ref="svg:marker"/>\r
+ <element ref="svg:clipPath"/>\r
+ <element ref="svg:mask"/>\r
+ <element ref="svg:linearGradient"/>\r
+ <element ref="svg:radialGradient"/>\r
+ <element ref="svg:pattern"/>\r
+ <element ref="svg:filter"/>\r
+ <element ref="svg:cursor"/>\r
+ <element ref="svg:font"/>\r
+ <element ref="svg:animate"/>\r
+ <element ref="svg:set"/>\r
+ <element ref="svg:animateMotion"/>\r
+ <element ref="svg:animateColor"/>\r
+ <element ref="svg:animateTransform"/>\r
+ <element ref="svg:color-profile"/>\r
+ <element ref="svg:font-face"/>\r
+ </choice>\r
+ <attributeGroup ref="svg:stdAttrs"/>\r
+ <attributeGroup ref="svg:testAttrs"/>\r
+ <attributeGroup ref="svg:langSpaceAttrs"/>\r
+ <attribute name="externalResourcesRequired" type="boolean" use="optional"/>\r
+ <attribute name="class" type="svg:ClassListType" use="optional"/>\r
+ <attribute name="style" type="svg:StyleSheetType" use="optional"/>\r
+ <attributeGroup ref="svg:PresentationAttributes-All"/>\r
+ <attribute name="transform" type="svg:TransformListType" use="optional"/>\r
+ <attributeGroup ref="svg:graphicsElementEvents"/>\r
+ </complexType>\r
+ <complexType name="descType" mixed="true">\r
+ <attributeGroup ref="svg:stdAttrs"/>\r
+ <attributeGroup ref="svg:langSpaceAttrs"/>\r
+ <attribute name="class" type="svg:ClassListType" use="optional"/>\r
+ <attribute name="style" type="svg:StyleSheetType" use="optional"/>\r
+ <attribute name="content" type="string" fixed="structured text"/>\r
+ </complexType>\r
+ <complexType name="titleType" mixed="true">\r
+ <attributeGroup ref="svg:stdAttrs"/>\r
+ <attributeGroup ref="svg:langSpaceAttrs"/>\r
+ <attribute name="class" type="svg:ClassListType" use="optional"/>\r
+ <attribute name="style" type="svg:StyleSheetType" use="optional"/>\r
+ <attribute name="content" type="string" fixed="structured text"/>\r
+ </complexType>\r
+ <complexType name="symbolType">\r
+ <choice minOccurs="0" maxOccurs="unbounded">\r
+ <element ref="svg:desc"/>\r
+ <element ref="svg:title"/>\r
+ <element ref="svg:metadata"/>\r
+ <element ref="svg:defs"/>\r
+ <element ref="svg:path"/>\r
+ <element ref="svg:text"/>\r
+ <element ref="svg:rect"/>\r
+ <element ref="svg:circle"/>\r
+ <element ref="svg:ellipse"/>\r
+ <element ref="svg:line"/>\r
+ <element ref="svg:polyline"/>\r
+ <element ref="svg:polygon"/>\r
+ <element ref="svg:use"/>\r
+ <element ref="svg:image"/>\r
+ <element ref="svg:svg"/>\r
+ <element ref="svg:g"/>\r
+ <element ref="svg:view"/>\r
+ <element ref="svg:switch"/>\r
+ <element ref="svg:a"/>\r
+ <element ref="svg:altGlyphDef"/>\r
+ <element ref="svg:script"/>\r
+ <element ref="svg:style"/>\r
+ <element ref="svg:symbol"/>\r
+ <element ref="svg:marker"/>\r
+ <element ref="svg:clipPath"/>\r
+ <element ref="svg:mask"/>\r
+ <element ref="svg:linearGradient"/>\r
+ <element ref="svg:radialGradient"/>\r
+ <element ref="svg:pattern"/>\r
+ <element ref="svg:filter"/>\r
+ <element ref="svg:cursor"/>\r
+ <element ref="svg:font"/>\r
+ <element ref="svg:animate"/>\r
+ <element ref="svg:set"/>\r
+ <element ref="svg:animateMotion"/>\r
+ <element ref="svg:animateColor"/>\r
+ <element ref="svg:animateTransform"/>\r
+ <element ref="svg:color-profile"/>\r
+ <element ref="svg:font-face"/>\r
+ </choice>\r
+ <attributeGroup ref="svg:stdAttrs"/>\r
+ <attributeGroup ref="svg:langSpaceAttrs"/>\r
+ <attribute name="externalResourcesRequired" type="boolean" use="optional"/>\r
+ <attribute name="class" type="svg:ClassListType" use="optional"/>\r
+ <attribute name="style" type="svg:StyleSheetType" use="optional"/>\r
+ <attributeGroup ref="svg:PresentationAttributes-All"/>\r
+ <attribute name="viewBox" type="svg:ViewBoxSpecType" use="optional"/>\r
+ <attribute name="preserveAspectRatio" type="svg:PreserveAspectRatioSpecType" default="xMidYMid meet"/>\r
+ <attributeGroup ref="svg:graphicsElementEvents"/>\r
+ </complexType>\r
+ <complexType name="useType">\r
+ <sequence>\r
+ <group ref="svg:descTitleMetadata" minOccurs="0" maxOccurs="unbounded"/>\r
+ <choice minOccurs="0" maxOccurs="unbounded">\r
+ <element ref="svg:animate"/>\r
+ <element ref="svg:set"/>\r
+ <element ref="svg:animateMotion"/>\r
+ <element ref="svg:animateColor"/>\r
+ <element ref="svg:animateTransform"/>\r
+ </choice>\r
+ </sequence>\r
+ <attributeGroup ref="svg:stdAttrs"/>\r
+ <attributeGroup ref="svg:xlinkRefAttrsEmbed"/>\r
+ <attribute ref="xlink:href" use="required"/>\r
+ <attributeGroup ref="svg:testAttrs"/>\r
+ <attributeGroup ref="svg:langSpaceAttrs"/>\r
+ <attribute name="externalResourcesRequired" type="boolean" use="optional"/>\r
+ <attribute name="class" type="svg:ClassListType" use="optional"/>\r
+ <attribute name="style" type="svg:StyleSheetType" use="optional"/>\r
+ <attributeGroup ref="svg:PresentationAttributes-All"/>\r
+ <attribute name="transform" type="svg:TransformListType" use="optional"/>\r
+ <attributeGroup ref="svg:graphicsElementEvents"/>\r
+ <attribute name="x" type="svg:CoordinateType" use="optional"/>\r
+ <attribute name="y" type="svg:CoordinateType" use="optional"/>\r
+ <attribute name="width" type="svg:LengthType" use="optional"/>\r
+ <attribute name="height" type="svg:LengthType" use="optional"/>\r
+ </complexType>\r
+ <complexType name="imageType">\r
+ <sequence>\r
+ <group ref="svg:descTitleMetadata" minOccurs="0" maxOccurs="unbounded"/>\r
+ <choice minOccurs="0" maxOccurs="unbounded">\r
+ <element ref="svg:animate"/>\r
+ <element ref="svg:set"/>\r
+ <element ref="svg:animateMotion"/>\r
+ <element ref="svg:animateColor"/>\r
+ <element ref="svg:animateTransform"/>\r
+ <!-- this should probably be a named element group -->\r
+ </choice>\r
+ </sequence>\r
+ <attributeGroup ref="svg:stdAttrs"/>\r
+ <attributeGroup ref="svg:xlinkRefAttrs"/>\r
+ <attribute ref="xlink:href" use="optional"/>\r
+ <attributeGroup ref="svg:testAttrs"/>\r
+ <attributeGroup ref="svg:PresentationAttributes-Color"/>\r
+ <attributeGroup ref="svg:langSpaceAttrs"/>\r
+ <attribute name="externalResourcesRequired" type="boolean" use="optional"/>\r
+ <attribute name="class" type="svg:ClassListType" use="optional"/>\r
+ <attribute name="style" type="svg:StyleSheetType" use="optional"/>\r
+ <attributeGroup ref="svg:PresentationAttributes-Graphics"/>\r
+ <attributeGroup ref="svg:PresentationAttributes-Viewports"/>\r
+ <attribute name="transform" type="svg:TransformListType" use="optional"/>\r
+ <attributeGroup ref="svg:graphicsElementEvents"/>\r
+ <attribute name="x" type="svg:CoordinateType" use="optional"/>\r
+ <attribute name="y" type="svg:CoordinateType" use="optional"/>\r
+ <attribute name="width" type="svg:LengthType" use="required"/>\r
+ <attribute name="height" type="svg:LengthType" use="required"/>\r
+ </complexType>\r
+ <complexType name="switchType">\r
+ <sequence>\r
+ <group ref="svg:descTitleMetadata" minOccurs="0" maxOccurs="unbounded"/>\r
+ <choice minOccurs="0" maxOccurs="unbounded">\r
+ <element ref="svg:path"/>\r
+ <element ref="svg:text"/>\r
+ <element ref="svg:rect"/>\r
+ <element ref="svg:circle"/>\r
+ <element ref="svg:ellipse"/>\r
+ <element ref="svg:line"/>\r
+ <element ref="svg:polyline"/>\r
+ <element ref="svg:polygon"/>\r
+ <element ref="svg:use"/>\r
+ <element ref="svg:image"/>\r
+ <element ref="svg:svg"/>\r
+ <element ref="svg:g"/>\r
+ <element ref="svg:switch"/>\r
+ <element ref="svg:a"/>\r
+ <element ref="svg:foreignObject"/>\r
+ <element ref="svg:animate"/>\r
+ <element ref="svg:set"/>\r
+ <element ref="svg:animateMotion"/>\r
+ <element ref="svg:animateColor"/>\r
+ <element ref="svg:animateTransform"/>\r
+ </choice>\r
+ </sequence>\r
+ <attributeGroup ref="svg:stdAttrs"/>\r
+ <attributeGroup ref="svg:testAttrs"/>\r
+ <attributeGroup ref="svg:langSpaceAttrs"/>\r
+ <attribute name="externalResourcesRequired" type="boolean" use="optional"/>\r
+ <attribute name="class" type="svg:ClassListType" use="optional"/>\r
+ <attribute name="style" type="svg:StyleSheetType" use="optional"/>\r
+ <attributeGroup ref="svg:PresentationAttributes-All"/>\r
+ <attribute name="transform" type="svg:TransformListType" use="optional"/>\r
+ <attributeGroup ref="svg:graphicsElementEvents"/>\r
+ </complexType>\r
+ <complexType name="styleType" mixed="true">\r
+ <attributeGroup ref="svg:stdAttrs"/>\r
+ <attribute ref="xml:space" fixed="preserve"/>\r
+ <attribute name="type" type="svg:ContentTypeType" use="required"/>\r
+ <attribute name="media" type="svg:MediaDescType" use="optional"/>\r
+ <attribute name="title" type="string" use="optional"/>\r
+ </complexType>\r
+ <complexType name="pathType">\r
+ <sequence>\r
+ <group ref="svg:descTitleMetadata" minOccurs="0" maxOccurs="unbounded"/>\r
+ <choice minOccurs="0" maxOccurs="unbounded">\r
+ <element ref="svg:animate"/>\r
+ <element ref="svg:set"/>\r
+ <element ref="svg:animateMotion"/>\r
+ <element ref="svg:animateColor"/>\r
+ <element ref="svg:animateTransform"/>\r
+ </choice>\r
+ </sequence>\r
+ <attributeGroup ref="svg:stdAttrs"/>\r
+ <attributeGroup ref="svg:testAttrs"/>\r
+ <attributeGroup ref="svg:langSpaceAttrs"/>\r
+ <attribute name="externalResourcesRequired" type="boolean" use="optional"/>\r
+ <attribute name="class" type="svg:ClassListType" use="optional"/>\r
+ <attribute name="style" type="svg:StyleSheetType" use="optional"/>\r
+ <attributeGroup ref="svg:PresentationAttributes-Color"/>\r
+ <attributeGroup ref="svg:PresentationAttributes-FillStroke"/>\r
+ <attributeGroup ref="svg:PresentationAttributes-Graphics"/>\r
+ <attributeGroup ref="svg:PresentationAttributes-Markers"/>\r
+ <attribute name="transform" type="svg:TransformListType" use="optional"/>\r
+ <attributeGroup ref="svg:graphicsElementEvents"/>\r
+ <attribute name="d" type="svg:PathDataType" use="required"/>\r
+ <attribute name="pathLength" type="double" use="optional"/>\r
+ </complexType>\r
+ <complexType name="rectType">\r
+ <sequence>\r
+ <group ref="svg:descTitleMetadata" minOccurs="0" maxOccurs="unbounded"/>\r
+ <choice minOccurs="0" maxOccurs="unbounded">\r
+ <element ref="svg:animate"/>\r
+ <element ref="svg:set"/>\r
+ <element ref="svg:animateMotion"/>\r
+ <element ref="svg:animateColor"/>\r
+ <element ref="svg:animateTransform"/>\r
+ </choice>\r
+ </sequence>\r
+ <attributeGroup ref="svg:stdAttrs"/>\r
+ <attributeGroup ref="svg:testAttrs"/>\r
+ <attributeGroup ref="svg:langSpaceAttrs"/>\r
+ <attribute name="externalResourcesRequired" type="boolean" use="optional"/>\r
+ <attribute name="class" type="svg:ClassListType" use="optional"/>\r
+ <attribute name="style" type="svg:StyleSheetType" use="optional"/>\r
+ <attributeGroup ref="svg:PresentationAttributes-Color"/>\r
+ <attributeGroup ref="svg:PresentationAttributes-FillStroke"/>\r
+ <attributeGroup ref="svg:PresentationAttributes-Graphics"/>\r
+ <attribute name="transform" type="svg:TransformListType" use="optional"/>\r
+ <attributeGroup ref="svg:graphicsElementEvents"/>\r
+ <attribute name="x" type="svg:CoordinateType" use="optional"/>\r
+ <attribute name="y" type="svg:CoordinateType" use="optional"/>\r
+ <attribute name="width" type="svg:LengthType" use="required"/>\r
+ <attribute name="height" type="svg:LengthType" use="required"/>\r
+ <attribute name="rx" type="svg:LengthType" use="optional"/>\r
+ <attribute name="ry" type="svg:LengthType" use="optional"/>\r
+ </complexType>\r
+ <complexType name="circleType">\r
+ <sequence>\r
+ <group ref="svg:descTitleMetadata" minOccurs="0" maxOccurs="unbounded"/>\r
+ <choice minOccurs="0" maxOccurs="unbounded">\r
+ <element ref="svg:animate"/>\r
+ <element ref="svg:set"/>\r
+ <element ref="svg:animateMotion"/>\r
+ <element ref="svg:animateColor"/>\r
+ <element ref="svg:animateTransform"/>\r
+ </choice>\r
+ </sequence>\r
+ <attributeGroup ref="svg:stdAttrs"/>\r
+ <attributeGroup ref="svg:testAttrs"/>\r
+ <attributeGroup ref="svg:langSpaceAttrs"/>\r
+ <attribute name="externalResourcesRequired" type="boolean" use="optional"/>\r
+ <attribute name="class" type="svg:ClassListType" use="optional"/>\r
+ <attribute name="style" type="svg:StyleSheetType" use="optional"/>\r
+ <attributeGroup ref="svg:PresentationAttributes-Color"/>\r
+ <attributeGroup ref="svg:PresentationAttributes-FillStroke"/>\r
+ <attributeGroup ref="svg:PresentationAttributes-Graphics"/>\r
+ <attribute name="transform" type="svg:TransformListType" use="optional"/>\r
+ <attributeGroup ref="svg:graphicsElementEvents"/>\r
+ <attribute name="cx" type="svg:CoordinateType" use="optional"/>\r
+ <attribute name="cy" type="svg:CoordinateType" use="optional"/>\r
+ <attribute name="r" type="svg:LengthType" use="required"/>\r
+ </complexType>\r
+ <complexType name="ellipseType">\r
+ <sequence>\r
+ <group ref="svg:descTitleMetadata" minOccurs="0" maxOccurs="unbounded"/>\r
+ <choice minOccurs="0" maxOccurs="unbounded">\r
+ <element ref="svg:animate"/>\r
+ <element ref="svg:set"/>\r
+ <element ref="svg:animateMotion"/>\r
+ <element ref="svg:animateColor"/>\r
+ <element ref="svg:animateTransform"/>\r
+ </choice>\r
+ </sequence>\r
+ <attributeGroup ref="svg:stdAttrs"/>\r
+ <attributeGroup ref="svg:testAttrs"/>\r
+ <attributeGroup ref="svg:langSpaceAttrs"/>\r
+ <attribute name="externalResourcesRequired" type="boolean" use="optional"/>\r
+ <attribute name="class" type="svg:ClassListType" use="optional"/>\r
+ <attribute name="style" type="svg:StyleSheetType" use="optional"/>\r
+ <attributeGroup ref="svg:PresentationAttributes-Color"/>\r
+ <attributeGroup ref="svg:PresentationAttributes-FillStroke"/>\r
+ <attributeGroup ref="svg:PresentationAttributes-Graphics"/>\r
+ <attribute name="transform" type="svg:TransformListType" use="optional"/>\r
+ <attributeGroup ref="svg:graphicsElementEvents"/>\r
+ <attribute name="cx" type="svg:CoordinateType" use="optional"/>\r
+ <attribute name="cy" type="svg:CoordinateType" use="optional"/>\r
+ <attribute name="rx" type="svg:LengthType" use="required"/>\r
+ <attribute name="ry" type="svg:LengthType" use="required"/>\r
+ </complexType>\r
+ <complexType name="lineType">\r
+ <sequence>\r
+ <group ref="svg:descTitleMetadata" minOccurs="0" maxOccurs="unbounded"/>\r
+ <choice minOccurs="0" maxOccurs="unbounded">\r
+ <element ref="svg:animate"/>\r
+ <element ref="svg:set"/>\r
+ <element ref="svg:animateMotion"/>\r
+ <element ref="svg:animateColor"/>\r
+ <element ref="svg:animateTransform"/>\r
+ </choice>\r
+ </sequence>\r
+ <attributeGroup ref="svg:stdAttrs"/>\r
+ <attributeGroup ref="svg:testAttrs"/>\r
+ <attributeGroup ref="svg:langSpaceAttrs"/>\r
+ <attribute name="externalResourcesRequired" type="boolean" use="optional"/>\r
+ <attribute name="class" type="svg:ClassListType" use="optional"/>\r
+ <attribute name="style" type="svg:StyleSheetType" use="optional"/>\r
+ <attributeGroup ref="svg:PresentationAttributes-Color"/>\r
+ <attributeGroup ref="svg:PresentationAttributes-FillStroke"/>\r
+ <attributeGroup ref="svg:PresentationAttributes-Graphics"/>\r
+ <attributeGroup ref="svg:PresentationAttributes-Markers"/>\r
+ <attribute name="transform" type="svg:TransformListType" use="optional"/>\r
+ <attributeGroup ref="svg:graphicsElementEvents"/>\r
+ <attribute name="x1" type="svg:CoordinateType" use="optional"/>\r
+ <attribute name="y1" type="svg:CoordinateType" use="optional"/>\r
+ <attribute name="x2" type="svg:CoordinateType" use="optional"/>\r
+ <attribute name="y2" type="svg:CoordinateType" use="optional"/>\r
+ </complexType>\r
+ <complexType name="polylineType">\r
+ <sequence>\r
+ <group ref="svg:descTitleMetadata" minOccurs="0" maxOccurs="unbounded"/>\r
+ <choice minOccurs="0" maxOccurs="unbounded">\r
+ <element ref="svg:animate"/>\r
+ <element ref="svg:set"/>\r
+ <element ref="svg:animateMotion"/>\r
+ <element ref="svg:animateColor"/>\r
+ <element ref="svg:animateTransform"/>\r
+ </choice>\r
+ </sequence>\r
+ <attributeGroup ref="svg:stdAttrs"/>\r
+ <attributeGroup ref="svg:testAttrs"/>\r
+ <attributeGroup ref="svg:langSpaceAttrs"/>\r
+ <attribute name="externalResourcesRequired" type="boolean" use="optional"/>\r
+ <attribute name="class" type="svg:ClassListType" use="optional"/>\r
+ <attribute name="style" type="svg:StyleSheetType" use="optional"/>\r
+ <attributeGroup ref="svg:PresentationAttributes-Color"/>\r
+ <attributeGroup ref="svg:PresentationAttributes-FillStroke"/>\r
+ <attributeGroup ref="svg:PresentationAttributes-Graphics"/>\r
+ <attributeGroup ref="svg:PresentationAttributes-Markers"/>\r
+ <attribute name="transform" type="svg:TransformListType" use="optional"/>\r
+ <attributeGroup ref="svg:graphicsElementEvents"/>\r
+ <attribute name="points" type="svg:PointsType" use="required"/>\r
+ </complexType>\r
+ <complexType name="polygonType">\r
+ <sequence>\r
+ <group ref="svg:descTitleMetadata" minOccurs="0" maxOccurs="unbounded"/>\r
+ <choice minOccurs="0" maxOccurs="unbounded">\r
+ <element ref="svg:animate"/>\r
+ <element ref="svg:set"/>\r
+ <element ref="svg:animateMotion"/>\r
+ <element ref="svg:animateColor"/>\r
+ <element ref="svg:animateTransform"/>\r
+ </choice>\r
+ </sequence>\r
+ <attributeGroup ref="svg:stdAttrs"/>\r
+ <attributeGroup ref="svg:testAttrs"/>\r
+ <attributeGroup ref="svg:langSpaceAttrs"/>\r
+ <attribute name="externalResourcesRequired" type="boolean" use="optional"/>\r
+ <attribute name="class" type="svg:ClassListType" use="optional"/>\r
+ <attribute name="style" type="svg:StyleSheetType" use="optional"/>\r
+ <attributeGroup ref="svg:PresentationAttributes-Color"/>\r
+ <attributeGroup ref="svg:PresentationAttributes-FillStroke"/>\r
+ <attributeGroup ref="svg:PresentationAttributes-Graphics"/>\r
+ <attributeGroup ref="svg:PresentationAttributes-Markers"/>\r
+ <attribute name="transform" type="svg:TransformListType" use="optional"/>\r
+ <attributeGroup ref="svg:graphicsElementEvents"/>\r
+ <attribute name="points" type="svg:PointsType" use="required"/>\r
+ </complexType>\r
+ <complexType name="textType" mixed="true">\r
+ <choice minOccurs="0" maxOccurs="unbounded">\r
+ <element ref="svg:desc"/>\r
+ <element ref="svg:title"/>\r
+ <element ref="svg:metadata"/>\r
+ <element ref="svg:tspan"/>\r
+ <element ref="svg:tref"/>\r
+ <element ref="svg:textPath"/>\r
+ <element ref="svg:altGlyph"/>\r
+ <element ref="svg:a"/>\r
+ <element ref="svg:animate"/>\r
+ <element ref="svg:set"/>\r
+ <element ref="svg:animateMotion"/>\r
+ <element ref="svg:animateColor"/>\r
+ <element ref="svg:animateTransform"/>\r
+ </choice>\r
+ <attributeGroup ref="svg:stdAttrs"/>\r
+ <attributeGroup ref="svg:testAttrs"/>\r
+ <attributeGroup ref="svg:langSpaceAttrs"/>\r
+ <attribute name="externalResourcesRequired" type="boolean" use="optional"/>\r
+ <attribute name="class" type="svg:ClassListType" use="optional"/>\r
+ <attribute name="style" type="svg:StyleSheetType" use="optional"/>\r
+ <attributeGroup ref="svg:PresentationAttributes-Color"/>\r
+ <attributeGroup ref="svg:PresentationAttributes-FillStroke"/>\r
+ <attributeGroup ref="svg:PresentationAttributes-FontSpecification"/>\r
+ <attributeGroup ref="svg:PresentationAttributes-Graphics"/>\r
+ <attributeGroup ref="svg:PresentationAttributes-TextContentElements"/>\r
+ <attributeGroup ref="svg:PresentationAttributes-TextElements"/>\r
+ <attribute name="transform" type="svg:TransformListType" use="optional"/>\r
+ <attributeGroup ref="svg:graphicsElementEvents"/>\r
+ <attribute name="x" type="svg:CoordinateType" use="optional"/>\r
+ <attribute name="y" type="svg:CoordinateType" use="optional"/>\r
+ <attribute name="textLength" type="svg:LengthType" use="optional"/>\r
+ <attribute name="lengthAdjust" use="optional">\r
+ <simpleType>\r
+ <restriction base="string">\r
+ <enumeration value="spacing"/>\r
+ <enumeration value="spacingAndGlyphs"/>\r
+ </restriction>\r
+ </simpleType>\r
+ </attribute>\r
+ </complexType>\r
+ <complexType name="tspanType" mixed="true">\r
+ <choice minOccurs="0" maxOccurs="unbounded">\r
+ <element ref="svg:desc"/>\r
+ <element ref="svg:title"/>\r
+ <element ref="svg:metadata"/>\r
+ <element ref="svg:tspan"/>\r
+ <element ref="svg:tref"/>\r
+ <element ref="svg:altGlyph"/>\r
+ <element ref="svg:a"/>\r
+ <element ref="svg:animate"/>\r
+ <element ref="svg:set"/>\r
+ <element ref="svg:animateColor"/>\r
+ </choice>\r
+ <attributeGroup ref="svg:stdAttrs"/>\r
+ <attributeGroup ref="svg:testAttrs"/>\r
+ <attributeGroup ref="svg:langSpaceAttrs"/>\r
+ <attribute name="externalResourcesRequired" type="boolean" use="optional"/>\r
+ <attribute name="class" type="svg:ClassListType" use="optional"/>\r
+ <attribute name="style" type="svg:StyleSheetType" use="optional"/>\r
+ <attributeGroup ref="svg:PresentationAttributes-Color"/>\r
+ <attributeGroup ref="svg:PresentationAttributes-FillStroke"/>\r
+ <attributeGroup ref="svg:PresentationAttributes-FontSpecification"/>\r
+ <attributeGroup ref="svg:PresentationAttributes-Graphics"/>\r
+ <attributeGroup ref="svg:PresentationAttributes-TextContentElements"/>\r
+ <attributeGroup ref="svg:graphicsElementEvents"/>\r
+ <attribute name="x" type="svg:CoordinatesType" use="optional"/>\r
+ <attribute name="y" type="svg:CoordinatesType" use="optional"/>\r
+ <attribute name="dx" type="svg:LengthsType" use="optional"/>\r
+ <attribute name="dy" type="svg:LengthsType" use="optional"/>\r
+ <attribute name="rotate" type="string" use="optional"/>\r
+ <attribute name="textLength" type="svg:LengthType" use="optional"/>\r
+ <attribute name="lengthAdjust" use="optional">\r
+ <simpleType>\r
+ <restriction base="string">\r
+ <enumeration value="spacing"/>\r
+ <enumeration value="spacingAndGlyphs"/>\r
+ </restriction>\r
+ </simpleType>\r
+ </attribute>\r
+ </complexType>\r
+ <complexType name="trefType">\r
+ <choice minOccurs="0" maxOccurs="unbounded">\r
+ <element ref="svg:desc"/>\r
+ <element ref="svg:title"/>\r
+ <element ref="svg:metadata"/>\r
+ <element ref="svg:animate"/>\r
+ <element ref="svg:set"/>\r
+ <element ref="svg:animateColor"/>\r
+ </choice>\r
+ <attributeGroup ref="svg:stdAttrs"/>\r
+ <attributeGroup ref="svg:xlinkRefAttrs"/>\r
+ <attribute ref="xlink:href" use="optional"/>\r
+ <attributeGroup ref="svg:testAttrs"/>\r
+ <attributeGroup ref="svg:langSpaceAttrs"/>\r
+ <attribute name="externalResourcesRequired" type="boolean" use="optional"/>\r
+ <attribute name="class" type="svg:ClassListType" use="optional"/>\r
+ <attribute name="style" type="svg:StyleSheetType" use="optional"/>\r
+ <attributeGroup ref="svg:PresentationAttributes-Color"/>\r
+ <attributeGroup ref="svg:PresentationAttributes-FillStroke"/>\r
+ <attributeGroup ref="svg:PresentationAttributes-FontSpecification"/>\r
+ <attributeGroup ref="svg:PresentationAttributes-Graphics"/>\r
+ <attributeGroup ref="svg:PresentationAttributes-TextContentElements"/>\r
+ <attributeGroup ref="svg:graphicsElementEvents"/>\r
+ <attribute name="x" type="svg:CoordinatesType" use="optional"/>\r
+ <attribute name="y" type="svg:CoordinatesType" use="optional"/>\r
+ <attribute name="dx" type="svg:LengthsType" use="optional"/>\r
+ <attribute name="dy" type="svg:LengthsType" use="optional"/>\r
+ <attribute name="rotate" type="string" use="optional"/>\r
+ <attribute name="textLength" type="svg:LengthType" use="optional"/>\r
+ <attribute name="lengthAdjust" use="optional">\r
+ <simpleType>\r
+ <restriction base="string">\r
+ <enumeration value="spacing"/>\r
+ <enumeration value="spacingAndGlyphs"/>\r
+ </restriction>\r
+ </simpleType>\r
+ </attribute>\r
+ </complexType>\r
+ <complexType name="textPathType" mixed="true">\r
+ <choice minOccurs="0" maxOccurs="unbounded">\r
+ <element ref="svg:desc"/>\r
+ <element ref="svg:title"/>\r
+ <element ref="svg:metadata"/>\r
+ <element ref="svg:tspan"/>\r
+ <element ref="svg:tref"/>\r
+ <element ref="svg:altGlyph"/>\r
+ <element ref="svg:a"/>\r
+ <element ref="svg:animate"/>\r
+ <element ref="svg:set"/>\r
+ <element ref="svg:animateColor"/>\r
+ </choice>\r
+ <attributeGroup ref="svg:stdAttrs"/>\r
+ <attributeGroup ref="svg:xlinkRefAttrs"/>\r
+ <attribute ref="xlink:href" use="optional"/>\r
+ <attributeGroup ref="svg:langSpaceAttrs"/>\r
+ <attributeGroup ref="svg:testAttrs"/>\r
+ <attribute name="externalResourcesRequired" type="boolean" use="optional"/>\r
+ <attribute name="class" type="svg:ClassListType" use="optional"/>\r
+ <attribute name="style" type="svg:StyleSheetType" use="optional"/>\r
+ <attributeGroup ref="svg:PresentationAttributes-FillStroke"/>\r
+ <attributeGroup ref="svg:PresentationAttributes-FontSpecification"/>\r
+ <attributeGroup ref="svg:PresentationAttributes-Graphics"/>\r
+ <attributeGroup ref="svg:PresentationAttributes-TextContentElements"/>\r
+ <attributeGroup ref="svg:graphicsElementEvents"/>\r
+ <attribute name="startOffset" type="string" use="optional"/>\r
+ <attribute name="textLength" type="svg:LengthType" use="optional"/>\r
+ <attribute name="lengthAdjust" use="optional">\r
+ <simpleType>\r
+ <restriction base="string">\r
+ <enumeration value="spacing"/>\r
+ <enumeration value="spacingAndGlyphs"/>\r
+ </restriction>\r
+ </simpleType>\r
+ </attribute>\r
+ <attribute name="method" use="optional">\r
+ <simpleType>\r
+ <restriction base="string">\r
+ <enumeration value="align"/>\r
+ <enumeration value="stretch"/>\r
+ </restriction>\r
+ </simpleType>\r
+ </attribute>\r
+ <attribute name="spacing" use="optional">\r
+ <simpleType>\r
+ <restriction base="string">\r
+ <enumeration value="auto"/>\r
+ <enumeration value="exact"/>\r
+ </restriction>\r
+ </simpleType>\r
+ </attribute>\r
+ </complexType>\r
+ <complexType name="altGlyphType" mixed="true">\r
+ <sequence minOccurs="0" maxOccurs="unbounded"/>\r
+ <attributeGroup ref="svg:stdAttrs"/>\r
+ <attributeGroup ref="svg:xlinkRefAttrs"/>\r
+ <attribute ref="xlink:href" use="optional"/>\r
+ <attribute name="glyphRef" type="string" use="optional"/>\r
+ <attribute name="format" type="string" use="optional"/>\r
+ <attributeGroup ref="svg:testAttrs"/>\r
+ <attributeGroup ref="svg:langSpaceAttrs"/>\r
+ <attribute name="externalResourcesRequired" type="boolean" use="optional"/>\r
+ <attribute name="class" type="svg:ClassListType" use="optional"/>\r
+ <attribute name="style" type="svg:StyleSheetType" use="optional"/>\r
+ <attributeGroup ref="svg:PresentationAttributes-Color"/>\r
+ <attributeGroup ref="svg:PresentationAttributes-FillStroke"/>\r
+ <attributeGroup ref="svg:PresentationAttributes-FontSpecification"/>\r
+ <attributeGroup ref="svg:PresentationAttributes-Graphics"/>\r
+ <attributeGroup ref="svg:PresentationAttributes-TextContentElements"/>\r
+ <attributeGroup ref="svg:graphicsElementEvents"/>\r
+ <attribute name="x" type="svg:CoordinatesType" use="optional"/>\r
+ <attribute name="y" type="svg:CoordinatesType" use="optional"/>\r
+ <attribute name="dx" type="svg:LengthsType" use="optional"/>\r
+ <attribute name="dy" type="svg:LengthsType" use="optional"/>\r
+ <attribute name="rotate" type="string" use="optional"/>\r
+ </complexType>\r
+ <complexType name="altGlyphDefType">\r
+ <choice maxOccurs="unbounded">\r
+ <element ref="svg:altGlyphItem"/>\r
+ <element ref="svg:glyphRef"/>\r
+ </choice>\r
+ <attributeGroup ref="svg:stdAttrs"/>\r
+ </complexType>\r
+ <complexType name="altGlyphItemType">\r
+ <sequence maxOccurs="unbounded">\r
+ <element ref="svg:glyphRef"/>\r
+ </sequence>\r
+ <attributeGroup ref="svg:stdAttrs"/>\r
+ </complexType>\r
+ <complexType name="glyphRefType">\r
+ <attributeGroup ref="svg:stdAttrs"/>\r
+ <attributeGroup ref="svg:xlinkRefAttrs"/>\r
+ <attribute ref="xlink:href" use="optional"/>\r
+ <attribute name="class" type="svg:ClassListType" use="optional"/>\r
+ <attribute name="style" type="svg:StyleSheetType" use="optional"/>\r
+ <attributeGroup ref="svg:PresentationAttributes-FontSpecification"/>\r
+ <attribute name="glyphRef" type="string" use="required"/>\r
+ <attribute name="format" type="string" use="required"/>\r
+ <attribute name="x" type="svg:CoordinatesType" use="optional"/>\r
+ <attribute name="y" type="svg:CoordinatesType" use="optional"/>\r
+ <attribute name="dx" type="svg:LengthsType" use="optional"/>\r
+ <attribute name="dy" type="svg:LengthsType" use="optional"/>\r
+ </complexType>\r
+ <complexType name="markerType">\r
+ <choice minOccurs="0" maxOccurs="unbounded">\r
+ <element ref="svg:desc"/>\r
+ <element ref="svg:title"/>\r
+ <element ref="svg:metadata"/>\r
+ <element ref="svg:defs"/>\r
+ <element ref="svg:path"/>\r
+ <element ref="svg:text"/>\r
+ <element ref="svg:rect"/>\r
+ <element ref="svg:circle"/>\r
+ <element ref="svg:ellipse"/>\r
+ <element ref="svg:line"/>\r
+ <element ref="svg:polyline"/>\r
+ <element ref="svg:polygon"/>\r
+ <element ref="svg:use"/>\r
+ <element ref="svg:image"/>\r
+ <element ref="svg:svg"/>\r
+ <element ref="svg:g"/>\r
+ <element ref="svg:view"/>\r
+ <element ref="svg:switch"/>\r
+ <element ref="svg:a"/>\r
+ <element ref="svg:altGlyphDef"/>\r
+ <element ref="svg:script"/>\r
+ <element ref="svg:style"/>\r
+ <element ref="svg:symbol"/>\r
+ <element ref="svg:marker"/>\r
+ <element ref="svg:clipPath"/>\r
+ <element ref="svg:mask"/>\r
+ <element ref="svg:linearGradient"/>\r
+ <element ref="svg:radialGradient"/>\r
+ <element ref="svg:pattern"/>\r
+ <element ref="svg:filter"/>\r
+ <element ref="svg:cursor"/>\r
+ <element ref="svg:font"/>\r
+ <element ref="svg:animate"/>\r
+ <element ref="svg:set"/>\r
+ <element ref="svg:animateMotion"/>\r
+ <element ref="svg:animateColor"/>\r
+ <element ref="svg:animateTransform"/>\r
+ <element ref="svg:color-profile"/>\r
+ <element ref="svg:font-face"/>\r
+ </choice>\r
+ <attributeGroup ref="svg:stdAttrs"/>\r
+ <attributeGroup ref="svg:langSpaceAttrs"/>\r
+ <attribute name="externalResourcesRequired" type="boolean" use="optional"/>\r
+ <attribute name="class" type="svg:ClassListType" use="optional"/>\r
+ <attribute name="style" type="svg:StyleSheetType" use="optional"/>\r
+ <attributeGroup ref="svg:PresentationAttributes-All"/>\r
+ <attribute name="viewBox" type="svg:ViewBoxSpecType" use="optional"/>\r
+ <attribute name="preserveAspectRatio" type="svg:PreserveAspectRatioSpecType" default="xMidYMid meet"/>\r
+ <attribute name="refX" type="svg:CoordinateType" use="optional"/>\r
+ <attribute name="refY" type="svg:CoordinateType" use="optional"/>\r
+ <attribute name="markerUnits" use="optional">\r
+ <simpleType>\r
+ <restriction base="string">\r
+ <enumeration value="strokeWidth"/>\r
+ <enumeration value="userSpaceOnUse"/>\r
+ <enumeration value="userSpace"/>\r
+ </restriction>\r
+ </simpleType>\r
+ </attribute>\r
+ <attribute name="markerWidth" type="svg:LengthType" use="optional"/>\r
+ <attribute name="markerHeight" type="svg:LengthType" use="optional"/>\r
+ <attribute name="orient" type="string" use="optional"/>\r
+ </complexType>\r
+ <complexType name="color-profileType">\r
+ <sequence>\r
+ <group ref="svg:descTitleMetadata" minOccurs="0" maxOccurs="unbounded"/>\r
+ </sequence>\r
+ <attributeGroup ref="svg:stdAttrs"/>\r
+ <attributeGroup ref="svg:xlinkRefAttrs"/>\r
+ <attribute ref="xlink:href" use="optional"/>\r
+ <attribute name="local" type="string" use="optional"/>\r
+ <attribute name="name" type="string" use="required"/>\r
+ <attribute name="rendering-intent" default="auto">\r
+ <simpleType>\r
+ <restriction base="string">\r
+ <enumeration value="auto"/>\r
+ <enumeration value="perceptual"/>\r
+ <enumeration value="relative-colorimetric"/>\r
+ <enumeration value="saturation"/>\r
+ <enumeration value="absolute-colorimetric"/>\r
+ </restriction>\r
+ </simpleType>\r
+ </attribute>\r
+ </complexType>\r
+ <complexType name="linearGradientType">\r
+ <sequence>\r
+ <group ref="svg:descTitleMetadata" minOccurs="0" maxOccurs="unbounded"/>\r
+ <choice minOccurs="0" maxOccurs="unbounded">\r
+ <element ref="svg:stop"/>\r
+ <element ref="svg:animate"/>\r
+ <element ref="svg:set"/>\r
+ <element ref="svg:animateTransform"/>\r
+ </choice>\r
+ </sequence>\r
+ <attributeGroup ref="svg:stdAttrs"/>\r
+ <attributeGroup ref="svg:xlinkRefAttrs"/>\r
+ <attribute ref="xlink:href" use="optional"/>\r
+ <attribute name="externalResourcesRequired" type="boolean" use="optional"/>\r
+ <attribute name="class" type="svg:ClassListType" use="optional"/>\r
+ <attribute name="style" type="svg:StyleSheetType" use="optional"/>\r
+ <attribute name="gradientUnits" use="optional"><!-- @@ need to add more attributes here @@ -->\r
+ <simpleType>\r
+ <restriction base="string">\r
+ <enumeration value="userSpaceOnUse"/>\r
+ <enumeration value="userSpace"/>\r
+ <enumeration value="objectBoundingBox"/>\r
+ </restriction>\r
+ </simpleType>\r
+ </attribute>\r
+ <attribute name="gradientTransform" type="svg:TransformListType" use="optional"/>\r
+ <attribute name="x1" type="svg:CoordinateType" use="optional"/>\r
+ <attribute name="y1" type="svg:CoordinateType" use="optional"/>\r
+ <attribute name="x2" type="svg:CoordinateType" use="optional"/>\r
+ <attribute name="y2" type="svg:CoordinateType" use="optional"/>\r
+ <attribute name="spreadMethod" default="pad">\r
+ <simpleType>\r
+ <restriction base="string">\r
+ <enumeration value="pad"/>\r
+ <enumeration value="reflect"/>\r
+ <enumeration value="repeat"/>\r
+ </restriction>\r
+ </simpleType>\r
+ </attribute>\r
+ </complexType>\r
+ <complexType name="radialGradientType">\r
+ <sequence>\r
+ <group ref="svg:descTitleMetadata" minOccurs="0" maxOccurs="unbounded"/>\r
+ <choice minOccurs="0" maxOccurs="unbounded">\r
+ <element ref="svg:stop"/>\r
+ <element ref="svg:animate"/>\r
+ <element ref="svg:set"/>\r
+ <element ref="svg:animateTransform"/>\r
+ </choice>\r
+ </sequence>\r
+ <attributeGroup ref="svg:stdAttrs"/>\r
+ <attributeGroup ref="svg:xlinkRefAttrs"/>\r
+ <attribute ref="xlink:href" use="optional"/>\r
+ <attribute name="externalResourcesRequired" type="boolean" use="optional"/>\r
+ <attribute name="gradientUnits" use="optional">\r
+ <simpleType>\r
+ <restriction base="string">\r
+ <enumeration value="userSpaceOnUse"/>\r
+ <enumeration value="userSpace"/>\r
+ <enumeration value="objectBoundingBox"/>\r
+ </restriction>\r
+ </simpleType>\r
+ </attribute>\r
+ <attribute name="gradientTransform" type="svg:TransformListType" use="optional"/>\r
+ <attribute name="cx" type="svg:CoordinateType" use="optional"/>\r
+ <attribute name="cy" type="svg:CoordinateType" use="optional"/>\r
+ <attribute name="r" type="svg:LengthType" use="optional"/>\r
+ <attribute name="fx" type="svg:CoordinateType" use="optional"/>\r
+ <attribute name="fy" type="svg:CoordinateType" use="optional"/>\r
+ <attribute name="spreadMethod" default="pad">\r
+ <simpleType>\r
+ <restriction base="string">\r
+ <enumeration value="pad"/>\r
+ <enumeration value="reflect"/>\r
+ <enumeration value="repeat"/>\r
+ </restriction>\r
+ </simpleType>\r
+ </attribute>\r
+ </complexType>\r
+ <complexType name="stopType">\r
+ <choice minOccurs="0" maxOccurs="unbounded">\r
+ <element ref="svg:animate"/>\r
+ <element ref="svg:set"/>\r
+ <element ref="svg:animateColor"/>\r
+ </choice>\r
+ <attributeGroup ref="svg:stdAttrs"/>\r
+ <attribute name="class" type="svg:ClassListType" use="optional"/>\r
+ <attribute name="style" type="svg:StyleSheetType" use="optional"/>\r
+ <attributeGroup ref="svg:PresentationAttributes-Gradients"/>\r
+ <attribute name="offset" type="svg:LengthType" use="required"/>\r
+ </complexType>\r
+ <complexType name="patternType">\r
+ <choice minOccurs="0" maxOccurs="unbounded">\r
+ <element ref="svg:desc"/>\r
+ <element ref="svg:title"/>\r
+ <element ref="svg:metadata"/>\r
+ <element ref="svg:defs"/>\r
+ <element ref="svg:path"/>\r
+ <element ref="svg:text"/>\r
+ <element ref="svg:rect"/>\r
+ <element ref="svg:circle"/>\r
+ <element ref="svg:ellipse"/>\r
+ <element ref="svg:line"/>\r
+ <element ref="svg:polyline"/>\r
+ <element ref="svg:polygon"/>\r
+ <element ref="svg:use"/>\r
+ <element ref="svg:image"/>\r
+ <element ref="svg:svg"/>\r
+ <element ref="svg:g"/>\r
+ <element ref="svg:view"/>\r
+ <element ref="svg:switch"/>\r
+ <element ref="svg:a"/>\r
+ <element ref="svg:altGlyphDef"/>\r
+ <element ref="svg:script"/>\r
+ <element ref="svg:style"/>\r
+ <element ref="svg:symbol"/>\r
+ <element ref="svg:marker"/>\r
+ <element ref="svg:clipPath"/>\r
+ <element ref="svg:mask"/>\r
+ <element ref="svg:linearGradient"/>\r
+ <element ref="svg:radialGradient"/>\r
+ <element ref="svg:pattern"/>\r
+ <element ref="svg:filter"/>\r
+ <element ref="svg:cursor"/>\r
+ <element ref="svg:font"/>\r
+ <element ref="svg:animate"/>\r
+ <element ref="svg:set"/>\r
+ <element ref="svg:animateMotion"/>\r
+ <element ref="svg:animateColor"/>\r
+ <element ref="svg:animateTransform"/>\r
+ <element ref="svg:color-profile"/>\r
+ <element ref="svg:font-face"/>\r
+ </choice>\r
+ <attributeGroup ref="svg:stdAttrs"/>\r
+ <attributeGroup ref="svg:xlinkRefAttrs"/>\r
+ <attribute ref="xlink:href" use="optional"/>\r
+ <attributeGroup ref="svg:testAttrs"/>\r
+ <attributeGroup ref="svg:langSpaceAttrs"/>\r
+ <attribute name="externalResourcesRequired" type="boolean" use="optional"/>\r
+ <attribute name="class" type="svg:ClassListType" use="optional"/>\r
+ <attribute name="style" type="svg:StyleSheetType" use="optional"/>\r
+ <attributeGroup ref="svg:PresentationAttributes-All"/>\r
+ <attribute name="viewBox" type="svg:ViewBoxSpecType" use="optional"/>\r
+ <attribute name="preserveAspectRatio" type="svg:PreserveAspectRatioSpecType" default="xMidYMid meet"/>\r
+ <attribute name="patternUnits" use="optional">\r
+ <simpleType>\r
+ <restriction base="string">\r
+ <enumeration value="userSpaceOnUse"/>\r
+ <enumeration value="userSpace"/>\r
+ <enumeration value="objectBoundingBox"/>\r
+ </restriction>\r
+ </simpleType>\r
+ </attribute>\r
+ <attribute name="patternTransform" type="svg:TransformListType" use="optional"/>\r
+ <attribute name="x" type="svg:CoordinateType" use="optional"/>\r
+ <attribute name="y" type="svg:CoordinateType" use="optional"/>\r
+ <attribute name="width" type="svg:LengthType" use="required"/>\r
+ <attribute name="height" type="svg:LengthType" use="required"/>\r
+ </complexType>\r
+ <complexType name="clipPathType">\r
+ <sequence>\r
+ <group ref="svg:descTitleMetadata" minOccurs="0" maxOccurs="unbounded"/>\r
+ <choice minOccurs="0" maxOccurs="unbounded">\r
+ <element ref="svg:path"/>\r
+ <element ref="svg:text"/>\r
+ <element ref="svg:rect"/>\r
+ <element ref="svg:circle"/>\r
+ <element ref="svg:ellipse"/>\r
+ <element ref="svg:line"/>\r
+ <element ref="svg:polyline"/>\r
+ <element ref="svg:polygon"/>\r
+ <element ref="svg:use"/>\r
+ <element ref="svg:animate"/>\r
+ <element ref="svg:set"/>\r
+ <element ref="svg:animateMotion"/>\r
+ <element ref="svg:animateColor"/>\r
+ <element ref="svg:animateTransform"/>\r
+ </choice>\r
+ </sequence>\r
+ <attributeGroup ref="svg:stdAttrs"/>\r
+ <attributeGroup ref="svg:testAttrs"/>\r
+ <attributeGroup ref="svg:langSpaceAttrs"/>\r
+ <attribute name="externalResourcesRequired" type="boolean" use="optional"/>\r
+ <attribute name="class" type="svg:ClassListType" use="optional"/>\r
+ <attribute name="style" type="svg:StyleSheetType" use="optional"/>\r
+ <attributeGroup ref="svg:PresentationAttributes-FillStroke"/>\r
+ <attributeGroup ref="svg:PresentationAttributes-FontSpecification"/>\r
+ <attributeGroup ref="svg:PresentationAttributes-Graphics"/>\r
+ <attributeGroup ref="svg:PresentationAttributes-TextContentElements"/>\r
+ <attributeGroup ref="svg:PresentationAttributes-TextElements"/>\r
+ <attribute name="transform" type="svg:TransformListType" use="optional"/>\r
+ <attribute name="clipPathUnits" use="optional">\r
+ <simpleType>\r
+ <restriction base="string">\r
+ <enumeration value="userSpaceOnUse"/>\r
+ <enumeration value="userSpace"/>\r
+ <enumeration value="objectBoundingBox"/>\r
+ </restriction>\r
+ </simpleType>\r
+ </attribute>\r
+ </complexType>\r
+ <complexType name="maskType">\r
+ <sequence>\r
+ <choice minOccurs="0" maxOccurs="unbounded">\r
+ <element ref="svg:desc"/>\r
+ <element ref="svg:title"/>\r
+ <element ref="svg:metadata"/>\r
+ <element ref="svg:defs"/>\r
+ <element ref="svg:path"/>\r
+ <element ref="svg:text"/>\r
+ <element ref="svg:rect"/>\r
+ <element ref="svg:circle"/>\r
+ <element ref="svg:ellipse"/>\r
+ <element ref="svg:line"/>\r
+ <element ref="svg:polyline"/>\r
+ <element ref="svg:polygon"/>\r
+ <element ref="svg:use"/>\r
+ <element ref="svg:image"/>\r
+ <element ref="svg:svg"/>\r
+ <element ref="svg:g"/>\r
+ <element ref="svg:view"/>\r
+ <element ref="svg:switch"/>\r
+ <element ref="svg:a"/>\r
+ <element ref="svg:altGlyphDef"/>\r
+ <element ref="svg:script"/>\r
+ <element ref="svg:style"/>\r
+ <element ref="svg:symbol"/>\r
+ <element ref="svg:marker"/>\r
+ <element ref="svg:clipPath"/>\r
+ <element ref="svg:mask"/>\r
+ <element ref="svg:linearGradient"/>\r
+ <element ref="svg:radialGradient"/>\r
+ <element ref="svg:pattern"/>\r
+ <element ref="svg:filter"/>\r
+ <element ref="svg:cursor"/>\r
+ <element ref="svg:font"/>\r
+ <element ref="svg:animate"/>\r
+ <element ref="svg:set"/>\r
+ <element ref="svg:animateMotion"/>\r
+ <element ref="svg:animateColor"/>\r
+ <element ref="svg:animateTransform"/>\r
+ <element ref="svg:color-profile"/>\r
+ <element ref="svg:font-face"/>\r
+ </choice>\r
+ </sequence>\r
+ <attributeGroup ref="svg:stdAttrs"/>\r
+ <attributeGroup ref="svg:testAttrs"/>\r
+ <attributeGroup ref="svg:langSpaceAttrs"/>\r
+ <attribute name="externalResourcesRequired" type="boolean" use="optional"/>\r
+ <attribute name="class" type="svg:ClassListType" use="optional"/>\r
+ <attribute name="style" type="svg:StyleSheetType" use="optional"/>\r
+ <attributeGroup ref="svg:PresentationAttributes-All"/>\r
+ <attribute name="transform" type="svg:TransformListType" use="optional"/>\r
+ <attribute name="maskUnits" use="optional">\r
+ <simpleType>\r
+ <restriction base="string">\r
+ <enumeration value="userSpaceOnUse"/>\r
+ <enumeration value="userSpace"/>\r
+ <enumeration value="objectBoundingBox"/>\r
+ </restriction>\r
+ </simpleType>\r
+ </attribute>\r
+ <attribute name="x" type="svg:CoordinateType" use="optional"/>\r
+ <attribute name="y" type="svg:CoordinateType" use="optional"/>\r
+ <attribute name="width" type="svg:LengthType" use="optional"/>\r
+ <attribute name="height" type="svg:LengthType" use="optional"/>\r
+ </complexType>\r
+ <complexType name="filterType">\r
+ <sequence>\r
+ <group ref="svg:descTitleMetadata" minOccurs="0" maxOccurs="unbounded"/>\r
+ <choice minOccurs="0" maxOccurs="unbounded">\r
+ <element ref="svg:feBlend"/>\r
+ <element ref="svg:feFlood"/>\r
+ <element ref="svg:feColorMatrix"/>\r
+ <element ref="svg:feComponentTransfer"/>\r
+ <element ref="svg:feComposite"/>\r
+ <element ref="svg:feConvolveMatrix"/>\r
+ <element ref="svg:feDiffuseLighting"/>\r
+ <element ref="svg:feDisplacementMap"/>\r
+ <element ref="svg:feGaussianBlur"/>\r
+ <element ref="svg:feImage"/>\r
+ <element ref="svg:feMerge"/>\r
+ <element ref="svg:feMorphology"/>\r
+ <element ref="svg:feOffset"/>\r
+ <element ref="svg:feSpecularLighting"/>\r
+ <element ref="svg:feTile"/>\r
+ <element ref="svg:feTurbulence"/>\r
+ <element ref="svg:animate"/>\r
+ <element ref="svg:set"/>\r
+ </choice>\r
+ </sequence>\r
+ <attributeGroup ref="svg:stdAttrs"/>\r
+ <attributeGroup ref="svg:xlinkRefAttrs"/>\r
+ <attribute ref="xlink:href" use="optional"/>\r
+ <attributeGroup ref="svg:langSpaceAttrs"/>\r
+ <attribute name="externalResourcesRequired" type="boolean" use="optional"/>\r
+ <attribute name="class" type="svg:ClassListType" use="optional"/>\r
+ <attribute name="style" type="svg:StyleSheetType" use="optional"/>\r
+ <attributeGroup ref="svg:PresentationAttributes-All"/>\r
+ <attribute name="filterUnits" use="optional">\r
+ <simpleType>\r
+ <restriction base="string">\r
+ <enumeration value="userSpaceOnUse"/>\r
+ <enumeration value="userSpace"/>\r
+ <enumeration value="objectBoundingBox"/>\r
+ </restriction>\r
+ </simpleType>\r
+ </attribute>\r
+ <attribute name="primitiveUnits" use="optional">\r
+ <simpleType>\r
+ <restriction base="string">\r
+ <enumeration value="userSpaceOnUse"/>\r
+ <enumeration value="userSpace"/>\r
+ <enumeration value="objectBoundingBox"/>\r
+ </restriction>\r
+ </simpleType>\r
+ </attribute>\r
+ <attribute name="x" type="svg:CoordinateType" use="optional"/>\r
+ <attribute name="y" type="svg:CoordinateType" use="optional"/>\r
+ <attribute name="width" type="svg:LengthType" use="optional"/>\r
+ <attribute name="height" type="svg:LengthType" use="optional"/>\r
+ <attribute name="filterRes" type="string" use="optional"/>\r
+ </complexType>\r
+ <complexType name="feDistantLightType">\r
+ <choice minOccurs="0" maxOccurs="unbounded">\r
+ <element ref="svg:animate"/>\r
+ <element ref="svg:set"/>\r
+ </choice>\r
+ <attributeGroup ref="svg:stdAttrs"/>\r
+ <attribute name="azimuth" type="double" use="optional"/>\r
+ <attribute name="elevation" type="double" use="optional"/>\r
+ </complexType>\r
+ <complexType name="fePointLightType">\r
+ <choice minOccurs="0" maxOccurs="unbounded">\r
+ <element ref="svg:animate"/>\r
+ <element ref="svg:set"/>\r
+ </choice>\r
+ <attributeGroup ref="svg:stdAttrs"/>\r
+ <attribute name="x" type="double" use="optional"/>\r
+ <attribute name="y" type="double" use="optional"/>\r
+ <attribute name="z" type="double" use="optional"/>\r
+ </complexType>\r
+ <complexType name="feSpotLightType">\r
+ <choice minOccurs="0" maxOccurs="unbounded">\r
+ <element ref="svg:animate"/>\r
+ <element ref="svg:set"/>\r
+ </choice>\r
+ <attributeGroup ref="svg:stdAttrs"/>\r
+ <attribute name="x" type="double" use="optional"/>\r
+ <attribute name="y" type="double" use="optional"/>\r
+ <attribute name="z" type="double" use="optional"/>\r
+ <attribute name="pointsAtX" type="double" use="optional"/>\r
+ <attribute name="pointsAtY" type="double" use="optional"/>\r
+ <attribute name="pointsAtZ" type="double" use="optional"/>\r
+ <attribute name="specularExponent" type="double" use="optional"/>\r
+ <attribute name="limitingConeAngle" type="double" use="optional"/>\r
+ </complexType>\r
+ <complexType name="feBlendType">\r
+ <choice minOccurs="0" maxOccurs="unbounded">\r
+ <element ref="svg:animate"/>\r
+ <element ref="svg:set"/>\r
+ </choice>\r
+ <attributeGroup ref="svg:stdAttrs"/>\r
+ <attributeGroup ref="svg:filter_primitive_attributes_with_in"/>\r
+ <attribute name="in2" type="string" use="required"/>\r
+ <attribute name="mode" default="normal">\r
+ <simpleType>\r
+ <restriction base="string">\r
+ <enumeration value="normal"/>\r
+ <enumeration value="multiply"/>\r
+ <enumeration value="screen"/>\r
+ <enumeration value="darken"/>\r
+ <enumeration value="lighten"/>\r
+ </restriction>\r
+ </simpleType>\r
+ </attribute>\r
+ </complexType>\r
+ <complexType name="feColorMatrixType">\r
+ <choice minOccurs="0" maxOccurs="unbounded">\r
+ <element ref="svg:animate"/>\r
+ <element ref="svg:set"/>\r
+ </choice>\r
+ <attributeGroup ref="svg:stdAttrs"/>\r
+ <attributeGroup ref="svg:filter_primitive_attributes_with_in"/>\r
+ <attribute name="type" default="matrix">\r
+ <simpleType>\r
+ <restriction base="string">\r
+ <enumeration value="matrix"/>\r
+ <enumeration value="saturate"/>\r
+ <enumeration value="hueRotate"/>\r
+ <enumeration value="luminanceToAlpha"/>\r
+ </restriction>\r
+ </simpleType>\r
+ </attribute>\r
+ <attribute name="values" type="string" use="optional"/>\r
+ </complexType>\r
+ <complexType name="feComponentTransferType">\r
+ <sequence>\r
+ <element ref="svg:feFuncR" minOccurs="0"/>\r
+ <element ref="svg:feFuncG" minOccurs="0"/>\r
+ <element ref="svg:feFuncB" minOccurs="0"/>\r
+ <element ref="svg:feFuncA" minOccurs="0"/>\r
+ </sequence>\r
+ <attributeGroup ref="svg:stdAttrs"/>\r
+ <attributeGroup ref="svg:filter_primitive_attributes_with_in"/>\r
+ </complexType>\r
+ <complexType name="feFuncRType">\r
+ <choice minOccurs="0" maxOccurs="unbounded">\r
+ <element ref="svg:animate"/>\r
+ <element ref="svg:set"/>\r
+ </choice>\r
+ <attributeGroup ref="svg:stdAttrs"/>\r
+ <attributeGroup ref="svg:component_transfer_function_attributes"/>\r
+ </complexType>\r
+ <complexType name="feFuncGType">\r
+ <choice minOccurs="0" maxOccurs="unbounded">\r
+ <element ref="svg:animate"/>\r
+ <element ref="svg:set"/>\r
+ </choice>\r
+ <attributeGroup ref="svg:stdAttrs"/>\r
+ <attributeGroup ref="svg:component_transfer_function_attributes"/>\r
+ </complexType>\r
+ <complexType name="feFuncBType">\r
+ <choice minOccurs="0" maxOccurs="unbounded">\r
+ <element ref="svg:animate"/>\r
+ <element ref="svg:set"/>\r
+ </choice>\r
+ <attributeGroup ref="svg:stdAttrs"/>\r
+ <attributeGroup ref="svg:component_transfer_function_attributes"/>\r
+ </complexType>\r
+ <complexType name="feFuncAType">\r
+ <choice minOccurs="0" maxOccurs="unbounded">\r
+ <element ref="svg:animate"/>\r
+ <element ref="svg:set"/>\r
+ </choice>\r
+ <attributeGroup ref="svg:stdAttrs"/>\r
+ <attributeGroup ref="svg:component_transfer_function_attributes"/>\r
+ </complexType>\r
+ <complexType name="feCompositeType">\r
+ <choice minOccurs="0" maxOccurs="unbounded">\r
+ <element ref="svg:animate"/>\r
+ <element ref="svg:set"/>\r
+ </choice>\r
+ <attributeGroup ref="svg:stdAttrs"/>\r
+ <attributeGroup ref="svg:filter_primitive_attributes_with_in"/>\r
+ <attribute name="in2" type="string" use="required"/>\r
+ <attribute name="operator" default="over">\r
+ <simpleType>\r
+ <restriction base="string">\r
+ <enumeration value="over"/>\r
+ <enumeration value="in"/>\r
+ <enumeration value="out"/>\r
+ <enumeration value="atop"/>\r
+ <enumeration value="xor"/>\r
+ <enumeration value="arithmetic"/>\r
+ </restriction>\r
+ </simpleType>\r
+ </attribute>\r
+ <attribute name="k1" type="double" use="optional"/>\r
+ <attribute name="k2" type="double" use="optional"/>\r
+ <attribute name="k3" type="double" use="optional"/>\r
+ <attribute name="k4" type="double" use="optional"/>\r
+ </complexType>\r
+ <complexType name="feConvolveMatrixType">\r
+ <choice minOccurs="0" maxOccurs="unbounded">\r
+ <element ref="svg:animate"/>\r
+ <element ref="svg:set"/>\r
+ </choice>\r
+ <attributeGroup ref="svg:filter_primitive_attributes_with_in"/>\r
+ <attribute name="order" type="string" use="required"/>\r
+ <attribute name="kernelMatrix" type="string" use="required"/>\r
+ <attribute name="divisor" type="double" use="optional"/>\r
+ <attribute name="bias" type="double" use="optional"/>\r
+ <attribute name="targetX" type="integer" use="optional"/>\r
+ <attribute name="targetY" type="integer" use="optional"/>\r
+ <attribute name="edgeMode" default="duplicate">\r
+ <simpleType>\r
+ <restriction base="string">\r
+ <enumeration value="duplicate"/>\r
+ <enumeration value="wrap"/>\r
+ <enumeration value="none"/>\r
+ </restriction>\r
+ </simpleType>\r
+ </attribute>\r
+ <attribute name="kernelUnitLength" type="string" use="optional"/>\r
+ <attribute name="preserveAlpha" type="boolean" use="optional"/>\r
+ </complexType>\r
+ <complexType name="feDiffuseLightingType">\r
+ <sequence>\r
+ <choice>\r
+ <element ref="svg:feDistantLight"/>\r
+ <element ref="svg:fePointLight"/>\r
+ <element ref="svg:feSpotLight"/>\r
+ </choice>\r
+ <choice minOccurs="0" maxOccurs="unbounded">\r
+ <element ref="svg:animate"/>\r
+ <element ref="svg:set"/>\r
+ <element ref="svg:animateColor"/>\r
+ </choice>\r
+ </sequence>\r
+ <attributeGroup ref="svg:stdAttrs"/>\r
+ <attribute name="class" type="svg:ClassListType" use="optional"/>\r
+ <attribute name="style" type="svg:StyleSheetType" use="optional"/>\r
+ <attributeGroup ref="svg:PresentationAttributes-LightingEffects"/>\r
+ <attributeGroup ref="svg:filter_primitive_attributes_with_in"/>\r
+ <attribute name="surfaceScale" type="double" use="optional"/>\r
+ <attribute name="diffuseConstant" type="double" use="optional"/>\r
+ </complexType>\r
+ <complexType name="feDisplacementMapType">\r
+ <choice minOccurs="0" maxOccurs="unbounded">\r
+ <element ref="svg:animate"/>\r
+ <element ref="svg:set"/>\r
+ </choice>\r
+ <attributeGroup ref="svg:stdAttrs"/>\r
+ <attributeGroup ref="svg:filter_primitive_attributes_with_in"/>\r
+ <attribute name="in2" type="string" use="required"/>\r
+ <attribute name="scale" type="double" use="optional"/>\r
+ <attribute name="xChannelSelector" default="A">\r
+ <simpleType>\r
+ <restriction base="string">\r
+ <enumeration value="R"/>\r
+ <enumeration value="G"/>\r
+ <enumeration value="B"/>\r
+ <enumeration value="A"/>\r
+ </restriction>\r
+ </simpleType>\r
+ </attribute>\r
+ <attribute name="yChannelSelector" default="A">\r
+ <simpleType>\r
+ <restriction base="string">\r
+ <enumeration value="R"/>\r
+ <enumeration value="G"/>\r
+ <enumeration value="B"/>\r
+ <enumeration value="A"/>\r
+ </restriction>\r
+ </simpleType>\r
+ </attribute>\r
+ </complexType>\r
+ <complexType name="feFloodType">\r
+ <choice minOccurs="0" maxOccurs="unbounded">\r
+ <element ref="svg:animate"/>\r
+ <element ref="svg:set"/>\r
+ <element ref="svg:animateColor"/>\r
+ </choice>\r
+ <attributeGroup ref="svg:stdAttrs"/>\r
+ <attribute name="class" type="svg:ClassListType" use="optional"/>\r
+ <attribute name="style" type="svg:StyleSheetType" use="optional"/>\r
+ <attributeGroup ref="svg:PresentationAttributes-feFlood"/>\r
+ <attributeGroup ref="svg:filter_primitive_attributes_with_in"/>\r
+ </complexType>\r
+ <complexType name="feGaussianBlurType">\r
+ <choice minOccurs="0" maxOccurs="unbounded">\r
+ <element ref="svg:animate"/>\r
+ <element ref="svg:set"/>\r
+ </choice>\r
+ <attributeGroup ref="svg:stdAttrs"/>\r
+ <attributeGroup ref="svg:filter_primitive_attributes_with_in"/>\r
+ <attribute name="stdDeviation" type="string" use="optional"/>\r
+ </complexType>\r
+ <complexType name="feImageType">\r
+ <choice minOccurs="0" maxOccurs="unbounded">\r
+ <element ref="svg:animate"/>\r
+ <element ref="svg:set"/>\r
+ <element ref="svg:animateTransform"/>\r
+ </choice>\r
+ <attributeGroup ref="svg:stdAttrs"/>\r
+ <attributeGroup ref="svg:xlinkRefAttrs"/>\r
+ <attribute ref="xlink:href" use="optional"/>\r
+ <attributeGroup ref="svg:langSpaceAttrs"/>\r
+ <attribute name="externalResourcesRequired" type="boolean" use="optional"/>\r
+ <attribute name="class" type="svg:ClassListType" use="optional"/>\r
+ <attribute name="style" type="svg:StyleSheetType" use="optional"/>\r
+ <attributeGroup ref="svg:PresentationAttributes-All"/>\r
+ <attribute name="transform" type="svg:TransformListType" use="optional"/>\r
+ <attributeGroup ref="svg:filter_primitive_attributes"/>\r
+ </complexType>\r
+ <complexType name="feMergeType">\r
+ <sequence minOccurs="0" maxOccurs="unbounded">\r
+ <element ref="svg:feMergeNode"/>\r
+ </sequence>\r
+ <attributeGroup ref="svg:stdAttrs"/>\r
+ <attributeGroup ref="svg:filter_primitive_attributes"/>\r
+ </complexType>\r
+ <complexType name="feMergeNodeType">\r
+ <choice minOccurs="0" maxOccurs="unbounded">\r
+ <element ref="svg:animate"/>\r
+ <element ref="svg:set"/>\r
+ </choice>\r
+ <attributeGroup ref="svg:stdAttrs"/>\r
+ <attribute name="in" type="string" use="optional"/>\r
+ </complexType>\r
+ <complexType name="feMorphologyType">\r
+ <choice minOccurs="0" maxOccurs="unbounded">\r
+ <element ref="svg:animate"/>\r
+ <element ref="svg:set"/>\r
+ </choice>\r
+ <attributeGroup ref="svg:stdAttrs"/>\r
+ <attributeGroup ref="svg:filter_primitive_attributes_with_in"/>\r
+ <attribute name="operator" default="erode">\r
+ <simpleType>\r
+ <restriction base="string">\r
+ <enumeration value="erode"/>\r
+ <enumeration value="dilate"/>\r
+ </restriction>\r
+ </simpleType>\r
+ </attribute>\r
+ <attribute name="radius" type="svg:LengthType" use="optional"/>\r
+ </complexType>\r
+ <complexType name="feOffsetType">\r
+ <choice minOccurs="0" maxOccurs="unbounded">\r
+ <element ref="svg:animate"/>\r
+ <element ref="svg:set"/>\r
+ </choice>\r
+ <attributeGroup ref="svg:stdAttrs"/>\r
+ <attributeGroup ref="svg:filter_primitive_attributes_with_in"/>\r
+ <attribute name="dx" type="svg:LengthType" use="optional"/>\r
+ <attribute name="dy" type="svg:LengthType" use="optional"/>\r
+ </complexType>\r
+ <complexType name="feSpecularLightingType">\r
+ <sequence>\r
+ <choice>\r
+ <element ref="svg:feDistantLight"/>\r
+ <element ref="svg:fePointLight"/>\r
+ <element ref="svg:feSpotLight"/>\r
+ </choice>\r
+ <choice minOccurs="0" maxOccurs="unbounded">\r
+ <element ref="svg:animate"/>\r
+ <element ref="svg:set"/>\r
+ <element ref="svg:animateColor"/>\r
+ </choice>\r
+ </sequence>\r
+ <attributeGroup ref="svg:stdAttrs"/>\r
+ <attribute name="class" type="svg:ClassListType" use="optional"/>\r
+ <attribute name="style" type="svg:StyleSheetType" use="optional"/>\r
+ <attributeGroup ref="svg:PresentationAttributes-LightingEffects"/>\r
+ <attributeGroup ref="svg:filter_primitive_attributes_with_in"/>\r
+ <attribute name="surfaceScale" type="double" use="optional"/>\r
+ <attribute name="specularConstant" type="double" use="optional"/>\r
+ <attribute name="specularExponent" type="double" use="optional"/>\r
+ </complexType>\r
+ <complexType name="feTileType">\r
+ <choice minOccurs="0" maxOccurs="unbounded">\r
+ <element ref="svg:animate"/>\r
+ <element ref="svg:set"/>\r
+ </choice>\r
+ <attributeGroup ref="svg:stdAttrs"/>\r
+ <attributeGroup ref="svg:filter_primitive_attributes_with_in"/>\r
+ </complexType>\r
+ <complexType name="feTurbulenceType">\r
+ <choice minOccurs="0" maxOccurs="unbounded">\r
+ <element ref="svg:animate"/>\r
+ <element ref="svg:set"/>\r
+ </choice>\r
+ <attributeGroup ref="svg:stdAttrs"/>\r
+ <attributeGroup ref="svg:filter_primitive_attributes"/>\r
+ <attribute name="baseFrequency" type="string" use="optional"/>\r
+ <attribute name="numOctaves" type="integer" use="optional"/>\r
+ <attribute name="seed" type="double" use="optional"/>\r
+ <attribute name="stitchTiles" default="noStitch">\r
+ <simpleType>\r
+ <restriction base="string">\r
+ <enumeration value="stitch"/>\r
+ <enumeration value="noStitch"/>\r
+ </restriction>\r
+ </simpleType>\r
+ </attribute>\r
+ <attribute name="type" default="turbulence">\r
+ <simpleType>\r
+ <restriction base="string">\r
+ <enumeration value="fractalNoise"/>\r
+ <enumeration value="turbulence"/>\r
+ </restriction>\r
+ </simpleType>\r
+ </attribute>\r
+ </complexType>\r
+ <complexType name="cursorType">\r
+ <group ref="svg:descTitleMetadata" minOccurs="0" maxOccurs="unbounded"/>\r
+ <attributeGroup ref="svg:stdAttrs"/>\r
+ <attributeGroup ref="svg:xlinkRefAttrs"/>\r
+ <attribute ref="xlink:href" use="optional"/>\r
+ <attributeGroup ref="svg:testAttrs"/>\r
+ <attribute name="externalResourcesRequired" type="boolean" use="optional"/>\r
+ <attribute name="x" type="svg:CoordinateType" use="optional"/>\r
+ <attribute name="y" type="svg:CoordinateType" use="optional"/>\r
+ </complexType>\r
+ <complexType name="aType" mixed="true">\r
+ <choice minOccurs="0" maxOccurs="unbounded">\r
+ <element ref="svg:desc"/>\r
+ <element ref="svg:title"/>\r
+ <element ref="svg:metadata"/>\r
+ <element ref="svg:defs"/>\r
+ <element ref="svg:path"/>\r
+ <element ref="svg:text"/>\r
+ <element ref="svg:rect"/>\r
+ <element ref="svg:circle"/>\r
+ <element ref="svg:ellipse"/>\r
+ <element ref="svg:line"/>\r
+ <element ref="svg:polyline"/>\r
+ <element ref="svg:polygon"/>\r
+ <element ref="svg:use"/>\r
+ <element ref="svg:image"/>\r
+ <element ref="svg:svg"/>\r
+ <element ref="svg:g"/>\r
+ <element ref="svg:view"/>\r
+ <element ref="svg:switch"/>\r
+ <element ref="svg:a"/>\r
+ <element ref="svg:altGlyphDef"/>\r
+ <element ref="svg:script"/>\r
+ <element ref="svg:style"/>\r
+ <element ref="svg:symbol"/>\r
+ <element ref="svg:marker"/>\r
+ <element ref="svg:clipPath"/>\r
+ <element ref="svg:mask"/>\r
+ <element ref="svg:linearGradient"/>\r
+ <element ref="svg:radialGradient"/>\r
+ <element ref="svg:pattern"/>\r
+ <element ref="svg:filter"/>\r
+ <element ref="svg:cursor"/>\r
+ <element ref="svg:font"/>\r
+ <element ref="svg:animate"/>\r
+ <element ref="svg:set"/>\r
+ <element ref="svg:animateMotion"/>\r
+ <element ref="svg:animateColor"/>\r
+ <element ref="svg:animateTransform"/>\r
+ <element ref="svg:color-profile"/>\r
+ <element ref="svg:font-face"/>\r
+ </choice>\r
+ <attributeGroup ref="svg:stdAttrs"/>\r
+ <attribute ref="xlink:type" fixed="simple"/>\r
+ <attribute ref="xlink:role"/>\r
+ <attribute ref="xlink:arcrole"/>\r
+ <attribute ref="xlink:title"/>\r
+ <attribute ref="xlink:show"/>\r
+ <attribute ref="xlink:actuate" fixed="onRequest"/>\r
+ <attribute ref="xlink:href" use="optional"/>\r
+ <attributeGroup ref="svg:testAttrs"/>\r
+ <attributeGroup ref="svg:langSpaceAttrs"/>\r
+ <attribute name="externalResourcesRequired" type="boolean" use="optional"/>\r
+ <attribute name="class" type="svg:ClassListType" use="optional"/>\r
+ <attribute name="style" type="svg:StyleSheetType" use="optional"/>\r
+ <attributeGroup ref="svg:PresentationAttributes-All"/>\r
+ <attribute name="transform" type="svg:TransformListType" use="optional"/>\r
+ <attributeGroup ref="svg:graphicsElementEvents"/>\r
+ <attribute name="target" type="NMTOKEN" use="optional"/>\r
+ <!-- don't use attribute declarations to declare namespaces \r
+ attribute ref="xmlns:xlink" type="string" fixed="http://www.w3.org/1999/xlink"/>\r
+ -->\r
+ <!-- change from string to URI -->\r
+ </complexType>\r
+ <complexType name="viewType">\r
+ <group ref="svg:descTitleMetadata" minOccurs="0" maxOccurs="unbounded"/>\r
+ <attributeGroup ref="svg:stdAttrs"/>\r
+ <attribute name="externalResourcesRequired" type="boolean" use="optional"/>\r
+ <attribute name="viewBox" type="svg:ViewBoxSpecType" use="optional"/>\r
+ <attribute name="preserveAspectRatio" type="svg:PreserveAspectRatioSpecType" default="xMidYMid meet"/>\r
+ <attribute name="zoomAndPan" default="magnify">\r
+ <simpleType>\r
+ <restriction base="string">\r
+ <enumeration value="disable"/>\r
+ <enumeration value="magnify"/>\r
+ <enumeration value="zoom"/>\r
+ </restriction>\r
+ </simpleType>\r
+ </attribute>\r
+ <attribute name="viewTarget" type="string" use="optional"/>\r
+ </complexType>\r
+ <complexType name="scriptType" mixed="true">\r
+ <attributeGroup ref="svg:stdAttrs"/>\r
+ <attributeGroup ref="svg:xlinkRefAttrs"/>\r
+ <attribute ref="xlink:href" use="optional"/>\r
+ <attribute name="externalResourcesRequired" type="boolean" use="optional"/>\r
+ <attribute name="type" type="svg:ContentTypeType" use="required"/>\r
+ </complexType>\r
+ <complexType name="animateType">\r
+ <group ref="svg:descTitleMetadata" minOccurs="0" maxOccurs="unbounded"/>\r
+ <attributeGroup ref="svg:stdAttrs"/>\r
+ <attributeGroup ref="svg:testAttrs"/>\r
+ <attribute name="externalResourcesRequired" type="boolean" use="optional"/>\r
+ <attributeGroup ref="svg:animationEvents"/>\r
+ <attributeGroup ref="svg:animTargetAttrs"/>\r
+ <attributeGroup ref="svg:animTimingAttrs"/>\r
+ <attributeGroup ref="svg:animValueAttrs"/>\r
+ <attributeGroup ref="svg:animAdditionAttrs"/>\r
+ </complexType>\r
+ <complexType name="setType">\r
+ <group ref="svg:descTitleMetadata" minOccurs="0" maxOccurs="unbounded"/>\r
+ <attributeGroup ref="svg:stdAttrs"/>\r
+ <attributeGroup ref="svg:testAttrs"/>\r
+ <attribute name="externalResourcesRequired" type="boolean" use="optional"/>\r
+ <attributeGroup ref="svg:animationEvents"/>\r
+ <attributeGroup ref="svg:animTargetAttrs"/>\r
+ <attributeGroup ref="svg:animTimingAttrs"/>\r
+ <attribute name="to" type="string" use="optional"/>\r
+ </complexType>\r
+ <complexType name="animateMotionType">\r
+ <sequence>\r
+ <group ref="svg:descTitleMetadata" minOccurs="0" maxOccurs="unbounded"/>\r
+ <element ref="svg:mpath" minOccurs="0"/>\r
+ </sequence>\r
+ <attributeGroup ref="svg:stdAttrs"/>\r
+ <attributeGroup ref="svg:testAttrs"/>\r
+ <attribute name="externalResourcesRequired" type="boolean" use="optional"/>\r
+ <attributeGroup ref="svg:animationEvents"/>\r
+ <attributeGroup ref="svg:animElementAttrs"/>\r
+ <attributeGroup ref="svg:animTimingAttrs"/>\r
+ <attributeGroup ref="svg:animValueAttrs"/>\r
+ <attributeGroup ref="svg:animAdditionAttrs"/>\r
+ <attribute name="path" type="string" use="optional"/>\r
+ <attribute name="keyPoints" type="string" use="optional"/>\r
+ <attribute name="rotate" type="string" use="optional"/>\r
+ <attribute name="origin" type="string" use="optional"/>\r
+ </complexType>\r
+ <complexType name="mpathType">\r
+ <group ref="svg:descTitleMetadata" minOccurs="0" maxOccurs="unbounded"/>\r
+ <attributeGroup ref="svg:stdAttrs"/>\r
+ <attributeGroup ref="svg:xlinkRefAttrs"/>\r
+ <attribute ref="xlink:href" use="optional"/>\r
+ <attribute name="externalResourcesRequired" type="boolean" use="optional"/>\r
+ </complexType>\r
+ <complexType name="animateColorType">\r
+ <group ref="svg:descTitleMetadata" minOccurs="0" maxOccurs="unbounded"/>\r
+ <attributeGroup ref="svg:stdAttrs"/>\r
+ <attributeGroup ref="svg:testAttrs"/>\r
+ <attribute name="externalResourcesRequired" type="boolean" use="optional"/>\r
+ <attributeGroup ref="svg:animationEvents"/>\r
+ <attributeGroup ref="svg:animTargetAttrs"/>\r
+ <attributeGroup ref="svg:animTimingAttrs"/>\r
+ <attributeGroup ref="svg:animValueAttrs"/>\r
+ <attributeGroup ref="svg:animAdditionAttrs"/>\r
+ </complexType>\r
+ <complexType name="animateTransformType">\r
+ <group ref="svg:descTitleMetadata" minOccurs="0" maxOccurs="unbounded"/>\r
+ <attributeGroup ref="svg:stdAttrs"/>\r
+ <attributeGroup ref="svg:testAttrs"/>\r
+ <attribute name="externalResourcesRequired" type="boolean" use="optional"/>\r
+ <attributeGroup ref="svg:animationEvents"/>\r
+ <attributeGroup ref="svg:animTargetAttrs"/>\r
+ <attributeGroup ref="svg:animTimingAttrs"/>\r
+ <attributeGroup ref="svg:animValueAttrs"/>\r
+ <attributeGroup ref="svg:animAdditionAttrs"/>\r
+ <attribute name="type" default="translate">\r
+ <simpleType>\r
+ <restriction base="string">\r
+ <enumeration value="translate"/>\r
+ <enumeration value="scale"/>\r
+ <enumeration value="rotate"/>\r
+ <enumeration value="skewX"/>\r
+ <enumeration value="skewY"/>\r
+ </restriction>\r
+ </simpleType>\r
+ </attribute>\r
+ </complexType>\r
+ <complexType name="fontType">\r
+ <sequence>\r
+ <group ref="svg:descTitleMetadata" minOccurs="0" maxOccurs="unbounded"/>\r
+ <element ref="svg:font-face"/>\r
+ <element ref="svg:missing-glyph"/>\r
+ <choice minOccurs="0" maxOccurs="unbounded">\r
+ <element ref="svg:glyph"/>\r
+ <element ref="svg:hkern"/>\r
+ <element ref="svg:vkern"/>\r
+ </choice>\r
+ </sequence>\r
+ <attributeGroup ref="svg:stdAttrs"/>\r
+ <attribute name="externalResourcesRequired" type="boolean" use="optional"/>\r
+ <attribute name="class" type="svg:ClassListType" use="optional"/>\r
+ <attribute name="style" type="svg:StyleSheetType" use="optional"/>\r
+ <attributeGroup ref="svg:PresentationAttributes-All"/>\r
+ <attribute name="horiz-origin-x" type="double" use="optional"/>\r
+ <attribute name="horiz-origin-y" type="double" use="optional"/>\r
+ <attribute name="horiz-adv-x" type="double" use="required"/>\r
+ <attribute name="vert-origin-x" type="double" use="optional"/>\r
+ <attribute name="vert-origin-y" type="double" use="optional"/>\r
+ <attribute name="vert-adv-y" type="double" use="optional"/>\r
+ </complexType>\r
+ <complexType name="glyphType">\r
+ <choice minOccurs="0" maxOccurs="unbounded">\r
+ <element ref="svg:desc"/>\r
+ <element ref="svg:title"/>\r
+ <element ref="svg:metadata"/>\r
+ <element ref="svg:defs"/>\r
+ <element ref="svg:path"/>\r
+ <element ref="svg:text"/>\r
+ <element ref="svg:rect"/>\r
+ <element ref="svg:circle"/>\r
+ <element ref="svg:ellipse"/>\r
+ <element ref="svg:line"/>\r
+ <element ref="svg:polyline"/>\r
+ <element ref="svg:polygon"/>\r
+ <element ref="svg:use"/>\r
+ <element ref="svg:image"/>\r
+ <element ref="svg:svg"/>\r
+ <element ref="svg:g"/>\r
+ <element ref="svg:view"/>\r
+ <element ref="svg:switch"/>\r
+ <element ref="svg:a"/>\r
+ <element ref="svg:altGlyphDef"/>\r
+ <element ref="svg:script"/>\r
+ <element ref="svg:style"/>\r
+ <element ref="svg:symbol"/>\r
+ <element ref="svg:marker"/>\r
+ <element ref="svg:clipPath"/>\r
+ <element ref="svg:mask"/>\r
+ <element ref="svg:linearGradient"/>\r
+ <element ref="svg:radialGradient"/>\r
+ <element ref="svg:pattern"/>\r
+ <element ref="svg:filter"/>\r
+ <element ref="svg:cursor"/>\r
+ <element ref="svg:font"/>\r
+ <element ref="svg:animate"/>\r
+ <element ref="svg:set"/>\r
+ <element ref="svg:animateMotion"/>\r
+ <element ref="svg:animateColor"/>\r
+ <element ref="svg:animateTransform"/>\r
+ <element ref="svg:color-profile"/>\r
+ <element ref="svg:font-face"/>\r
+ </choice>\r
+ <attributeGroup ref="svg:stdAttrs"/>\r
+ <attribute name="class" type="svg:ClassListType" use="optional"/>\r
+ <attribute name="style" type="svg:StyleSheetType" use="optional"/>\r
+ <attributeGroup ref="svg:PresentationAttributes-All"/>\r
+ <attribute name="unicode" type="string" use="optional"/>\r
+ <attribute name="glyph-name" type="string" use="optional"/>\r
+ <attribute name="d" type="svg:PathDataType" use="optional"/>\r
+ <attribute name="vert-text-orient" type="string" use="optional"/>\r
+ <attribute name="arabic" type="string" use="optional"/>\r
+ <attribute name="han" type="string" use="optional"/>\r
+ <attribute name="horiz-adv-x" type="double" use="optional"/>\r
+ <attribute name="vert-adv-y" type="double" use="optional"/>\r
+ </complexType>\r
+ <complexType name="missing-glyphType">\r
+ <choice minOccurs="0" maxOccurs="unbounded">\r
+ <element ref="svg:desc"/>\r
+ <element ref="svg:title"/>\r
+ <element ref="svg:metadata"/>\r
+ <element ref="svg:defs"/>\r
+ <element ref="svg:path"/>\r
+ <element ref="svg:text"/>\r
+ <element ref="svg:rect"/>\r
+ <element ref="svg:circle"/>\r
+ <element ref="svg:ellipse"/>\r
+ <element ref="svg:line"/>\r
+ <element ref="svg:polyline"/>\r
+ <element ref="svg:polygon"/>\r
+ <element ref="svg:use"/>\r
+ <element ref="svg:image"/>\r
+ <element ref="svg:svg"/>\r
+ <element ref="svg:g"/>\r
+ <element ref="svg:view"/>\r
+ <element ref="svg:switch"/>\r
+ <element ref="svg:a"/>\r
+ <element ref="svg:altGlyphDef"/>\r
+ <element ref="svg:script"/>\r
+ <element ref="svg:style"/>\r
+ <element ref="svg:symbol"/>\r
+ <element ref="svg:marker"/>\r
+ <element ref="svg:clipPath"/>\r
+ <element ref="svg:mask"/>\r
+ <element ref="svg:linearGradient"/>\r
+ <element ref="svg:radialGradient"/>\r
+ <element ref="svg:pattern"/>\r
+ <element ref="svg:filter"/>\r
+ <element ref="svg:cursor"/>\r
+ <element ref="svg:font"/>\r
+ <element ref="svg:animate"/>\r
+ <element ref="svg:set"/>\r
+ <element ref="svg:animateMotion"/>\r
+ <element ref="svg:animateColor"/>\r
+ <element ref="svg:animateTransform"/>\r
+ <element ref="svg:color-profile"/>\r
+ <element ref="svg:font-face"/>\r
+ </choice>\r
+ <attributeGroup ref="svg:stdAttrs"/>\r
+ <attribute name="class" type="svg:ClassListType" use="optional"/>\r
+ <attribute name="style" type="svg:StyleSheetType" use="optional"/>\r
+ <attributeGroup ref="svg:PresentationAttributes-All"/>\r
+ <attribute name="d" type="svg:PathDataType" use="optional"/>\r
+ <attribute name="horiz-adv-x" type="double" use="optional"/>\r
+ <attribute name="vert-adv-y" type="double" use="optional"/>\r
+ </complexType>\r
+ <complexType name="hkernType">\r
+ <attributeGroup ref="svg:stdAttrs"/>\r
+ <attribute name="u1" type="string" use="optional"/>\r
+ <attribute name="g1" type="string" use="optional"/>\r
+ <attribute name="u2" type="string" use="optional"/>\r
+ <attribute name="g2" type="string" use="optional"/>\r
+ <attribute name="k" type="double" use="required"/>\r
+ </complexType>\r
+ <complexType name="vkernType">\r
+ <attributeGroup ref="svg:stdAttrs"/>\r
+ <attribute name="u1" type="string" use="optional"/>\r
+ <attribute name="g1" type="string" use="optional"/>\r
+ <attribute name="u2" type="string" use="optional"/>\r
+ <attribute name="g2" type="string" use="optional"/>\r
+ <attribute name="k" type="double" use="required"/>\r
+ </complexType>\r
+ <complexType name="font-faceType">\r
+ <sequence>\r
+ <group ref="svg:descTitleMetadata" minOccurs="0" maxOccurs="unbounded"/>\r
+ <element ref="svg:font-face-src"/>\r
+ <element ref="svg:definition-src"/>\r
+ </sequence>\r
+ <attributeGroup ref="svg:stdAttrs"/>\r
+ <attribute name="font-family" type="svg:FontFamilyValueType" use="optional"/>\r
+ <attribute name="font-style" type="string" use="optional"/>\r
+ <attribute name="font-variant" type="string" use="optional"/>\r
+ <attribute name="font-weight" type="string" use="optional"/>\r
+ <attribute name="font-stretch" type="string" use="optional"/>\r
+ <attribute name="font-size" type="svg:FontSizeValueType" use="optional"/>\r
+ <attribute name="unicode-range" type="string" use="optional"/>\r
+ <attribute name="units-per-em" type="double" use="optional"/>\r
+ <attribute name="panose-1" type="string" use="optional"/>\r
+ <attribute name="stemv" type="double" use="optional"/>\r
+ <attribute name="stemh" type="double" use="optional"/>\r
+ <attribute name="slope" type="double" use="optional"/>\r
+ <attribute name="cap-height" type="double" use="optional"/>\r
+ <attribute name="x-height" type="double" use="optional"/>\r
+ <attribute name="accent-height" type="double" use="optional"/>\r
+ <attribute name="ascent" type="double" use="optional"/>\r
+ <attribute name="descent" type="double" use="optional"/>\r
+ <attribute name="widths" type="string" use="optional"/>\r
+ <attribute name="bbox" type="string" use="optional"/>\r
+ <attribute name="ideographic" type="double" use="optional"/>\r
+ <attribute name="baseline" type="double" use="optional"/>\r
+ <attribute name="centerline" type="double" use="optional"/>\r
+ <attribute name="mathline" type="double" use="optional"/>\r
+ <attribute name="hanging" type="double" use="optional"/>\r
+ <attribute name="topline" type="double" use="optional"/>\r
+ <attribute name="underline-position" type="double" use="optional"/>\r
+ <attribute name="underline-thickness" type="double" use="optional"/>\r
+ <attribute name="strikethrough-position" type="double" use="optional"/>\r
+ <attribute name="strikethrough-thickness" type="double" use="optional"/>\r
+ <attribute name="overline-position" type="double" use="optional"/>\r
+ <attribute name="overline-thickness" type="double" use="optional"/>\r
+ </complexType>\r
+ <complexType name="font-face-srcType">\r
+ <choice maxOccurs="unbounded">\r
+ <element ref="svg:font-face-uri"/>\r
+ <element ref="svg:font-face-name"/>\r
+ </choice>\r
+ <attributeGroup ref="svg:stdAttrs"/>\r
+ </complexType>\r
+ <complexType name="font-face-uriType">\r
+ <sequence>\r
+ <element ref="svg:font-face-format"/>\r
+ </sequence>\r
+ <attributeGroup ref="svg:stdAttrs"/>\r
+ <attributeGroup ref="svg:xlinkRefAttrs"/>\r
+ <attribute ref="xlink:href" use="optional"/>\r
+ </complexType>\r
+ <complexType name="font-face-formatType">\r
+ <attributeGroup ref="svg:stdAttrs"/>\r
+ <attribute name="string" type="string" use="optional"/>\r
+ </complexType>\r
+ <complexType name="font-face-nameType">\r
+ <attributeGroup ref="svg:stdAttrs"/>\r
+ <attribute name="name" type="string" use="optional"/>\r
+ </complexType>\r
+ <complexType name="definition-srcType">\r
+ <attributeGroup ref="svg:stdAttrs"/>\r
+ <attributeGroup ref="svg:xlinkRefAttrs"/>\r
+ <attribute ref="xlink:href" use="optional"/>\r
+ </complexType>\r
+ <complexType name="metadataType" mixed="true">\r
+ <sequence minOccurs="0" maxOccurs="unbounded"/>\r
+ <attributeGroup ref="svg:stdAttrs"/>\r
+ </complexType>\r
+ <complexType name="foreignObjectType" mixed="true">\r
+ <sequence minOccurs="0" maxOccurs="unbounded"/>\r
+ <attributeGroup ref="svg:stdAttrs"/>\r
+ <attributeGroup ref="svg:testAttrs"/>\r
+ <attributeGroup ref="svg:langSpaceAttrs"/>\r
+ <attribute name="externalResourcesRequired" type="boolean" use="optional"/>\r
+ <attribute name="class" type="svg:ClassListType" use="optional"/>\r
+ <attribute name="style" type="svg:StyleSheetType" use="optional"/>\r
+ <attributeGroup ref="svg:PresentationAttributes-All"/>\r
+ <attribute name="transform" type="svg:TransformListType" use="optional"/>\r
+ <attributeGroup ref="svg:graphicsElementEvents"/>\r
+ <attribute name="x" type="svg:CoordinateType" use="optional"/>\r
+ <attribute name="y" type="svg:CoordinateType" use="optional"/>\r
+ <attribute name="width" type="svg:LengthType" use="required"/>\r
+ <attribute name="height" type="svg:LengthType" use="required"/>\r
+ <attribute name="content" type="string" fixed="structured text"/>\r
+ </complexType>\r
+</schema>\r
--- /dev/null
+<!-- DTD for XML Schemas: Part 1: Structures
+ Public Identifier: "-//W3C//DTD XMLSCHEMA 200102//EN"
+ Official Location: http://www.w3.org/2001/XMLSchema.dtd -->
+<!-- $Id: XMLSchema.dtd,v 1.31 2001/10/24 15:50:16 ht Exp $ -->
+<!-- Note this DTD is NOT normative, or even definitive. --> <!--d-->
+<!-- prose copy in the structures REC is the definitive version --> <!--d-->
+<!-- (which shouldn't differ from this one except for this --> <!--d-->
+<!-- comment and entity expansions, but just in case) --> <!--d-->
+<!-- With the exception of cases with multiple namespace
+ prefixes for the XML Schema namespace, any XML document which is
+ not valid per this DTD given redefinitions in its internal subset of the
+ 'p' and 's' parameter entities below appropriate to its namespace
+ declaration of the XML Schema namespace is almost certainly not
+ a valid schema. -->
+
+<!-- The simpleType element and its constituent parts
+ are defined in XML Schema: Part 2: Datatypes -->
+<!ENTITY % xs-datatypes PUBLIC 'datatypes' 'datatypes.dtd' >
+
+<!ENTITY % p 'xs:'> <!-- can be overriden in the internal subset of a
+ schema document to establish a different
+ namespace prefix -->
+<!ENTITY % s ':xs'> <!-- if %p is defined (e.g. as foo:) then you must
+ also define %s as the suffix for the appropriate
+ namespace declaration (e.g. :foo) -->
+<!ENTITY % nds 'xmlns%s;'>
+
+<!-- Define all the element names, with optional prefix -->
+<!ENTITY % schema "%p;schema">
+<!ENTITY % complexType "%p;complexType">
+<!ENTITY % complexContent "%p;complexContent">
+<!ENTITY % simpleContent "%p;simpleContent">
+<!ENTITY % extension "%p;extension">
+<!ENTITY % element "%p;element">
+<!ENTITY % unique "%p;unique">
+<!ENTITY % key "%p;key">
+<!ENTITY % keyref "%p;keyref">
+<!ENTITY % selector "%p;selector">
+<!ENTITY % field "%p;field">
+<!ENTITY % group "%p;group">
+<!ENTITY % all "%p;all">
+<!ENTITY % choice "%p;choice">
+<!ENTITY % sequence "%p;sequence">
+<!ENTITY % any "%p;any">
+<!ENTITY % anyAttribute "%p;anyAttribute">
+<!ENTITY % attribute "%p;attribute">
+<!ENTITY % attributeGroup "%p;attributeGroup">
+<!ENTITY % include "%p;include">
+<!ENTITY % import "%p;import">
+<!ENTITY % redefine "%p;redefine">
+<!ENTITY % notation "%p;notation">
+
+<!-- annotation elements -->
+<!ENTITY % annotation "%p;annotation">
+<!ENTITY % appinfo "%p;appinfo">
+<!ENTITY % documentation "%p;documentation">
+
+<!-- Customisation entities for the ATTLIST of each element type.
+ Define one of these if your schema takes advantage of the
+ anyAttribute='##other' in the schema for schemas -->
+
+<!ENTITY % schemaAttrs ''>
+<!ENTITY % complexTypeAttrs ''>
+<!ENTITY % complexContentAttrs ''>
+<!ENTITY % simpleContentAttrs ''>
+<!ENTITY % extensionAttrs ''>
+<!ENTITY % elementAttrs ''>
+<!ENTITY % groupAttrs ''>
+<!ENTITY % allAttrs ''>
+<!ENTITY % choiceAttrs ''>
+<!ENTITY % sequenceAttrs ''>
+<!ENTITY % anyAttrs ''>
+<!ENTITY % anyAttributeAttrs ''>
+<!ENTITY % attributeAttrs ''>
+<!ENTITY % attributeGroupAttrs ''>
+<!ENTITY % uniqueAttrs ''>
+<!ENTITY % keyAttrs ''>
+<!ENTITY % keyrefAttrs ''>
+<!ENTITY % selectorAttrs ''>
+<!ENTITY % fieldAttrs ''>
+<!ENTITY % includeAttrs ''>
+<!ENTITY % importAttrs ''>
+<!ENTITY % redefineAttrs ''>
+<!ENTITY % notationAttrs ''>
+<!ENTITY % annotationAttrs ''>
+<!ENTITY % appinfoAttrs ''>
+<!ENTITY % documentationAttrs ''>
+
+<!ENTITY % complexDerivationSet "CDATA">
+ <!-- #all or space-separated list drawn from derivationChoice -->
+<!ENTITY % blockSet "CDATA">
+ <!-- #all or space-separated list drawn from
+ derivationChoice + 'substitution' -->
+
+<!ENTITY % mgs '%all; | %choice; | %sequence;'>
+<!ENTITY % cs '%choice; | %sequence;'>
+<!ENTITY % formValues '(qualified|unqualified)'>
+
+
+<!ENTITY % attrDecls '((%attribute;| %attributeGroup;)*,(%anyAttribute;)?)'>
+
+<!ENTITY % particleAndAttrs '((%mgs; | %group;)?, %attrDecls;)'>
+
+<!-- This is used in part2 -->
+<!ENTITY % restriction1 '((%mgs; | %group;)?)'>
+
+%xs-datatypes;
+
+<!-- the duplication below is to produce an unambiguous content model
+ which allows annotation everywhere -->
+<!ELEMENT %schema; ((%include; | %import; | %redefine; | %annotation;)*,
+ ((%simpleType; | %complexType;
+ | %element; | %attribute;
+ | %attributeGroup; | %group;
+ | %notation; ),
+ (%annotation;)*)* )>
+<!ATTLIST %schema;
+ targetNamespace %URIref; #IMPLIED
+ version CDATA #IMPLIED
+ %nds; %URIref; #FIXED 'http://www.w3.org/2001/XMLSchema'
+ xmlns CDATA #IMPLIED
+ finalDefault %complexDerivationSet; ''
+ blockDefault %blockSet; ''
+ id ID #IMPLIED
+ elementFormDefault %formValues; 'unqualified'
+ attributeFormDefault %formValues; 'unqualified'
+ xml:lang CDATA #IMPLIED
+ %schemaAttrs;>
+<!-- Note the xmlns declaration is NOT in the Schema for Schemas,
+ because at the Infoset level where schemas operate,
+ xmlns(:prefix) is NOT an attribute! -->
+<!-- The declaration of xmlns is a convenience for schema authors -->
+
+<!-- The id attribute here and below is for use in external references
+ from non-schemas using simple fragment identifiers.
+ It is NOT used for schema-to-schema reference, internal or
+ external. -->
+
+<!-- a type is a named content type specification which allows attribute
+ declarations-->
+<!-- -->
+
+<!ELEMENT %complexType; ((%annotation;)?,
+ (%simpleContent;|%complexContent;|
+ %particleAndAttrs;))>
+
+<!ATTLIST %complexType;
+ name %NCName; #IMPLIED
+ id ID #IMPLIED
+ abstract %boolean; #IMPLIED
+ final %complexDerivationSet; #IMPLIED
+ block %complexDerivationSet; #IMPLIED
+ mixed (true|false) 'false'
+ %complexTypeAttrs;>
+
+<!-- particleAndAttrs is shorthand for a root type -->
+<!-- mixed is disallowed if simpleContent, overriden if complexContent
+ has one too. -->
+
+<!-- If anyAttribute appears in one or more referenced attributeGroups
+ and/or explicitly, the intersection of the permissions is used -->
+
+<!ELEMENT %complexContent; ((%annotation;)?, (%restriction;|%extension;))>
+<!ATTLIST %complexContent;
+ mixed (true|false) #IMPLIED
+ id ID #IMPLIED
+ %complexContentAttrs;>
+
+<!-- restriction should use the branch defined above, not the simple
+ one from part2; extension should use the full model -->
+
+<!ELEMENT %simpleContent; ((%annotation;)?, (%restriction;|%extension;))>
+<!ATTLIST %simpleContent;
+ id ID #IMPLIED
+ %simpleContentAttrs;>
+
+<!-- restriction should use the simple branch from part2, not the
+ one defined above; extension should have no particle -->
+
+<!ELEMENT %extension; ((%annotation;)?, (%particleAndAttrs;))>
+<!ATTLIST %extension;
+ base %QName; #REQUIRED
+ id ID #IMPLIED
+ %extensionAttrs;>
+
+<!-- an element is declared by either:
+ a name and a type (either nested or referenced via the type attribute)
+ or a ref to an existing element declaration -->
+
+<!ELEMENT %element; ((%annotation;)?, (%complexType;| %simpleType;)?,
+ (%unique; | %key; | %keyref;)*)>
+<!-- simpleType or complexType only if no type|ref attribute -->
+<!-- ref not allowed at top level -->
+<!ATTLIST %element;
+ name %NCName; #IMPLIED
+ id ID #IMPLIED
+ ref %QName; #IMPLIED
+ type %QName; #IMPLIED
+ minOccurs %nonNegativeInteger; #IMPLIED
+ maxOccurs CDATA #IMPLIED
+ nillable %boolean; #IMPLIED
+ substitutionGroup %QName; #IMPLIED
+ abstract %boolean; #IMPLIED
+ final %complexDerivationSet; #IMPLIED
+ block %blockSet; #IMPLIED
+ default CDATA #IMPLIED
+ fixed CDATA #IMPLIED
+ form %formValues; #IMPLIED
+ %elementAttrs;>
+<!-- type and ref are mutually exclusive.
+ name and ref are mutually exclusive, one is required -->
+<!-- In the absence of type AND ref, type defaults to type of
+ substitutionGroup, if any, else the ur-type, i.e. unconstrained -->
+<!-- default and fixed are mutually exclusive -->
+
+<!ELEMENT %group; ((%annotation;)?,(%mgs;)?)>
+<!ATTLIST %group;
+ name %NCName; #IMPLIED
+ ref %QName; #IMPLIED
+ minOccurs %nonNegativeInteger; #IMPLIED
+ maxOccurs CDATA #IMPLIED
+ id ID #IMPLIED
+ %groupAttrs;>
+
+<!ELEMENT %all; ((%annotation;)?, (%element;)*)>
+<!ATTLIST %all;
+ minOccurs (1) #IMPLIED
+ maxOccurs (1) #IMPLIED
+ id ID #IMPLIED
+ %allAttrs;>
+
+<!ELEMENT %choice; ((%annotation;)?, (%element;| %group;| %cs; | %any;)*)>
+<!ATTLIST %choice;
+ minOccurs %nonNegativeInteger; #IMPLIED
+ maxOccurs CDATA #IMPLIED
+ id ID #IMPLIED
+ %choiceAttrs;>
+
+<!ELEMENT %sequence; ((%annotation;)?, (%element;| %group;| %cs; | %any;)*)>
+<!ATTLIST %sequence;
+ minOccurs %nonNegativeInteger; #IMPLIED
+ maxOccurs CDATA #IMPLIED
+ id ID #IMPLIED
+ %sequenceAttrs;>
+
+<!-- an anonymous grouping in a model, or
+ a top-level named group definition, or a reference to same -->
+
+<!-- Note that if order is 'all', group is not allowed inside.
+ If order is 'all' THIS group must be alone (or referenced alone) at
+ the top level of a content model -->
+<!-- If order is 'all', minOccurs==maxOccurs==1 on element/any inside -->
+<!-- Should allow minOccurs=0 inside order='all' . . . -->
+
+<!ELEMENT %any; (%annotation;)?>
+<!ATTLIST %any;
+ namespace CDATA '##any'
+ processContents (skip|lax|strict) 'strict'
+ minOccurs %nonNegativeInteger; '1'
+ maxOccurs CDATA '1'
+ id ID #IMPLIED
+ %anyAttrs;>
+
+<!-- namespace is interpreted as follows:
+ ##any - - any non-conflicting WFXML at all
+
+ ##other - - any non-conflicting WFXML from namespace other
+ than targetNamespace
+
+ ##local - - any unqualified non-conflicting WFXML/attribute
+ one or - - any non-conflicting WFXML from
+ more URI the listed namespaces
+ references
+
+ ##targetNamespace ##local may appear in the above list,
+ with the obvious meaning -->
+
+<!ELEMENT %anyAttribute; (%annotation;)?>
+<!ATTLIST %anyAttribute;
+ namespace CDATA '##any'
+ processContents (skip|lax|strict) 'strict'
+ id ID #IMPLIED
+ %anyAttributeAttrs;>
+<!-- namespace is interpreted as for 'any' above -->
+
+<!-- simpleType only if no type|ref attribute -->
+<!-- ref not allowed at top level, name iff at top level -->
+<!ELEMENT %attribute; ((%annotation;)?, (%simpleType;)?)>
+<!ATTLIST %attribute;
+ name %NCName; #IMPLIED
+ id ID #IMPLIED
+ ref %QName; #IMPLIED
+ type %QName; #IMPLIED
+ use (prohibited|optional|required) #IMPLIED
+ default CDATA #IMPLIED
+ fixed CDATA #IMPLIED
+ form %formValues; #IMPLIED
+ %attributeAttrs;>
+<!-- type and ref are mutually exclusive.
+ name and ref are mutually exclusive, one is required -->
+<!-- default for use is optional when nested, none otherwise -->
+<!-- default and fixed are mutually exclusive -->
+<!-- type attr and simpleType content are mutually exclusive -->
+
+<!-- an attributeGroup is a named collection of attribute decls, or a
+ reference thereto -->
+<!ELEMENT %attributeGroup; ((%annotation;)?,
+ (%attribute; | %attributeGroup;)*,
+ (%anyAttribute;)?) >
+<!ATTLIST %attributeGroup;
+ name %NCName; #IMPLIED
+ id ID #IMPLIED
+ ref %QName; #IMPLIED
+ %attributeGroupAttrs;>
+
+<!-- ref iff no content, no name. ref iff not top level -->
+
+<!-- better reference mechanisms -->
+<!ELEMENT %unique; ((%annotation;)?, %selector;, (%field;)+)>
+<!ATTLIST %unique;
+ name %NCName; #REQUIRED
+ id ID #IMPLIED
+ %uniqueAttrs;>
+
+<!ELEMENT %key; ((%annotation;)?, %selector;, (%field;)+)>
+<!ATTLIST %key;
+ name %NCName; #REQUIRED
+ id ID #IMPLIED
+ %keyAttrs;>
+
+<!ELEMENT %keyref; ((%annotation;)?, %selector;, (%field;)+)>
+<!ATTLIST %keyref;
+ name %NCName; #REQUIRED
+ refer %QName; #REQUIRED
+ id ID #IMPLIED
+ %keyrefAttrs;>
+
+<!ELEMENT %selector; ((%annotation;)?)>
+<!ATTLIST %selector;
+ xpath %XPathExpr; #REQUIRED
+ id ID #IMPLIED
+ %selectorAttrs;>
+<!ELEMENT %field; ((%annotation;)?)>
+<!ATTLIST %field;
+ xpath %XPathExpr; #REQUIRED
+ id ID #IMPLIED
+ %fieldAttrs;>
+
+<!-- Schema combination mechanisms -->
+<!ELEMENT %include; (%annotation;)?>
+<!ATTLIST %include;
+ schemaLocation %URIref; #REQUIRED
+ id ID #IMPLIED
+ %includeAttrs;>
+
+<!ELEMENT %import; (%annotation;)?>
+<!ATTLIST %import;
+ namespace %URIref; #IMPLIED
+ schemaLocation %URIref; #IMPLIED
+ id ID #IMPLIED
+ %importAttrs;>
+
+<!ELEMENT %redefine; (%annotation; | %simpleType; | %complexType; |
+ %attributeGroup; | %group;)*>
+<!ATTLIST %redefine;
+ schemaLocation %URIref; #REQUIRED
+ id ID #IMPLIED
+ %redefineAttrs;>
+
+<!ELEMENT %notation; (%annotation;)?>
+<!ATTLIST %notation;
+ name %NCName; #REQUIRED
+ id ID #IMPLIED
+ public CDATA #REQUIRED
+ system %URIref; #IMPLIED
+ %notationAttrs;>
+
+<!-- Annotation is either application information or documentation -->
+<!-- By having these here they are available for datatypes as well
+ as all the structures elements -->
+
+<!ELEMENT %annotation; (%appinfo; | %documentation;)*>
+<!ATTLIST %annotation; %annotationAttrs;>
+
+<!-- User must define annotation elements in internal subset for this
+ to work -->
+<!ELEMENT %appinfo; ANY> <!-- too restrictive -->
+<!ATTLIST %appinfo;
+ source %URIref; #IMPLIED
+ id ID #IMPLIED
+ %appinfoAttrs;>
+<!ELEMENT %documentation; ANY> <!-- too restrictive -->
+<!ATTLIST %documentation;
+ source %URIref; #IMPLIED
+ id ID #IMPLIED
+ xml:lang CDATA #IMPLIED
+ %documentationAttrs;>
+
+<!NOTATION XMLSchemaStructures PUBLIC
+ 'structures' 'http://www.w3.org/2001/XMLSchema.xsd' >
+<!NOTATION XML PUBLIC
+ 'REC-xml-1998-0210' 'http://www.w3.org/TR/1998/REC-xml-19980210' >
--- /dev/null
+<?xml version='1.0' encoding='UTF-8'?>
+<!-- XML Schema schema for XML Schemas: Part 1: Structures -->
+<!-- Note this schema is NOT the normative structures schema. -->
+<!-- The prose copy in the structures REC is the normative -->
+<!-- version (which shouldn't differ from this one except for -->
+<!-- this comment and entity expansions, but just in case -->
+<!DOCTYPE xs:schema PUBLIC "-//W3C//DTD XMLSCHEMA 200102//EN" "XMLSchema.dtd" [
+
+<!-- provide ID type information even for parsers which only read the
+ internal subset -->
+<!ATTLIST xs:schema id ID #IMPLIED>
+<!ATTLIST xs:complexType id ID #IMPLIED>
+<!ATTLIST xs:complexContent id ID #IMPLIED>
+<!ATTLIST xs:simpleContent id ID #IMPLIED>
+<!ATTLIST xs:extension id ID #IMPLIED>
+<!ATTLIST xs:element id ID #IMPLIED>
+<!ATTLIST xs:group id ID #IMPLIED>
+<!ATTLIST xs:all id ID #IMPLIED>
+<!ATTLIST xs:choice id ID #IMPLIED>
+<!ATTLIST xs:sequence id ID #IMPLIED>
+<!ATTLIST xs:any id ID #IMPLIED>
+<!ATTLIST xs:anyAttribute id ID #IMPLIED>
+<!ATTLIST xs:attribute id ID #IMPLIED>
+<!ATTLIST xs:attributeGroup id ID #IMPLIED>
+<!ATTLIST xs:unique id ID #IMPLIED>
+<!ATTLIST xs:key id ID #IMPLIED>
+<!ATTLIST xs:keyref id ID #IMPLIED>
+<!ATTLIST xs:selector id ID #IMPLIED>
+<!ATTLIST xs:field id ID #IMPLIED>
+<!ATTLIST xs:include id ID #IMPLIED>
+<!ATTLIST xs:import id ID #IMPLIED>
+<!ATTLIST xs:redefine id ID #IMPLIED>
+<!ATTLIST xs:notation id ID #IMPLIED>
+<!--
+ keep this schema XML1.0 DTD valid
+ -->
+ <!ENTITY % schemaAttrs 'xmlns:hfp CDATA #IMPLIED'>
+
+ <!ELEMENT hfp:hasFacet EMPTY>
+ <!ATTLIST hfp:hasFacet
+ name NMTOKEN #REQUIRED>
+
+ <!ELEMENT hfp:hasProperty EMPTY>
+ <!ATTLIST hfp:hasProperty
+ name NMTOKEN #REQUIRED
+ value CDATA #REQUIRED>
+<!--
+ Make sure that processors that do not read the external
+ subset will know about the various IDs we declare
+ -->
+ <!ATTLIST xs:simpleType id ID #IMPLIED>
+ <!ATTLIST xs:maxExclusive id ID #IMPLIED>
+ <!ATTLIST xs:minExclusive id ID #IMPLIED>
+ <!ATTLIST xs:maxInclusive id ID #IMPLIED>
+ <!ATTLIST xs:minInclusive id ID #IMPLIED>
+ <!ATTLIST xs:totalDigits id ID #IMPLIED>
+ <!ATTLIST xs:fractionDigits id ID #IMPLIED>
+ <!ATTLIST xs:length id ID #IMPLIED>
+ <!ATTLIST xs:minLength id ID #IMPLIED>
+ <!ATTLIST xs:maxLength id ID #IMPLIED>
+ <!ATTLIST xs:enumeration id ID #IMPLIED>
+ <!ATTLIST xs:pattern id ID #IMPLIED>
+ <!ATTLIST xs:appinfo id ID #IMPLIED>
+ <!ATTLIST xs:documentation id ID #IMPLIED>
+ <!ATTLIST xs:list id ID #IMPLIED>
+ <!ATTLIST xs:union id ID #IMPLIED>
+ ]>
+<xs:schema targetNamespace="http://www.w3.org/2001/XMLSchema" blockDefault="#all" elementFormDefault="qualified" version="1.0" xmlns:xs="http://www.w3.org/2001/XMLSchema" xml:lang="EN" xmlns:hfp="http://www.w3.org/2001/XMLSchema-hasFacetAndProperty">
+ <xs:annotation>
+ <xs:documentation>
+ Part 1 version: Id: structures.xsd,v 1.2 2004/01/15 11:34:25 ht Exp
+ Part 2 version: Id: datatypes.xsd,v 1.3 2004/01/23 18:11:13 ht Exp
+ </xs:documentation>
+ </xs:annotation>
+
+ <xs:annotation>
+ <xs:documentation source="http://www.w3.org/TR/2004/PER-xmlschema-1-20040318/structures.html">
+ The schema corresponding to this document is normative,
+ with respect to the syntactic constraints it expresses in the
+ XML Schema language. The documentation (within <documentation> elements)
+ below, is not normative, but rather highlights important aspects of
+ the W3C Recommendation of which this is a part</xs:documentation>
+ </xs:annotation>
+
+ <xs:annotation>
+ <xs:documentation>
+ The simpleType element and all of its members are defined
+ towards the end of this schema document</xs:documentation>
+ </xs:annotation>
+
+ <xs:import namespace="http://www.w3.org/XML/1998/namespace" schemaLocation="xml.xsd">
+ <xs:annotation>
+ <xs:documentation>
+ Get access to the xml: attribute groups for xml:lang
+ as declared on 'schema' and 'documentation' below
+ </xs:documentation>
+ </xs:annotation>
+ </xs:import>
+
+ <xs:complexType name="openAttrs">
+ <xs:annotation>
+ <xs:documentation>
+ This type is extended by almost all schema types
+ to allow attributes from other namespaces to be
+ added to user schemas.
+ </xs:documentation>
+ </xs:annotation>
+ <xs:complexContent>
+ <xs:restriction base="xs:anyType">
+ <xs:anyAttribute namespace="##other" processContents="lax"/>
+ </xs:restriction>
+ </xs:complexContent>
+ </xs:complexType>
+
+ <xs:complexType name="annotated">
+ <xs:annotation>
+ <xs:documentation>
+ This type is extended by all types which allow annotation
+ other than <schema> itself
+ </xs:documentation>
+ </xs:annotation>
+ <xs:complexContent>
+ <xs:extension base="xs:openAttrs">
+ <xs:sequence>
+ <xs:element ref="xs:annotation" minOccurs="0"/>
+ </xs:sequence>
+ <xs:attribute name="id" type="xs:ID"/>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+
+ <xs:group name="schemaTop">
+ <xs:annotation>
+ <xs:documentation>
+ This group is for the
+ elements which occur freely at the top level of schemas.
+ All of their types are based on the "annotated" type by extension.</xs:documentation>
+ </xs:annotation>
+ <xs:choice>
+ <xs:group ref="xs:redefinable"/>
+ <xs:element ref="xs:element"/>
+ <xs:element ref="xs:attribute"/>
+ <xs:element ref="xs:notation"/>
+ </xs:choice>
+ </xs:group>
+
+ <xs:group name="redefinable">
+ <xs:annotation>
+ <xs:documentation>
+ This group is for the
+ elements which can self-redefine (see <redefine> below).</xs:documentation>
+ </xs:annotation>
+ <xs:choice>
+ <xs:element ref="xs:simpleType"/>
+ <xs:element ref="xs:complexType"/>
+ <xs:element ref="xs:group"/>
+ <xs:element ref="xs:attributeGroup"/>
+ </xs:choice>
+ </xs:group>
+
+ <xs:simpleType name="formChoice">
+ <xs:annotation>
+ <xs:documentation>
+ A utility type, not for public use</xs:documentation>
+ </xs:annotation>
+ <xs:restriction base="xs:NMTOKEN">
+ <xs:enumeration value="qualified"/>
+ <xs:enumeration value="unqualified"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="reducedDerivationControl">
+ <xs:annotation>
+ <xs:documentation>
+ A utility type, not for public use</xs:documentation>
+ </xs:annotation>
+ <xs:restriction base="xs:derivationControl">
+ <xs:enumeration value="extension"/>
+ <xs:enumeration value="restriction"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="derivationSet">
+ <xs:annotation>
+ <xs:documentation>
+ A utility type, not for public use</xs:documentation>
+ <xs:documentation>
+ #all or (possibly empty) subset of {extension, restriction}</xs:documentation>
+ </xs:annotation>
+ <xs:union>
+ <xs:simpleType>
+ <xs:restriction base="xs:token">
+ <xs:enumeration value="#all"/>
+ </xs:restriction>
+ </xs:simpleType>
+ <xs:simpleType>
+ <xs:list itemType="xs:reducedDerivationControl"/>
+ </xs:simpleType>
+ </xs:union>
+ </xs:simpleType>
+
+ <xs:simpleType name="typeDerivationControl">
+ <xs:annotation>
+ <xs:documentation>
+ A utility type, not for public use</xs:documentation>
+ </xs:annotation>
+ <xs:restriction base="xs:derivationControl">
+ <xs:enumeration value="extension"/>
+ <xs:enumeration value="restriction"/>
+ <xs:enumeration value="list"/>
+ <xs:enumeration value="union"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="fullDerivationSet">
+ <xs:annotation>
+ <xs:documentation>
+ A utility type, not for public use</xs:documentation>
+ <xs:documentation>
+ #all or (possibly empty) subset of {extension, restriction, list, union}</xs:documentation>
+ </xs:annotation>
+ <xs:union>
+ <xs:simpleType>
+ <xs:restriction base="xs:token">
+ <xs:enumeration value="#all"/>
+ </xs:restriction>
+ </xs:simpleType>
+ <xs:simpleType>
+ <xs:list itemType="xs:typeDerivationControl"/>
+ </xs:simpleType>
+ </xs:union>
+ </xs:simpleType>
+
+ <xs:element name="schema" id="schema">
+ <xs:annotation>
+ <xs:documentation source="http://www.w3.org/TR/xmlschema-1/#element-schema"/>
+ </xs:annotation>
+ <xs:complexType>
+ <xs:complexContent>
+ <xs:extension base="xs:openAttrs">
+ <xs:sequence>
+ <xs:choice minOccurs="0" maxOccurs="unbounded">
+ <xs:element ref="xs:include"/>
+ <xs:element ref="xs:import"/>
+ <xs:element ref="xs:redefine"/>
+ <xs:element ref="xs:annotation"/>
+ </xs:choice>
+ <xs:sequence minOccurs="0" maxOccurs="unbounded">
+ <xs:group ref="xs:schemaTop"/>
+ <xs:element ref="xs:annotation" minOccurs="0" maxOccurs="unbounded"/>
+ </xs:sequence>
+ </xs:sequence>
+ <xs:attribute name="targetNamespace" type="xs:anyURI"/>
+ <xs:attribute name="version" type="xs:token"/>
+ <xs:attribute name="finalDefault" type="xs:fullDerivationSet" use="optional" default=""/>
+ <xs:attribute name="blockDefault" type="xs:blockSet" use="optional" default=""/>
+ <xs:attribute name="attributeFormDefault" type="xs:formChoice" use="optional" default="unqualified"/>
+ <xs:attribute name="elementFormDefault" type="xs:formChoice" use="optional" default="unqualified"/>
+ <xs:attribute name="id" type="xs:ID"/>
+ <xs:attribute ref="xml:lang"/>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+
+ <xs:key name="element">
+ <xs:selector xpath="xs:element"/>
+ <xs:field xpath="@name"/>
+ </xs:key>
+
+ <xs:key name="attribute">
+ <xs:selector xpath="xs:attribute"/>
+ <xs:field xpath="@name"/>
+ </xs:key>
+
+ <xs:key name="type">
+ <xs:selector xpath="xs:complexType|xs:simpleType"/>
+ <xs:field xpath="@name"/>
+ </xs:key>
+
+ <xs:key name="group">
+ <xs:selector xpath="xs:group"/>
+ <xs:field xpath="@name"/>
+ </xs:key>
+
+ <xs:key name="attributeGroup">
+ <xs:selector xpath="xs:attributeGroup"/>
+ <xs:field xpath="@name"/>
+ </xs:key>
+
+ <xs:key name="notation">
+ <xs:selector xpath="xs:notation"/>
+ <xs:field xpath="@name"/>
+ </xs:key>
+
+ <xs:key name="identityConstraint">
+ <xs:selector xpath=".//xs:key|.//xs:unique|.//xs:keyref"/>
+ <xs:field xpath="@name"/>
+ </xs:key>
+
+ </xs:element>
+
+ <xs:simpleType name="allNNI">
+ <xs:annotation><xs:documentation>
+ for maxOccurs</xs:documentation></xs:annotation>
+ <xs:union memberTypes="xs:nonNegativeInteger">
+ <xs:simpleType>
+ <xs:restriction base="xs:NMTOKEN">
+ <xs:enumeration value="unbounded"/>
+ </xs:restriction>
+ </xs:simpleType>
+ </xs:union>
+ </xs:simpleType>
+
+ <xs:attributeGroup name="occurs">
+ <xs:annotation><xs:documentation>
+ for all particles</xs:documentation></xs:annotation>
+ <xs:attribute name="minOccurs" type="xs:nonNegativeInteger" use="optional" default="1"/>
+ <xs:attribute name="maxOccurs" type="xs:allNNI" use="optional" default="1"/>
+ </xs:attributeGroup>
+
+ <xs:attributeGroup name="defRef">
+ <xs:annotation><xs:documentation>
+ for element, group and attributeGroup,
+ which both define and reference</xs:documentation></xs:annotation>
+ <xs:attribute name="name" type="xs:NCName"/>
+ <xs:attribute name="ref" type="xs:QName"/>
+ </xs:attributeGroup>
+
+ <xs:group name="typeDefParticle">
+ <xs:annotation>
+ <xs:documentation>
+ 'complexType' uses this</xs:documentation></xs:annotation>
+ <xs:choice>
+ <xs:element name="group" type="xs:groupRef"/>
+ <xs:element ref="xs:all"/>
+ <xs:element ref="xs:choice"/>
+ <xs:element ref="xs:sequence"/>
+ </xs:choice>
+ </xs:group>
+
+
+
+ <xs:group name="nestedParticle">
+ <xs:choice>
+ <xs:element name="element" type="xs:localElement"/>
+ <xs:element name="group" type="xs:groupRef"/>
+ <xs:element ref="xs:choice"/>
+ <xs:element ref="xs:sequence"/>
+ <xs:element ref="xs:any"/>
+ </xs:choice>
+ </xs:group>
+
+ <xs:group name="particle">
+ <xs:choice>
+ <xs:element name="element" type="xs:localElement"/>
+ <xs:element name="group" type="xs:groupRef"/>
+ <xs:element ref="xs:all"/>
+ <xs:element ref="xs:choice"/>
+ <xs:element ref="xs:sequence"/>
+ <xs:element ref="xs:any"/>
+ </xs:choice>
+ </xs:group>
+
+ <xs:complexType name="attribute">
+ <xs:complexContent>
+ <xs:extension base="xs:annotated">
+ <xs:sequence>
+ <xs:element name="simpleType" minOccurs="0" type="xs:localSimpleType"/>
+ </xs:sequence>
+ <xs:attributeGroup ref="xs:defRef"/>
+ <xs:attribute name="type" type="xs:QName"/>
+ <xs:attribute name="use" use="optional" default="optional">
+ <xs:simpleType>
+ <xs:restriction base="xs:NMTOKEN">
+ <xs:enumeration value="prohibited"/>
+ <xs:enumeration value="optional"/>
+ <xs:enumeration value="required"/>
+ </xs:restriction>
+ </xs:simpleType>
+ </xs:attribute>
+ <xs:attribute name="default" type="xs:string"/>
+ <xs:attribute name="fixed" type="xs:string"/>
+ <xs:attribute name="form" type="xs:formChoice"/>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+
+ <xs:complexType name="topLevelAttribute">
+ <xs:complexContent>
+ <xs:restriction base="xs:attribute">
+ <xs:sequence>
+ <xs:element ref="xs:annotation" minOccurs="0"/>
+ <xs:element name="simpleType" minOccurs="0" type="xs:localSimpleType"/>
+ </xs:sequence>
+ <xs:attribute name="ref" use="prohibited"/>
+ <xs:attribute name="form" use="prohibited"/>
+ <xs:attribute name="use" use="prohibited"/>
+ <xs:attribute name="name" use="required" type="xs:NCName"/>
+ <xs:anyAttribute namespace="##other" processContents="lax"/>
+ </xs:restriction>
+ </xs:complexContent>
+ </xs:complexType>
+
+ <xs:group name="attrDecls">
+ <xs:sequence>
+ <xs:choice minOccurs="0" maxOccurs="unbounded">
+ <xs:element name="attribute" type="xs:attribute"/>
+ <xs:element name="attributeGroup" type="xs:attributeGroupRef"/>
+ </xs:choice>
+ <xs:element ref="xs:anyAttribute" minOccurs="0"/>
+ </xs:sequence>
+ </xs:group>
+
+ <xs:element name="anyAttribute" type="xs:wildcard" id="anyAttribute">
+ <xs:annotation>
+ <xs:documentation source="http://www.w3.org/TR/xmlschema-1/#element-anyAttribute"/>
+ </xs:annotation>
+ </xs:element>
+
+ <xs:group name="complexTypeModel">
+ <xs:choice>
+ <xs:element ref="xs:simpleContent"/>
+ <xs:element ref="xs:complexContent"/>
+ <xs:sequence>
+ <xs:annotation>
+ <xs:documentation>
+ This branch is short for
+ <complexContent>
+ <restriction base="xs:anyType">
+ ...
+ </restriction>
+ </complexContent></xs:documentation>
+ </xs:annotation>
+ <xs:group ref="xs:typeDefParticle" minOccurs="0"/>
+ <xs:group ref="xs:attrDecls"/>
+ </xs:sequence>
+ </xs:choice>
+ </xs:group>
+
+ <xs:complexType name="complexType" abstract="true">
+ <xs:complexContent>
+ <xs:extension base="xs:annotated">
+ <xs:group ref="xs:complexTypeModel"/>
+ <xs:attribute name="name" type="xs:NCName">
+ <xs:annotation>
+ <xs:documentation>
+ Will be restricted to required or forbidden</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="mixed" type="xs:boolean" use="optional" default="false">
+ <xs:annotation>
+ <xs:documentation>
+ Not allowed if simpleContent child is chosen.
+ May be overriden by setting on complexContent child.</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="abstract" type="xs:boolean" use="optional" default="false"/>
+ <xs:attribute name="final" type="xs:derivationSet"/>
+ <xs:attribute name="block" type="xs:derivationSet"/>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+
+ <xs:complexType name="topLevelComplexType">
+ <xs:complexContent>
+ <xs:restriction base="xs:complexType">
+ <xs:sequence>
+ <xs:element ref="xs:annotation" minOccurs="0"/>
+ <xs:group ref="xs:complexTypeModel"/>
+ </xs:sequence>
+ <xs:attribute name="name" type="xs:NCName" use="required"/>
+ <xs:anyAttribute namespace="##other" processContents="lax"/>
+ </xs:restriction>
+ </xs:complexContent>
+ </xs:complexType>
+
+ <xs:complexType name="localComplexType">
+ <xs:complexContent>
+ <xs:restriction base="xs:complexType">
+ <xs:sequence>
+ <xs:element ref="xs:annotation" minOccurs="0"/>
+ <xs:group ref="xs:complexTypeModel"/>
+ </xs:sequence>
+ <xs:attribute name="name" use="prohibited"/>
+ <xs:attribute name="abstract" use="prohibited"/>
+ <xs:attribute name="final" use="prohibited"/>
+ <xs:attribute name="block" use="prohibited"/>
+ <xs:anyAttribute namespace="##other" processContents="lax"/>
+ </xs:restriction>
+ </xs:complexContent>
+ </xs:complexType>
+
+ <xs:complexType name="restrictionType">
+ <xs:complexContent>
+ <xs:extension base="xs:annotated">
+ <xs:sequence>
+ <xs:choice minOccurs="0">
+ <xs:group ref="xs:typeDefParticle"/>
+ <xs:group ref="xs:simpleRestrictionModel"/>
+ </xs:choice>
+ <xs:group ref="xs:attrDecls"/>
+ </xs:sequence>
+ <xs:attribute name="base" type="xs:QName" use="required"/>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+
+ <xs:complexType name="complexRestrictionType">
+ <xs:complexContent>
+ <xs:restriction base="xs:restrictionType">
+ <xs:sequence>
+ <xs:element ref="xs:annotation" minOccurs="0"/>
+ <xs:choice minOccurs="0">
+ <xs:annotation>
+ <xs:documentation>This choice is added simply to
+ make this a valid restriction per the REC</xs:documentation>
+ </xs:annotation>
+ <xs:group ref="xs:typeDefParticle"/>
+ </xs:choice>
+ <xs:group ref="xs:attrDecls"/>
+ </xs:sequence>
+ <xs:anyAttribute namespace="##other" processContents="lax"/>
+ </xs:restriction>
+ </xs:complexContent>
+ </xs:complexType>
+
+ <xs:complexType name="extensionType">
+ <xs:complexContent>
+ <xs:extension base="xs:annotated">
+ <xs:sequence>
+ <xs:group ref="xs:typeDefParticle" minOccurs="0"/>
+ <xs:group ref="xs:attrDecls"/>
+ </xs:sequence>
+ <xs:attribute name="base" type="xs:QName" use="required"/>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+
+ <xs:element name="complexContent" id="complexContent">
+ <xs:annotation>
+ <xs:documentation source="http://www.w3.org/TR/xmlschema-1/#element-complexContent"/>
+ </xs:annotation>
+ <xs:complexType>
+ <xs:complexContent>
+ <xs:extension base="xs:annotated">
+ <xs:choice>
+ <xs:element name="restriction" type="xs:complexRestrictionType"/>
+ <xs:element name="extension" type="xs:extensionType"/>
+ </xs:choice>
+ <xs:attribute name="mixed" type="xs:boolean">
+ <xs:annotation>
+ <xs:documentation>
+ Overrides any setting on complexType parent.</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+ </xs:element>
+
+ <xs:complexType name="simpleRestrictionType">
+ <xs:complexContent>
+ <xs:restriction base="xs:restrictionType">
+ <xs:sequence>
+ <xs:element ref="xs:annotation" minOccurs="0"/>
+ <xs:choice minOccurs="0">
+ <xs:annotation>
+ <xs:documentation>This choice is added simply to
+ make this a valid restriction per the REC</xs:documentation>
+ </xs:annotation>
+ <xs:group ref="xs:simpleRestrictionModel"/>
+ </xs:choice>
+ <xs:group ref="xs:attrDecls"/>
+ </xs:sequence>
+ <xs:anyAttribute namespace="##other" processContents="lax"/>
+ </xs:restriction>
+ </xs:complexContent>
+ </xs:complexType>
+
+ <xs:complexType name="simpleExtensionType">
+ <xs:complexContent>
+ <xs:restriction base="xs:extensionType">
+ <xs:sequence>
+ <xs:annotation>
+ <xs:documentation>
+ No typeDefParticle group reference</xs:documentation>
+ </xs:annotation>
+ <xs:element ref="xs:annotation" minOccurs="0"/>
+ <xs:group ref="xs:attrDecls"/>
+ </xs:sequence>
+ <xs:anyAttribute namespace="##other" processContents="lax"/>
+ </xs:restriction>
+ </xs:complexContent>
+ </xs:complexType>
+
+ <xs:element name="simpleContent" id="simpleContent">
+ <xs:annotation>
+ <xs:documentation source="http://www.w3.org/TR/xmlschema-1/#element-simpleContent"/>
+ </xs:annotation>
+ <xs:complexType>
+ <xs:complexContent>
+ <xs:extension base="xs:annotated">
+ <xs:choice>
+ <xs:element name="restriction" type="xs:simpleRestrictionType"/>
+ <xs:element name="extension" type="xs:simpleExtensionType"/>
+ </xs:choice>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+ </xs:element>
+
+ <xs:element name="complexType" type="xs:topLevelComplexType" id="complexType">
+ <xs:annotation>
+ <xs:documentation source="http://www.w3.org/TR/xmlschema-1/#element-complexType"/>
+ </xs:annotation>
+ </xs:element>
+
+
+ <xs:simpleType name="blockSet">
+ <xs:annotation>
+ <xs:documentation>
+ A utility type, not for public use</xs:documentation>
+ <xs:documentation>
+ #all or (possibly empty) subset of {substitution, extension,
+ restriction}</xs:documentation>
+ </xs:annotation>
+ <xs:union>
+ <xs:simpleType>
+ <xs:restriction base="xs:token">
+ <xs:enumeration value="#all"/>
+ </xs:restriction>
+ </xs:simpleType>
+ <xs:simpleType>
+ <xs:list>
+ <xs:simpleType>
+ <xs:restriction base="xs:derivationControl">
+ <xs:enumeration value="extension"/>
+ <xs:enumeration value="restriction"/>
+ <xs:enumeration value="substitution"/>
+ </xs:restriction>
+ </xs:simpleType>
+ </xs:list>
+ </xs:simpleType>
+ </xs:union>
+ </xs:simpleType>
+
+ <xs:complexType name="element" abstract="true">
+ <xs:annotation>
+ <xs:documentation>
+ The element element can be used either
+ at the top level to define an element-type binding globally,
+ or within a content model to either reference a globally-defined
+ element or type or declare an element-type binding locally.
+ The ref form is not allowed at the top level.</xs:documentation>
+ </xs:annotation>
+
+ <xs:complexContent>
+ <xs:extension base="xs:annotated">
+ <xs:sequence>
+ <xs:choice minOccurs="0">
+ <xs:element name="simpleType" type="xs:localSimpleType"/>
+ <xs:element name="complexType" type="xs:localComplexType"/>
+ </xs:choice>
+ <xs:group ref="xs:identityConstraint" minOccurs="0" maxOccurs="unbounded"/>
+ </xs:sequence>
+ <xs:attributeGroup ref="xs:defRef"/>
+ <xs:attribute name="type" type="xs:QName"/>
+ <xs:attribute name="substitutionGroup" type="xs:QName"/>
+ <xs:attributeGroup ref="xs:occurs"/>
+ <xs:attribute name="default" type="xs:string"/>
+ <xs:attribute name="fixed" type="xs:string"/>
+ <xs:attribute name="nillable" type="xs:boolean" use="optional" default="false"/>
+ <xs:attribute name="abstract" type="xs:boolean" use="optional" default="false"/>
+ <xs:attribute name="final" type="xs:derivationSet"/>
+ <xs:attribute name="block" type="xs:blockSet"/>
+ <xs:attribute name="form" type="xs:formChoice"/>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+
+ <xs:complexType name="topLevelElement">
+ <xs:complexContent>
+ <xs:restriction base="xs:element">
+ <xs:sequence>
+ <xs:element ref="xs:annotation" minOccurs="0"/>
+ <xs:choice minOccurs="0">
+ <xs:element name="simpleType" type="xs:localSimpleType"/>
+ <xs:element name="complexType" type="xs:localComplexType"/>
+ </xs:choice>
+ <xs:group ref="xs:identityConstraint" minOccurs="0" maxOccurs="unbounded"/>
+ </xs:sequence>
+ <xs:attribute name="ref" use="prohibited"/>
+ <xs:attribute name="form" use="prohibited"/>
+ <xs:attribute name="minOccurs" use="prohibited"/>
+ <xs:attribute name="maxOccurs" use="prohibited"/>
+ <xs:attribute name="name" use="required" type="xs:NCName"/>
+ <xs:anyAttribute namespace="##other" processContents="lax"/>
+ </xs:restriction>
+ </xs:complexContent>
+ </xs:complexType>
+
+ <xs:complexType name="localElement">
+ <xs:complexContent>
+ <xs:restriction base="xs:element">
+ <xs:sequence>
+ <xs:element ref="xs:annotation" minOccurs="0"/>
+ <xs:choice minOccurs="0">
+ <xs:element name="simpleType" type="xs:localSimpleType"/>
+ <xs:element name="complexType" type="xs:localComplexType"/>
+ </xs:choice>
+ <xs:group ref="xs:identityConstraint" minOccurs="0" maxOccurs="unbounded"/>
+ </xs:sequence>
+ <xs:attribute name="substitutionGroup" use="prohibited"/>
+ <xs:attribute name="final" use="prohibited"/>
+ <xs:attribute name="abstract" use="prohibited"/>
+ <xs:anyAttribute namespace="##other" processContents="lax"/>
+ </xs:restriction>
+ </xs:complexContent>
+ </xs:complexType>
+
+ <xs:element name="element" type="xs:topLevelElement" id="element">
+ <xs:annotation>
+ <xs:documentation source="http://www.w3.org/TR/xmlschema-1/#element-element"/>
+ </xs:annotation>
+ </xs:element>
+
+ <xs:complexType name="group" abstract="true">
+ <xs:annotation>
+ <xs:documentation>
+ group type for explicit groups, named top-level groups and
+ group references</xs:documentation>
+ </xs:annotation>
+ <xs:complexContent>
+ <xs:extension base="xs:annotated">
+ <xs:group ref="xs:particle" minOccurs="0" maxOccurs="unbounded"/>
+ <xs:attributeGroup ref="xs:defRef"/>
+ <xs:attributeGroup ref="xs:occurs"/>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+
+ <xs:complexType name="realGroup">
+ <xs:complexContent>
+ <xs:restriction base="xs:group">
+ <xs:sequence>
+ <xs:element ref="xs:annotation" minOccurs="0"/>
+ <xs:choice minOccurs="0" maxOccurs="1">
+ <xs:element ref="xs:all"/>
+ <xs:element ref="xs:choice"/>
+ <xs:element ref="xs:sequence"/>
+ </xs:choice>
+ </xs:sequence>
+ <xs:anyAttribute namespace="##other" processContents="lax"/>
+ </xs:restriction>
+ </xs:complexContent>
+ </xs:complexType>
+
+ <xs:complexType name="namedGroup">
+ <xs:complexContent>
+ <xs:restriction base="xs:realGroup">
+ <xs:sequence>
+ <xs:element ref="xs:annotation" minOccurs="0"/>
+ <xs:choice minOccurs="1" maxOccurs="1">
+ <xs:element name="all">
+ <xs:complexType>
+ <xs:complexContent>
+ <xs:restriction base="xs:all">
+ <xs:group ref="xs:allModel"/>
+ <xs:attribute name="minOccurs" use="prohibited"/>
+ <xs:attribute name="maxOccurs" use="prohibited"/>
+ <xs:anyAttribute namespace="##other" processContents="lax"/>
+ </xs:restriction>
+ </xs:complexContent>
+ </xs:complexType>
+ </xs:element>
+ <xs:element name="choice" type="xs:simpleExplicitGroup"/>
+ <xs:element name="sequence" type="xs:simpleExplicitGroup"/>
+ </xs:choice>
+ </xs:sequence>
+ <xs:attribute name="name" use="required" type="xs:NCName"/>
+ <xs:attribute name="ref" use="prohibited"/>
+ <xs:attribute name="minOccurs" use="prohibited"/>
+ <xs:attribute name="maxOccurs" use="prohibited"/>
+ <xs:anyAttribute namespace="##other" processContents="lax"/>
+ </xs:restriction>
+ </xs:complexContent>
+ </xs:complexType>
+
+ <xs:complexType name="groupRef">
+ <xs:complexContent>
+ <xs:restriction base="xs:realGroup">
+ <xs:sequence>
+ <xs:element ref="xs:annotation" minOccurs="0"/>
+ </xs:sequence>
+ <xs:attribute name="ref" use="required" type="xs:QName"/>
+ <xs:attribute name="name" use="prohibited"/>
+ <xs:anyAttribute namespace="##other" processContents="lax"/>
+ </xs:restriction>
+ </xs:complexContent>
+ </xs:complexType>
+
+ <xs:complexType name="explicitGroup">
+ <xs:annotation>
+ <xs:documentation>
+ group type for the three kinds of group</xs:documentation>
+ </xs:annotation>
+ <xs:complexContent>
+ <xs:restriction base="xs:group">
+ <xs:sequence>
+ <xs:element ref="xs:annotation" minOccurs="0"/>
+ <xs:group ref="xs:nestedParticle" minOccurs="0" maxOccurs="unbounded"/>
+ </xs:sequence>
+ <xs:attribute name="name" type="xs:NCName" use="prohibited"/>
+ <xs:attribute name="ref" type="xs:QName" use="prohibited"/>
+ <xs:anyAttribute namespace="##other" processContents="lax"/>
+ </xs:restriction>
+ </xs:complexContent>
+ </xs:complexType>
+
+ <xs:complexType name="simpleExplicitGroup">
+ <xs:complexContent>
+ <xs:restriction base="xs:explicitGroup">
+ <xs:sequence>
+ <xs:element ref="xs:annotation" minOccurs="0"/>
+ <xs:group ref="xs:nestedParticle" minOccurs="0" maxOccurs="unbounded"/>
+ </xs:sequence>
+ <xs:attribute name="minOccurs" use="prohibited"/>
+ <xs:attribute name="maxOccurs" use="prohibited"/>
+ <xs:anyAttribute namespace="##other" processContents="lax"/>
+ </xs:restriction>
+ </xs:complexContent>
+ </xs:complexType>
+
+ <xs:group name="allModel">
+ <xs:sequence>
+ <xs:element ref="xs:annotation" minOccurs="0"/>
+ <xs:choice minOccurs="0" maxOccurs="unbounded">
+ <xs:annotation>
+ <xs:documentation>This choice with min/max is here to
+ avoid a pblm with the Elt:All/Choice/Seq
+ Particle derivation constraint</xs:documentation>
+ </xs:annotation>
+ <xs:element name="element" type="xs:narrowMaxMin"/>
+ </xs:choice>
+ </xs:sequence>
+ </xs:group>
+
+
+ <xs:complexType name="narrowMaxMin">
+ <xs:annotation>
+ <xs:documentation>restricted max/min</xs:documentation>
+ </xs:annotation>
+ <xs:complexContent>
+ <xs:restriction base="xs:localElement">
+ <xs:sequence>
+ <xs:element ref="xs:annotation" minOccurs="0"/>
+ <xs:choice minOccurs="0">
+ <xs:element name="simpleType" type="xs:localSimpleType"/>
+ <xs:element name="complexType" type="xs:localComplexType"/>
+ </xs:choice>
+ <xs:group ref="xs:identityConstraint" minOccurs="0" maxOccurs="unbounded"/>
+ </xs:sequence>
+ <xs:attribute name="minOccurs" use="optional" default="1">
+ <xs:simpleType>
+ <xs:restriction base="xs:nonNegativeInteger">
+ <xs:enumeration value="0"/>
+ <xs:enumeration value="1"/>
+ </xs:restriction>
+ </xs:simpleType>
+ </xs:attribute>
+ <xs:attribute name="maxOccurs" use="optional" default="1">
+ <xs:simpleType>
+ <xs:restriction base="xs:allNNI">
+ <xs:enumeration value="0"/>
+ <xs:enumeration value="1"/>
+ </xs:restriction>
+ </xs:simpleType>
+ </xs:attribute>
+ <xs:anyAttribute namespace="##other" processContents="lax"/>
+ </xs:restriction>
+ </xs:complexContent>
+ </xs:complexType>
+
+ <xs:complexType name="all">
+ <xs:annotation>
+ <xs:documentation>
+ Only elements allowed inside</xs:documentation>
+ </xs:annotation>
+ <xs:complexContent>
+ <xs:restriction base="xs:explicitGroup">
+ <xs:group ref="xs:allModel"/>
+ <xs:attribute name="minOccurs" use="optional" default="1">
+ <xs:simpleType>
+ <xs:restriction base="xs:nonNegativeInteger">
+ <xs:enumeration value="0"/>
+ <xs:enumeration value="1"/>
+ </xs:restriction>
+ </xs:simpleType>
+ </xs:attribute>
+ <xs:attribute name="maxOccurs" use="optional" default="1">
+ <xs:simpleType>
+ <xs:restriction base="xs:allNNI">
+ <xs:enumeration value="1"/>
+ </xs:restriction>
+ </xs:simpleType>
+ </xs:attribute>
+ <xs:anyAttribute namespace="##other" processContents="lax"/>
+ </xs:restriction>
+ </xs:complexContent>
+ </xs:complexType>
+
+ <xs:element name="all" id="all" type="xs:all">
+ <xs:annotation>
+ <xs:documentation source="http://www.w3.org/TR/xmlschema-1/#element-all"/>
+ </xs:annotation>
+ </xs:element>
+
+ <xs:element name="choice" type="xs:explicitGroup" id="choice">
+ <xs:annotation>
+ <xs:documentation source="http://www.w3.org/TR/xmlschema-1/#element-choice"/>
+ </xs:annotation>
+ </xs:element>
+
+ <xs:element name="sequence" type="xs:explicitGroup" id="sequence">
+ <xs:annotation>
+ <xs:documentation source="http://www.w3.org/TR/xmlschema-1/#element-sequence"/>
+ </xs:annotation>
+ </xs:element>
+
+ <xs:element name="group" type="xs:namedGroup" id="group">
+ <xs:annotation>
+ <xs:documentation source="http://www.w3.org/TR/xmlschema-1/#element-group"/>
+ </xs:annotation>
+ </xs:element>
+
+ <xs:complexType name="wildcard">
+ <xs:complexContent>
+ <xs:extension base="xs:annotated">
+ <xs:attribute name="namespace" type="xs:namespaceList" use="optional" default="##any"/>
+ <xs:attribute name="processContents" use="optional" default="strict">
+ <xs:simpleType>
+ <xs:restriction base="xs:NMTOKEN">
+ <xs:enumeration value="skip"/>
+ <xs:enumeration value="lax"/>
+ <xs:enumeration value="strict"/>
+ </xs:restriction>
+ </xs:simpleType>
+ </xs:attribute>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+
+ <xs:element name="any" id="any">
+ <xs:annotation>
+ <xs:documentation source="http://www.w3.org/TR/xmlschema-1/#element-any"/>
+ </xs:annotation>
+ <xs:complexType>
+ <xs:complexContent>
+ <xs:extension base="xs:wildcard">
+ <xs:attributeGroup ref="xs:occurs"/>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+ </xs:element>
+
+ <xs:annotation>
+ <xs:documentation>
+ simple type for the value of the 'namespace' attr of
+ 'any' and 'anyAttribute'</xs:documentation>
+ </xs:annotation>
+ <xs:annotation>
+ <xs:documentation>
+ Value is
+ ##any - - any non-conflicting WFXML/attribute at all
+
+ ##other - - any non-conflicting WFXML/attribute from
+ namespace other than targetNS
+
+ ##local - - any unqualified non-conflicting WFXML/attribute
+
+ one or - - any non-conflicting WFXML/attribute from
+ more URI the listed namespaces
+ references
+ (space separated)
+
+ ##targetNamespace or ##local may appear in the above list, to
+ refer to the targetNamespace of the enclosing
+ schema or an absent targetNamespace respectively</xs:documentation>
+ </xs:annotation>
+
+ <xs:simpleType name="namespaceList">
+ <xs:annotation>
+ <xs:documentation>
+ A utility type, not for public use</xs:documentation>
+ </xs:annotation>
+ <xs:union>
+ <xs:simpleType>
+ <xs:restriction base="xs:token">
+ <xs:enumeration value="##any"/>
+ <xs:enumeration value="##other"/>
+ </xs:restriction>
+ </xs:simpleType>
+ <xs:simpleType>
+ <xs:list>
+ <xs:simpleType>
+ <xs:union memberTypes="xs:anyURI">
+ <xs:simpleType>
+ <xs:restriction base="xs:token">
+ <xs:enumeration value="##targetNamespace"/>
+ <xs:enumeration value="##local"/>
+ </xs:restriction>
+ </xs:simpleType>
+ </xs:union>
+ </xs:simpleType>
+ </xs:list>
+ </xs:simpleType>
+ </xs:union>
+ </xs:simpleType>
+
+ <xs:element name="attribute" type="xs:topLevelAttribute" id="attribute">
+ <xs:annotation>
+ <xs:documentation source="http://www.w3.org/TR/xmlschema-1/#element-attribute"/>
+ </xs:annotation>
+ </xs:element>
+
+ <xs:complexType name="attributeGroup" abstract="true">
+ <xs:complexContent>
+ <xs:extension base="xs:annotated">
+ <xs:group ref="xs:attrDecls"/>
+ <xs:attributeGroup ref="xs:defRef"/>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+
+ <xs:complexType name="namedAttributeGroup">
+ <xs:complexContent>
+ <xs:restriction base="xs:attributeGroup">
+ <xs:sequence>
+ <xs:element ref="xs:annotation" minOccurs="0"/>
+ <xs:group ref="xs:attrDecls"/>
+ </xs:sequence>
+ <xs:attribute name="name" use="required" type="xs:NCName"/>
+ <xs:attribute name="ref" use="prohibited"/>
+ <xs:anyAttribute namespace="##other" processContents="lax"/>
+ </xs:restriction>
+ </xs:complexContent>
+ </xs:complexType>
+
+ <xs:complexType name="attributeGroupRef">
+ <xs:complexContent>
+ <xs:restriction base="xs:attributeGroup">
+ <xs:sequence>
+ <xs:element ref="xs:annotation" minOccurs="0"/>
+ </xs:sequence>
+ <xs:attribute name="ref" use="required" type="xs:QName"/>
+ <xs:attribute name="name" use="prohibited"/>
+ <xs:anyAttribute namespace="##other" processContents="lax"/>
+ </xs:restriction>
+ </xs:complexContent>
+ </xs:complexType>
+
+ <xs:element name="attributeGroup" type="xs:namedAttributeGroup" id="attributeGroup">
+ <xs:annotation>
+ <xs:documentation source="http://www.w3.org/TR/xmlschema-1/#element-attributeGroup"/>
+ </xs:annotation>
+ </xs:element>
+
+ <xs:element name="include" id="include">
+ <xs:annotation>
+ <xs:documentation source="http://www.w3.org/TR/xmlschema-1/#element-include"/>
+ </xs:annotation>
+ <xs:complexType>
+ <xs:complexContent>
+ <xs:extension base="xs:annotated">
+ <xs:attribute name="schemaLocation" type="xs:anyURI" use="required"/>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+ </xs:element>
+
+ <xs:element name="redefine" id="redefine">
+ <xs:annotation>
+ <xs:documentation source="http://www.w3.org/TR/xmlschema-1/#element-redefine"/>
+ </xs:annotation>
+ <xs:complexType>
+ <xs:complexContent>
+ <xs:extension base="xs:openAttrs">
+ <xs:choice minOccurs="0" maxOccurs="unbounded">
+ <xs:element ref="xs:annotation"/>
+ <xs:group ref="xs:redefinable"/>
+ </xs:choice>
+ <xs:attribute name="schemaLocation" type="xs:anyURI" use="required"/>
+ <xs:attribute name="id" type="xs:ID"/>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+ </xs:element>
+
+ <xs:element name="import" id="import">
+ <xs:annotation>
+ <xs:documentation source="http://www.w3.org/TR/xmlschema-1/#element-import"/>
+ </xs:annotation>
+ <xs:complexType>
+ <xs:complexContent>
+ <xs:extension base="xs:annotated">
+ <xs:attribute name="namespace" type="xs:anyURI"/>
+ <xs:attribute name="schemaLocation" type="xs:anyURI"/>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+ </xs:element>
+
+ <xs:element name="selector" id="selector">
+ <xs:annotation>
+ <xs:documentation source="http://www.w3.org/TR/xmlschema-1/#element-selector"/>
+ </xs:annotation>
+ <xs:complexType>
+ <xs:complexContent>
+ <xs:extension base="xs:annotated">
+ <xs:attribute name="xpath" use="required">
+ <xs:simpleType>
+ <xs:annotation>
+ <xs:documentation>A subset of XPath expressions for use
+in selectors</xs:documentation>
+ <xs:documentation>A utility type, not for public
+use</xs:documentation>
+ </xs:annotation>
+ <xs:restriction base="xs:token">
+ <xs:annotation>
+ <xs:documentation>The following pattern is intended to allow XPath
+ expressions per the following EBNF:
+ Selector ::= Path ( '|' Path )*
+ Path ::= ('.//')? Step ( '/' Step )*
+ Step ::= '.' | NameTest
+ NameTest ::= QName | '*' | NCName ':' '*'
+ child:: is also allowed
+ </xs:documentation>
+ </xs:annotation>
+ <xs:pattern value="(\.//)?(((child::)?((\i\c*:)?(\i\c*|\*)))|\.)(/(((child::)?((\i\c*:)?(\i\c*|\*)))|\.))*(\|(\.//)?(((child::)?((\i\c*:)?(\i\c*|\*)))|\.)(/(((child::)?((\i\c*:)?(\i\c*|\*)))|\.))*)*">
+ </xs:pattern>
+ </xs:restriction>
+ </xs:simpleType>
+ </xs:attribute>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+ </xs:element>
+
+ <xs:element name="field" id="field">
+ <xs:annotation>
+ <xs:documentation source="http://www.w3.org/TR/xmlschema-1/#element-field"/>
+ </xs:annotation>
+ <xs:complexType>
+ <xs:complexContent>
+ <xs:extension base="xs:annotated">
+ <xs:attribute name="xpath" use="required">
+ <xs:simpleType>
+ <xs:annotation>
+ <xs:documentation>A subset of XPath expressions for use
+in fields</xs:documentation>
+ <xs:documentation>A utility type, not for public
+use</xs:documentation>
+ </xs:annotation>
+ <xs:restriction base="xs:token">
+ <xs:annotation>
+ <xs:documentation>The following pattern is intended to allow XPath
+ expressions per the same EBNF as for selector,
+ with the following change:
+ Path ::= ('.//')? ( Step '/' )* ( Step | '@' NameTest )
+ </xs:documentation>
+ </xs:annotation>
+ <xs:pattern value="(\.//)?((((child::)?((\i\c*:)?(\i\c*|\*)))|\.)/)*((((child::)?((\i\c*:)?(\i\c*|\*)))|\.)|((attribute::|@)((\i\c*:)?(\i\c*|\*))))(\|(\.//)?((((child::)?((\i\c*:)?(\i\c*|\*)))|\.)/)*((((child::)?((\i\c*:)?(\i\c*|\*)))|\.)|((attribute::|@)((\i\c*:)?(\i\c*|\*)))))*">
+ </xs:pattern>
+ </xs:restriction>
+ </xs:simpleType>
+ </xs:attribute>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+ </xs:element>
+
+ <xs:complexType name="keybase">
+ <xs:complexContent>
+ <xs:extension base="xs:annotated">
+ <xs:sequence>
+ <xs:element ref="xs:selector"/>
+ <xs:element ref="xs:field" minOccurs="1" maxOccurs="unbounded"/>
+ </xs:sequence>
+ <xs:attribute name="name" type="xs:NCName" use="required"/>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+
+ <xs:group name="identityConstraint">
+ <xs:annotation>
+ <xs:documentation>The three kinds of identity constraints, all with
+ type of or derived from 'keybase'.
+ </xs:documentation>
+ </xs:annotation>
+ <xs:choice>
+ <xs:element ref="xs:unique"/>
+ <xs:element ref="xs:key"/>
+ <xs:element ref="xs:keyref"/>
+ </xs:choice>
+ </xs:group>
+
+ <xs:element name="unique" type="xs:keybase" id="unique">
+ <xs:annotation>
+ <xs:documentation source="http://www.w3.org/TR/xmlschema-1/#element-unique"/>
+ </xs:annotation>
+ </xs:element>
+ <xs:element name="key" type="xs:keybase" id="key">
+ <xs:annotation>
+ <xs:documentation source="http://www.w3.org/TR/xmlschema-1/#element-key"/>
+ </xs:annotation>
+ </xs:element>
+ <xs:element name="keyref" id="keyref">
+ <xs:annotation>
+ <xs:documentation source="http://www.w3.org/TR/xmlschema-1/#element-keyref"/>
+ </xs:annotation>
+ <xs:complexType>
+ <xs:complexContent>
+ <xs:extension base="xs:keybase">
+ <xs:attribute name="refer" type="xs:QName" use="required"/>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+ </xs:element>
+
+ <xs:element name="notation" id="notation">
+ <xs:annotation>
+ <xs:documentation source="http://www.w3.org/TR/xmlschema-1/#element-notation"/>
+ </xs:annotation>
+ <xs:complexType>
+ <xs:complexContent>
+ <xs:extension base="xs:annotated">
+ <xs:attribute name="name" type="xs:NCName" use="required"/>
+ <xs:attribute name="public" type="xs:public"/>
+ <xs:attribute name="system" type="xs:anyURI"/>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+ </xs:element>
+
+ <xs:simpleType name="public">
+ <xs:annotation>
+ <xs:documentation>
+ A utility type, not for public use</xs:documentation>
+ <xs:documentation>
+ A public identifier, per ISO 8879</xs:documentation>
+ </xs:annotation>
+ <xs:restriction base="xs:token"/>
+ </xs:simpleType>
+
+ <xs:element name="appinfo" id="appinfo">
+ <xs:annotation>
+ <xs:documentation source="http://www.w3.org/TR/xmlschema-1/#element-appinfo"/>
+ </xs:annotation>
+ <xs:complexType mixed="true">
+ <xs:sequence minOccurs="0" maxOccurs="unbounded">
+ <xs:any processContents="lax"/>
+ </xs:sequence>
+ <xs:attribute name="source" type="xs:anyURI"/>
+ <xs:anyAttribute namespace="##other" processContents="lax"/>
+ </xs:complexType>
+ </xs:element>
+
+ <xs:element name="documentation" id="documentation">
+ <xs:annotation>
+ <xs:documentation source="http://www.w3.org/TR/xmlschema-1/#element-documentation"/>
+ </xs:annotation>
+ <xs:complexType mixed="true">
+ <xs:sequence minOccurs="0" maxOccurs="unbounded">
+ <xs:any processContents="lax"/>
+ </xs:sequence>
+ <xs:attribute name="source" type="xs:anyURI"/>
+ <xs:attribute ref="xml:lang"/>
+ <xs:anyAttribute namespace="##other" processContents="lax"/>
+ </xs:complexType>
+ </xs:element>
+
+ <xs:element name="annotation" id="annotation">
+ <xs:annotation>
+ <xs:documentation source="http://www.w3.org/TR/xmlschema-1/#element-annotation"/>
+ </xs:annotation>
+ <xs:complexType>
+ <xs:complexContent>
+ <xs:extension base="xs:openAttrs">
+ <xs:choice minOccurs="0" maxOccurs="unbounded">
+ <xs:element ref="xs:appinfo"/>
+ <xs:element ref="xs:documentation"/>
+ </xs:choice>
+ <xs:attribute name="id" type="xs:ID"/>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+ </xs:element>
+
+ <xs:annotation>
+ <xs:documentation>
+ notations for use within XML Schema schemas</xs:documentation>
+ </xs:annotation>
+
+ <xs:notation name="XMLSchemaStructures" public="structures" system="http://www.w3.org/2000/08/XMLSchema.xsd"/>
+ <xs:notation name="XML" public="REC-xml-19980210" system="http://www.w3.org/TR/1998/REC-xml-19980210"/>
+
+ <xs:complexType name="anyType" mixed="true">
+ <xs:annotation>
+ <xs:documentation>
+ Not the real urType, but as close an approximation as we can
+ get in the XML representation</xs:documentation>
+ </xs:annotation>
+ <xs:sequence>
+ <xs:any minOccurs="0" maxOccurs="unbounded" processContents="lax"/>
+ </xs:sequence>
+ <xs:anyAttribute processContents="lax"/>
+ </xs:complexType>
+
+ <xs:annotation>
+ <xs:documentation>
+ First the built-in primitive datatypes. These definitions are for
+ information only, the real built-in definitions are magic.
+ </xs:documentation>
+
+ <xs:documentation>
+ For each built-in datatype in this schema (both primitive and
+ derived) can be uniquely addressed via a URI constructed
+ as follows:
+ 1) the base URI is the URI of the XML Schema namespace
+ 2) the fragment identifier is the name of the datatype
+
+ For example, to address the int datatype, the URI is:
+
+ http://www.w3.org/2001/XMLSchema#int
+
+ Additionally, each facet definition element can be uniquely
+ addressed via a URI constructed as follows:
+ 1) the base URI is the URI of the XML Schema namespace
+ 2) the fragment identifier is the name of the facet
+
+ For example, to address the maxInclusive facet, the URI is:
+
+ http://www.w3.org/2001/XMLSchema#maxInclusive
+
+ Additionally, each facet usage in a built-in datatype definition
+ can be uniquely addressed via a URI constructed as follows:
+ 1) the base URI is the URI of the XML Schema namespace
+ 2) the fragment identifier is the name of the datatype, followed
+ by a period (".") followed by the name of the facet
+
+ For example, to address the usage of the maxInclusive facet in
+ the definition of int, the URI is:
+
+ http://www.w3.org/2001/XMLSchema#int.maxInclusive
+
+ </xs:documentation>
+ </xs:annotation>
+
+ <xs:simpleType name="string" id="string">
+ <xs:annotation>
+ <xs:appinfo>
+ <hfp:hasFacet name="length"/>
+ <hfp:hasFacet name="minLength"/>
+ <hfp:hasFacet name="maxLength"/>
+ <hfp:hasFacet name="pattern"/>
+ <hfp:hasFacet name="enumeration"/>
+ <hfp:hasFacet name="whiteSpace"/>
+ <hfp:hasProperty name="ordered" value="false"/>
+ <hfp:hasProperty name="bounded" value="false"/>
+ <hfp:hasProperty name="cardinality" value="countably infinite"/>
+ <hfp:hasProperty name="numeric" value="false"/>
+ </xs:appinfo>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#string"/>
+ </xs:annotation>
+ <xs:restriction base="xs:anySimpleType">
+ <xs:whiteSpace value="preserve" id="string.preserve"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="boolean" id="boolean">
+ <xs:annotation>
+ <xs:appinfo>
+ <hfp:hasFacet name="pattern"/>
+ <hfp:hasFacet name="whiteSpace"/>
+ <hfp:hasProperty name="ordered" value="false"/>
+ <hfp:hasProperty name="bounded" value="false"/>
+ <hfp:hasProperty name="cardinality" value="finite"/>
+ <hfp:hasProperty name="numeric" value="false"/>
+ </xs:appinfo>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#boolean"/>
+ </xs:annotation>
+ <xs:restriction base="xs:anySimpleType">
+ <xs:whiteSpace value="collapse" fixed="true"
+ id="boolean.whiteSpace"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="float" id="float">
+ <xs:annotation>
+ <xs:appinfo>
+ <hfp:hasFacet name="pattern"/>
+ <hfp:hasFacet name="enumeration"/>
+ <hfp:hasFacet name="whiteSpace"/>
+ <hfp:hasFacet name="maxInclusive"/>
+ <hfp:hasFacet name="maxExclusive"/>
+ <hfp:hasFacet name="minInclusive"/>
+ <hfp:hasFacet name="minExclusive"/>
+ <hfp:hasProperty name="ordered" value="total"/>
+ <hfp:hasProperty name="bounded" value="true"/>
+ <hfp:hasProperty name="cardinality" value="finite"/>
+ <hfp:hasProperty name="numeric" value="true"/>
+ </xs:appinfo>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#float"/>
+ </xs:annotation>
+ <xs:restriction base="xs:anySimpleType">
+ <xs:whiteSpace value="collapse" fixed="true"
+ id="float.whiteSpace"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="double" id="double">
+ <xs:annotation>
+ <xs:appinfo>
+ <hfp:hasFacet name="pattern"/>
+ <hfp:hasFacet name="enumeration"/>
+ <hfp:hasFacet name="whiteSpace"/>
+ <hfp:hasFacet name="maxInclusive"/>
+ <hfp:hasFacet name="maxExclusive"/>
+ <hfp:hasFacet name="minInclusive"/>
+ <hfp:hasFacet name="minExclusive"/>
+ <hfp:hasProperty name="ordered" value="total"/>
+ <hfp:hasProperty name="bounded" value="true"/>
+ <hfp:hasProperty name="cardinality" value="finite"/>
+ <hfp:hasProperty name="numeric" value="true"/>
+ </xs:appinfo>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#double"/>
+ </xs:annotation>
+ <xs:restriction base="xs:anySimpleType">
+ <xs:whiteSpace value="collapse" fixed="true"
+ id="double.whiteSpace"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="decimal" id="decimal">
+ <xs:annotation>
+ <xs:appinfo>
+ <hfp:hasFacet name="totalDigits"/>
+ <hfp:hasFacet name="fractionDigits"/>
+ <hfp:hasFacet name="pattern"/>
+ <hfp:hasFacet name="whiteSpace"/>
+ <hfp:hasFacet name="enumeration"/>
+ <hfp:hasFacet name="maxInclusive"/>
+ <hfp:hasFacet name="maxExclusive"/>
+ <hfp:hasFacet name="minInclusive"/>
+ <hfp:hasFacet name="minExclusive"/>
+ <hfp:hasProperty name="ordered" value="total"/>
+ <hfp:hasProperty name="bounded" value="false"/>
+ <hfp:hasProperty name="cardinality"
+ value="countably infinite"/>
+ <hfp:hasProperty name="numeric" value="true"/>
+ </xs:appinfo>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#decimal"/>
+ </xs:annotation>
+ <xs:restriction base="xs:anySimpleType">
+ <xs:whiteSpace value="collapse" fixed="true"
+ id="decimal.whiteSpace"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="duration" id="duration">
+ <xs:annotation>
+ <xs:appinfo>
+ <hfp:hasFacet name="pattern"/>
+ <hfp:hasFacet name="enumeration"/>
+ <hfp:hasFacet name="whiteSpace"/>
+ <hfp:hasFacet name="maxInclusive"/>
+ <hfp:hasFacet name="maxExclusive"/>
+ <hfp:hasFacet name="minInclusive"/>
+ <hfp:hasFacet name="minExclusive"/>
+ <hfp:hasProperty name="ordered" value="partial"/>
+ <hfp:hasProperty name="bounded" value="false"/>
+ <hfp:hasProperty name="cardinality"
+ value="countably infinite"/>
+ <hfp:hasProperty name="numeric" value="false"/>
+ </xs:appinfo>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#duration"/>
+ </xs:annotation>
+ <xs:restriction base="xs:anySimpleType">
+ <xs:whiteSpace value="collapse" fixed="true"
+ id="duration.whiteSpace"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="dateTime" id="dateTime">
+ <xs:annotation>
+ <xs:appinfo>
+ <hfp:hasFacet name="pattern"/>
+ <hfp:hasFacet name="enumeration"/>
+ <hfp:hasFacet name="whiteSpace"/>
+ <hfp:hasFacet name="maxInclusive"/>
+ <hfp:hasFacet name="maxExclusive"/>
+ <hfp:hasFacet name="minInclusive"/>
+ <hfp:hasFacet name="minExclusive"/>
+ <hfp:hasProperty name="ordered" value="partial"/>
+ <hfp:hasProperty name="bounded" value="false"/>
+ <hfp:hasProperty name="cardinality"
+ value="countably infinite"/>
+ <hfp:hasProperty name="numeric" value="false"/>
+ </xs:appinfo>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#dateTime"/>
+ </xs:annotation>
+ <xs:restriction base="xs:anySimpleType">
+ <xs:whiteSpace value="collapse" fixed="true"
+ id="dateTime.whiteSpace"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="time" id="time">
+ <xs:annotation>
+ <xs:appinfo>
+ <hfp:hasFacet name="pattern"/>
+ <hfp:hasFacet name="enumeration"/>
+ <hfp:hasFacet name="whiteSpace"/>
+ <hfp:hasFacet name="maxInclusive"/>
+ <hfp:hasFacet name="maxExclusive"/>
+ <hfp:hasFacet name="minInclusive"/>
+ <hfp:hasFacet name="minExclusive"/>
+ <hfp:hasProperty name="ordered" value="partial"/>
+ <hfp:hasProperty name="bounded" value="false"/>
+ <hfp:hasProperty name="cardinality"
+ value="countably infinite"/>
+ <hfp:hasProperty name="numeric" value="false"/>
+ </xs:appinfo>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#time"/>
+ </xs:annotation>
+ <xs:restriction base="xs:anySimpleType">
+ <xs:whiteSpace value="collapse" fixed="true"
+ id="time.whiteSpace"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="date" id="date">
+ <xs:annotation>
+ <xs:appinfo>
+ <hfp:hasFacet name="pattern"/>
+ <hfp:hasFacet name="enumeration"/>
+ <hfp:hasFacet name="whiteSpace"/>
+ <hfp:hasFacet name="maxInclusive"/>
+ <hfp:hasFacet name="maxExclusive"/>
+ <hfp:hasFacet name="minInclusive"/>
+ <hfp:hasFacet name="minExclusive"/>
+ <hfp:hasProperty name="ordered" value="partial"/>
+ <hfp:hasProperty name="bounded" value="false"/>
+ <hfp:hasProperty name="cardinality"
+ value="countably infinite"/>
+ <hfp:hasProperty name="numeric" value="false"/>
+ </xs:appinfo>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#date"/>
+ </xs:annotation>
+ <xs:restriction base="xs:anySimpleType">
+ <xs:whiteSpace value="collapse" fixed="true"
+ id="date.whiteSpace"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="gYearMonth" id="gYearMonth">
+ <xs:annotation>
+ <xs:appinfo>
+ <hfp:hasFacet name="pattern"/>
+ <hfp:hasFacet name="enumeration"/>
+ <hfp:hasFacet name="whiteSpace"/>
+ <hfp:hasFacet name="maxInclusive"/>
+ <hfp:hasFacet name="maxExclusive"/>
+ <hfp:hasFacet name="minInclusive"/>
+ <hfp:hasFacet name="minExclusive"/>
+ <hfp:hasProperty name="ordered" value="partial"/>
+ <hfp:hasProperty name="bounded" value="false"/>
+ <hfp:hasProperty name="cardinality"
+ value="countably infinite"/>
+ <hfp:hasProperty name="numeric" value="false"/>
+ </xs:appinfo>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#gYearMonth"/>
+ </xs:annotation>
+ <xs:restriction base="xs:anySimpleType">
+ <xs:whiteSpace value="collapse" fixed="true"
+ id="gYearMonth.whiteSpace"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="gYear" id="gYear">
+ <xs:annotation>
+ <xs:appinfo>
+ <hfp:hasFacet name="pattern"/>
+ <hfp:hasFacet name="enumeration"/>
+ <hfp:hasFacet name="whiteSpace"/>
+ <hfp:hasFacet name="maxInclusive"/>
+ <hfp:hasFacet name="maxExclusive"/>
+ <hfp:hasFacet name="minInclusive"/>
+ <hfp:hasFacet name="minExclusive"/>
+ <hfp:hasProperty name="ordered" value="partial"/>
+ <hfp:hasProperty name="bounded" value="false"/>
+ <hfp:hasProperty name="cardinality"
+ value="countably infinite"/>
+ <hfp:hasProperty name="numeric" value="false"/>
+ </xs:appinfo>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#gYear"/>
+ </xs:annotation>
+ <xs:restriction base="xs:anySimpleType">
+ <xs:whiteSpace value="collapse" fixed="true"
+ id="gYear.whiteSpace"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="gMonthDay" id="gMonthDay">
+ <xs:annotation>
+ <xs:appinfo>
+ <hfp:hasFacet name="pattern"/>
+ <hfp:hasFacet name="enumeration"/>
+ <hfp:hasFacet name="whiteSpace"/>
+ <hfp:hasFacet name="maxInclusive"/>
+ <hfp:hasFacet name="maxExclusive"/>
+ <hfp:hasFacet name="minInclusive"/>
+ <hfp:hasFacet name="minExclusive"/>
+ <hfp:hasProperty name="ordered" value="partial"/>
+ <hfp:hasProperty name="bounded" value="false"/>
+ <hfp:hasProperty name="cardinality"
+ value="countably infinite"/>
+ <hfp:hasProperty name="numeric" value="false"/>
+ </xs:appinfo>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#gMonthDay"/>
+ </xs:annotation>
+ <xs:restriction base="xs:anySimpleType">
+ <xs:whiteSpace value="collapse" fixed="true"
+ id="gMonthDay.whiteSpace"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="gDay" id="gDay">
+ <xs:annotation>
+ <xs:appinfo>
+ <hfp:hasFacet name="pattern"/>
+ <hfp:hasFacet name="enumeration"/>
+ <hfp:hasFacet name="whiteSpace"/>
+ <hfp:hasFacet name="maxInclusive"/>
+ <hfp:hasFacet name="maxExclusive"/>
+ <hfp:hasFacet name="minInclusive"/>
+ <hfp:hasFacet name="minExclusive"/>
+ <hfp:hasProperty name="ordered" value="partial"/>
+ <hfp:hasProperty name="bounded" value="false"/>
+ <hfp:hasProperty name="cardinality"
+ value="countably infinite"/>
+ <hfp:hasProperty name="numeric" value="false"/>
+ </xs:appinfo>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#gDay"/>
+ </xs:annotation>
+ <xs:restriction base="xs:anySimpleType">
+ <xs:whiteSpace value="collapse" fixed="true"
+ id="gDay.whiteSpace"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="gMonth" id="gMonth">
+ <xs:annotation>
+ <xs:appinfo>
+ <hfp:hasFacet name="pattern"/>
+ <hfp:hasFacet name="enumeration"/>
+ <hfp:hasFacet name="whiteSpace"/>
+ <hfp:hasFacet name="maxInclusive"/>
+ <hfp:hasFacet name="maxExclusive"/>
+ <hfp:hasFacet name="minInclusive"/>
+ <hfp:hasFacet name="minExclusive"/>
+ <hfp:hasProperty name="ordered" value="partial"/>
+ <hfp:hasProperty name="bounded" value="false"/>
+ <hfp:hasProperty name="cardinality"
+ value="countably infinite"/>
+ <hfp:hasProperty name="numeric" value="false"/>
+ </xs:appinfo>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#gMonth"/>
+ </xs:annotation>
+ <xs:restriction base="xs:anySimpleType">
+ <xs:whiteSpace value="collapse" fixed="true"
+ id="gMonth.whiteSpace"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="hexBinary" id="hexBinary">
+ <xs:annotation>
+ <xs:appinfo>
+ <hfp:hasFacet name="length"/>
+ <hfp:hasFacet name="minLength"/>
+ <hfp:hasFacet name="maxLength"/>
+ <hfp:hasFacet name="pattern"/>
+ <hfp:hasFacet name="enumeration"/>
+ <hfp:hasFacet name="whiteSpace"/>
+ <hfp:hasProperty name="ordered" value="false"/>
+ <hfp:hasProperty name="bounded" value="false"/>
+ <hfp:hasProperty name="cardinality"
+ value="countably infinite"/>
+ <hfp:hasProperty name="numeric" value="false"/>
+ </xs:appinfo>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#binary"/>
+ </xs:annotation>
+ <xs:restriction base="xs:anySimpleType">
+ <xs:whiteSpace value="collapse" fixed="true"
+ id="hexBinary.whiteSpace"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="base64Binary" id="base64Binary">
+ <xs:annotation>
+ <xs:appinfo>
+ <hfp:hasFacet name="length"/>
+ <hfp:hasFacet name="minLength"/>
+ <hfp:hasFacet name="maxLength"/>
+ <hfp:hasFacet name="pattern"/>
+ <hfp:hasFacet name="enumeration"/>
+ <hfp:hasFacet name="whiteSpace"/>
+ <hfp:hasProperty name="ordered" value="false"/>
+ <hfp:hasProperty name="bounded" value="false"/>
+ <hfp:hasProperty name="cardinality"
+ value="countably infinite"/>
+ <hfp:hasProperty name="numeric" value="false"/>
+ </xs:appinfo>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#base64Binary"/>
+ </xs:annotation>
+ <xs:restriction base="xs:anySimpleType">
+ <xs:whiteSpace value="collapse" fixed="true"
+ id="base64Binary.whiteSpace"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="anyURI" id="anyURI">
+ <xs:annotation>
+ <xs:appinfo>
+ <hfp:hasFacet name="length"/>
+ <hfp:hasFacet name="minLength"/>
+ <hfp:hasFacet name="maxLength"/>
+ <hfp:hasFacet name="pattern"/>
+ <hfp:hasFacet name="enumeration"/>
+ <hfp:hasFacet name="whiteSpace"/>
+ <hfp:hasProperty name="ordered" value="false"/>
+ <hfp:hasProperty name="bounded" value="false"/>
+ <hfp:hasProperty name="cardinality"
+ value="countably infinite"/>
+ <hfp:hasProperty name="numeric" value="false"/>
+ </xs:appinfo>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#anyURI"/>
+ </xs:annotation>
+ <xs:restriction base="xs:anySimpleType">
+ <xs:whiteSpace value="collapse" fixed="true"
+ id="anyURI.whiteSpace"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="QName" id="QName">
+ <xs:annotation>
+ <xs:appinfo>
+ <hfp:hasFacet name="length"/>
+ <hfp:hasFacet name="minLength"/>
+ <hfp:hasFacet name="maxLength"/>
+ <hfp:hasFacet name="pattern"/>
+ <hfp:hasFacet name="enumeration"/>
+ <hfp:hasFacet name="whiteSpace"/>
+ <hfp:hasProperty name="ordered" value="false"/>
+ <hfp:hasProperty name="bounded" value="false"/>
+ <hfp:hasProperty name="cardinality"
+ value="countably infinite"/>
+ <hfp:hasProperty name="numeric" value="false"/>
+ </xs:appinfo>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#QName"/>
+ </xs:annotation>
+ <xs:restriction base="xs:anySimpleType">
+ <xs:whiteSpace value="collapse" fixed="true"
+ id="QName.whiteSpace"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="NOTATION" id="NOTATION">
+ <xs:annotation>
+ <xs:appinfo>
+ <hfp:hasFacet name="length"/>
+ <hfp:hasFacet name="minLength"/>
+ <hfp:hasFacet name="maxLength"/>
+ <hfp:hasFacet name="pattern"/>
+ <hfp:hasFacet name="enumeration"/>
+ <hfp:hasFacet name="whiteSpace"/>
+ <hfp:hasProperty name="ordered" value="false"/>
+ <hfp:hasProperty name="bounded" value="false"/>
+ <hfp:hasProperty name="cardinality"
+ value="countably infinite"/>
+ <hfp:hasProperty name="numeric" value="false"/>
+ </xs:appinfo>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#NOTATION"/>
+ <xs:documentation>
+ NOTATION cannot be used directly in a schema; rather a type
+ must be derived from it by specifying at least one enumeration
+ facet whose value is the name of a NOTATION declared in the
+ schema.
+ </xs:documentation>
+ </xs:annotation>
+ <xs:restriction base="xs:anySimpleType">
+ <xs:whiteSpace value="collapse" fixed="true"
+ id="NOTATION.whiteSpace"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:annotation>
+ <xs:documentation>
+ Now the derived primitive types
+ </xs:documentation>
+ </xs:annotation>
+
+ <xs:simpleType name="normalizedString" id="normalizedString">
+ <xs:annotation>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#normalizedString"/>
+ </xs:annotation>
+ <xs:restriction base="xs:string">
+ <xs:whiteSpace value="replace"
+ id="normalizedString.whiteSpace"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="token" id="token">
+ <xs:annotation>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#token"/>
+ </xs:annotation>
+ <xs:restriction base="xs:normalizedString">
+ <xs:whiteSpace value="collapse" id="token.whiteSpace"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="language" id="language">
+ <xs:annotation>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#language"/>
+ </xs:annotation>
+ <xs:restriction base="xs:token">
+ <xs:pattern
+ value="[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*"
+ id="language.pattern">
+ <xs:annotation>
+ <xs:documentation
+ source="http://www.ietf.org/rfc/rfc3066.txt">
+ pattern specifies the content of section 2.12 of XML 1.0e2
+ and RFC 3066 (Revised version of RFC 1766).
+ </xs:documentation>
+ </xs:annotation>
+ </xs:pattern>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="IDREFS" id="IDREFS">
+ <xs:annotation>
+ <xs:appinfo>
+ <hfp:hasFacet name="length"/>
+ <hfp:hasFacet name="minLength"/>
+ <hfp:hasFacet name="maxLength"/>
+ <hfp:hasFacet name="enumeration"/>
+ <hfp:hasFacet name="whiteSpace"/>
+ <hfp:hasFacet name="pattern"/>
+ <hfp:hasProperty name="ordered" value="false"/>
+ <hfp:hasProperty name="bounded" value="false"/>
+ <hfp:hasProperty name="cardinality"
+ value="countably infinite"/>
+ <hfp:hasProperty name="numeric" value="false"/>
+ </xs:appinfo>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#IDREFS"/>
+ </xs:annotation>
+ <xs:restriction>
+ <xs:simpleType>
+ <xs:list itemType="xs:IDREF"/>
+ </xs:simpleType>
+ <xs:minLength value="1" id="IDREFS.minLength"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="ENTITIES" id="ENTITIES">
+ <xs:annotation>
+ <xs:appinfo>
+ <hfp:hasFacet name="length"/>
+ <hfp:hasFacet name="minLength"/>
+ <hfp:hasFacet name="maxLength"/>
+ <hfp:hasFacet name="enumeration"/>
+ <hfp:hasFacet name="whiteSpace"/>
+ <hfp:hasFacet name="pattern"/>
+ <hfp:hasProperty name="ordered" value="false"/>
+ <hfp:hasProperty name="bounded" value="false"/>
+ <hfp:hasProperty name="cardinality"
+ value="countably infinite"/>
+ <hfp:hasProperty name="numeric" value="false"/>
+ </xs:appinfo>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#ENTITIES"/>
+ </xs:annotation>
+ <xs:restriction>
+ <xs:simpleType>
+ <xs:list itemType="xs:ENTITY"/>
+ </xs:simpleType>
+ <xs:minLength value="1" id="ENTITIES.minLength"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="NMTOKEN" id="NMTOKEN">
+ <xs:annotation>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#NMTOKEN"/>
+ </xs:annotation>
+ <xs:restriction base="xs:token">
+ <xs:pattern value="\c+" id="NMTOKEN.pattern">
+ <xs:annotation>
+ <xs:documentation
+ source="http://www.w3.org/TR/REC-xml#NT-Nmtoken">
+ pattern matches production 7 from the XML spec
+ </xs:documentation>
+ </xs:annotation>
+ </xs:pattern>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="NMTOKENS" id="NMTOKENS">
+ <xs:annotation>
+ <xs:appinfo>
+ <hfp:hasFacet name="length"/>
+ <hfp:hasFacet name="minLength"/>
+ <hfp:hasFacet name="maxLength"/>
+ <hfp:hasFacet name="enumeration"/>
+ <hfp:hasFacet name="whiteSpace"/>
+ <hfp:hasFacet name="pattern"/>
+ <hfp:hasProperty name="ordered" value="false"/>
+ <hfp:hasProperty name="bounded" value="false"/>
+ <hfp:hasProperty name="cardinality"
+ value="countably infinite"/>
+ <hfp:hasProperty name="numeric" value="false"/>
+ </xs:appinfo>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#NMTOKENS"/>
+ </xs:annotation>
+ <xs:restriction>
+ <xs:simpleType>
+ <xs:list itemType="xs:NMTOKEN"/>
+ </xs:simpleType>
+ <xs:minLength value="1" id="NMTOKENS.minLength"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="Name" id="Name">
+ <xs:annotation>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#Name"/>
+ </xs:annotation>
+ <xs:restriction base="xs:token">
+ <xs:pattern value="\i\c*" id="Name.pattern">
+ <xs:annotation>
+ <xs:documentation
+ source="http://www.w3.org/TR/REC-xml#NT-Name">
+ pattern matches production 5 from the XML spec
+ </xs:documentation>
+ </xs:annotation>
+ </xs:pattern>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="NCName" id="NCName">
+ <xs:annotation>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#NCName"/>
+ </xs:annotation>
+ <xs:restriction base="xs:Name">
+ <xs:pattern value="[\i-[:]][\c-[:]]*" id="NCName.pattern">
+ <xs:annotation>
+ <xs:documentation
+ source="http://www.w3.org/TR/REC-xml-names/#NT-NCName">
+ pattern matches production 4 from the Namespaces in XML spec
+ </xs:documentation>
+ </xs:annotation>
+ </xs:pattern>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="ID" id="ID">
+ <xs:annotation>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#ID"/>
+ </xs:annotation>
+ <xs:restriction base="xs:NCName"/>
+ </xs:simpleType>
+
+ <xs:simpleType name="IDREF" id="IDREF">
+ <xs:annotation>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#IDREF"/>
+ </xs:annotation>
+ <xs:restriction base="xs:NCName"/>
+ </xs:simpleType>
+
+ <xs:simpleType name="ENTITY" id="ENTITY">
+ <xs:annotation>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#ENTITY"/>
+ </xs:annotation>
+ <xs:restriction base="xs:NCName"/>
+ </xs:simpleType>
+
+ <xs:simpleType name="integer" id="integer">
+ <xs:annotation>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#integer"/>
+ </xs:annotation>
+ <xs:restriction base="xs:decimal">
+ <xs:fractionDigits value="0" fixed="true" id="integer.fractionDigits"/>
+ <xs:pattern value="[\-+]?[0-9]+"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="nonPositiveInteger" id="nonPositiveInteger">
+ <xs:annotation>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#nonPositiveInteger"/>
+ </xs:annotation>
+ <xs:restriction base="xs:integer">
+ <xs:maxInclusive value="0" id="nonPositiveInteger.maxInclusive"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="negativeInteger" id="negativeInteger">
+ <xs:annotation>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#negativeInteger"/>
+ </xs:annotation>
+ <xs:restriction base="xs:nonPositiveInteger">
+ <xs:maxInclusive value="-1" id="negativeInteger.maxInclusive"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="long" id="long">
+ <xs:annotation>
+ <xs:appinfo>
+ <hfp:hasProperty name="bounded" value="true"/>
+ <hfp:hasProperty name="cardinality" value="finite"/>
+ </xs:appinfo>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#long"/>
+ </xs:annotation>
+ <xs:restriction base="xs:integer">
+ <xs:minInclusive value="-9223372036854775808" id="long.minInclusive"/>
+ <xs:maxInclusive value="9223372036854775807" id="long.maxInclusive"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="int" id="int">
+ <xs:annotation>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#int"/>
+ </xs:annotation>
+ <xs:restriction base="xs:long">
+ <xs:minInclusive value="-2147483648" id="int.minInclusive"/>
+ <xs:maxInclusive value="2147483647" id="int.maxInclusive"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="short" id="short">
+ <xs:annotation>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#short"/>
+ </xs:annotation>
+ <xs:restriction base="xs:int">
+ <xs:minInclusive value="-32768" id="short.minInclusive"/>
+ <xs:maxInclusive value="32767" id="short.maxInclusive"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="byte" id="byte">
+ <xs:annotation>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#byte"/>
+ </xs:annotation>
+ <xs:restriction base="xs:short">
+ <xs:minInclusive value="-128" id="byte.minInclusive"/>
+ <xs:maxInclusive value="127" id="byte.maxInclusive"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="nonNegativeInteger" id="nonNegativeInteger">
+ <xs:annotation>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#nonNegativeInteger"/>
+ </xs:annotation>
+ <xs:restriction base="xs:integer">
+ <xs:minInclusive value="0" id="nonNegativeInteger.minInclusive"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="unsignedLong" id="unsignedLong">
+ <xs:annotation>
+ <xs:appinfo>
+ <hfp:hasProperty name="bounded" value="true"/>
+ <hfp:hasProperty name="cardinality" value="finite"/>
+ </xs:appinfo>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#unsignedLong"/>
+ </xs:annotation>
+ <xs:restriction base="xs:nonNegativeInteger">
+ <xs:maxInclusive value="18446744073709551615"
+ id="unsignedLong.maxInclusive"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="unsignedInt" id="unsignedInt">
+ <xs:annotation>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#unsignedInt"/>
+ </xs:annotation>
+ <xs:restriction base="xs:unsignedLong">
+ <xs:maxInclusive value="4294967295"
+ id="unsignedInt.maxInclusive"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="unsignedShort" id="unsignedShort">
+ <xs:annotation>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#unsignedShort"/>
+ </xs:annotation>
+ <xs:restriction base="xs:unsignedInt">
+ <xs:maxInclusive value="65535"
+ id="unsignedShort.maxInclusive"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="unsignedByte" id="unsignedByte">
+ <xs:annotation>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#unsignedByte"/>
+ </xs:annotation>
+ <xs:restriction base="xs:unsignedShort">
+ <xs:maxInclusive value="255" id="unsignedByte.maxInclusive"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="positiveInteger" id="positiveInteger">
+ <xs:annotation>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#positiveInteger"/>
+ </xs:annotation>
+ <xs:restriction base="xs:nonNegativeInteger">
+ <xs:minInclusive value="1" id="positiveInteger.minInclusive"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="derivationControl">
+ <xs:annotation>
+ <xs:documentation>
+ A utility type, not for public use</xs:documentation>
+ </xs:annotation>
+ <xs:restriction base="xs:NMTOKEN">
+ <xs:enumeration value="substitution"/>
+ <xs:enumeration value="extension"/>
+ <xs:enumeration value="restriction"/>
+ <xs:enumeration value="list"/>
+ <xs:enumeration value="union"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:group name="simpleDerivation">
+ <xs:choice>
+ <xs:element ref="xs:restriction"/>
+ <xs:element ref="xs:list"/>
+ <xs:element ref="xs:union"/>
+ </xs:choice>
+ </xs:group>
+
+ <xs:simpleType name="simpleDerivationSet">
+ <xs:annotation>
+ <xs:documentation>
+ #all or (possibly empty) subset of {restriction, union, list}
+ </xs:documentation>
+ <xs:documentation>
+ A utility type, not for public use</xs:documentation>
+ </xs:annotation>
+ <xs:union>
+ <xs:simpleType>
+ <xs:restriction base="xs:token">
+ <xs:enumeration value="#all"/>
+ </xs:restriction>
+ </xs:simpleType>
+ <xs:simpleType>
+ <xs:list>
+ <xs:simpleType>
+ <xs:restriction base="xs:derivationControl">
+ <xs:enumeration value="list"/>
+ <xs:enumeration value="union"/>
+ <xs:enumeration value="restriction"/>
+ </xs:restriction>
+ </xs:simpleType>
+ </xs:list>
+ </xs:simpleType>
+ </xs:union>
+ </xs:simpleType>
+
+ <xs:complexType name="simpleType" abstract="true">
+ <xs:complexContent>
+ <xs:extension base="xs:annotated">
+ <xs:group ref="xs:simpleDerivation"/>
+ <xs:attribute name="final" type="xs:simpleDerivationSet"/>
+ <xs:attribute name="name" type="xs:NCName">
+ <xs:annotation>
+ <xs:documentation>
+ Can be restricted to required or forbidden
+ </xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+
+ <xs:complexType name="topLevelSimpleType">
+ <xs:complexContent>
+ <xs:restriction base="xs:simpleType">
+ <xs:sequence>
+ <xs:element ref="xs:annotation" minOccurs="0"/>
+ <xs:group ref="xs:simpleDerivation"/>
+ </xs:sequence>
+ <xs:attribute name="name" use="required"
+ type="xs:NCName">
+ <xs:annotation>
+ <xs:documentation>
+ Required at the top level
+ </xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:anyAttribute namespace="##other" processContents="lax"/>
+ </xs:restriction>
+ </xs:complexContent>
+ </xs:complexType>
+
+ <xs:complexType name="localSimpleType">
+ <xs:complexContent>
+ <xs:restriction base="xs:simpleType">
+ <xs:sequence>
+ <xs:element ref="xs:annotation" minOccurs="0"/>
+ <xs:group ref="xs:simpleDerivation"/>
+ </xs:sequence>
+ <xs:attribute name="name" use="prohibited">
+ <xs:annotation>
+ <xs:documentation>
+ Forbidden when nested
+ </xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="final" use="prohibited"/>
+ <xs:anyAttribute namespace="##other" processContents="lax"/>
+ </xs:restriction>
+ </xs:complexContent>
+ </xs:complexType>
+
+ <xs:element name="simpleType" type="xs:topLevelSimpleType" id="simpleType">
+ <xs:annotation>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#element-simpleType"/>
+ </xs:annotation>
+ </xs:element>
+
+ <xs:group name="facets">
+ <xs:annotation>
+ <xs:documentation>
+ We should use a substitution group for facets, but
+ that's ruled out because it would allow users to
+ add their own, which we're not ready for yet.
+ </xs:documentation>
+ </xs:annotation>
+ <xs:choice>
+ <xs:element ref="xs:minExclusive"/>
+ <xs:element ref="xs:minInclusive"/>
+ <xs:element ref="xs:maxExclusive"/>
+ <xs:element ref="xs:maxInclusive"/>
+ <xs:element ref="xs:totalDigits"/>
+ <xs:element ref="xs:fractionDigits"/>
+ <xs:element ref="xs:length"/>
+ <xs:element ref="xs:minLength"/>
+ <xs:element ref="xs:maxLength"/>
+ <xs:element ref="xs:enumeration"/>
+ <xs:element ref="xs:whiteSpace"/>
+ <xs:element ref="xs:pattern"/>
+ </xs:choice>
+ </xs:group>
+
+ <xs:group name="simpleRestrictionModel">
+ <xs:sequence>
+ <xs:element name="simpleType" type="xs:localSimpleType" minOccurs="0"/>
+ <xs:group ref="xs:facets" minOccurs="0" maxOccurs="unbounded"/>
+ </xs:sequence>
+ </xs:group>
+
+ <xs:element name="restriction" id="restriction">
+ <xs:complexType>
+ <xs:annotation>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#element-restriction">
+ base attribute and simpleType child are mutually
+ exclusive, but one or other is required
+ </xs:documentation>
+ </xs:annotation>
+ <xs:complexContent>
+ <xs:extension base="xs:annotated">
+ <xs:group ref="xs:simpleRestrictionModel"/>
+ <xs:attribute name="base" type="xs:QName" use="optional"/>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+ </xs:element>
+
+ <xs:element name="list" id="list">
+ <xs:complexType>
+ <xs:annotation>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#element-list">
+ itemType attribute and simpleType child are mutually
+ exclusive, but one or other is required
+ </xs:documentation>
+ </xs:annotation>
+ <xs:complexContent>
+ <xs:extension base="xs:annotated">
+ <xs:sequence>
+ <xs:element name="simpleType" type="xs:localSimpleType"
+ minOccurs="0"/>
+ </xs:sequence>
+ <xs:attribute name="itemType" type="xs:QName" use="optional"/>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+ </xs:element>
+
+ <xs:element name="union" id="union">
+ <xs:complexType>
+ <xs:annotation>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#element-union">
+ memberTypes attribute must be non-empty or there must be
+ at least one simpleType child
+ </xs:documentation>
+ </xs:annotation>
+ <xs:complexContent>
+ <xs:extension base="xs:annotated">
+ <xs:sequence>
+ <xs:element name="simpleType" type="xs:localSimpleType"
+ minOccurs="0" maxOccurs="unbounded"/>
+ </xs:sequence>
+ <xs:attribute name="memberTypes" use="optional">
+ <xs:simpleType>
+ <xs:list itemType="xs:QName"/>
+ </xs:simpleType>
+ </xs:attribute>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+ </xs:element>
+
+ <xs:complexType name="facet">
+ <xs:complexContent>
+ <xs:extension base="xs:annotated">
+ <xs:attribute name="value" use="required"/>
+ <xs:attribute name="fixed" type="xs:boolean" use="optional"
+ default="false"/>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+
+ <xs:complexType name="noFixedFacet">
+ <xs:complexContent>
+ <xs:restriction base="xs:facet">
+ <xs:sequence>
+ <xs:element ref="xs:annotation" minOccurs="0"/>
+ </xs:sequence>
+ <xs:attribute name="fixed" use="prohibited"/>
+ <xs:anyAttribute namespace="##other" processContents="lax"/>
+ </xs:restriction>
+ </xs:complexContent>
+ </xs:complexType>
+
+ <xs:element name="minExclusive" id="minExclusive" type="xs:facet">
+ <xs:annotation>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#element-minExclusive"/>
+ </xs:annotation>
+ </xs:element>
+ <xs:element name="minInclusive" id="minInclusive" type="xs:facet">
+ <xs:annotation>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#element-minInclusive"/>
+ </xs:annotation>
+ </xs:element>
+
+ <xs:element name="maxExclusive" id="maxExclusive" type="xs:facet">
+ <xs:annotation>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#element-maxExclusive"/>
+ </xs:annotation>
+ </xs:element>
+ <xs:element name="maxInclusive" id="maxInclusive" type="xs:facet">
+ <xs:annotation>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#element-maxInclusive"/>
+ </xs:annotation>
+ </xs:element>
+
+ <xs:complexType name="numFacet">
+ <xs:complexContent>
+ <xs:restriction base="xs:facet">
+ <xs:sequence>
+ <xs:element ref="xs:annotation" minOccurs="0"/>
+ </xs:sequence>
+ <xs:attribute name="value" type="xs:nonNegativeInteger" use="required"/>
+ <xs:anyAttribute namespace="##other" processContents="lax"/>
+ </xs:restriction>
+ </xs:complexContent>
+ </xs:complexType>
+
+ <xs:element name="totalDigits" id="totalDigits">
+ <xs:annotation>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#element-totalDigits"/>
+ </xs:annotation>
+ <xs:complexType>
+ <xs:complexContent>
+ <xs:restriction base="xs:numFacet">
+ <xs:sequence>
+ <xs:element ref="xs:annotation" minOccurs="0"/>
+ </xs:sequence>
+ <xs:attribute name="value" type="xs:positiveInteger" use="required"/>
+ <xs:anyAttribute namespace="##other" processContents="lax"/>
+ </xs:restriction>
+ </xs:complexContent>
+ </xs:complexType>
+ </xs:element>
+ <xs:element name="fractionDigits" id="fractionDigits" type="xs:numFacet">
+ <xs:annotation>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#element-fractionDigits"/>
+ </xs:annotation>
+ </xs:element>
+
+ <xs:element name="length" id="length" type="xs:numFacet">
+ <xs:annotation>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#element-length"/>
+ </xs:annotation>
+ </xs:element>
+ <xs:element name="minLength" id="minLength" type="xs:numFacet">
+ <xs:annotation>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#element-minLength"/>
+ </xs:annotation>
+ </xs:element>
+ <xs:element name="maxLength" id="maxLength" type="xs:numFacet">
+ <xs:annotation>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#element-maxLength"/>
+ </xs:annotation>
+ </xs:element>
+
+ <xs:element name="enumeration" id="enumeration" type="xs:noFixedFacet">
+ <xs:annotation>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#element-enumeration"/>
+ </xs:annotation>
+ </xs:element>
+
+ <xs:element name="whiteSpace" id="whiteSpace">
+ <xs:annotation>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#element-whiteSpace"/>
+ </xs:annotation>
+ <xs:complexType>
+ <xs:complexContent>
+ <xs:restriction base="xs:facet">
+ <xs:sequence>
+ <xs:element ref="xs:annotation" minOccurs="0"/>
+ </xs:sequence>
+ <xs:attribute name="value" use="required">
+ <xs:simpleType>
+ <xs:restriction base="xs:NMTOKEN">
+ <xs:enumeration value="preserve"/>
+ <xs:enumeration value="replace"/>
+ <xs:enumeration value="collapse"/>
+ </xs:restriction>
+ </xs:simpleType>
+ </xs:attribute>
+ <xs:anyAttribute namespace="##other" processContents="lax"/>
+ </xs:restriction>
+ </xs:complexContent>
+ </xs:complexType>
+ </xs:element>
+
+ <xs:element name="pattern" id="pattern">
+ <xs:annotation>
+ <xs:documentation
+ source="http://www.w3.org/TR/xmlschema-2/#element-pattern"/>
+ </xs:annotation>
+ <xs:complexType>
+ <xs:complexContent>
+ <xs:restriction base="xs:noFixedFacet">
+ <xs:sequence>
+ <xs:element ref="xs:annotation" minOccurs="0"/>
+ </xs:sequence>
+ <xs:attribute name="value" type="xs:string" use="required"/>
+ <xs:anyAttribute namespace="##other" processContents="lax"/>
+ </xs:restriction>
+ </xs:complexContent>
+ </xs:complexType>
+ </xs:element>
+
+</xs:schema>
--- /dev/null
+<?xml version="1.0"?>
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
+ xmlns:cr="http://argeo.org/ns/cr"
+ targetNamespace="http://argeo.org/ns/cr" elementFormDefault="qualified"
+ xml:lang="en-GB">
+
+ <xs:element name="root">
+ <xs:complexType>
+ <xs:sequence>
+ <xs:any minOccurs="0" />
+ </xs:sequence>
+ </xs:complexType>
+ </xs:element>
+
+ <xs:attribute name="uuid" type="cr:uuid" />
+
+ <!-- UUID -->
+ <xs:simpleType name="uuid">
+ <xs:annotation>
+ <xs:documentation>An UUID as defined in RFC4122.
+ </xs:documentation>
+ </xs:annotation>
+ <xs:restriction base="xs:string">
+ <xs:pattern
+ value="([0-9]|[a-f]|[A-F]){8}-([0-9]|[a-f]|[A-F]){4}-([0-9]|[a-f]|[A-F]){4}-([0-9]|[a-f]|[A-F]){4}-([0-9]|[a-f]|[A-F]){12}" />
+ </xs:restriction>
+ </xs:simpleType>
+
+ <!-- UUID URN -->
+ <xs:simpleType name="uuidUrn">
+ <xs:annotation>
+ <xs:documentation>The URN of an UUID as defined in RFC4122.
+ </xs:documentation>
+ </xs:annotation>
+ <xs:restriction base="xs:anyURI">
+ <xs:pattern
+ value="urn:uuid:([0-9]|[a-f]|[A-F]){8}-([0-9]|[a-f]|[A-F]){4}-([0-9]|[a-f]|[A-F]){4}-([0-9]|[a-f]|[A-F]){4}-([0-9]|[a-f]|[A-F]){12}" />
+ </xs:restriction>
+ </xs:simpleType>
+</xs:schema>
\ No newline at end of file
--- /dev/null
+<!--
+ DTD for XML Schemas: Part 2: Datatypes
+ $Id: datatypes.dtd,v 1.23 2001/03/16 17:36:30 ht Exp $
+ Note this DTD is NOT normative, or even definitive. - - the
+ prose copy in the datatypes REC is the definitive version
+ (which shouldn't differ from this one except for this comment
+ and entity expansions, but just in case)
+ -->
+
+<!--
+ This DTD cannot be used on its own, it is intended
+ only for incorporation in XMLSchema.dtd, q.v.
+ -->
+
+<!-- Define all the element names, with optional prefix -->
+<!ENTITY % simpleType "%p;simpleType">
+<!ENTITY % restriction "%p;restriction">
+<!ENTITY % list "%p;list">
+<!ENTITY % union "%p;union">
+<!ENTITY % maxExclusive "%p;maxExclusive">
+<!ENTITY % minExclusive "%p;minExclusive">
+<!ENTITY % maxInclusive "%p;maxInclusive">
+<!ENTITY % minInclusive "%p;minInclusive">
+<!ENTITY % totalDigits "%p;totalDigits">
+<!ENTITY % fractionDigits "%p;fractionDigits">
+<!ENTITY % length "%p;length">
+<!ENTITY % minLength "%p;minLength">
+<!ENTITY % maxLength "%p;maxLength">
+<!ENTITY % enumeration "%p;enumeration">
+<!ENTITY % whiteSpace "%p;whiteSpace">
+<!ENTITY % pattern "%p;pattern">
+
+<!--
+ Customisation entities for the ATTLIST of each element
+ type. Define one of these if your schema takes advantage
+ of the anyAttribute='##other' in the schema for schemas
+ -->
+
+<!ENTITY % simpleTypeAttrs "">
+<!ENTITY % restrictionAttrs "">
+<!ENTITY % listAttrs "">
+<!ENTITY % unionAttrs "">
+<!ENTITY % maxExclusiveAttrs "">
+<!ENTITY % minExclusiveAttrs "">
+<!ENTITY % maxInclusiveAttrs "">
+<!ENTITY % minInclusiveAttrs "">
+<!ENTITY % totalDigitsAttrs "">
+<!ENTITY % fractionDigitsAttrs "">
+<!ENTITY % lengthAttrs "">
+<!ENTITY % minLengthAttrs "">
+<!ENTITY % maxLengthAttrs "">
+<!ENTITY % enumerationAttrs "">
+<!ENTITY % whiteSpaceAttrs "">
+<!ENTITY % patternAttrs "">
+
+<!-- Define some entities for informative use as attribute
+ types -->
+<!ENTITY % URIref "CDATA">
+<!ENTITY % XPathExpr "CDATA">
+<!ENTITY % QName "NMTOKEN">
+<!ENTITY % QNames "NMTOKENS">
+<!ENTITY % NCName "NMTOKEN">
+<!ENTITY % nonNegativeInteger "NMTOKEN">
+<!ENTITY % boolean "(true|false)">
+<!ENTITY % simpleDerivationSet "CDATA">
+<!--
+ #all or space-separated list drawn from derivationChoice
+ -->
+
+<!--
+ Note that the use of 'facet' below is less restrictive
+ than is really intended: There should in fact be no
+ more than one of each of minInclusive, minExclusive,
+ maxInclusive, maxExclusive, totalDigits, fractionDigits,
+ length, maxLength, minLength within datatype,
+ and the min- and max- variants of Inclusive and Exclusive
+ are mutually exclusive. On the other hand, pattern and
+ enumeration may repeat.
+ -->
+<!ENTITY % minBound "(%minInclusive; | %minExclusive;)">
+<!ENTITY % maxBound "(%maxInclusive; | %maxExclusive;)">
+<!ENTITY % bounds "%minBound; | %maxBound;">
+<!ENTITY % numeric "%totalDigits; | %fractionDigits;">
+<!ENTITY % ordered "%bounds; | %numeric;">
+<!ENTITY % unordered
+ "%pattern; | %enumeration; | %whiteSpace; | %length; |
+ %maxLength; | %minLength;">
+<!ENTITY % facet "%ordered; | %unordered;">
+<!ENTITY % facetAttr
+ "value CDATA #REQUIRED
+ id ID #IMPLIED">
+<!ENTITY % fixedAttr "fixed %boolean; #IMPLIED">
+<!ENTITY % facetModel "(%annotation;)?">
+<!ELEMENT %simpleType;
+ ((%annotation;)?, (%restriction; | %list; | %union;))>
+<!ATTLIST %simpleType;
+ name %NCName; #IMPLIED
+ final %simpleDerivationSet; #IMPLIED
+ id ID #IMPLIED
+ %simpleTypeAttrs;>
+<!-- name is required at top level -->
+<!ELEMENT %restriction; ((%annotation;)?,
+ (%restriction1; |
+ ((%simpleType;)?,(%facet;)*)),
+ (%attrDecls;))>
+<!ATTLIST %restriction;
+ base %QName; #IMPLIED
+ id ID #IMPLIED
+ %restrictionAttrs;>
+<!--
+ base and simpleType child are mutually exclusive,
+ one is required.
+
+ restriction is shared between simpleType and
+ simpleContent and complexContent (in XMLSchema.xsd).
+ restriction1 is for the latter cases, when this
+ is restricting a complex type, as is attrDecls.
+ -->
+<!ELEMENT %list; ((%annotation;)?,(%simpleType;)?)>
+<!ATTLIST %list;
+ itemType %QName; #IMPLIED
+ id ID #IMPLIED
+ %listAttrs;>
+<!--
+ itemType and simpleType child are mutually exclusive,
+ one is required
+ -->
+<!ELEMENT %union; ((%annotation;)?,(%simpleType;)*)>
+<!ATTLIST %union;
+ id ID #IMPLIED
+ memberTypes %QNames; #IMPLIED
+ %unionAttrs;>
+<!--
+ At least one item in memberTypes or one simpleType
+ child is required
+ -->
+
+<!ELEMENT %maxExclusive; %facetModel;>
+<!ATTLIST %maxExclusive;
+ %facetAttr;
+ %fixedAttr;
+ %maxExclusiveAttrs;>
+<!ELEMENT %minExclusive; %facetModel;>
+<!ATTLIST %minExclusive;
+ %facetAttr;
+ %fixedAttr;
+ %minExclusiveAttrs;>
+
+<!ELEMENT %maxInclusive; %facetModel;>
+<!ATTLIST %maxInclusive;
+ %facetAttr;
+ %fixedAttr;
+ %maxInclusiveAttrs;>
+<!ELEMENT %minInclusive; %facetModel;>
+<!ATTLIST %minInclusive;
+ %facetAttr;
+ %fixedAttr;
+ %minInclusiveAttrs;>
+
+<!ELEMENT %totalDigits; %facetModel;>
+<!ATTLIST %totalDigits;
+ %facetAttr;
+ %fixedAttr;
+ %totalDigitsAttrs;>
+<!ELEMENT %fractionDigits; %facetModel;>
+<!ATTLIST %fractionDigits;
+ %facetAttr;
+ %fixedAttr;
+ %fractionDigitsAttrs;>
+
+<!ELEMENT %length; %facetModel;>
+<!ATTLIST %length;
+ %facetAttr;
+ %fixedAttr;
+ %lengthAttrs;>
+<!ELEMENT %minLength; %facetModel;>
+<!ATTLIST %minLength;
+ %facetAttr;
+ %fixedAttr;
+ %minLengthAttrs;>
+<!ELEMENT %maxLength; %facetModel;>
+<!ATTLIST %maxLength;
+ %facetAttr;
+ %fixedAttr;
+ %maxLengthAttrs;>
+
+<!-- This one can be repeated -->
+<!ELEMENT %enumeration; %facetModel;>
+<!ATTLIST %enumeration;
+ %facetAttr;
+ %enumerationAttrs;>
+
+<!ELEMENT %whiteSpace; %facetModel;>
+<!ATTLIST %whiteSpace;
+ %facetAttr;
+ %fixedAttr;
+ %whiteSpaceAttrs;>
+
+<!-- This one can be repeated -->
+<!ELEMENT %pattern; %facetModel;>
+<!ATTLIST %pattern;
+ %facetAttr;
+ %patternAttrs;>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" targetNamespace="http://www.w3.org/1999/XSL/Transform" elementFormDefault="qualified">
+
+<!-- ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -->
+<xs:annotation>
+ <xs:documentation>
+
+ This is a schema for XSLT 2.0 stylesheets.
+
+ It defines all the elements that appear in the XSLT namespace; it also
+ provides hooks that allow the inclusion of user-defined literal result elements,
+ extension instructions, and top-level data elements.
+
+ The schema is derived (with kind permission) from a schema for XSLT 1.0 stylesheets
+ produced by Asir S Vedamuthu of WebMethods Inc.
+
+ This schema is available for use under the conditions of the W3C Software License
+ published at http://www.w3.org/Consortium/Legal/copyright-software-19980720
+
+ The schema is organized as follows:
+
+ PART A: definitions of complex types and model groups used as the basis
+ for element definitions
+ PART B: definitions of individual XSLT elements
+ PART C: definitions for literal result elements
+ PART D: definitions of simple types used in attribute definitions
+
+ This schema does not attempt to define all the constraints that apply to a valid
+ XSLT 2.0 stylesheet module. It is the intention that all valid stylesheet modules
+ should conform to this schema; however, the schema is non-normative and in the event
+ of any conflict, the text of the Recommendation takes precedence.
+
+ This schema does not implement the special rules that apply when a stylesheet
+ has sections that use forwards-compatible-mode. In this mode, setting version="3.0"
+ allows elements from the XSLT namespace to be used that are not defined in XSLT 2.0.
+
+ Simplified stylesheets (those with a literal result element as the outermost element)
+ will validate against this schema only if validation starts in lax mode.
+
+ This version is dated 2007-03-16
+ Authors: Michael H Kay, Saxonica Limited
+ Jeni Tennison, Jeni Tennison Consulting Ltd.
+
+ 2007-03-15: added xsl:document element
+ revised xsl:sequence element
+ see http://www.w3.org/Bugs/Public/show_bug.cgi?id=4237
+
+ </xs:documentation>
+</xs:annotation>
+<!-- ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -->
+
+<!--
+The declaration of xml:space and xml:lang may need to be commented out because
+of problems processing the schema using various tools
+-->
+
+<xs:import namespace="http://www.w3.org/XML/1998/namespace" schemaLocation="xml.xsd"/>
+
+<!--
+ An XSLT stylesheet may contain an in-line schema within an xsl:import-schema element,
+ so the Schema for schemas needs to be imported
+-->
+
+<xs:import namespace="http://www.w3.org/2001/XMLSchema" schemaLocation="XMLSchema.xsd"/>
+
+<!-- ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -->
+<xs:annotation>
+ <xs:documentation>
+ PART A: definitions of complex types and model groups used as the basis
+ for element definitions
+ </xs:documentation>
+</xs:annotation>
+<!-- ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -->
+
+<xs:complexType name="generic-element-type" mixed="true">
+ <xs:attribute name="default-collation" type="xsl:uri-list"/>
+ <xs:attribute name="exclude-result-prefixes" type="xsl:prefix-list-or-all"/>
+ <xs:attribute name="extension-element-prefixes" type="xsl:prefix-list"/>
+ <xs:attribute name="use-when" type="xsl:expression"/>
+ <xs:attribute name="xpath-default-namespace" type="xs:anyURI"/>
+ <xs:anyAttribute namespace="##other" processContents="lax"/>
+</xs:complexType>
+
+<xs:complexType name="versioned-element-type" mixed="true">
+ <xs:complexContent>
+ <xs:extension base="xsl:generic-element-type">
+ <xs:attribute name="version" type="xs:decimal" use="optional"/>
+ </xs:extension>
+ </xs:complexContent>
+</xs:complexType>
+
+<xs:complexType name="element-only-versioned-element-type" mixed="false">
+ <xs:complexContent>
+ <xs:restriction base="xsl:versioned-element-type">
+ <xs:anyAttribute namespace="##other" processContents="lax"/>
+ </xs:restriction>
+ </xs:complexContent>
+</xs:complexType>
+
+<xs:complexType name="sequence-constructor">
+ <xs:complexContent mixed="true">
+ <xs:extension base="xsl:versioned-element-type">
+ <xs:group ref="xsl:sequence-constructor-group" minOccurs="0" maxOccurs="unbounded"/>
+ </xs:extension>
+ </xs:complexContent>
+</xs:complexType>
+
+<xs:group name="sequence-constructor-group">
+ <xs:choice>
+ <xs:element ref="xsl:variable"/>
+ <xs:element ref="xsl:instruction"/>
+ <xs:group ref="xsl:result-elements"/>
+ </xs:choice>
+</xs:group>
+
+<xs:element name="declaration" type="xsl:generic-element-type" abstract="true"/>
+
+<xs:element name="instruction" type="xsl:versioned-element-type" abstract="true"/>
+
+<!-- ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -->
+<xs:annotation>
+ <xs:documentation>
+ PART B: definitions of individual XSLT elements
+ Elements are listed in alphabetical order.
+ </xs:documentation>
+</xs:annotation>
+<!-- ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -->
+
+<xs:element name="analyze-string" substitutionGroup="xsl:instruction">
+ <xs:complexType>
+ <xs:complexContent>
+ <xs:extension base="xsl:element-only-versioned-element-type">
+ <xs:sequence>
+ <xs:element ref="xsl:matching-substring" minOccurs="0"/>
+ <xs:element ref="xsl:non-matching-substring" minOccurs="0"/>
+ <xs:element ref="xsl:fallback" minOccurs="0" maxOccurs="unbounded"/>
+ </xs:sequence>
+ <xs:attribute name="select" type="xsl:expression" use="required"/>
+ <xs:attribute name="regex" type="xsl:avt" use="required"/>
+ <xs:attribute name="flags" type="xsl:avt" default=""/>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+</xs:element>
+
+<xs:element name="apply-imports" substitutionGroup="xsl:instruction">
+ <xs:complexType>
+ <xs:complexContent>
+ <xs:extension base="xsl:element-only-versioned-element-type">
+ <xs:sequence>
+ <xs:element ref="xsl:with-param" minOccurs="0" maxOccurs="unbounded"/>
+ </xs:sequence>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+</xs:element>
+
+<xs:element name="apply-templates" substitutionGroup="xsl:instruction">
+ <xs:complexType>
+ <xs:complexContent>
+ <xs:extension base="xsl:element-only-versioned-element-type">
+ <xs:choice minOccurs="0" maxOccurs="unbounded">
+ <xs:element ref="xsl:sort"/>
+ <xs:element ref="xsl:with-param"/>
+ </xs:choice>
+ <xs:attribute name="select" type="xsl:expression" default="child::node()"/>
+ <xs:attribute name="mode" type="xsl:mode"/>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+</xs:element>
+
+<xs:element name="attribute" substitutionGroup="xsl:instruction">
+ <xs:complexType>
+ <xs:complexContent mixed="true">
+ <xs:extension base="xsl:sequence-constructor">
+ <xs:attribute name="name" type="xsl:avt" use="required"/>
+ <xs:attribute name="namespace" type="xsl:avt"/>
+ <xs:attribute name="select" type="xsl:expression"/>
+ <xs:attribute name="separator" type="xsl:avt"/>
+ <xs:attribute name="type" type="xsl:QName"/>
+ <xs:attribute name="validation" type="xsl:validation-type"/>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+</xs:element>
+
+<xs:element name="attribute-set" substitutionGroup="xsl:declaration">
+ <xs:complexType>
+ <xs:complexContent>
+ <xs:extension base="xsl:element-only-versioned-element-type">
+ <xs:sequence minOccurs="0" maxOccurs="unbounded">
+ <xs:element ref="xsl:attribute"/>
+ </xs:sequence>
+ <xs:attribute name="name" type="xsl:QName" use="required"/>
+ <xs:attribute name="use-attribute-sets" type="xsl:QNames" default=""/>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+</xs:element>
+
+<xs:element name="call-template" substitutionGroup="xsl:instruction">
+ <xs:complexType>
+ <xs:complexContent>
+ <xs:extension base="xsl:element-only-versioned-element-type">
+ <xs:sequence>
+ <xs:element ref="xsl:with-param" minOccurs="0" maxOccurs="unbounded"/>
+ </xs:sequence>
+ <xs:attribute name="name" type="xsl:QName" use="required"/>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+</xs:element>
+
+<xs:element name="character-map" substitutionGroup="xsl:declaration">
+ <xs:complexType>
+ <xs:complexContent>
+ <xs:extension base="xsl:element-only-versioned-element-type">
+ <xs:sequence>
+ <xs:element ref="xsl:output-character" minOccurs="0" maxOccurs="unbounded"/>
+ </xs:sequence>
+ <xs:attribute name="name" type="xsl:QName" use="required"/>
+ <xs:attribute name="use-character-maps" type="xsl:QNames" default=""/>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+</xs:element>
+
+<xs:element name="choose" substitutionGroup="xsl:instruction">
+ <xs:complexType>
+ <xs:complexContent>
+ <xs:extension base="xsl:element-only-versioned-element-type">
+ <xs:sequence>
+ <xs:element ref="xsl:when" maxOccurs="unbounded"/>
+ <xs:element ref="xsl:otherwise" minOccurs="0"/>
+ </xs:sequence>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+</xs:element>
+
+<xs:element name="comment" substitutionGroup="xsl:instruction">
+ <xs:complexType>
+ <xs:complexContent mixed="true">
+ <xs:extension base="xsl:sequence-constructor">
+ <xs:attribute name="select" type="xsl:expression"/>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+</xs:element>
+
+<xs:element name="copy" substitutionGroup="xsl:instruction">
+ <xs:complexType>
+ <xs:complexContent mixed="true">
+ <xs:extension base="xsl:sequence-constructor">
+ <xs:attribute name="copy-namespaces" type="xsl:yes-or-no" default="yes"/>
+ <xs:attribute name="inherit-namespaces" type="xsl:yes-or-no" default="yes"/>
+ <xs:attribute name="use-attribute-sets" type="xsl:QNames" default=""/>
+ <xs:attribute name="type" type="xsl:QName"/>
+ <xs:attribute name="validation" type="xsl:validation-type"/>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+</xs:element>
+
+<xs:element name="copy-of" substitutionGroup="xsl:instruction">
+ <xs:complexType>
+ <xs:complexContent mixed="true">
+ <xs:extension base="xsl:versioned-element-type">
+ <xs:attribute name="select" type="xsl:expression" use="required"/>
+ <xs:attribute name="copy-namespaces" type="xsl:yes-or-no" default="yes"/>
+ <xs:attribute name="type" type="xsl:QName"/>
+ <xs:attribute name="validation" type="xsl:validation-type"/>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+</xs:element>
+
+<xs:element name="document" substitutionGroup="xsl:instruction">
+ <xs:complexType>
+ <xs:complexContent mixed="true">
+ <xs:extension base="xsl:sequence-constructor">
+ <xs:attribute name="type" type="xsl:QName"/>
+ <xs:attribute name="validation" type="xsl:validation-type"/>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+</xs:element>
+
+<xs:element name="decimal-format" substitutionGroup="xsl:declaration">
+ <xs:complexType>
+ <xs:complexContent>
+ <xs:extension base="xsl:element-only-versioned-element-type">
+ <xs:attribute name="name" type="xsl:QName"/>
+ <xs:attribute name="decimal-separator" type="xsl:char" default="."/>
+ <xs:attribute name="grouping-separator" type="xsl:char" default=","/>
+ <xs:attribute name="infinity" type="xs:string" default="Infinity"/>
+ <xs:attribute name="minus-sign" type="xsl:char" default="-"/>
+ <xs:attribute name="NaN" type="xs:string" default="NaN"/>
+ <xs:attribute name="percent" type="xsl:char" default="%"/>
+ <xs:attribute name="per-mille" type="xsl:char" default="‰"/>
+ <xs:attribute name="zero-digit" type="xsl:char" default="0"/>
+ <xs:attribute name="digit" type="xsl:char" default="#"/>
+ <xs:attribute name="pattern-separator" type="xsl:char" default=";"/>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+</xs:element>
+
+<xs:element name="element" substitutionGroup="xsl:instruction">
+ <xs:complexType mixed="true">
+ <xs:complexContent>
+ <xs:extension base="xsl:sequence-constructor">
+ <xs:attribute name="name" type="xsl:avt" use="required"/>
+ <xs:attribute name="namespace" type="xsl:avt"/>
+ <xs:attribute name="inherit-namespaces" type="xsl:yes-or-no" default="yes"/>
+ <xs:attribute name="use-attribute-sets" type="xsl:QNames" default=""/>
+ <xs:attribute name="type" type="xsl:QName"/>
+ <xs:attribute name="validation" type="xsl:validation-type"/>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+</xs:element>
+
+<xs:element name="fallback" substitutionGroup="xsl:instruction" type="xsl:sequence-constructor"/>
+
+<xs:element name="for-each" substitutionGroup="xsl:instruction">
+ <xs:complexType>
+ <xs:complexContent mixed="true">
+ <xs:extension base="xsl:versioned-element-type">
+ <xs:sequence>
+ <xs:element ref="xsl:sort" minOccurs="0" maxOccurs="unbounded"/>
+ <xs:group ref="xsl:sequence-constructor-group" minOccurs="0" maxOccurs="unbounded"/>
+ </xs:sequence>
+ <xs:attribute name="select" type="xsl:expression" use="required"/>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+</xs:element>
+
+<xs:element name="for-each-group" substitutionGroup="xsl:instruction">
+ <xs:complexType>
+ <xs:complexContent mixed="true">
+ <xs:extension base="xsl:versioned-element-type">
+ <xs:sequence>
+ <xs:element ref="xsl:sort" minOccurs="0" maxOccurs="unbounded"/>
+ <xs:group ref="xsl:sequence-constructor-group" minOccurs="0" maxOccurs="unbounded"/>
+ </xs:sequence>
+ <xs:attribute name="select" type="xsl:expression" use="required"/>
+ <xs:attribute name="group-by" type="xsl:expression"/>
+ <xs:attribute name="group-adjacent" type="xsl:expression"/>
+ <xs:attribute name="group-starting-with" type="xsl:pattern"/>
+ <xs:attribute name="group-ending-with" type="xsl:pattern"/>
+ <xs:attribute name="collation" type="xs:anyURI"/>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+</xs:element>
+
+<xs:element name="function" substitutionGroup="xsl:declaration">
+ <xs:complexType>
+ <xs:complexContent mixed="true">
+ <xs:extension base="xsl:versioned-element-type">
+ <xs:sequence>
+ <xs:element ref="xsl:param" minOccurs="0" maxOccurs="unbounded"/>
+ <xs:group ref="xsl:sequence-constructor-group" minOccurs="0" maxOccurs="unbounded"/>
+ </xs:sequence>
+ <xs:attribute name="name" type="xsl:QName" use="required"/>
+ <xs:attribute name="override" type="xsl:yes-or-no" default="yes"/>
+ <xs:attribute name="as" type="xsl:sequence-type" default="item()*"/>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+</xs:element>
+
+<xs:element name="if" substitutionGroup="xsl:instruction">
+ <xs:complexType>
+ <xs:complexContent mixed="true">
+ <xs:extension base="xsl:sequence-constructor">
+ <xs:attribute name="test" type="xsl:expression" use="required"/>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+</xs:element>
+
+<xs:element name="import">
+ <xs:complexType>
+ <xs:complexContent>
+ <xs:extension base="xsl:element-only-versioned-element-type">
+ <xs:attribute name="href" type="xs:anyURI" use="required"/>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+</xs:element>
+
+<xs:element name="import-schema" substitutionGroup="xsl:declaration">
+ <xs:complexType>
+ <xs:complexContent>
+ <xs:extension base="xsl:element-only-versioned-element-type">
+ <xs:sequence>
+ <xs:element ref="xs:schema" minOccurs="0" maxOccurs="1"/>
+ </xs:sequence>
+ <xs:attribute name="namespace" type="xs:anyURI"/>
+ <xs:attribute name="schema-location" type="xs:anyURI"/>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+</xs:element>
+
+<xs:element name="include" substitutionGroup="xsl:declaration">
+ <xs:complexType>
+ <xs:complexContent>
+ <xs:extension base="xsl:element-only-versioned-element-type">
+ <xs:attribute name="href" type="xs:anyURI" use="required"/>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+</xs:element>
+
+<xs:element name="key" substitutionGroup="xsl:declaration">
+ <xs:complexType>
+ <xs:complexContent mixed="true">
+ <xs:extension base="xsl:sequence-constructor">
+ <xs:attribute name="name" type="xsl:QName" use="required"/>
+ <xs:attribute name="match" type="xsl:pattern" use="required"/>
+ <xs:attribute name="use" type="xsl:expression"/>
+ <xs:attribute name="collation" type="xs:anyURI"/>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+</xs:element>
+
+<xs:element name="matching-substring" type="xsl:sequence-constructor"/>
+
+<xs:element name="message" substitutionGroup="xsl:instruction">
+ <xs:complexType>
+ <xs:complexContent mixed="true">
+ <xs:extension base="xsl:sequence-constructor">
+ <xs:attribute name="select" type="xsl:expression"/>
+ <xs:attribute name="terminate" type="xsl:avt" default="no"/>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+</xs:element>
+
+<xs:element name="namespace" substitutionGroup="xsl:instruction">
+ <xs:complexType>
+ <xs:complexContent mixed="true">
+ <xs:extension base="xsl:sequence-constructor">
+ <xs:attribute name="name" type="xsl:avt" use="required"/>
+ <xs:attribute name="select" type="xsl:expression"/>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+</xs:element>
+
+<xs:element name="namespace-alias" substitutionGroup="xsl:declaration">
+ <xs:complexType>
+ <xs:complexContent>
+ <xs:extension base="xsl:element-only-versioned-element-type">
+ <xs:attribute name="stylesheet-prefix" type="xsl:prefix-or-default" use="required"/>
+ <xs:attribute name="result-prefix" type="xsl:prefix-or-default" use="required"/>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+</xs:element>
+
+<xs:element name="next-match" substitutionGroup="xsl:instruction">
+ <xs:complexType>
+ <xs:complexContent>
+ <xs:extension base="xsl:element-only-versioned-element-type">
+ <xs:choice minOccurs="0" maxOccurs="unbounded">
+ <xs:element ref="xsl:with-param"/>
+ <xs:element ref="xsl:fallback"/>
+ </xs:choice>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+</xs:element>
+
+<xs:element name="non-matching-substring" type="xsl:sequence-constructor"/>
+
+<xs:element name="number" substitutionGroup="xsl:instruction">
+ <xs:complexType>
+ <xs:complexContent mixed="true">
+ <xs:extension base="xsl:versioned-element-type">
+ <xs:attribute name="value" type="xsl:expression"/>
+ <xs:attribute name="select" type="xsl:expression"/>
+ <xs:attribute name="level" type="xsl:level" default="single"/>
+ <xs:attribute name="count" type="xsl:pattern"/>
+ <xs:attribute name="from" type="xsl:pattern"/>
+ <xs:attribute name="format" type="xsl:avt" default="1"/>
+ <xs:attribute name="lang" type="xsl:avt"/>
+ <xs:attribute name="letter-value" type="xsl:avt"/>
+ <xs:attribute name="ordinal" type="xsl:avt"/>
+ <xs:attribute name="grouping-separator" type="xsl:avt"/>
+ <xs:attribute name="grouping-size" type="xsl:avt"/>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+</xs:element>
+
+<xs:element name="otherwise" type="xsl:sequence-constructor"/>
+
+<xs:element name="output" substitutionGroup="xsl:declaration">
+ <xs:complexType>
+ <xs:complexContent mixed="true">
+ <xs:extension base="xsl:generic-element-type">
+ <xs:attribute name="name" type="xsl:QName"/>
+ <xs:attribute name="method" type="xsl:method"/>
+ <xs:attribute name="byte-order-mark" type="xsl:yes-or-no"/>
+ <xs:attribute name="cdata-section-elements" type="xsl:QNames"/>
+ <xs:attribute name="doctype-public" type="xs:string"/>
+ <xs:attribute name="doctype-system" type="xs:string"/>
+ <xs:attribute name="encoding" type="xs:string"/>
+ <xs:attribute name="escape-uri-attributes" type="xsl:yes-or-no"/>
+ <xs:attribute name="include-content-type" type="xsl:yes-or-no"/>
+ <xs:attribute name="indent" type="xsl:yes-or-no"/>
+ <xs:attribute name="media-type" type="xs:string"/>
+ <xs:attribute name="normalization-form" type="xs:NMTOKEN"/>
+ <xs:attribute name="omit-xml-declaration" type="xsl:yes-or-no"/>
+ <xs:attribute name="standalone" type="xsl:yes-or-no-or-omit"/>
+ <xs:attribute name="undeclare-prefixes" type="xsl:yes-or-no"/>
+ <xs:attribute name="use-character-maps" type="xsl:QNames"/>
+ <xs:attribute name="version" type="xs:NMTOKEN"/>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+</xs:element>
+
+<xs:element name="output-character">
+ <xs:complexType>
+ <xs:complexContent>
+ <xs:extension base="xsl:element-only-versioned-element-type">
+ <xs:attribute name="character" type="xsl:char" use="required"/>
+ <xs:attribute name="string" type="xs:string" use="required"/>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+</xs:element>
+
+<xs:element name="param">
+ <xs:complexType>
+ <xs:complexContent mixed="true">
+ <xs:extension base="xsl:sequence-constructor">
+ <xs:attribute name="name" type="xsl:QName" use="required"/>
+ <xs:attribute name="select" type="xsl:expression"/>
+ <xs:attribute name="as" type="xsl:sequence-type"/>
+ <xs:attribute name="required" type="xsl:yes-or-no"/>
+ <xs:attribute name="tunnel" type="xsl:yes-or-no"/>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+</xs:element>
+
+<xs:element name="perform-sort" substitutionGroup="xsl:instruction">
+ <xs:complexType>
+ <xs:complexContent mixed="true">
+ <xs:extension base="xsl:versioned-element-type">
+ <xs:sequence>
+ <xs:element ref="xsl:sort" minOccurs="1" maxOccurs="unbounded"/>
+ <xs:group ref="xsl:sequence-constructor-group" minOccurs="0" maxOccurs="unbounded"/>
+ </xs:sequence>
+ <xs:attribute name="select" type="xsl:expression"/>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+</xs:element>
+
+<xs:element name="preserve-space" substitutionGroup="xsl:declaration">
+ <xs:complexType>
+ <xs:complexContent>
+ <xs:extension base="xsl:element-only-versioned-element-type">
+ <xs:attribute name="elements" type="xsl:nametests" use="required"/>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+</xs:element>
+
+<xs:element name="processing-instruction" substitutionGroup="xsl:instruction">
+ <xs:complexType>
+ <xs:complexContent mixed="true">
+ <xs:extension base="xsl:sequence-constructor">
+ <xs:attribute name="name" type="xsl:avt" use="required"/>
+ <xs:attribute name="select" type="xsl:expression"/>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+</xs:element>
+
+<xs:element name="result-document" substitutionGroup="xsl:instruction">
+ <xs:complexType>
+ <xs:complexContent mixed="true">
+ <xs:extension base="xsl:sequence-constructor">
+ <xs:attribute name="format" type="xsl:avt"/>
+ <xs:attribute name="href" type="xsl:avt"/>
+ <xs:attribute name="type" type="xsl:QName"/>
+ <xs:attribute name="validation" type="xsl:validation-type"/>
+ <xs:attribute name="method" type="xsl:avt"/>
+ <xs:attribute name="byte-order-mark" type="xsl:avt"/>
+ <xs:attribute name="cdata-section-elements" type="xsl:avt"/>
+ <xs:attribute name="doctype-public" type="xsl:avt"/>
+ <xs:attribute name="doctype-system" type="xsl:avt"/>
+ <xs:attribute name="encoding" type="xsl:avt"/>
+ <xs:attribute name="escape-uri-attributes" type="xsl:avt"/>
+ <xs:attribute name="include-content-type" type="xsl:avt"/>
+ <xs:attribute name="indent" type="xsl:avt"/>
+ <xs:attribute name="media-type" type="xsl:avt"/>
+ <xs:attribute name="normalization-form" type="xsl:avt"/>
+ <xs:attribute name="omit-xml-declaration" type="xsl:avt"/>
+ <xs:attribute name="standalone" type="xsl:avt"/>
+ <xs:attribute name="undeclare-prefixes" type="xsl:avt"/>
+ <xs:attribute name="use-character-maps" type="xsl:QNames"/>
+ <xs:attribute name="output-version" type="xsl:avt"/>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+</xs:element>
+
+<xs:element name="sequence" substitutionGroup="xsl:instruction">
+ <xs:complexType>
+ <xs:complexContent mixed="true">
+ <xs:extension base="xsl:element-only-versioned-element-type">
+ <xs:sequence minOccurs="0" maxOccurs="unbounded">
+ <xs:element ref="xsl:fallback"/>
+ </xs:sequence>
+ <xs:attribute name="select" type="xsl:expression"/>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+</xs:element>
+
+<xs:element name="sort">
+ <xs:complexType>
+ <xs:complexContent mixed="true">
+ <xs:extension base="xsl:sequence-constructor">
+ <xs:attribute name="select" type="xsl:expression"/>
+ <xs:attribute name="lang" type="xsl:avt"/>
+ <xs:attribute name="data-type" type="xsl:avt" default="text"/>
+ <xs:attribute name="order" type="xsl:avt" default="ascending"/>
+ <xs:attribute name="case-order" type="xsl:avt"/>
+ <xs:attribute name="collation" type="xsl:avt"/>
+ <xs:attribute name="stable" type="xsl:yes-or-no"/>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+</xs:element>
+
+<xs:element name="strip-space" substitutionGroup="xsl:declaration">
+ <xs:complexType>
+ <xs:complexContent>
+ <xs:extension base="xsl:element-only-versioned-element-type">
+ <xs:attribute name="elements" type="xsl:nametests" use="required"/>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+</xs:element>
+
+<xs:element name="stylesheet" substitutionGroup="xsl:transform"/>
+
+<xs:element name="template" substitutionGroup="xsl:declaration">
+ <xs:complexType>
+ <xs:complexContent mixed="true">
+ <xs:extension base="xsl:versioned-element-type">
+ <xs:sequence>
+ <xs:element ref="xsl:param" minOccurs="0" maxOccurs="unbounded"/>
+ <xs:group ref="xsl:sequence-constructor-group" minOccurs="0" maxOccurs="unbounded"/>
+ </xs:sequence>
+ <xs:attribute name="match" type="xsl:pattern"/>
+ <xs:attribute name="priority" type="xs:decimal"/>
+ <xs:attribute name="mode" type="xsl:modes"/>
+ <xs:attribute name="name" type="xsl:QName"/>
+ <xs:attribute name="as" type="xsl:sequence-type" default="item()*"/>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+</xs:element>
+
+<xs:complexType name="text-element-base-type">
+ <xs:simpleContent>
+ <xs:restriction base="xsl:versioned-element-type">
+ <xs:simpleType>
+ <xs:restriction base="xs:string"/>
+ </xs:simpleType>
+ <xs:anyAttribute namespace="##other" processContents="lax"/>
+ </xs:restriction>
+ </xs:simpleContent>
+</xs:complexType>
+
+<xs:element name="text" substitutionGroup="xsl:instruction">
+ <xs:complexType>
+ <xs:simpleContent>
+ <xs:extension base="xsl:text-element-base-type">
+ <xs:attribute name="disable-output-escaping" type="xsl:yes-or-no" default="no"/>
+ </xs:extension>
+ </xs:simpleContent>
+ </xs:complexType>
+</xs:element>
+
+<xs:complexType name="transform-element-base-type">
+ <xs:complexContent>
+ <xs:restriction base="xsl:element-only-versioned-element-type">
+ <xs:attribute name="version" type="xs:decimal" use="required"/>
+ <xs:anyAttribute namespace="##other" processContents="lax"/>
+ </xs:restriction>
+ </xs:complexContent>
+</xs:complexType>
+
+<xs:element name="transform">
+ <xs:complexType>
+ <xs:complexContent>
+ <xs:extension base="xsl:transform-element-base-type">
+ <xs:sequence>
+ <xs:element ref="xsl:import" minOccurs="0" maxOccurs="unbounded"/>
+ <xs:choice minOccurs="0" maxOccurs="unbounded">
+ <xs:element ref="xsl:declaration"/>
+ <xs:element ref="xsl:variable"/>
+ <xs:element ref="xsl:param"/>
+ <xs:any namespace="##other" processContents="lax"/> <!-- weaker than XSLT 1.0 -->
+ </xs:choice>
+ </xs:sequence>
+ <xs:attribute name="id" type="xs:ID"/>
+ <xs:attribute name="default-validation" type="xsl:validation-strip-or-preserve" default="strip"/>
+ <xs:attribute name="input-type-annotations" type="xsl:input-type-annotations-type" default="unspecified"/>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+</xs:element>
+
+<xs:element name="value-of" substitutionGroup="xsl:instruction">
+ <xs:complexType>
+ <xs:complexContent mixed="true">
+ <xs:extension base="xsl:sequence-constructor">
+ <xs:attribute name="select" type="xsl:expression"/>
+ <xs:attribute name="separator" type="xsl:avt"/>
+ <xs:attribute name="disable-output-escaping" type="xsl:yes-or-no" default="no"/>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+</xs:element>
+
+<xs:element name="variable">
+ <xs:complexType>
+ <xs:complexContent mixed="true">
+ <xs:extension base="xsl:sequence-constructor">
+ <xs:attribute name="name" type="xsl:QName" use="required"/>
+ <xs:attribute name="select" type="xsl:expression" use="optional"/>
+ <xs:attribute name="as" type="xsl:sequence-type" use="optional"/>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+</xs:element>
+
+<xs:element name="when">
+ <xs:complexType>
+ <xs:complexContent mixed="true">
+ <xs:extension base="xsl:sequence-constructor">
+ <xs:attribute name="test" type="xsl:expression" use="required"/>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+</xs:element>
+
+<xs:element name="with-param">
+ <xs:complexType>
+ <xs:complexContent mixed="true">
+ <xs:extension base="xsl:sequence-constructor">
+ <xs:attribute name="name" type="xsl:QName" use="required"/>
+ <xs:attribute name="select" type="xsl:expression"/>
+ <xs:attribute name="as" type="xsl:sequence-type"/>
+ <xs:attribute name="tunnel" type="xsl:yes-or-no"/>
+ </xs:extension>
+ </xs:complexContent>
+ </xs:complexType>
+</xs:element>
+
+<!-- ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -->
+<xs:annotation>
+ <xs:documentation>
+ PART C: definition of literal result elements
+
+ There are three ways to define the literal result elements
+ permissible in a stylesheet.
+
+ (a) do nothing. This allows any element to be used as a literal
+ result element, provided it is not in the XSLT namespace
+
+ (b) declare all permitted literal result elements as members
+ of the xsl:literal-result-element substitution group
+
+ (c) redefine the model group xsl:result-elements to accommodate
+ all permitted literal result elements.
+
+ Literal result elements are allowed to take certain attributes
+ in the XSLT namespace. These are defined in the attribute group
+ literal-result-element-attributes, which can be included in the
+ definition of any literal result element.
+
+ </xs:documentation>
+</xs:annotation>
+<!-- ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -->
+
+<xs:element name="literal-result-element" abstract="true" type="xs:anyType"/>
+
+<xs:attributeGroup name="literal-result-element-attributes">
+ <xs:attribute name="default-collation" form="qualified" type="xsl:uri-list"/>
+ <xs:attribute name="extension-element-prefixes" form="qualified" type="xsl:prefixes"/>
+ <xs:attribute name="exclude-result-prefixes" form="qualified" type="xsl:prefixes"/>
+ <xs:attribute name="xpath-default-namespace" form="qualified" type="xs:anyURI"/>
+ <xs:attribute name="inherit-namespaces" form="qualified" type="xsl:yes-or-no" default="yes"/>
+ <xs:attribute name="use-attribute-sets" form="qualified" type="xsl:QNames" default=""/>
+ <xs:attribute name="use-when" form="qualified" type="xsl:expression"/>
+ <xs:attribute name="version" form="qualified" type="xs:decimal"/>
+ <xs:attribute name="type" form="qualified" type="xsl:QName"/>
+ <xs:attribute name="validation" form="qualified" type="xsl:validation-type"/>
+</xs:attributeGroup>
+
+<xs:group name="result-elements">
+ <xs:choice>
+ <xs:element ref="xsl:literal-result-element"/>
+ <xs:any namespace="##other" processContents="lax"/>
+ <xs:any namespace="##local" processContents="lax"/>
+ </xs:choice>
+</xs:group>
+
+
+<!-- ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -->
+<xs:annotation>
+ <xs:documentation>
+ PART D: definitions of simple types used in stylesheet attributes
+ </xs:documentation>
+</xs:annotation>
+<!-- ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -->
+
+<xs:simpleType name="avt">
+ <xs:annotation>
+ <xs:documentation>
+ This type is used for all attributes that allow an attribute value template.
+ The general rules for the syntax of attribute value templates, and the specific
+ rules for each such attribute, are described in the XSLT 2.0 Recommendation.
+ </xs:documentation>
+ </xs:annotation>
+ <xs:restriction base="xs:string"/>
+</xs:simpleType>
+
+<xs:simpleType name="char">
+ <xs:annotation>
+ <xs:documentation>
+ A string containing exactly one character.
+ </xs:documentation>
+ </xs:annotation>
+ <xs:restriction base="xs:string">
+ <xs:length value="1"/>
+ </xs:restriction>
+</xs:simpleType>
+
+<xs:simpleType name="expression">
+ <xs:annotation>
+ <xs:documentation>
+ An XPath 2.0 expression.
+ </xs:documentation>
+ </xs:annotation>
+ <xs:restriction base="xs:token">
+ <xs:pattern value=".+"/>
+ </xs:restriction>
+</xs:simpleType>
+
+<xs:simpleType name="input-type-annotations-type">
+ <xs:annotation>
+ <xs:documentation>
+ Describes how type annotations in source documents are handled.
+ </xs:documentation>
+ </xs:annotation>
+ <xs:restriction base="xs:token">
+ <xs:enumeration value="preserve"/>
+ <xs:enumeration value="strip"/>
+ <xs:enumeration value="unspecified"/>
+ </xs:restriction>
+</xs:simpleType>
+
+<xs:simpleType name="level">
+ <xs:annotation>
+ <xs:documentation>
+ The level attribute of xsl:number:
+ one of single, multiple, or any.
+ </xs:documentation>
+ </xs:annotation>
+ <xs:restriction base="xs:NCName">
+ <xs:enumeration value="single"/>
+ <xs:enumeration value="multiple"/>
+ <xs:enumeration value="any"/>
+ </xs:restriction>
+</xs:simpleType>
+
+<xs:simpleType name="mode">
+ <xs:annotation>
+ <xs:documentation>
+ The mode attribute of xsl:apply-templates:
+ either a QName, or #current, or #default.
+ </xs:documentation>
+ </xs:annotation>
+ <xs:union memberTypes="xsl:QName">
+ <xs:simpleType>
+ <xs:restriction base="xs:token">
+ <xs:enumeration value="#default"/>
+ <xs:enumeration value="#current"/>
+ </xs:restriction>
+ </xs:simpleType>
+ </xs:union>
+</xs:simpleType>
+
+<xs:simpleType name="modes">
+ <xs:annotation>
+ <xs:documentation>
+ The mode attribute of xsl:template:
+ either a list, each member being either a QName or #default;
+ or the value #all
+ </xs:documentation>
+ </xs:annotation>
+ <xs:union>
+ <xs:simpleType>
+ <xs:list>
+ <xs:simpleType>
+ <xs:union memberTypes="xsl:QName">
+ <xs:simpleType>
+ <xs:restriction base="xs:token">
+ <xs:enumeration value="#default"/>
+ </xs:restriction>
+ </xs:simpleType>
+ </xs:union>
+ </xs:simpleType>
+ </xs:list>
+ </xs:simpleType>
+ <xs:simpleType>
+ <xs:restriction base="xs:token">
+ <xs:enumeration value="#all"/>
+ </xs:restriction>
+ </xs:simpleType>
+ </xs:union>
+</xs:simpleType>
+
+<xs:simpleType name="nametests">
+ <xs:annotation>
+ <xs:documentation>
+ A list of NameTests, as defined in the XPath 2.0 Recommendation.
+ Each NameTest is either a QName, or "*", or "prefix:*", or "*:localname"
+ </xs:documentation>
+ </xs:annotation>
+ <xs:list>
+ <xs:simpleType>
+ <xs:union memberTypes="xsl:QName">
+ <xs:simpleType>
+ <xs:restriction base="xs:token">
+ <xs:enumeration value="*"/>
+ </xs:restriction>
+ </xs:simpleType>
+ <xs:simpleType>
+ <xs:restriction base="xs:token">
+ <xs:pattern value="\i\c*:\*"/>
+ <xs:pattern value="\*:\i\c*"/>
+ </xs:restriction>
+ </xs:simpleType>
+ </xs:union>
+ </xs:simpleType>
+ </xs:list>
+</xs:simpleType>
+
+<xs:simpleType name="prefixes">
+ <xs:list itemType="xs:NCName"/>
+</xs:simpleType>
+
+<xs:simpleType name="prefix-list-or-all">
+ <xs:union memberTypes="xsl:prefix-list">
+ <xs:simpleType>
+ <xs:restriction base="xs:token">
+ <xs:enumeration value="#all"/>
+ </xs:restriction>
+ </xs:simpleType>
+ </xs:union>
+</xs:simpleType>
+
+<xs:simpleType name="prefix-list">
+ <xs:list itemType="xsl:prefix-or-default"/>
+</xs:simpleType>
+
+<xs:simpleType name="method">
+ <xs:annotation>
+ <xs:documentation>
+ The method attribute of xsl:output:
+ Either one of the recognized names "xml", "xhtml", "html", "text",
+ or a QName that must include a prefix.
+ </xs:documentation>
+ </xs:annotation>
+ <xs:union>
+ <xs:simpleType>
+ <xs:restriction base="xs:NCName">
+ <xs:enumeration value="xml"/>
+ <xs:enumeration value="xhtml"/>
+ <xs:enumeration value="html"/>
+ <xs:enumeration value="text"/>
+ </xs:restriction>
+ </xs:simpleType>
+ <xs:simpleType>
+ <xs:restriction base="xsl:QName">
+ <xs:pattern value="\c*:\c*"/>
+ </xs:restriction>
+ </xs:simpleType>
+ </xs:union>
+</xs:simpleType>
+
+<xs:simpleType name="pattern">
+ <xs:annotation>
+ <xs:documentation>
+ A match pattern as defined in the XSLT 2.0 Recommendation.
+ The syntax for patterns is a restricted form of the syntax for
+ XPath 2.0 expressions.
+ </xs:documentation>
+ </xs:annotation>
+ <xs:restriction base="xsl:expression"/>
+</xs:simpleType>
+
+<xs:simpleType name="prefix-or-default">
+ <xs:annotation>
+ <xs:documentation>
+ Either a namespace prefix, or #default.
+ Used in the xsl:namespace-alias element.
+ </xs:documentation>
+ </xs:annotation>
+ <xs:union memberTypes="xs:NCName">
+ <xs:simpleType>
+ <xs:restriction base="xs:token">
+ <xs:enumeration value="#default"/>
+ </xs:restriction>
+ </xs:simpleType>
+ </xs:union>
+</xs:simpleType>
+
+<xs:simpleType name="QNames">
+ <xs:annotation>
+ <xs:documentation>
+ A list of QNames.
+ Used in the [xsl:]use-attribute-sets attribute of various elements,
+ and in the cdata-section-elements attribute of xsl:output
+ </xs:documentation>
+ </xs:annotation>
+ <xs:list itemType="xsl:QName"/>
+</xs:simpleType>
+
+<xs:simpleType name="QName">
+ <xs:annotation>
+ <xs:documentation>
+ A QName.
+ This schema does not use the built-in type xs:QName, but rather defines its own
+ QName type. Although xs:QName would define the correct validation on these attributes,
+ a schema processor would expand unprefixed QNames incorrectly when constructing the PSVI,
+ because (as defined in XML Schema errata) an unprefixed xs:QName is assumed to be in
+ the default namespace, which is not the correct assumption for XSLT.
+ The data type is defined as a restriction of the built-in type Name, restricted
+ so that it can only contain one colon which must not be the first or last character.
+ </xs:documentation>
+ </xs:annotation>
+ <xs:restriction base="xs:Name">
+ <xs:pattern value="([^:]+:)?[^:]+"/>
+ </xs:restriction>
+</xs:simpleType>
+
+<xs:simpleType name="sequence-type">
+ <xs:annotation>
+ <xs:documentation>
+ The description of a data type, conforming to the
+ SequenceType production defined in the XPath 2.0 Recommendation
+ </xs:documentation>
+ </xs:annotation>
+ <xs:restriction base="xs:token">
+ <xs:pattern value=".+"/>
+ </xs:restriction>
+</xs:simpleType>
+
+<xs:simpleType name="uri-list">
+ <xs:list itemType="xs:anyURI"/>
+</xs:simpleType>
+
+<xs:simpleType name="validation-strip-or-preserve">
+ <xs:annotation>
+ <xs:documentation>
+ Describes different ways of type-annotating an element or attribute.
+ </xs:documentation>
+ </xs:annotation>
+ <xs:restriction base="xsl:validation-type">
+ <xs:enumeration value="preserve"/>
+ <xs:enumeration value="strip"/>
+ </xs:restriction>
+</xs:simpleType>
+
+<xs:simpleType name="validation-type">
+ <xs:annotation>
+ <xs:documentation>
+ Describes different ways of type-annotating an element or attribute.
+ </xs:documentation>
+ </xs:annotation>
+ <xs:restriction base="xs:token">
+ <xs:enumeration value="strict"/>
+ <xs:enumeration value="lax"/>
+ <xs:enumeration value="preserve"/>
+ <xs:enumeration value="strip"/>
+ </xs:restriction>
+</xs:simpleType>
+
+<xs:simpleType name="yes-or-no">
+ <xs:annotation>
+ <xs:documentation>
+ One of the values "yes" or "no".
+ </xs:documentation>
+ </xs:annotation>
+ <xs:restriction base="xs:token">
+ <xs:enumeration value="yes"/>
+ <xs:enumeration value="no"/>
+ </xs:restriction>
+</xs:simpleType>
+
+<xs:simpleType name="yes-or-no-or-omit">
+ <xs:annotation>
+ <xs:documentation>
+ One of the values "yes" or "no" or "omit".
+ </xs:documentation>
+ </xs:annotation>
+ <xs:restriction base="xs:token">
+ <xs:enumeration value="yes"/>
+ <xs:enumeration value="no"/>
+ <xs:enumeration value="omit"/>
+ </xs:restriction>
+</xs:simpleType>
+
+</xs:schema>
\ No newline at end of file
--- /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.
+-->
+
+<schema xmlns="http://www.w3.org/2001/XMLSchema"
+ targetNamespace="DAV:"
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns:dav="DAV:"
+ elementFormDefault="qualified">
+
+ <element name="activelock">
+ <complexType>
+ <sequence>
+ <element ref="dav:lockscope"/>
+ <element ref="dav:locktype"/>
+ <element ref="dav:depth"/>
+ <element ref="dav:owner" minOccurs="0" maxOccurs="1"/>
+ <element ref="dav:timeout" minOccurs="0" maxOccurs="1"/>
+ <element ref="dav:locktoken" minOccurs="0" maxOccurs="1"/>
+ </sequence>
+ </complexType>
+ </element>
+
+ <element name="lockentry">
+ <complexType>
+ <sequence>
+ <element ref="dav:lockscope"/>
+ <element ref="dav:locktype"/>
+ </sequence>
+ </complexType>
+ </element>
+
+ <element name="lockinfo">
+ <complexType>
+ <sequence>
+ <element ref="dav:lockscope"/>
+ <element ref="dav:locktype"/>
+ <element ref="dav:owner" minOccurs="0" maxOccurs="1"/>
+ </sequence>
+ </complexType>
+ </element>
+
+ <element name="locktype">
+ <complexType>
+ <sequence>
+ <element ref="dav:write"/>
+ </sequence>
+ </complexType>
+ </element>
+
+ <element name="write">
+ <complexType/>
+ </element>
+
+ <element name="lockscope">
+ <complexType>
+ <choice>
+ <element ref="dav:exclusive"/>
+ <element ref="dav:shared"/>
+ </choice>
+ </complexType>
+ </element>
+
+ <element name="exclusive">
+ <complexType/>
+ </element>
+
+ <element name="shared">
+ <complexType/>
+ </element>
+
+ <element name="depth" type="xsd:string"/>
+
+ <element name="owner">
+ <complexType mixed="true">
+ <sequence>
+ <any namespace="http://www.w3.org/namespace/"/>
+ </sequence>
+ </complexType>
+ </element>
+
+ <element name="timeout" type="xsd:string"/>
+
+ <element name="locktoken">
+ <complexType>
+ <sequence>
+ <element ref="dav:href" maxOccurs="unbounded"/>
+ </sequence>
+ </complexType>
+ </element>
+
+ <element name="href" type="xsd:string"/>
+
+ <element name="link">
+ <complexType>
+ <sequence>
+ <element ref="dav:src" maxOccurs="unbounded"/>
+ <element ref="dav:dst" maxOccurs="unbounded"/>
+ </sequence>
+ </complexType>
+ </element>
+
+ <element name="dst" type="xsd:string"/>
+
+ <element name="src" type="xsd:string"/>
+
+ <element name="multistatus">
+ <complexType>
+ <sequence>
+ <element ref="dav:response" maxOccurs="unbounded"/>
+ <element ref="dav:responsedescription" minOccurs="0" maxOccurs="1"/>
+ <element ref="dav:sync-token" minOccurs="0" maxOccurs="1"/>
+ </sequence>
+ </complexType>
+ </element>
+
+ <element name="response">
+ <complexType>
+ <sequence>
+ <element ref="dav:href" minOccurs="1" maxOccurs="unbounded"/>
+ <choice>
+ <sequence>
+ <element ref="dav:status"/>
+ </sequence>
+ <element ref="dav:propstat" maxOccurs="unbounded"/>
+ </choice>
+ <element ref="dav:error" minOccurs="0" maxOccurs="1"/>
+ <element ref="dav:responsedescription" minOccurs="0" maxOccurs="1"/>
+ <element ref="dav:location" minOccurs="0" maxOccurs="1"/>
+ </sequence>
+ </complexType>
+ </element>
+
+ <element name="status" type="xsd:string"/>
+
+ <element name="error">
+ <complexType>
+ <sequence>
+ <any namespace="##any"/>
+ </sequence>
+ </complexType>
+ </element>
+
+ <element name="propstat">
+ <complexType>
+ <sequence>
+ <element ref="dav:prop"/>
+ <element ref="dav:status"/>
+ <element ref="dav:error" minOccurs="0" maxOccurs="1"/>
+ <element ref="dav:responsedescription" minOccurs="0" maxOccurs="1"/>
+ </sequence>
+ </complexType>
+ </element>
+
+ <element name="responsedescription" type="xsd:string"/>
+
+ <element name="location">
+ <complexType>
+ <sequence>
+ <element ref="dav:href" maxOccurs="1"/>
+ </sequence>
+ </complexType>
+ </element>
+
+ <element name="prop">
+ <complexType>
+ <all>
+ <element ref="dav:creationdate" minOccurs="0"/>
+ <element ref="dav:displayname" minOccurs="0"/>
+ <element ref="dav:getcontentlanguage" minOccurs="0"/>
+ <element ref="dav:getcontentlength" minOccurs="0"/>
+ <element ref="dav:getcontenttype" minOccurs="0"/>
+ <element ref="dav:getetag" minOccurs="0"/>
+ <element ref="dav:getlastmodified" minOccurs="0"/>
+ <element ref="dav:lockdiscovery" minOccurs="0"/>
+ <element ref="dav:resourcetype" minOccurs="0"/>
+ <element ref="dav:supportedlock" minOccurs="0"/>
+ <element ref="dav:supported-report-set" minOccurs="0"/>
+ <element ref="dav:quota-available-bytes" minOccurs="0"/>
+ <element ref="dav:quota-used-bytes" minOccurs="0"/>
+ <!-- Microsoft has some own elements in DAV namespace - don't use it for now -->
+ <!--
+ <element ref="dav:contentclass" minOccurs="0"/>
+ <element ref="dav:defaultdocument" minOccurs="0"/>
+ <element ref="dav:href" minOccurs="0"/>
+ <element ref="dav:iscollection" minOccurs="0"/>
+ <element ref="dav:ishidden" minOccurs="0"/>
+ <element ref="dav:isreadonly" minOccurs="0"/>
+ <element ref="dav:isroot" minOccurs="0"/>
+ <element ref="dav:isstructureddocument" minOccurs="0"/>
+ <element ref="dav:lastaccessed" minOccurs="0"/>
+ <element ref="dav:name" minOccurs="0"/>
+ <element ref="dav:parentname" minOccurs="0"/>
+ -->
+ <any processContents="skip" namespace="##other" maxOccurs="unbounded" />
+ </all>
+ </complexType>
+ </element>
+
+ <element name="propertybehavior">
+ <complexType>
+ <choice>
+ <element ref="dav:omit"/>
+ <element ref="dav:keepalive"/>
+ </choice>
+ </complexType>
+ </element>
+
+ <element name="omit">
+ <complexType/>
+ </element>
+
+ <element name="keepalive">
+ <complexType mixed="true">
+ <sequence>
+ <element ref="dav:href" maxOccurs="unbounded"/>
+ </sequence>
+ </complexType>
+ </element>
+
+ <element name="propertyupdate">
+ <complexType>
+ <choice maxOccurs="unbounded">
+ <element ref="dav:remove"/>
+ <element ref="dav:set"/>
+ </choice>
+ </complexType>
+ </element>
+
+ <element name="remove">
+ <complexType>
+ <sequence>
+ <element ref="dav:prop"/>
+ </sequence>
+ </complexType>
+ </element>
+
+ <element name="set">
+ <complexType>
+ <sequence>
+ <element ref="dav:prop"/>
+ </sequence>
+ </complexType>
+ </element>
+
+ <element name="propfind">
+ <complexType>
+ <choice>
+ <element ref="dav:allprop"/>
+ <element ref="dav:propname"/>
+ <element ref="dav:prop"/>
+ </choice>
+ </complexType>
+ </element>
+
+ <element name="allprop">
+ <complexType/>
+ </element>
+
+ <element name="propname">
+ <complexType/>
+ </element>
+
+ <element name="collection">
+ <complexType/>
+ </element>
+
+ <element name="creationdate">
+ <complexType mixed="true">
+ <sequence/>
+ </complexType>
+ </element>
+
+ <element name="displayname">
+ <complexType mixed="true">
+ <sequence/>
+ </complexType>
+ </element>
+
+ <element name="getcontentlanguage">
+ <complexType mixed="true">
+ <sequence/>
+ </complexType>
+ </element>
+
+ <element name="getcontentlength">
+ <complexType mixed="true">
+ <sequence/>
+ </complexType>
+ </element>
+
+ <element name="getcontenttype">
+ <complexType mixed="true">
+ <sequence/>
+ </complexType>
+ </element>
+
+ <element name="getetag">
+ <complexType mixed="true">
+ <sequence/>
+ </complexType>
+ </element>
+
+ <element name="getlastmodified">
+ <complexType mixed="true">
+ <sequence/>
+ </complexType>
+ </element>
+
+ <element name="lockdiscovery">
+ <complexType>
+ <sequence minOccurs="0" maxOccurs="unbounded">
+ <element ref="dav:activelock"/>
+ </sequence>
+ </complexType>
+ </element>
+
+ <element name="resourcetype">
+ <complexType>
+ <sequence>
+ <element ref="dav:collection" minOccurs="0"/>
+ <any processContents="skip" namespace="##other" minOccurs="0" maxOccurs="unbounded" />
+ </sequence>
+ </complexType>
+ </element>
+
+ <element name="supportedlock">
+ <complexType>
+ <sequence minOccurs="0" maxOccurs="unbounded">
+ <element ref="dav:lockentry"/>
+ </sequence>
+ </complexType>
+ </element>
+
+ <element name="source">
+ <complexType>
+ <sequence minOccurs="0" maxOccurs="unbounded">
+ <element ref="dav:link"/>
+ </sequence>
+ </complexType>
+ </element>
+
+ <element name="quota-available-bytes">
+ <complexType mixed="true">
+ <sequence/>
+ </complexType>
+ </element>
+
+ <element name="quota-used-bytes">
+ <complexType mixed="true">
+ <sequence/>
+ </complexType>
+ </element>
+
+ <element name="searchrequest">
+ <complexType>
+ <sequence>
+ <any processContents="skip" namespace="##other" minOccurs="1" maxOccurs="1" />
+ </sequence>
+ </complexType>
+ </element>
+
+ <element name="supported-report-set">
+ <complexType>
+ <sequence>
+ <element maxOccurs="unbounded" ref="dav:supported-report"/>
+ </sequence>
+ </complexType>
+ </element>
+
+ <element name="supported-report">
+ <complexType>
+ <sequence>
+ <element ref="dav:report"/>
+ </sequence>
+ </complexType>
+ </element>
+
+ <element name="report">
+ <complexType>
+ <sequence>
+ <any processContents="skip" namespace="##other" minOccurs="1" maxOccurs="1"/>
+ </sequence>
+ </complexType>
+ </element>
+
+ <element name="sync-collection">
+ <complexType>
+ <sequence>
+ <element ref="dav:sync-token" minOccurs="1" maxOccurs="1"/>
+ <element ref="dav:sync-level" minOccurs="1" maxOccurs="1"/>
+ <element ref="dav:limit" minOccurs="0" maxOccurs="1"/>
+ <element ref="dav:prop" minOccurs="1" maxOccurs="1"/>
+ </sequence>
+ </complexType>
+ </element>
+
+ <element name="sync-token" type="anyURI"/>
+
+ <element name="sync-level" type="string">
+ <simpleType>
+ <restriction base="string">
+ <enumeration value="1"/>
+ <enumeration value="infinite"/>
+ </restriction>
+ </simpleType>
+ </element>
+
+ <element name="limit">
+ <complexType>
+ <sequence>
+ <element ref="dav:nresults" minOccurs="1" maxOccurs="1"/>
+ </sequence>
+ </complexType>
+ </element>
+
+ <element name="nresults" type="integer"/>
+
+ <!-- Microsoft has some own elements in DAV namespace - don't use it for now -->
+ <!--
+ <element name="contentclass" type="xsd:string"/>
+ <element name="defaultdocument" type="xsd:string"/>
+ <element name="iscollection" type="xsd:string"/>
+ <element name="ishidden" type="xsd:string"/>
+ <element name="isreadonly" type="xsd:string"/>
+ <element name="isroot" type="xsd:string"/>
+ <element name="isstructureddocument" type="xsd:string"/>
+ <element name="lastaccessed" type="xsd:string"/>
+ <element name="name" type="xsd:string"/>
+ <element name="parentname" type="xsd:string"/>
+ -->
+</schema>
+
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.w3.org/1999/xlink" xmlns:xlink="http://www.w3.org/1999/xlink">
+
+ <xs:annotation>
+ <xs:documentation>This schema document provides attribute declarations and
+attribute group, complex type and simple type definitions which can be used in
+the construction of user schemas to define the structure of particular linking
+constructs, e.g.
+<![CDATA[
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
+ xmlns:xl="http://www.w3.org/1999/xlink">
+
+ <xs:import namespace="http://www.w3.org/1999/xlink"
+ location="http://www.w3.org/1999/xlink.xsd">
+
+ <xs:element name="mySimple">
+ <xs:complexType>
+ ...
+ <xs:attributeGroup ref="xl:simpleAttrs"/>
+ ...
+ </xs:complexType>
+ </xs:element>
+ ...
+</xs:schema>]]></xs:documentation>
+ </xs:annotation>
+
+ <xs:import namespace="http://www.w3.org/XML/1998/namespace" schemaLocation="http://www.w3.org/2001/xml.xsd"/>
+
+ <xs:attribute name="type" type="xlink:typeType"/>
+
+ <xs:simpleType name="typeType">
+ <xs:restriction base="xs:token">
+ <xs:enumeration value="simple"/>
+ <xs:enumeration value="extended"/>
+ <xs:enumeration value="title"/>
+ <xs:enumeration value="resource"/>
+ <xs:enumeration value="locator"/>
+ <xs:enumeration value="arc"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:attribute name="href" type="xlink:hrefType"/>
+
+ <xs:simpleType name="hrefType">
+ <xs:restriction base="xs:anyURI"/>
+ </xs:simpleType>
+
+ <xs:attribute name="role" type="xlink:roleType"/>
+
+ <xs:simpleType name="roleType">
+ <xs:restriction base="xs:anyURI">
+ <xs:minLength value="1"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:attribute name="arcrole" type="xlink:arcroleType"/>
+
+ <xs:simpleType name="arcroleType">
+ <xs:restriction base="xs:anyURI">
+ <xs:minLength value="1"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:attribute name="title" type="xlink:titleAttrType"/>
+
+ <xs:simpleType name="titleAttrType">
+ <xs:restriction base="xs:string"/>
+ </xs:simpleType>
+
+ <xs:attribute name="show" type="xlink:showType"/>
+
+ <xs:simpleType name="showType">
+ <xs:restriction base="xs:token">
+ <xs:enumeration value="new"/>
+ <xs:enumeration value="replace"/>
+ <xs:enumeration value="embed"/>
+ <xs:enumeration value="other"/>
+ <xs:enumeration value="none"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:attribute name="actuate" type="xlink:actuateType"/>
+
+ <xs:simpleType name="actuateType">
+ <xs:restriction base="xs:token">
+ <xs:enumeration value="onLoad"/>
+ <xs:enumeration value="onRequest"/>
+ <xs:enumeration value="other"/>
+ <xs:enumeration value="none"/>
+ </xs:restriction>
+ </xs:simpleType>
+
+ <xs:attribute name="label" type="xlink:labelType"/>
+
+ <xs:simpleType name="labelType">
+ <xs:restriction base="xs:NCName"/>
+ </xs:simpleType>
+
+ <xs:attribute name="from" type="xlink:fromType"/>
+
+ <xs:simpleType name="fromType">
+ <xs:restriction base="xs:NCName"/>
+ </xs:simpleType>
+
+ <xs:attribute name="to" type="xlink:toType"/>
+
+ <xs:simpleType name="toType">
+ <xs:restriction base="xs:NCName"/>
+ </xs:simpleType>
+
+ <xs:attributeGroup name="simpleAttrs">
+ <xs:attribute ref="xlink:type" fixed="simple"/>
+ <xs:attribute ref="xlink:href"/>
+ <xs:attribute ref="xlink:role"/>
+ <xs:attribute ref="xlink:arcrole"/>
+ <xs:attribute ref="xlink:title"/>
+ <xs:attribute ref="xlink:show"/>
+ <xs:attribute ref="xlink:actuate"/>
+ </xs:attributeGroup>
+
+ <xs:group name="simpleModel">
+ <xs:sequence>
+ <xs:any processContents="lax" minOccurs="0" maxOccurs="unbounded"/>
+ </xs:sequence>
+ </xs:group>
+
+ <xs:complexType mixed="true" name="simple">
+ <xs:annotation>
+ <xs:documentation>
+ Intended for use as the type of user-declared elements to make them
+ simple links.
+ </xs:documentation>
+ </xs:annotation>
+ <xs:group ref="xlink:simpleModel"/>
+ <xs:attributeGroup ref="xlink:simpleAttrs"/>
+ </xs:complexType>
+
+ <xs:attributeGroup name="extendedAttrs">
+ <xs:attribute ref="xlink:type" fixed="extended" use="required"/>
+ <xs:attribute ref="xlink:role"/>
+ <xs:attribute ref="xlink:title"/>
+ </xs:attributeGroup>
+
+ <xs:group name="extendedModel">
+ <xs:choice>
+ <xs:element ref="xlink:title"/>
+ <xs:element ref="xlink:resource"/>
+ <xs:element ref="xlink:locator"/>
+ <xs:element ref="xlink:arc"/>
+ </xs:choice>
+ </xs:group>
+
+ <xs:complexType name="extended">
+ <xs:annotation>
+ <xs:documentation>
+ Intended for use as the type of user-declared elements to make them
+ extended links.
+ Note that the elements referenced in the content model are all abstract.
+ The intention is that by simply declaring elements with these as their
+ substitutionGroup, all the right things will happen.
+ </xs:documentation>
+ </xs:annotation>
+ <xs:group ref="xlink:extendedModel" minOccurs="0" maxOccurs="unbounded"/>
+ <xs:attributeGroup ref="xlink:extendedAttrs"/>
+ </xs:complexType>
+
+ <xs:element name="title" type="xlink:titleEltType" abstract="true"/>
+
+ <xs:attributeGroup name="titleAttrs">
+ <xs:attribute ref="xlink:type" fixed="title" use="required"/>
+ <xs:attribute ref="xml:lang">
+ <xs:annotation>
+ <xs:documentation>
+ xml:lang is not required, but provides much of the
+ motivation for title elements in addition to attributes, and so
+ is provided here for convenience.
+ </xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ </xs:attributeGroup>
+
+ <xs:group name="titleModel">
+ <xs:sequence>
+ <xs:any processContents="lax" minOccurs="0" maxOccurs="unbounded"/>
+ </xs:sequence>
+ </xs:group>
+
+ <xs:complexType mixed="true" name="titleEltType">
+ <xs:group ref="xlink:titleModel"/>
+ <xs:attributeGroup ref="xlink:titleAttrs"/>
+ </xs:complexType>
+
+ <xs:element name="resource" type="xlink:resourceType" abstract="true"/>
+
+ <xs:attributeGroup name="resourceAttrs">
+ <xs:attribute ref="xlink:type" fixed="resource" use="required"/>
+ <xs:attribute ref="xlink:role"/>
+ <xs:attribute ref="xlink:title"/>
+ <xs:attribute ref="xlink:label"/>
+ </xs:attributeGroup>
+
+ <xs:group name="resourceModel">
+ <xs:sequence>
+ <xs:any processContents="lax" minOccurs="0" maxOccurs="unbounded"/>
+ </xs:sequence>
+ </xs:group>
+
+ <xs:complexType mixed="true" name="resourceType">
+ <xs:group ref="xlink:resourceModel"/>
+ <xs:attributeGroup ref="xlink:resourceAttrs"/>
+ </xs:complexType>
+
+ <xs:element name="locator" type="xlink:locatorType" abstract="true"/>
+
+ <xs:attributeGroup name="locatorAttrs">
+ <xs:attribute ref="xlink:type" fixed="locator" use="required"/>
+ <xs:attribute ref="xlink:href" use="required"/>
+ <xs:attribute ref="xlink:role"/>
+ <xs:attribute ref="xlink:title"/>
+ <xs:attribute ref="xlink:label">
+ <xs:annotation>
+ <xs:documentation>
+ label is not required, but locators have no particular
+ XLink function if they are not labeled.
+ </xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ </xs:attributeGroup>
+
+ <xs:group name="locatorModel">
+ <xs:sequence>
+ <xs:element ref="xlink:title" minOccurs="0" maxOccurs="unbounded"/>
+ </xs:sequence>
+ </xs:group>
+
+ <xs:complexType name="locatorType">
+ <xs:group ref="xlink:locatorModel"/>
+ <xs:attributeGroup ref="xlink:locatorAttrs"/>
+ </xs:complexType>
+
+ <xs:element name="arc" type="xlink:arcType" abstract="true"/>
+
+ <xs:attributeGroup name="arcAttrs">
+ <xs:attribute ref="xlink:type" fixed="arc" use="required"/>
+ <xs:attribute ref="xlink:arcrole"/>
+ <xs:attribute ref="xlink:title"/>
+ <xs:attribute ref="xlink:show"/>
+ <xs:attribute ref="xlink:actuate"/>
+ <xs:attribute ref="xlink:from"/>
+ <xs:attribute ref="xlink:to">
+ <xs:annotation>
+ <xs:documentation>
+ from and to have default behavior when values are missing
+ </xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ </xs:attributeGroup>
+
+ <xs:group name="arcModel">
+ <xs:sequence>
+ <xs:element ref="xlink:title" minOccurs="0" maxOccurs="unbounded"/>
+ </xs:sequence>
+ </xs:group>
+
+ <xs:complexType name="arcType">
+ <xs:group ref="xlink:arcModel"/>
+ <xs:attributeGroup ref="xlink:arcAttrs"/>
+ </xs:complexType>
+
+ <!-- Hack required for GML support -->
+ <xs:attributeGroup name="simpleLink">
+ <xs:attribute name="type" type="xs:string" use="optional" fixed="simple" form="qualified"/>
+ <xs:attribute ref="xlink:href" use="optional"/>
+ <xs:attribute ref="xlink:role" use="optional"/>
+ <xs:attribute ref="xlink:arcrole" use="optional"/>
+ <xs:attribute ref="xlink:title" use="optional"/>
+ <xs:attribute ref="xlink:show" use="optional"/>
+ <xs:attribute ref="xlink:actuate" use="optional"/>
+ </xs:attributeGroup>
+</xs:schema>
\ No newline at end of file
--- /dev/null
+<?xml version='1.0'?>
+<?xml-stylesheet href="../2008/09/xsd.xsl" type="text/xsl"?>
+<xs:schema targetNamespace="http://www.w3.org/XML/1998/namespace"
+ xmlns:xs="http://www.w3.org/2001/XMLSchema"
+ xmlns ="http://www.w3.org/1999/xhtml"
+ xml:lang="en">
+
+ <xs:annotation>
+ <xs:documentation>
+ <div>
+ <h1>About the XML namespace</h1>
+
+ <div class="bodytext">
+ <p>
+ This schema document describes the XML namespace, in a form
+ suitable for import by other schema documents.
+ </p>
+ <p>
+ See <a href="http://www.w3.org/XML/1998/namespace.html">
+ http://www.w3.org/XML/1998/namespace.html</a> and
+ <a href="http://www.w3.org/TR/REC-xml">
+ http://www.w3.org/TR/REC-xml</a> for information
+ about this namespace.
+ </p>
+ <p>
+ Note that local names in this namespace are intended to be
+ defined only by the World Wide Web Consortium or its subgroups.
+ The names currently defined in this namespace are listed below.
+ They should not be used with conflicting semantics by any Working
+ Group, specification, or document instance.
+ </p>
+ <p>
+ See further below in this document for more information about <a
+ href="#usage">how to refer to this schema document from your own
+ XSD schema documents</a> and about <a href="#nsversioning">the
+ namespace-versioning policy governing this schema document</a>.
+ </p>
+ </div>
+ </div>
+ </xs:documentation>
+ </xs:annotation>
+
+ <xs:attribute name="lang">
+ <xs:annotation>
+ <xs:documentation>
+ <div>
+
+ <h3>lang (as an attribute name)</h3>
+ <p>
+ denotes an attribute whose value
+ is a language code for the natural language of the content of
+ any element; its value is inherited. This name is reserved
+ by virtue of its definition in the XML specification.</p>
+
+ </div>
+ <div>
+ <h4>Notes</h4>
+ <p>
+ Attempting to install the relevant ISO 2- and 3-letter
+ codes as the enumerated possible values is probably never
+ going to be a realistic possibility.
+ </p>
+ <p>
+ See BCP 47 at <a href="http://www.rfc-editor.org/rfc/bcp/bcp47.txt">
+ http://www.rfc-editor.org/rfc/bcp/bcp47.txt</a>
+ and the IANA language subtag registry at
+ <a href="http://www.iana.org/assignments/language-subtag-registry">
+ http://www.iana.org/assignments/language-subtag-registry</a>
+ for further information.
+ </p>
+ <p>
+ The union allows for the 'un-declaration' of xml:lang with
+ the empty string.
+ </p>
+ </div>
+ </xs:documentation>
+ </xs:annotation>
+ <xs:simpleType>
+ <xs:union memberTypes="xs:language">
+ <xs:simpleType>
+ <xs:restriction base="xs:string">
+ <xs:enumeration value=""/>
+ </xs:restriction>
+ </xs:simpleType>
+ </xs:union>
+ </xs:simpleType>
+ </xs:attribute>
+
+ <xs:attribute name="space">
+ <xs:annotation>
+ <xs:documentation>
+ <div>
+
+ <h3>space (as an attribute name)</h3>
+ <p>
+ denotes an attribute whose
+ value is a keyword indicating what whitespace processing
+ discipline is intended for the content of the element; its
+ value is inherited. This name is reserved by virtue of its
+ definition in the XML specification.</p>
+
+ </div>
+ </xs:documentation>
+ </xs:annotation>
+ <xs:simpleType>
+ <xs:restriction base="xs:NCName">
+ <xs:enumeration value="default"/>
+ <xs:enumeration value="preserve"/>
+ </xs:restriction>
+ </xs:simpleType>
+ </xs:attribute>
+
+ <xs:attribute name="base" type="xs:anyURI"> <xs:annotation>
+ <xs:documentation>
+ <div>
+
+ <h3>base (as an attribute name)</h3>
+ <p>
+ denotes an attribute whose value
+ provides a URI to be used as the base for interpreting any
+ relative URIs in the scope of the element on which it
+ appears; its value is inherited. This name is reserved
+ by virtue of its definition in the XML Base specification.</p>
+
+ <p>
+ See <a
+ href="http://www.w3.org/TR/xmlbase/">http://www.w3.org/TR/xmlbase/</a>
+ for information about this attribute.
+ </p>
+ </div>
+ </xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+
+ <xs:attribute name="id" type="xs:ID">
+ <xs:annotation>
+ <xs:documentation>
+ <div>
+
+ <h3>id (as an attribute name)</h3>
+ <p>
+ denotes an attribute whose value
+ should be interpreted as if declared to be of type ID.
+ This name is reserved by virtue of its definition in the
+ xml:id specification.</p>
+
+ <p>
+ See <a
+ href="http://www.w3.org/TR/xml-id/">http://www.w3.org/TR/xml-id/</a>
+ for information about this attribute.
+ </p>
+ </div>
+ </xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+
+ <xs:attributeGroup name="specialAttrs">
+ <xs:attribute ref="xml:base"/>
+ <xs:attribute ref="xml:lang"/>
+ <xs:attribute ref="xml:space"/>
+ <xs:attribute ref="xml:id"/>
+ </xs:attributeGroup>
+
+ <xs:annotation>
+ <xs:documentation>
+ <div>
+
+ <h3>Father (in any context at all)</h3>
+
+ <div class="bodytext">
+ <p>
+ denotes Jon Bosak, the chair of
+ the original XML Working Group. This name is reserved by
+ the following decision of the W3C XML Plenary and
+ XML Coordination groups:
+ </p>
+ <blockquote>
+ <p>
+ In appreciation for his vision, leadership and
+ dedication the W3C XML Plenary on this 10th day of
+ February, 2000, reserves for Jon Bosak in perpetuity
+ the XML name "xml:Father".
+ </p>
+ </blockquote>
+ </div>
+ </div>
+ </xs:documentation>
+ </xs:annotation>
+
+ <xs:annotation>
+ <xs:documentation>
+ <div xml:id="usage" id="usage">
+ <h2><a name="usage">About this schema document</a></h2>
+
+ <div class="bodytext">
+ <p>
+ This schema defines attributes and an attribute group suitable
+ for use by schemas wishing to allow <code>xml:base</code>,
+ <code>xml:lang</code>, <code>xml:space</code> or
+ <code>xml:id</code> attributes on elements they define.
+ </p>
+ <p>
+ To enable this, such a schema must import this schema for
+ the XML namespace, e.g. as follows:
+ </p>
+ <pre>
+ <schema . . .>
+ . . .
+ <import namespace="http://www.w3.org/XML/1998/namespace"
+ schemaLocation="http://www.w3.org/2001/xml.xsd"/>
+ </pre>
+ <p>
+ or
+ </p>
+ <pre>
+ <import namespace="http://www.w3.org/XML/1998/namespace"
+ schemaLocation="http://www.w3.org/2009/01/xml.xsd"/>
+ </pre>
+ <p>
+ Subsequently, qualified reference to any of the attributes or the
+ group defined below will have the desired effect, e.g.
+ </p>
+ <pre>
+ <type . . .>
+ . . .
+ <attributeGroup ref="xml:specialAttrs"/>
+ </pre>
+ <p>
+ will define a type which will schema-validate an instance element
+ with any of those attributes.
+ </p>
+ </div>
+ </div>
+ </xs:documentation>
+ </xs:annotation>
+
+ <xs:annotation>
+ <xs:documentation>
+ <div id="nsversioning" xml:id="nsversioning">
+ <h2><a name="nsversioning">Versioning policy for this schema document</a></h2>
+ <div class="bodytext">
+ <p>
+ In keeping with the XML Schema WG's standard versioning
+ policy, this schema document will persist at
+ <a href="http://www.w3.org/2009/01/xml.xsd">
+ http://www.w3.org/2009/01/xml.xsd</a>.
+ </p>
+ <p>
+ At the date of issue it can also be found at
+ <a href="http://www.w3.org/2001/xml.xsd">
+ http://www.w3.org/2001/xml.xsd</a>.
+ </p>
+ <p>
+ The schema document at that URI may however change in the future,
+ in order to remain compatible with the latest version of XML
+ Schema itself, or with the XML namespace itself. In other words,
+ if the XML Schema or XML namespaces change, the version of this
+ document at <a href="http://www.w3.org/2001/xml.xsd">
+ http://www.w3.org/2001/xml.xsd
+ </a>
+ will change accordingly; the version at
+ <a href="http://www.w3.org/2009/01/xml.xsd">
+ http://www.w3.org/2009/01/xml.xsd
+ </a>
+ will not change.
+ </p>
+ <p>
+ Previous dated (and unchanging) versions of this schema
+ document are at:
+ </p>
+ <ul>
+ <li><a href="http://www.w3.org/2009/01/xml.xsd">
+ http://www.w3.org/2009/01/xml.xsd</a></li>
+ <li><a href="http://www.w3.org/2007/08/xml.xsd">
+ http://www.w3.org/2007/08/xml.xsd</a></li>
+ <li><a href="http://www.w3.org/2004/10/xml.xsd">
+ http://www.w3.org/2004/10/xml.xsd</a></li>
+ <li><a href="http://www.w3.org/2001/03/xml.xsd">
+ http://www.w3.org/2001/03/xml.xsd</a></li>
+ </ul>
+ </div>
+ </div>
+ </xs:documentation>
+ </xs:annotation>
+
+</xs:schema>
+
package org.argeo.cms.acr.xml;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
+import java.nio.CharBuffer;
+import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
+import java.util.List;
+import java.util.Objects;
import java.util.Optional;
import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ForkJoinPool;
import javax.xml.XMLConstants;
import javax.xml.namespace.NamespaceContext;
import javax.xml.namespace.QName;
+import javax.xml.transform.Result;
+import javax.xml.transform.Source;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMResult;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
import org.argeo.api.acr.Content;
import org.argeo.api.acr.ContentName;
-import org.argeo.api.acr.spi.AbstractContent;
+import org.argeo.api.acr.CrName;
import org.argeo.api.acr.spi.ProvidedContent;
import org.argeo.api.acr.spi.ProvidedSession;
+import org.argeo.cms.acr.AbstractContent;
+import org.argeo.cms.acr.ContentUtils;
import org.w3c.dom.Attr;
+import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
+import org.w3c.dom.DocumentFragment;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;
+/** Content persisted as a DOM element. */
public class DomContent extends AbstractContent implements ProvidedContent {
- private final ProvidedSession session;
private final DomContentProvider provider;
private final Element element;
private Boolean hasText = null;
public DomContent(ProvidedSession session, DomContentProvider contentProvider, Element element) {
- this.session = session;
+ super(session);
this.provider = contentProvider;
this.element = element;
}
@Override
public QName getName() {
+ if (isLocalRoot()) {// root
+ String mountPath = provider.getMountPath();
+ if (mountPath != null) {
+ if (ContentUtils.ROOT_SLASH.equals(mountPath)) {
+ return CrName.root.qName();
+ }
+ Content mountPoint = getSession().getMountPoint(mountPath);
+ QName mountPointName = mountPoint.getName();
+ return mountPointName;
+ }
+ }
return toQName(this.element);
}
+ protected boolean isLocalRoot() {
+ return element.getParentNode() == null || element.getParentNode() instanceof Document;
+ }
+
protected QName toQName(Node node) {
String prefix = node.getPrefix();
if (prefix == null) {
if (namespaceURI == null) {
return toQName(node, node.getLocalName());
} else {
- String contextPrefix = session.getPrefix(namespaceURI);
+ String contextPrefix = provider.getPrefix(namespaceURI);
if (contextPrefix == null)
throw new IllegalStateException("Namespace " + namespaceURI + " is unbound");
- return toQName(node, namespaceURI, node.getLocalName(), session);
+ return toQName(node, namespaceURI, node.getLocalName(), provider);
}
} else {
String namespaceURI = node.getNamespaceURI();
if (namespaceURI == null)
namespaceURI = node.getOwnerDocument().lookupNamespaceURI(prefix);
if (namespaceURI == null) {
- namespaceURI = session.getNamespaceURI(prefix);
+ namespaceURI = provider.getNamespaceURI(prefix);
if (XMLConstants.NULL_NS_URI.equals(namespaceURI))
throw new IllegalStateException("Prefix " + prefix + " is unbound");
// TODO bind the prefix in the document?
}
- return toQName(node, namespaceURI, node.getLocalName(), session);
+ return toQName(node, namespaceURI, node.getLocalName(), provider);
}
}
protected QName toQName(Node source, String namespaceURI, String localName, NamespaceContext namespaceContext) {
- return new ContentName(namespaceURI, localName, session);
+ return new ContentName(namespaceURI, localName, namespaceContext);
}
protected QName toQName(Node source, String localName) {
for (int i = 0; i < attributes.getLength(); i++) {
Attr attr = (Attr) attributes.item(i);
QName key = toQName(attr);
+ if (key.getNamespaceURI().equals(XMLConstants.XMLNS_ATTRIBUTE_NS_URI))
+ continue;// skip prefix mapping
result.add(key);
}
return result;
}
+ @SuppressWarnings("unchecked")
@Override
public <A> Optional<A> get(QName key, Class<A> clss) {
String namespaceUriOrNull = XMLConstants.NULL_NS_URI.equals(key.getNamespaceURI()) ? null
else
return Optional.empty();
} else
- return null;
+ return Optional.empty();
}
@Override
Object previous = get(key);
String namespaceUriOrNull = XMLConstants.NULL_NS_URI.equals(key.getNamespaceURI()) ? null
: key.getNamespaceURI();
+ String prefixToUse = registerPrefixIfNeeded(key);
element.setAttributeNS(namespaceUriOrNull,
- namespaceUriOrNull == null ? key.getLocalPart() : key.getPrefix() + ":" + key.getLocalPart(),
+ namespaceUriOrNull == null ? key.getLocalPart() : prefixToUse + ":" + key.getLocalPart(),
value.toString());
return previous;
}
-
-
+
+ protected String registerPrefixIfNeeded(QName name) {
+ String namespaceUriOrNull = XMLConstants.NULL_NS_URI.equals(name.getNamespaceURI()) ? null
+ : name.getNamespaceURI();
+ String prefixToUse;
+ if (namespaceUriOrNull != null) {
+ String registeredPrefix = provider.getPrefix(namespaceUriOrNull);
+ if (registeredPrefix != null) {
+ prefixToUse = registeredPrefix;
+ } else {
+ provider.registerPrefix(name.getPrefix(), namespaceUriOrNull);
+ prefixToUse = name.getPrefix();
+ }
+ } else {
+ prefixToUse = null;
+ }
+ return prefixToUse;
+ }
@Override
public boolean hasText() {
@Override
public Iterator<Content> iterator() {
NodeList nodeList = element.getChildNodes();
- return new ElementIterator(session, provider, nodeList);
+ return new ElementIterator(this, getSession(), provider, nodeList);
}
@Override
public Content getParent() {
- Node parent = element.getParentNode();
- if (parent == null)
- return null;
- if (!(parent instanceof Element))
+ Node parentNode = element.getParentNode();
+ if (isLocalRoot()) {
+ String mountPath = provider.getMountPath();
+ if (mountPath == null)
+ return null;
+ if (ContentUtils.ROOT_SLASH.equals(mountPath)) {
+ return null;
+ }
+ String[] parent = ContentUtils.getParentPath(mountPath);
+ if (ContentUtils.EMPTY.equals(parent[0]))
+ return null;
+ return getSession().get(parent[0]);
+ }
+ if (!(parentNode instanceof Element))
throw new IllegalStateException("Parent is not an element");
- return new DomContent(this, (Element) parent);
+ return new DomContent(this, (Element) parentNode);
}
@Override
Document document = this.element.getOwnerDocument();
String namespaceUriOrNull = XMLConstants.NULL_NS_URI.equals(name.getNamespaceURI()) ? null
: name.getNamespaceURI();
+ String prefixToUse = registerPrefixIfNeeded(name);
Element child = document.createElementNS(namespaceUriOrNull,
- namespaceUriOrNull == null ? name.getLocalPart() : name.getPrefix() + ":" + name.getLocalPart());
+ namespaceUriOrNull == null ? name.getLocalPart() : prefixToUse + ":" + name.getLocalPart());
element.appendChild(child);
return new DomContent(this, child);
}
}
- public ProvidedSession getSession() {
- return session;
+ @SuppressWarnings("unchecked")
+ @Override
+ public <A> A adapt(Class<A> clss) throws IllegalArgumentException {
+ if (CharBuffer.class.isAssignableFrom(clss)) {
+ String textContent = element.getTextContent();
+ CharBuffer buf = CharBuffer.wrap(textContent);
+ return (A) buf;
+ } else if (Source.class.isAssignableFrom(clss)) {
+ DOMSource source = new DOMSource(element);
+ return (A) source;
+ }
+ return super.adapt(clss);
+ }
+
+ @SuppressWarnings("unchecked")
+ public <A> CompletableFuture<A> write(Class<A> clss) {
+ if (String.class.isAssignableFrom(clss)) {
+ CompletableFuture<String> res = new CompletableFuture<>();
+ res.thenAccept((s) -> {
+ getSession().notifyModification(this);
+ element.setTextContent(s);
+ });
+ return (CompletableFuture<A>) res;
+ } else if (Source.class.isAssignableFrom(clss)) {
+ CompletableFuture<Source> res = new CompletableFuture<>();
+ res.thenAccept((source) -> {
+ try {
+ Transformer transformer = provider.getTransformerFactory().newTransformer();
+ DocumentFragment documentFragment = element.getOwnerDocument().createDocumentFragment();
+ DOMResult result = new DOMResult(documentFragment);
+ transformer.transform(source, result);
+ // Node parentNode = element.getParentNode();
+ Element resultElement = (Element) documentFragment.getFirstChild();
+ QName resultName = toQName(resultElement);
+ if (!resultName.equals(getName()))
+ throw new IllegalArgumentException(resultName + "+ is not compatible with " + getName());
+
+ // attributes
+ NamedNodeMap attrs = resultElement.getAttributes();
+ for (int i = 0; i < attrs.getLength(); i++) {
+ Attr attr2 = (Attr) element.getOwnerDocument().importNode(attrs.item(i), true);
+ element.getAttributes().setNamedItem(attr2);
+ }
+
+ // Move all the children
+ while (element.hasChildNodes()) {
+ element.removeChild(element.getFirstChild());
+ }
+ while (resultElement.hasChildNodes()) {
+ element.appendChild(resultElement.getFirstChild());
+ }
+// parentNode.replaceChild(resultNode, element);
+// element = (Element)resultNode;
+
+ } catch (DOMException | TransformerException e) {
+ throw new RuntimeException("Cannot write to element", e);
+ }
+ });
+ return (CompletableFuture<A>) res;
+ }
+ return super.write(clss);
}
+ @SuppressWarnings("unchecked")
+ @Override
+ public <C extends Closeable> C open(Class<C> clss) throws IOException, IllegalArgumentException {
+ if (InputStream.class.isAssignableFrom(clss)) {
+ PipedOutputStream out = new PipedOutputStream();
+ ForkJoinPool.commonPool().execute(() -> {
+ try {
+ Source source = new DOMSource(element);
+ Result result = new StreamResult(out);
+ provider.getTransformerFactory().newTransformer().transform(source, result);
+ out.flush();
+ out.close();
+ } catch (TransformerException | IOException e) {
+ throw new RuntimeException("Cannot read " + getPath(), e);
+ }
+ });
+ return (C) new PipedInputStream(out);
+ }
+ return super.open(clss);
+ }
+
+ @Override
+ public int getSiblingIndex() {
+ Node curr = element.getPreviousSibling();
+ int count = 1;
+ while (curr != null) {
+ if (curr instanceof Element) {
+ if (Objects.equals(curr.getNamespaceURI(), element.getNamespaceURI())
+ && Objects.equals(curr.getLocalName(), element.getLocalName())) {
+ count++;
+ }
+ }
+ curr = curr.getPreviousSibling();
+ }
+ return count;
+ }
+
+ /*
+ * TYPING
+ */
+ @Override
+ public List<QName> getContentClasses() {
+ List<QName> res = new ArrayList<>();
+ if (isLocalRoot()) {
+ String mountPath = provider.getMountPath();
+ if (mountPath != null) {
+ Content mountPoint = getSession().getMountPoint(mountPath);
+ res.addAll(mountPoint.getContentClasses());
+ }
+ } else {
+ res.add(getName());
+ }
+ return res;
+ }
+
+ @Override
+ public void addContentClasses(QName... contentClass) {
+ if (isLocalRoot()) {
+ String mountPath = provider.getMountPath();
+ if (mountPath != null) {
+ Content mountPoint = getSession().getMountPoint(mountPath);
+ mountPoint.addContentClasses(contentClass);
+ }
+ } else {
+ super.addContentClasses(contentClass);
+ }
+ }
+
+ /*
+ * MOUNT MANAGEMENT
+ */
+ @Override
+ public ProvidedContent getMountPoint(String relativePath) {
+ // FIXME use qualified names
+ Element childElement = (Element) element.getElementsByTagName(relativePath).item(0);
+ // TODO check that it is a mount
+ return new DomContent(this, childElement);
+ }
+
+ @Override
public DomContentProvider getProvider() {
return provider;
}
package org.argeo.cms.acr.xml;
+import java.io.IOException;
+import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import javax.xml.namespace.NamespaceContext;
+import javax.xml.transform.TransformerFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import org.argeo.api.acr.Content;
import org.argeo.api.acr.ContentNotFoundException;
+import org.argeo.api.acr.CrName;
+import org.argeo.api.acr.NamespaceUtils;
import org.argeo.api.acr.spi.ContentProvider;
+import org.argeo.api.acr.spi.ProvidedContent;
import org.argeo.api.acr.spi.ProvidedSession;
+import org.argeo.cms.acr.CmsContentRepository;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
public class DomContentProvider implements ContentProvider, NamespaceContext {
- private Document document;
+ private final Document document;
// XPath
// TODO centralise in some executor?
private final ThreadLocal<XPath> xPath;
- public DomContentProvider(Document document) {
+ private TransformerFactory transformerFactory;
+
+ private String mountPath;
+
+ public DomContentProvider(String mountPath, Document document) {
+ this.mountPath = mountPath;
this.document = document;
this.document.normalizeDocument();
+
+ transformerFactory = TransformerFactory.newInstance();
+
XPathFactory xPathFactory = XPathFactory.newInstance();
xPath = new ThreadLocal<>() {
@Override
protected XPath initialValue() {
// TODO set the document as namespace context?
- XPath res= xPathFactory.newXPath();
+ XPath res = xPathFactory.newXPath();
res.setNamespaceContext(DomContentProvider.this);
return res;
}
// }
@Override
- public Content get(ProvidedSession session, String mountPath, String relativePath) {
+ public ProvidedContent get(ProvidedSession session, String relativePath) {
if ("".equals(relativePath))
return new DomContent(session, this, document.getDocumentElement());
+
+ NodeList nodes = findContent(relativePath);
+ if (nodes.getLength() > 1)
+ throw new IllegalArgumentException("Multiple content found for " + relativePath + " under " + mountPath);
+ if (nodes.getLength() == 0)
+ throw new ContentNotFoundException(session, mountPath + "/" + relativePath,
+ "Path " + relativePath + " under " + mountPath + " was not found");
+ Element element = (Element) nodes.item(0);
+ return new DomContent(session, this, element);
+ }
+
+ protected NodeList findContent(String relativePath) {
if (relativePath.startsWith("/"))
throw new IllegalArgumentException("Relative path cannot start with /");
-
String xPathExpression = '/' + relativePath;
if ("/".equals(mountPath))
- xPathExpression = "/cr:root" + xPathExpression;
+ xPathExpression = "/" + CrName.root.qName() + xPathExpression;
try {
NodeList nodes = (NodeList) xPath.get().evaluate(xPathExpression, document, XPathConstants.NODESET);
- if (nodes.getLength() > 1)
- throw new IllegalArgumentException(
- "Multiple content found for " + relativePath + " under " + mountPath);
- if (nodes.getLength() == 0)
- throw new ContentNotFoundException("Path " + relativePath + " under " + mountPath + " was not found");
- Element element = (Element) nodes.item(0);
- return new DomContent(session, this, element);
+ return nodes;
} catch (XPathExpressionException e) {
throw new IllegalArgumentException("XPath expression " + xPathExpression + " cannot be evaluated", e);
}
+
+ }
+
+ @Override
+ public boolean exists(ProvidedSession session, String relativePath) {
+ if ("".equals(relativePath))
+ return true;
+ NodeList nodes = findContent(relativePath);
+ return nodes.getLength() != 0;
+ }
+
+ public void persist(ProvidedSession session) {
+ if (mountPath != null) {
+ Content mountPoint = session.getMountPoint(mountPath);
+ try (OutputStream out = mountPoint.open(OutputStream.class)) {
+ CmsContentRepository contentRepository = (CmsContentRepository) session.getRepository();
+ contentRepository.writeDom(document, out);
+ } catch (IOException e) {
+ throw new IllegalStateException("Cannot persist " + mountPath, e);
+ }
+ }
+ }
+
+ @Override
+ public String getMountPath() {
+ return mountPath;
+ }
+
+ public void registerPrefix(String prefix, String namespace) {
+ DomUtils.addNamespace(document.getDocumentElement(), prefix, namespace);
}
/*
*/
@Override
public String getNamespaceURI(String prefix) {
+ String namespaceURI = NamespaceUtils.getStandardNamespaceURI(prefix);
+ if (namespaceURI != null)
+ return namespaceURI;
return document.lookupNamespaceURI(prefix);
}
@Override
public String getPrefix(String namespaceURI) {
+ String prefix = NamespaceUtils.getStandardPrefix(namespaceURI);
+ if (prefix != null)
+ return prefix;
return document.lookupPrefix(namespaceURI);
}
return Collections.unmodifiableList(res).iterator();
}
+ TransformerFactory getTransformerFactory() {
+ return transformerFactory;
+ }
+
}
--- /dev/null
+package org.argeo.cms.acr.xml;
+
+import javax.xml.XMLConstants;
+
+import org.w3c.dom.Element;
+
+public class DomUtils {
+ public static void addNamespace(Element element, String prefix, String namespace) {
+ element.setAttributeNS(XMLConstants.XMLNS_ATTRIBUTE_NS_URI, XMLConstants.XMLNS_ATTRIBUTE + ":" + prefix,
+ namespace);
+ }
+
+// public static void writeDom(TransformerFactory transformerFactory, Document document, OutputStream out)
+// throws IOException {
+// try {
+// Transformer transformer = transformerFactory.newTransformer();
+// transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
+// transformer.setOutputProperty(OutputKeys.INDENT, "yes");
+// DOMSource source = new DOMSource(document);
+// StreamResult result = new StreamResult(out);
+// transformer.transform(source, result);
+// } catch (TransformerException e) {
+// throw new IOException("Cannot write dom", e);
+// }
+// }
+
+ /** singleton */
+ private DomUtils() {
+
+ }
+}
import java.util.Iterator;
import java.util.NoSuchElementException;
+import org.argeo.api.acr.ArgeoNamespace;
import org.argeo.api.acr.Content;
+import org.argeo.api.acr.CrName;
import org.argeo.api.acr.spi.ProvidedSession;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
class ElementIterator implements Iterator<Content> {
+ private DomContent parent;
private final ProvidedSession session;
private final DomContentProvider provider;
private final NodeList nodeList;
private final int length;
private Element nextElement = null;
- public ElementIterator(ProvidedSession session, DomContentProvider provider, NodeList nodeList) {
+ public ElementIterator(DomContent parent, ProvidedSession session, DomContentProvider provider, NodeList nodeList) {
+ this.parent = parent;
this.session = session;
this.provider = provider;
this.nodeList = nodeList;
public Content next() {
if (nextElement == null)
throw new NoSuchElementException();
- DomContent result = new DomContent(session, provider, nextElement);
+ Content result;
+ String isMount = nextElement.getAttributeNS(ArgeoNamespace.CR_NAMESPACE_URI, CrName.mount.qName().getLocalPart());
+ if (isMount.equals("true")) {
+ result = session.get(parent.getPath() + '/' + nextElement.getTagName());
+ }
+
+ else {
+ result = new DomContent(session, provider, nextElement);
+ }
currentIndex++;
nextElement = findNext();
return result;
--- /dev/null
+package org.argeo.cms.acr.xml;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.file.DirectoryStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Result;
+import javax.xml.transform.Source;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.TransformerFactoryConfigurationError;
+import javax.xml.transform.dom.DOMResult;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+import javax.xml.transform.stream.StreamSource;
+
+import org.w3c.dom.DOMException;
+import org.w3c.dom.Document;
+import org.xml.sax.SAXException;
+
+/**
+ * Consistently normalises an XML in order to ease diff (typically in a
+ * versioning system).
+ */
+public class XmlNormalizer {
+// private final static Logger logger = System.getLogger(XmlNormalizer.class.getName());
+
+ private DocumentBuilder documentBuilder;
+ private TransformerFactory transformerFactory;
+
+ private DOMSource stripSpaceXsl;
+
+ public XmlNormalizer() {
+ this(2);
+ }
+
+ public XmlNormalizer(int indent) {
+ try {
+ documentBuilder = DocumentBuilderFactory.newNSInstance().newDocumentBuilder();
+ transformerFactory = TransformerFactory.newInstance();
+ transformerFactory.setAttribute("indent-number", indent);
+ try (InputStream in = XmlNormalizer.class.getResourceAsStream("stripSpaces.xsl")) {
+ DOMResult result = new DOMResult();
+ transformerFactory.newTransformer().transform(new StreamSource(in), result);
+ stripSpaceXsl = new DOMSource(result.getNode());
+ }
+ } catch (ParserConfigurationException | TransformerFactoryConfigurationError | TransformerException
+ | IOException e) {
+ throw new IllegalStateException("Cannot initialise document builder and transformer", e);
+ }
+ }
+
+ public void normalizeXmlFiles(DirectoryStream<Path> ds) throws IOException {
+ for (Path path : ds) {
+ normalizeXmlFile(path);
+ }
+ }
+
+ public void normalizeXmlFile(Path path) throws IOException {
+ byte[] bytes = Files.readAllBytes(path);
+ try (ByteArrayInputStream in = new ByteArrayInputStream(bytes);
+ OutputStream out = Files.newOutputStream(path)) {
+ normalizeAndIndent(in, out);
+// logger.log(Level.DEBUG, () -> "Normalized XML " + path);
+ }
+ }
+
+ public void normalizeAndIndent(Source source, Result result) {
+ try {
+ Transformer transformer = transformerFactory.newTransformer(stripSpaceXsl);
+ transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
+ // transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
+ transformer.setOutputProperty(OutputKeys.INDENT, "yes");
+
+ transformer.transform(source, result);
+ } catch (TransformerException e) {
+ throw new RuntimeException("Cannot strip space from " + source, e);
+ }
+ }
+
+ public void normalizeAndIndent(InputStream in, OutputStream out) throws IOException {
+ try {
+ Document document = documentBuilder.parse(in);
+
+ // clear whitespaces outside tags
+ document.normalize();
+// XPath xPath = XPathFactory.newInstance().newXPath();
+// NodeList nodeList = (NodeList) xPath.evaluate("//text()[normalize-space()='']", document,
+// XPathConstants.NODESET);
+//
+// for (int i = 0; i < nodeList.getLength(); ++i) {
+// Node node = nodeList.item(i);
+// node.getParentNode().removeChild(node);
+// }
+
+ normalizeAndIndent(new DOMSource(document), new StreamResult(out));
+ } catch (DOMException | IllegalArgumentException | SAXException | TransformerFactoryConfigurationError e) {
+ throw new RuntimeException("Cannot normalise and indent XML", e);
+ }
+ }
+
+ public static void print(Source source, int indent) {
+ XmlNormalizer xmlNormalizer = new XmlNormalizer(indent);
+ xmlNormalizer.normalizeAndIndent(source, new StreamResult(System.out));
+ }
+
+ public static void print(Source source) {
+ print(source, 2);
+ }
+
+ public static void main(String[] args) throws IOException {
+ Path dir = Paths.get(args[0]);
+ XmlNormalizer xmlNormalizer = new XmlNormalizer();
+ DirectoryStream<Path> ds = Files.newDirectoryStream(dir, "*.svg");
+ xmlNormalizer.normalizeXmlFiles(ds);
+
+ }
+}
--- /dev/null
+<xsl:stylesheet version="1.0"
+ xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+ <xsl:strip-space elements="*" />
+ <xsl:output method="xml" encoding="UTF-8" />
+
+ <xsl:template match="@*|node()">
+ <xsl:copy>
+ <xsl:apply-templates select="@*|node()" />
+ </xsl:copy>
+ </xsl:template>
+
+</xsl:stylesheet>
\ No newline at end of file
import javax.security.auth.spi.LoginModule;
import org.argeo.api.cms.CmsLog;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.FrameworkUtil;
+import org.argeo.cms.internal.runtime.CmsContextImpl;
import org.osgi.service.useradmin.Authorization;
import org.osgi.service.useradmin.UserAdmin;
private Map<String, Object> sharedState = null;
// private state
- private BundleContext bc;
+// private BundleContext bc;
@SuppressWarnings("unchecked")
@Override
Map<String, ?> options) {
this.subject = subject;
this.sharedState = (Map<String, Object>) sharedState;
- try {
- bc = FrameworkUtil.getBundle(AnonymousLoginModule.class).getBundleContext();
- assert bc != null;
- } catch (Exception e) {
- throw new IllegalStateException("Cannot initialize login module", e);
- }
+// try {
+// bc = FrameworkUtil.getBundle(AnonymousLoginModule.class).getBundleContext();
+// assert bc != null;
+// } catch (Exception e) {
+// throw new IllegalStateException("Cannot initialize login module", e);
+// }
}
@Override
@Override
public boolean commit() throws LoginException {
- UserAdmin userAdmin = bc.getService(bc.getServiceReference(UserAdmin.class));
+ UserAdmin userAdmin = CmsContextImpl.getCmsContext().getUserAdmin();
Authorization authorization = userAdmin.getAuthorization(null);
RemoteAuthRequest request = (RemoteAuthRequest) sharedState.get(CmsAuthUtils.SHARED_STATE_HTTP_REQUEST);
Locale locale = Locale.getDefault();
package org.argeo.cms.auth;
+import static org.argeo.api.cms.CmsConstants.ROLE_ADMIN;
+import static org.argeo.api.cms.CmsConstants.ROLE_ANONYMOUS;
+import static org.argeo.api.cms.CmsConstants.ROLE_USER;
+import static org.argeo.api.cms.CmsConstants.ROLE_USER_ADMIN;
+
import java.security.Principal;
-import java.util.Collection;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.UUID;
-import javax.naming.InvalidNameException;
-import javax.naming.ldap.LdapName;
+//import javax.naming.InvalidNameException;
+//import javax.naming.ldap.LdapName;
import javax.security.auth.Subject;
import javax.security.auth.x500.X500Principal;
+import org.argeo.api.cms.AnonymousPrincipal;
+import org.argeo.api.cms.CmsConstants;
import org.argeo.api.cms.CmsSession;
import org.argeo.api.cms.CmsSessionId;
import org.argeo.api.cms.DataAdminPrincipal;
-import org.argeo.api.cms.AnonymousPrincipal;
-import org.argeo.api.cms.CmsConstants;
import org.argeo.cms.internal.auth.CmsSessionImpl;
import org.argeo.cms.internal.auth.ImpliedByPrincipal;
-import org.argeo.cms.internal.http.WebCmsSessionImpl;
-import org.argeo.cms.security.NodeSecurityUtils;
-import org.argeo.osgi.useradmin.AuthenticatingUser;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.InvalidSyntaxException;
-import org.osgi.framework.ServiceReference;
-import org.osgi.service.http.HttpContext;
+import org.argeo.cms.internal.auth.RemoteCmsSessionImpl;
+import org.argeo.cms.internal.runtime.CmsContextImpl;
+import org.argeo.cms.osgi.useradmin.AuthenticatingUser;
import org.osgi.service.useradmin.Authorization;
/** Centralises security related registrations. */
// Standard
final static String SHARED_STATE_NAME = AuthenticatingUser.SHARED_STATE_NAME;
final static String SHARED_STATE_PWD = AuthenticatingUser.SHARED_STATE_PWD;
- final static String HEADER_AUTHORIZATION = "Authorization";
- final static String HEADER_WWW_AUTHENTICATE = "WWW-Authenticate";
+// final static String HEADER_AUTHORIZATION = "Authorization";
+// final static String HEADER_WWW_AUTHENTICATE = "WWW-Authenticate";
// Argeo specific
final static String SHARED_STATE_HTTP_REQUEST = "org.argeo.cms.auth.http.request";
final static String SHARED_STATE_REMOTE_ADDR = "org.argeo.cms.auth.remote.addr";
final static String SHARED_STATE_REMOTE_PORT = "org.argeo.cms.auth.remote.port";
+ final static String SINGLE_USER_LOCAL_ID = "single-user";
+
+ private final static List<String> RESERVED_ROLES = Collections
+ .unmodifiableList(Arrays.asList(new String[] { ROLE_ADMIN, ROLE_ANONYMOUS, ROLE_USER, ROLE_USER_ADMIN }));
+
static void addAuthorization(Subject subject, Authorization authorization) {
assert subject != null;
checkSubjectEmpty(subject);
boolean singleUser = authorization instanceof SingleUserAuthorization;
Set<Principal> principals = subject.getPrincipals();
- try {
- String authName = authorization.getName();
-
- // determine user's principal
- final LdapName name;
- final Principal userPrincipal;
- if (authName == null) {
- name = NodeSecurityUtils.ROLE_ANONYMOUS_NAME;
- userPrincipal = new AnonymousPrincipal();
- principals.add(userPrincipal);
- } else {
- name = new LdapName(authName);
- NodeSecurityUtils.checkUserName(name);
- userPrincipal = new X500Principal(name.toString());
- principals.add(userPrincipal);
-
- if (singleUser) {
- principals.add(new ImpliedByPrincipal(NodeSecurityUtils.ROLE_ADMIN_NAME, userPrincipal));
- principals.add(new DataAdminPrincipal());
- }
+// try {
+ String authName = authorization.getName();
+
+ // determine user's principal
+// final LdapName name;
+ final Principal userPrincipal;
+ if (authName == null) {
+// name = NodeSecurityUtils.ROLE_ANONYMOUS_NAME;
+ userPrincipal = new AnonymousPrincipal();
+ principals.add(userPrincipal);
+ } else {
+// name = new LdapName(authName);
+ checkUserName(authName);
+ userPrincipal = new X500Principal(authName.toString());
+ principals.add(userPrincipal);
+
+ if (singleUser) {
+ principals.add(new ImpliedByPrincipal(CmsConstants.ROLE_ADMIN, userPrincipal));
+ principals.add(new DataAdminPrincipal());
}
+ }
- // Add roles provided by authorization
- for (String role : authorization.getRoles()) {
- LdapName roleName = new LdapName(role);
- if (roleName.equals(name)) {
- // skip
- } else if (roleName.equals(NodeSecurityUtils.ROLE_ANONYMOUS_NAME)) {
- // skip
- } else {
- NodeSecurityUtils.checkImpliedPrincipalName(roleName);
- principals.add(new ImpliedByPrincipal(roleName.toString(), userPrincipal));
- if (roleName.equals(NodeSecurityUtils.ROLE_ADMIN_NAME))
- principals.add(new DataAdminPrincipal());
- }
+ // Add roles provided by authorization
+ for (String role : authorization.getRoles()) {
+// LdapName roleName = new LdapName(role);
+ if (role.equals(authName)) {
+ // skip
+ } else if (role.equals(CmsConstants.ROLE_ANONYMOUS)) {
+ // skip
+ } else {
+// NodeSecurityUtils.checkImpliedPrincipalName(role);
+ principals.add(new ImpliedByPrincipal(role, userPrincipal));
+ if (role.equals(CmsConstants.ROLE_ADMIN))
+ principals.add(new DataAdminPrincipal());
}
-
- } catch (InvalidNameException e) {
- throw new IllegalArgumentException("Cannot commit", e);
}
+
+// } catch (InvalidNameException e) {
+// throw new IllegalArgumentException("Cannot commit", e);
+// }
}
private static void checkSubjectEmpty(Subject subject) {
// TODO move it to a service in order to avoid static synchronization
if (request != null) {
RemoteAuthSession httpSession = request.getSession();
- assert httpSession != null;
- String httpSessId = httpSession.getId();
+ String httpSessId = httpSession != null ? httpSession.getId() : null;
boolean anonymous = authorization.getName() == null;
String remoteUser = !anonymous ? authorization.getName() : CmsConstants.ROLE_ANONYMOUS;
- request.setAttribute(HttpContext.REMOTE_USER, remoteUser);
- request.setAttribute(HttpContext.AUTHORIZATION, authorization);
+ request.setAttribute(RemoteAuthRequest.REMOTE_USER, remoteUser);
+ request.setAttribute(RemoteAuthRequest.AUTHORIZATION, authorization);
CmsSessionImpl cmsSession;
- CmsSessionImpl currentLocalSession = CmsSessionImpl.getByLocalId(httpSessId);
+ CmsSessionImpl currentLocalSession = CmsContextImpl.getCmsContext().getCmsSessionByLocalId(httpSessId);
if (currentLocalSession != null) {
- boolean currentLocalSessionAnonymous = currentLocalSession.getAuthorization().getName() == null;
+ boolean currentLocalSessionAnonymous = currentLocalSession.isAnonymous();
if (!anonymous) {
if (currentLocalSessionAnonymous) {
currentLocalSession.close();
// new CMS session
- cmsSession = new WebCmsSessionImpl(subject, authorization, locale, request);
+ UUID cmsSessionUuid = CmsContextImpl.getCmsContext().getUuidFactory().timeUUID();
+ cmsSession = new RemoteCmsSessionImpl(cmsSessionUuid, subject, authorization, locale, request);
+ CmsContextImpl.getCmsContext().registerCmsSession(cmsSession);
} else if (!authorization.getName().equals(currentLocalSession.getAuthorization().getName())) {
throw new IllegalStateException("Inconsistent user " + authorization.getName()
+ " for existing CMS session " + currentLocalSession);
} else {
// keep current session
cmsSession = currentLocalSession;
- // keyring
- subject.getPrivateCredentials().addAll(cmsSession.getSecretKeys());
+ // credentials
+ // TODO control it more??
+ subject.getPrivateCredentials().addAll(cmsSession.getSubject().getPrivateCredentials());
+ subject.getPublicCredentials().addAll(cmsSession.getSubject().getPublicCredentials());
}
} else {// anonymous
if (!currentLocalSessionAnonymous) {
}
} else {
// new CMS session
- cmsSession = new WebCmsSessionImpl(subject, authorization, locale, request);
+ UUID cmsSessionUuid = CmsContextImpl.getCmsContext().getUuidFactory().timeUUID();
+ cmsSession = new RemoteCmsSessionImpl(cmsSessionUuid, subject, authorization, locale, request);
+ CmsContextImpl.getCmsContext().registerCmsSession(cmsSession);
}
if (cmsSession == null)// should be dead code (cf. SuppressWarning of the method)
subject.getPrivateCredentials().add(nodeSessionId);
} else {
UUID storedSessionId = subject.getPrivateCredentials(CmsSessionId.class).iterator().next().getUuid();
- // if (storedSessionId.equals(httpSessionId.getValue()))
- throw new IllegalStateException(
- "Subject already logged with session " + storedSessionId + " (not " + nodeSessionId + ")");
+ if (!storedSessionId.equals(nodeSessionId.getUuid()))
+ throw new IllegalStateException(
+ "Subject already logged with session " + storedSessionId + " (not " + nodeSessionId + ")");
}
+ request.setAttribute(CmsSession.class.getName(), cmsSession);
} else {
- CmsSessionImpl cmsSession = new CmsSessionImpl(subject, authorization, locale, "desktop");
+ CmsSessionImpl cmsSession = CmsContextImpl.getCmsContext().getCmsSessionByLocalId(SINGLE_USER_LOCAL_ID);
+ if (cmsSession == null) {
+ UUID cmsSessionUuid = CmsContextImpl.getCmsContext().getUuidFactory().timeUUID();
+ cmsSession = new CmsSessionImpl(cmsSessionUuid, subject, authorization, locale, SINGLE_USER_LOCAL_ID);
+ CmsContextImpl.getCmsContext().registerCmsSession(cmsSession);
+ }
CmsSessionId nodeSessionId = new CmsSessionId(cmsSession.getUuid());
subject.getPrivateCredentials().add(nodeSessionId);
}
}
- public static CmsSessionImpl cmsSessionFromHttpSession(BundleContext bc, String httpSessionId) {
- Authorization authorization = null;
- Collection<ServiceReference<CmsSession>> sr;
- try {
- sr = bc.getServiceReferences(CmsSession.class,
- "(" + CmsSession.SESSION_LOCAL_ID + "=" + httpSessionId + ")");
- } catch (InvalidSyntaxException e) {
- throw new IllegalArgumentException("Cannot get CMS session for id " + httpSessionId, e);
- }
- CmsSessionImpl cmsSession;
- if (sr.size() == 1) {
- cmsSession = (CmsSessionImpl) bc.getService(sr.iterator().next());
-// locale = cmsSession.getLocale();
- authorization = cmsSession.getAuthorization();
- if (authorization.getName() == null)
- return null;// anonymous is not sufficient
- } else if (sr.size() == 0)
- return null;
- else
- throw new IllegalStateException(sr.size() + ">1 web sessions detected for http session " + httpSessionId);
- return cmsSession;
- }
+// public static CmsSessionImpl cmsSessionFromHttpSession(BundleContext bc, String httpSessionId) {
+// Authorization authorization = null;
+// Collection<ServiceReference<CmsSession>> sr;
+// try {
+// sr = bc.getServiceReferences(CmsSession.class,
+// "(" + CmsSession.SESSION_LOCAL_ID + "=" + httpSessionId + ")");
+// } catch (InvalidSyntaxException e) {
+// throw new IllegalArgumentException("Cannot get CMS session for id " + httpSessionId, e);
+// }
+// CmsSessionImpl cmsSession;
+// if (sr.size() == 1) {
+// cmsSession = (CmsSessionImpl) bc.getService(sr.iterator().next());
+//// locale = cmsSession.getLocale();
+// authorization = cmsSession.getAuthorization();
+// if (authorization.getName() == null)
+// return null;// anonymous is not sufficient
+// } else if (sr.size() == 0)
+// return null;
+// else
+// throw new IllegalStateException(sr.size() + ">1 web sessions detected for http session " + httpSessionId);
+// return cmsSession;
+// }
public static <T extends Principal> T getSinglePrincipal(Subject subject, Class<T> clss) {
Set<T> principals = subject.getPrincipals(clss);
return principals.iterator().next();
}
+ private static void checkUserName(String name) throws IllegalArgumentException {
+ if (RESERVED_ROLES.contains(name))
+ throw new IllegalArgumentException(name + " is a reserved name");
+ }
+
private CmsAuthUtils() {
}
--- /dev/null
+package org.argeo.cms.auth;
+
+import javax.xml.namespace.QName;
+
+import org.argeo.api.acr.ArgeoNamespace;
+import org.argeo.api.acr.ContentName;
+import org.argeo.cms.SystemRole;
+
+/** Standard CMS system roles. */
+public enum CmsRole implements SystemRole {
+ userAdmin, //
+ groupAdmin, //
+ //
+ ;
+
+ private final static String QUALIFIER = "cms.";
+
+ private final ContentName name;
+
+ CmsRole() {
+ name = new ContentName(ArgeoNamespace.ROLE_NAMESPACE_URI, QUALIFIER + name());
+ }
+
+ @Override
+ public QName qName() {
+ return name;
+ }
+
+ @Override
+ public String toString() {
+ return name.toPrefixedString();
+ }
+}
--- /dev/null
+package org.argeo.cms.auth;
+
+import java.io.Console;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.Scanner;
+
+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;
+
+/** Callback handler to be used with a command line UI. */
+public class ConsoleCallbackHandler implements CallbackHandler {
+
+ @Override
+ public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
+ Console console = System.console();
+// if (console == null)
+// throw new IllegalStateException("No console available");
+
+ Scanner scanner = null;
+ PrintWriter writer;
+ if (console == null) {// IDE
+ scanner = new Scanner(System.in);
+ writer = new PrintWriter(System.out, true);
+ } else {
+ writer = console.writer();
+
+ }
+ for (int i = 0; i < callbacks.length; i++) {
+ if (callbacks[i] instanceof TextOutputCallback) {
+ TextOutputCallback callback = (TextOutputCallback) callbacks[i];
+ writer.printf(callback.getMessage());
+ } else if (callbacks[i] instanceof NameCallback) {
+ NameCallback callback = (NameCallback) callbacks[i];
+ writer.printf(callback.getPrompt());
+ if (callback.getDefaultName() != null)
+ writer.printf(" (" + callback.getDefaultName() + ")");
+ String answer = console != null ? console.readLine() : scanner.next();
+ if (callback.getDefaultName() != null && answer.trim().equals(""))
+ callback.setName(callback.getDefaultName());
+ else
+ callback.setName(answer);
+ } else if (callbacks[i] instanceof PasswordCallback) {
+ PasswordCallback callback = (PasswordCallback) callbacks[i];
+ writer.printf(callback.getPrompt());
+ char[] answer = console != null ? console.readPassword() : scanner.next().toCharArray();
+ callback.setPassword(answer);
+ Arrays.fill(answer, ' ');
+ }
+// else if (callbacks[i] instanceof LocaleChoice) {
+// LocaleChoice callback = (LocaleChoice) callbacks[i];
+// writer.write("Language");
+// writer.write("\n");
+// for (int j = 0; j < callback.getLocales().size(); j++) {
+// Locale locale = callback.getLocales().get(j);
+// writer.print(j + " : " + locale.getDisplayName() + "\n");
+// }
+// writer.write("(" + callback.getDefaultIndex() + ") : ");
+// String answer = console.readLine();
+// if (answer.trim().equals(""))
+// callback.setSelectedIndex(callback.getDefaultIndex());
+// else
+// callback.setSelectedIndex(new Integer(answer.trim()));
+// }
+ }
+ }
+
+}
+++ /dev/null
-package org.argeo.cms.auth;
-
-import java.security.AccessController;
-import java.security.Principal;
-import java.security.PrivilegedAction;
-import java.security.PrivilegedActionException;
-import java.security.PrivilegedExceptionAction;
-import java.util.HashSet;
-import java.util.Locale;
-import java.util.Set;
-import java.util.UUID;
-
-import javax.security.auth.Subject;
-import javax.security.auth.x500.X500Principal;
-
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.api.cms.CmsSession;
-import org.argeo.api.cms.CmsSessionId;
-import org.argeo.cms.internal.auth.CmsSessionImpl;
-import org.argeo.cms.internal.auth.ImpliedByPrincipal;
-import org.argeo.cms.internal.runtime.CmsContextImpl;
-import org.osgi.service.useradmin.Authorization;
-
-/**
- * Programmatic access to the currently authenticated user, within a CMS
- * context.
- */
-public final class CurrentUser {
- /*
- * CURRENT USER API
- */
-
- /**
- * Technical username of the currently authenticated user.
- *
- * @return the authenticated username or null if not authenticated / anonymous
- */
- public static String getUsername() {
- return getUsername(currentSubject());
- }
-
- /**
- * Human readable name of the currently authenticated user (typically first name
- * and last name).
- */
- public static String getDisplayName() {
- return getDisplayName(currentSubject());
- }
-
- /** Whether a user is currently authenticated. */
- public static boolean isAnonymous() {
- return isAnonymous(currentSubject());
- }
-
- /** Locale of the current user */
- public final static Locale locale() {
- return locale(currentSubject());
- }
-
- /** Roles of the currently logged-in user */
- public final static Set<String> roles() {
- return roles(currentSubject());
- }
-
- /** Returns true if the current user is in the specified role */
- public static boolean isInRole(String role) {
- Set<String> roles = roles();
- return roles.contains(role);
- }
-
- /** Executes as the current user */
- public final static <T> T doAs(PrivilegedAction<T> action) {
- return Subject.doAs(currentSubject(), action);
- }
-
- /** Executes as the current user */
- public final static <T> T tryAs(PrivilegedExceptionAction<T> action) throws PrivilegedActionException {
- return Subject.doAs(currentSubject(), action);
- }
-
- /*
- * WRAPPERS
- */
-
- public final static String getUsername(Subject subject) {
- if (subject == null)
- throw new IllegalArgumentException("Subject cannot be null");
- if (subject.getPrincipals(X500Principal.class).size() != 1)
- return CmsConstants.ROLE_ANONYMOUS;
- Principal principal = subject.getPrincipals(X500Principal.class).iterator().next();
- return principal.getName();
- }
-
- public final static String getDisplayName(Subject subject) {
- return getAuthorization(subject).toString();
- }
-
- public final static Set<String> roles(Subject subject) {
- Set<String> roles = new HashSet<String>();
- roles.add(getUsername(subject));
- for (Principal group : subject.getPrincipals(ImpliedByPrincipal.class)) {
- roles.add(group.getName());
- }
- return roles;
- }
-
- public final static Locale locale(Subject subject) {
- Set<Locale> locales = subject.getPublicCredentials(Locale.class);
- if (locales.isEmpty()) {
- Locale defaultLocale = CmsContextImpl.getCmsContext().getDefaultLocale();
- return defaultLocale;
- } else
- return locales.iterator().next();
- }
-
- /** Whether this user is currently authenticated. */
- public static boolean isAnonymous(Subject subject) {
- if (subject == null)
- return true;
- String username = getUsername(subject);
- return username == null || username.equalsIgnoreCase(CmsConstants.ROLE_ANONYMOUS);
- }
-
- public CmsSession getCmsSession() {
- Subject subject = currentSubject();
- CmsSessionId cmsSessionId = subject.getPrivateCredentials(CmsSessionId.class).iterator().next();
- return CmsSessionImpl.getByUuid(cmsSessionId.getUuid());
- }
-
- /*
- * HELPERS
- */
- private static Subject currentSubject() {
- Subject subject = getAccessControllerSubject();
- if (subject != null)
- return subject;
- throw new IllegalStateException("Cannot find related subject");
- }
-
- private static Subject getAccessControllerSubject() {
- return Subject.getSubject(AccessController.getContext());
- }
-
- private static Authorization getAuthorization(Subject subject) {
- return subject.getPrivateCredentials(Authorization.class).iterator().next();
- }
-
- public static boolean logoutCmsSession(Subject subject) {
- UUID nodeSessionId;
- if (subject.getPrivateCredentials(CmsSessionId.class).size() == 1)
- nodeSessionId = subject.getPrivateCredentials(CmsSessionId.class).iterator().next().getUuid();
- else
- return false;
- CmsSessionImpl cmsSession = CmsSessionImpl.getByUuid(nodeSessionId.toString());
-
- // FIXME logout all views
- // TODO check why it is sometimes null
- if (cmsSession != null)
- cmsSession.close();
- // if (log.isDebugEnabled())
- // log.debug("Logged out CMS session " + cmsSession.getUuid());
- return true;
- }
-
- private CurrentUser() {
- }
-}
subject.getPrincipals().removeAll(subject.getPrincipals(DataAdminPrincipal.class));
return true;
}
+
}
package org.argeo.cms.auth;
-import java.security.AccessController;
import java.util.Map;
import java.util.Set;
import javax.security.auth.login.LoginException;
import javax.security.auth.spi.LoginModule;
-import org.argeo.cms.security.PBEKeySpecCallback;
-import org.argeo.util.PasswordEncryption;
+import org.argeo.api.cms.keyring.PBEKeySpecCallback;
+import org.argeo.cms.util.CurrentSubject;
+import org.argeo.cms.util.PasswordEncryption;
/** Adds a secret key to the private credentials */
public class KeyringLoginModule implements LoginModule {
Map<String, ?> options) {
this.subject = subject;
if (subject == null) {
- subject = Subject.getSubject(AccessController.getContext());
+ this.subject = CurrentSubject.current();
}
this.callbackHandler = callbackHandler;
}
/** Transitional interface to decouple from the Servlet API. */
public interface RemoteAuthRequest {
+ final static String REMOTE_USER = "org.osgi.service.http.authentication.remote.user";
+ final static String AUTHORIZATION = "org.osgi.service.useradmin.authorization";
+
RemoteAuthSession getSession();
RemoteAuthSession createSession();
/** Transitional interface to decouple from the Servlet API. */
public interface RemoteAuthResponse {
- void setHeader(String keys, String value);
+ /** Set this header to a single value, possibly removing previous values. */
+ void setHeader(String headerName, String value);
+ /** Add a value to this header. */
+ void addHeader(String headerName, String value);
}
package org.argeo.cms.auth;
-import java.security.AccessControlContext;
-import java.security.AccessController;
import java.security.PrivilegedAction;
+import java.util.Base64;
import java.util.function.Supplier;
import javax.security.auth.Subject;
+import javax.security.auth.kerberos.KerberosTicket;
+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.api.cms.CmsSession;
-import org.argeo.cms.osgi.CmsOsgiUtils;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.FrameworkUtil;
+import org.argeo.cms.http.HttpHeader;
+import org.argeo.cms.http.HttpStatus;
+import org.argeo.cms.internal.runtime.CmsContextImpl;
+import org.argeo.cms.util.CurrentSubject;
+import org.ietf.jgss.GSSContext;
+import org.ietf.jgss.GSSException;
+import org.ietf.jgss.GSSManager;
+import org.ietf.jgss.GSSName;
+import org.ietf.jgss.Oid;
/** Remote authentication utilities. */
public class RemoteAuthUtils {
+ private final static CmsLog log = CmsLog.getLog(RemoteAuthUtils.class);
+
static final String REMOTE_USER = "org.osgi.service.http.authentication.remote.user";
- private static BundleContext bundleContext = FrameworkUtil.getBundle(RemoteAuthUtils.class).getBundleContext();
+ private final static Oid KERBEROS_OID;
+// private final static Oid KERB_V5_OID, KRB5_PRINCIPAL_NAME_OID;
+ static {
+ try {
+ KERBEROS_OID = new Oid("1.3.6.1.5.5.2");
+// KERB_V5_OID = new Oid("1.2.840.113554.1.2.2");
+// KRB5_PRINCIPAL_NAME_OID = new Oid("1.2.840.113554.1.2.2.1");
+ } catch (GSSException e) {
+ throw new IllegalStateException("Cannot create Kerberos OID", e);
+ }
+ }
/**
* Execute this supplier, using the CMS class loader as context classloader.
* Useful to log in to JCR.
*/
public final static <T> T doAs(Supplier<T> supplier, RemoteAuthRequest req) {
- ClassLoader currentContextCl = Thread.currentThread().getContextClassLoader();
- Thread.currentThread().setContextClassLoader(RemoteAuthUtils.class.getClassLoader());
- try {
- return Subject.doAs(
- Subject.getSubject((AccessControlContext) req.getAttribute(AccessControlContext.class.getName())),
- new PrivilegedAction<T>() {
+ CmsSession cmsSession = getCmsSession(req);
+ return CurrentSubject.callAs(cmsSession.getSubject(), () -> supplier.get());
+// ClassLoader currentContextCl = Thread.currentThread().getContextClassLoader();
+// Thread.currentThread().setContextClassLoader(RemoteAuthUtils.class.getClassLoader());
+// try {
+// return Subject.doAs(
+// Subject.getSubject((AccessControlContext) req.getAttribute(AccessControlContext.class.getName())),
+// new PrivilegedAction<T>() {
+//
+// @Override
+// public T run() {
+// return supplier.get();
+// }
+//
+// });
+// } finally {
+// Thread.currentThread().setContextClassLoader(currentContextCl);
+// }
+ }
+
+// public final static void configureRequestSecurity(RemoteAuthRequest req) {
+// if (req.getAttribute(AccessControlContext.class.getName()) != null)
+// throw new IllegalStateException("Request already authenticated.");
+// AccessControlContext acc = AccessController.getContext();
+// req.setAttribute(REMOTE_USER, CurrentUser.getUsername());
+// req.setAttribute(AccessControlContext.class.getName(), acc);
+// }
+//
+// public final static void clearRequestSecurity(RemoteAuthRequest req) {
+// if (req.getAttribute(AccessControlContext.class.getName()) == null)
+// throw new IllegalStateException("Cannot clear non-authenticated request.");
+// req.setAttribute(REMOTE_USER, null);
+// req.setAttribute(AccessControlContext.class.getName(), null);
+// }
+
+ public static CmsSession getCmsSession(RemoteAuthRequest req) {
+ CmsSession cmsSession = (CmsSession) req.getAttribute(CmsSession.class.getName());
+ if (cmsSession == null)
+ throw new IllegalStateException("Request must have a CMS session attribute");
+ return cmsSession;
+ }
+
+ public static String createGssToken(Subject subject, String service, String server) {
+ if (subject.getPrivateCredentials(KerberosTicket.class).isEmpty())
+ throw new IllegalArgumentException("Subject " + subject + " is not GSS authenticated.");
+ return Subject.doAs(subject, (PrivilegedAction<String>) () -> {
+ // !! different format than Kerberos
+ String serverPrinc = service + "@" + server;
+ GSSContext context = null;
+ String tokenStr = null;
+
+ try {
+ // Get service's principal name
+ GSSManager manager = GSSManager.getInstance();
+ // GSSName serverName = manager.createName(serverPrinc,
+ // GSSName.NT_HOSTBASED_SERVICE, KERBEROS_OID);
+ GSSName serverName = manager.createName(serverPrinc, GSSName.NT_HOSTBASED_SERVICE);
+
+ // Get the context for authentication
+ context = manager.createContext(serverName, KERBEROS_OID, null, GSSContext.DEFAULT_LIFETIME);
+ // context.requestMutualAuth(true); // Request mutual authentication
+ // context.requestConf(true); // Request confidentiality
+ context.requestCredDeleg(true);
- @Override
- public T run() {
- return supplier.get();
- }
+ byte[] token = new byte[0];
- });
+ // token is ignored on the first call
+ token = context.initSecContext(token, 0, token.length);
+
+ // Send a token to the server if one was generated by
+ // initSecContext
+ if (token != null) {
+ tokenStr = Base64.getEncoder().encodeToString(token);
+ // complete=true;
+ }
+ return tokenStr;
+
+ } catch (GSSException e) {
+ throw new IllegalStateException("Cannot authenticate to " + serverPrinc, e);
+ }
+ });
+ }
+
+ public static LoginContext anonymousLogin(RemoteAuthRequest remoteAuthRequest,
+ RemoteAuthResponse remoteAuthResponse) {
+ // anonymous
+ ClassLoader currentContextClassLoader = Thread.currentThread().getContextClassLoader();
+ try {
+ Thread.currentThread().setContextClassLoader(RemoteAuthUtils.class.getClassLoader());
+ LoginContext lc = CmsAuth.ANONYMOUS
+ .newLoginContext(new RemoteAuthCallbackHandler(remoteAuthRequest, remoteAuthResponse));
+ lc.login();
+ return lc;
+ } catch (LoginException e1) {
+ if (log.isDebugEnabled())
+ log.error("Cannot log in as anonymous", e1);
+ return null;
} finally {
- Thread.currentThread().setContextClassLoader(currentContextCl);
+ Thread.currentThread().setContextClassLoader(currentContextClassLoader);
}
}
- public final static void configureRequestSecurity(RemoteAuthRequest req) {
- if (req.getAttribute(AccessControlContext.class.getName()) != null)
- throw new IllegalStateException("Request already authenticated.");
- AccessControlContext acc = AccessController.getContext();
- req.setAttribute(REMOTE_USER, CurrentUser.getUsername());
- req.setAttribute(AccessControlContext.class.getName(), acc);
- }
+ public static int askForWwwAuth(RemoteAuthRequest remoteAuthRequest, RemoteAuthResponse remoteAuthResponse,
+ String realm, boolean forceBasic) {
+ boolean negotiateFailed = false;
+ if (remoteAuthRequest.getHeader(HttpHeader.AUTHORIZATION.getHeaderName()) != null) {
+ // we already tried, so we give up in order not too loop endlessly
+ if (remoteAuthRequest.getHeader(HttpHeader.AUTHORIZATION.getHeaderName())
+ .startsWith(HttpHeader.NEGOTIATE)) {
+ negotiateFailed = true;
+ } else {
+ return HttpStatus.FORBIDDEN.getCode();
+ }
+ }
- public final static void clearRequestSecurity(RemoteAuthRequest req) {
- if (req.getAttribute(AccessControlContext.class.getName()) == null)
- throw new IllegalStateException("Cannot clear non-authenticated request.");
- req.setAttribute(REMOTE_USER, null);
- req.setAttribute(AccessControlContext.class.getName(), null);
+ // response.setHeader(HttpUtils.HEADER_WWW_AUTHENTICATE, "basic
+ // realm=\"" + httpAuthRealm + "\"");
+ if (hasAcceptorCredentials() && !forceBasic && !negotiateFailed) {// SPNEGO
+ remoteAuthResponse.addHeader(HttpHeader.WWW_AUTHENTICATE.getHeaderName(), HttpHeader.NEGOTIATE);
+ // TODO make it configurable ?
+ remoteAuthResponse.addHeader(HttpHeader.WWW_AUTHENTICATE.getHeaderName(),
+ HttpHeader.BASIC + " " + HttpHeader.REALM + "=\"" + realm + "\"");
+ } else {
+ remoteAuthResponse.setHeader(HttpHeader.WWW_AUTHENTICATE.getHeaderName(),
+ HttpHeader.BASIC + " " + HttpHeader.REALM + "=\"" + realm + "\"");
+ }
+
+ // 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");
+
+ return HttpStatus.UNAUTHORIZED.getCode();
}
- public static CmsSession getCmsSession(RemoteAuthRequest req) {
- Subject subject = Subject
- .getSubject((AccessControlContext) req.getAttribute(AccessControlContext.class.getName()));
- CmsSession cmsSession = CmsOsgiUtils.getCmsSession(bundleContext, subject);
- return cmsSession;
+ private static boolean hasAcceptorCredentials() {
+ return CmsContextImpl.getCmsContext().getAcceptorCredentials() != null;
}
+
}
import javax.security.auth.login.LoginException;
import javax.security.auth.spi.LoginModule;
-import org.argeo.api.cms.CmsConstants;
import org.argeo.api.cms.CmsLog;
+import org.argeo.cms.CmsDeployProperty;
+import org.argeo.cms.http.HttpHeader;
import org.argeo.cms.internal.auth.CmsSessionImpl;
-import org.argeo.cms.internal.runtime.KernelUtils;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.FrameworkUtil;
-import org.osgi.service.http.HttpContext;
+import org.argeo.cms.internal.runtime.CmsContextImpl;
+import org.argeo.cms.internal.runtime.CmsStateImpl;
import org.osgi.service.useradmin.Authorization;
-/** Use the HTTP session as the basis for authentication. */
+/** Use a remote session as the basis for authentication. */
public class RemoteSessionLoginModule implements LoginModule {
private final static CmsLog log = CmsLog.getLog(RemoteSessionLoginModule.class);
private RemoteAuthRequest request = null;
private RemoteAuthResponse response = null;
- private BundleContext bc;
-
private Authorization authorization;
private Locale locale;
@Override
public void initialize(Subject subject, CallbackHandler callbackHandler, Map<String, ?> sharedState,
Map<String, ?> options) {
- bc = FrameworkUtil.getBundle(RemoteSessionLoginModule.class).getBundleContext();
- assert bc != null;
this.subject = subject;
this.callbackHandler = callbackHandler;
this.sharedState = (Map<String, Object>) sharedState;
public boolean login() throws LoginException {
if (callbackHandler == null)
return false;
- RemoteAuthCallback httpCallback = new RemoteAuthCallback();
+ RemoteAuthCallback remoteAuthCallback = new RemoteAuthCallback();
try {
- callbackHandler.handle(new Callback[] { httpCallback });
+ callbackHandler.handle(new Callback[] { remoteAuthCallback });
} catch (IOException e) {
throw new LoginException("Cannot handle http callback: " + e.getMessage());
} catch (UnsupportedCallbackException e) {
return false;
}
- request = httpCallback.getRequest();
+ request = remoteAuthCallback.getRequest();
if (request == null) {
- RemoteAuthSession httpSession = httpCallback.getHttpSession();
+ RemoteAuthSession httpSession = remoteAuthCallback.getHttpSession();
if (httpSession == null)
return false;
// TODO factorize with below
String httpSessionId = httpSession.getId();
-// if (log.isTraceEnabled())
-// log.trace("HTTP login: " + request.getPathInfo() + " #" + httpSessionId);
- CmsSessionImpl cmsSession = CmsAuthUtils.cmsSessionFromHttpSession(bc, httpSessionId);
- if (cmsSession != null) {
+ CmsSessionImpl cmsSession = CmsContextImpl.getCmsContext().getCmsSessionByLocalId(httpSessionId);
+ if (cmsSession != null && !cmsSession.isAnonymous()) {
authorization = cmsSession.getAuthorization();
locale = cmsSession.getLocale();
if (log.isTraceEnabled())
log.trace("Retrieved authorization from " + cmsSession);
}
} else {
- authorization = (Authorization) request.getAttribute(HttpContext.AUTHORIZATION);
+ authorization = (Authorization) request.getAttribute(RemoteAuthRequest.AUTHORIZATION);
if (authorization == null) {// search by session ID
RemoteAuthSession httpSession = request.getSession();
- if (httpSession == null) {
- // TODO make sure this is always safe
- if (log.isTraceEnabled())
- log.trace("Create http session");
- httpSession = request.createSession();
- }
- String httpSessionId = httpSession.getId();
-// if (log.isTraceEnabled())
-// log.trace("HTTP login: " + request.getPathInfo() + " #" + httpSessionId);
- CmsSessionImpl cmsSession = CmsAuthUtils.cmsSessionFromHttpSession(bc, httpSessionId);
- if (cmsSession != null) {
- authorization = cmsSession.getAuthorization();
- locale = cmsSession.getLocale();
- if (log.isTraceEnabled())
- log.trace("Retrieved authorization from " + cmsSession);
+ if (httpSession != null) {
+ String httpSessionId = httpSession.getId();
+ CmsSessionImpl cmsSession = CmsContextImpl.getCmsContext().getCmsSessionByLocalId(httpSessionId);
+ if (cmsSession != null && !cmsSession.isAnonymous()) {
+ authorization = cmsSession.getAuthorization();
+ locale = cmsSession.getLocale();
+ if (log.isTraceEnabled())
+ log.trace("Retrieved authorization from " + cmsSession);
+ }
+ }else {
+ request.createSession();
}
}
sharedState.put(CmsAuthUtils.SHARED_STATE_HTTP_REQUEST, request);
} else {
if (log.isTraceEnabled())
log.trace("HTTP login: " + true);
- request.setAttribute(HttpContext.AUTHORIZATION, authorization);
+ request.setAttribute(RemoteAuthRequest.AUTHORIZATION, authorization);
return true;
}
}
public boolean commit() throws LoginException {
byte[] outToken = (byte[]) sharedState.get(CmsAuthUtils.SHARED_STATE_SPNEGO_OUT_TOKEN);
if (outToken != null) {
- response.setHeader(CmsAuthUtils.HEADER_WWW_AUTHENTICATE,
+ response.setHeader(HttpHeader.WWW_AUTHENTICATE.getHeaderName(),
"Negotiate " + java.util.Base64.getEncoder().encodeToString(outToken));
}
}
private void extractHttpAuth(final RemoteAuthRequest httpRequest) {
- String authHeader = httpRequest.getHeader(CmsAuthUtils.HEADER_AUTHORIZATION);
+ String authHeader = httpRequest.getHeader(HttpHeader.AUTHORIZATION.getHeaderName());
extractHttpAuth(authHeader);
}
StringTokenizer st = new StringTokenizer(authHeader);
if (st.hasMoreTokens()) {
String basic = st.nextToken();
- if (basic.equalsIgnoreCase("Basic")) {
+ if (basic.equalsIgnoreCase(HttpHeader.BASIC)) {
try {
// TODO manipulate char[]
Base64.Decoder decoder = Base64.getDecoder();
} catch (Exception e) {
throw new IllegalStateException("Couldn't retrieve authentication", e);
}
- } else if (basic.equalsIgnoreCase("Negotiate")) {
+ } else if (basic.equalsIgnoreCase(HttpHeader.NEGOTIATE)) {
String spnegoToken = st.nextToken();
Base64.Decoder decoder = Base64.getDecoder();
byte[] authToken = decoder.decode(spnegoToken);
}
}
}
-
- // auth token
- // String mail = request.getParameter(LdapAttrs.mail.name());
- // String authPassword = request.getParameter(LdapAttrs.authPassword.name());
- // if (authPassword != null) {
- // sharedState.put(CmsAuthUtils.SHARED_STATE_PWD, authPassword);
- // if (mail != null)
- // sharedState.put(CmsAuthUtils.SHARED_STATE_NAME, mail);
- // }
}
private void extractClientCertificate(RemoteAuthRequest req) {
if (log.isDebugEnabled())
log.debug("Client certificate " + certDn + " verified by servlet container");
} // Reverse proxy verified the client certificate
- String clientDnHttpHeader = KernelUtils.getFrameworkProp(CmsConstants.HTTP_PROXY_SSL_DN);
+ String clientDnHttpHeader = CmsStateImpl.getDeployProperty(CmsContextImpl.getCmsContext().getCmsState(),
+ CmsDeployProperty.HTTP_PROXY_SSL_HEADER_DN);
if (clientDnHttpHeader != null) {
String certDn = req.getHeader(clientDnHttpHeader);
// TODO retrieve more cf. https://httpd.apache.org/docs/current/mod/mod_ssl.html
package org.argeo.cms.auth;
-import java.net.InetAddress;
-import java.net.UnknownHostException;
import java.util.Locale;
import java.util.Map;
import javax.security.auth.spi.LoginModule;
import javax.security.auth.x500.X500Principal;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.osgi.useradmin.IpaUtils;
-import org.argeo.osgi.useradmin.OsUserUtils;
-import org.argeo.util.naming.LdapAttrs;
+import org.argeo.api.acr.ldap.LdapAttr;
+import org.argeo.cms.directory.ldap.IpaUtils;
+import org.argeo.cms.internal.runtime.CmsContextImpl;
+import org.argeo.cms.osgi.useradmin.OsUserUtils;
import org.osgi.service.useradmin.Authorization;
/** Login module for when the system is owned by a single user. */
public class SingleUserLoginModule implements LoginModule {
- private final static CmsLog log = CmsLog.getLog(SingleUserLoginModule.class);
+// private final static CmsLog log = CmsLog.getLog(SingleUserLoginModule.class);
private Subject subject;
private Map<String, Object> sharedState = null;
Object username = sharedState.get(CmsAuthUtils.SHARED_STATE_NAME);
if (username == null)
throw new LoginException("No username available");
- String hostname;
- try {
- hostname = InetAddress.getLocalHost().getHostName();
- } catch (UnknownHostException e) {
- log.warn("Using localhost as hostname", e);
- hostname = "localhost";
- }
+ String hostname = CmsContextImpl.getCmsContext().getCmsState().getHostname();
String baseDn = ("." + hostname).replaceAll("\\.", ",dc=");
- X500Principal principal = new X500Principal(LdapAttrs.uid + "=" + username + baseDn);
+ X500Principal principal = new X500Principal(LdapAttr.uid + "=" + username + baseDn);
authorizationName = principal.getName();
}
locale = Locale.getDefault();
Authorization authorization = new SingleUserAuthorization(authorizationName);
CmsAuthUtils.addAuthorization(subject, authorization);
-
- // Add standard Java OS login
+
+ // Add standard Java OS login
OsUserUtils.loginAsSystemUser(subject);
// additional principals (must be after Authorization registration)
package org.argeo.cms.auth;
-import java.lang.reflect.Method;
import java.util.Map;
import javax.security.auth.Subject;
import org.argeo.api.cms.CmsLog;
import org.argeo.cms.internal.runtime.CmsContextImpl;
import org.ietf.jgss.GSSContext;
-import org.ietf.jgss.GSSCredential;
import org.ietf.jgss.GSSException;
import org.ietf.jgss.GSSManager;
import org.ietf.jgss.GSSName;
+import com.sun.security.jgss.GSSUtil;
+
/** SPNEGO login */
public class SpnegoLoginModule implements LoginModule {
private final static CmsLog log = CmsLog.getLog(SpnegoLoginModule.class);
@Override
public boolean login() throws LoginException {
byte[] spnegoToken = (byte[]) sharedState.get(CmsAuthUtils.SHARED_STATE_SPNEGO_TOKEN);
- if (spnegoToken == null)
+ if (spnegoToken == null) {
+ if (!sharedState.containsKey(CmsAuthUtils.SHARED_STATE_NAME)) {
+ // workaround: set shared state name to empty
+ // in order to avoid Krb5LoginModule printing to System.out
+ // TODO ask upstream to only log in debug mode
+ sharedState.put(CmsAuthUtils.SHARED_STATE_NAME, "");
+ }
return false;
+ }
gssContext = checkToken(spnegoToken);
if (gssContext == null)
return false;
- else
+ else {
+ if (!sharedState.containsKey(CmsAuthUtils.SHARED_STATE_NAME)) {
+ try {
+ if (gssContext.getCredDelegState()) {
+ // commit will succeeed only if we have credential delegation
+ GSSName name = gssContext.getSrcName();
+ String username = name.toString();
+ sharedState.put(CmsAuthUtils.SHARED_STATE_NAME, username);
+ }
+ } catch (GSSException e) {
+ throw new IllegalStateException("Cannot retrieve SPNEGO name", e);
+ }
+ }
return true;
- // try {
- // String clientName = gssContext.getSrcName().toString();
- // String role = clientName.substring(clientName.indexOf('@') + 1);
- //
- // log.debug("SpnegoUserRealm: established a security context");
- // log.debug("Client Principal is: " + gssContext.getSrcName());
- // log.debug("Server Principal is: " + gssContext.getTargName());
- // log.debug("Client Default Role: " + role);
- // } catch (GSSException e) {
- // // TODO Auto-generated catch block
- // e.printStackTrace();
- // }
+ }
}
@Override
return false;
try {
- Class<?> gssUtilsClass = Class.forName("com.sun.security.jgss.GSSUtil");
- Method createSubjectMethod = gssUtilsClass.getMethod("createSubject", GSSName.class, GSSCredential.class);
Subject gssSubject;
if (gssContext.getCredDelegState())
- gssSubject = (Subject) createSubjectMethod.invoke(null, gssContext.getSrcName(),
- gssContext.getDelegCred());
+ gssSubject = (Subject) GSSUtil.createSubject(gssContext.getSrcName(), gssContext.getDelegCred());
else
- gssSubject = (Subject) createSubjectMethod.invoke(null, gssContext.getSrcName(), null);
+ gssSubject = (Subject) GSSUtil.createSubject(gssContext.getSrcName(), null);
+ // without credential delegation we won't have access to the Kerberos key
subject.getPrincipals().addAll(gssSubject.getPrincipals());
subject.getPrivateCredentials().addAll(gssSubject.getPrivateCredentials());
return true;
private GSSContext checkToken(byte[] authToken) {
GSSManager manager = GSSManager.getInstance();
try {
- GSSContext gContext = manager.createContext(CmsContextImpl.getAcceptorCredentials());
-
+ GSSContext gContext = manager.createContext(CmsContextImpl.getCmsContext().getAcceptorCredentials());
if (gContext == null) {
log.debug("SpnegoUserRealm: failed to establish GSSContext");
} else {
}
- @Deprecated
- public static boolean hasAcceptorCredentials() {
- return CmsContextImpl.getAcceptorCredentials() != null;
- }
}
package org.argeo.cms.auth;
-import static org.argeo.util.naming.LdapAttrs.cn;
+import static org.argeo.api.acr.ldap.LdapAttr.cn;
import java.io.IOException;
+import java.security.Principal;
import java.security.PrivilegedAction;
import java.util.Arrays;
import java.util.HashSet;
import javax.security.auth.login.LoginException;
import javax.security.auth.spi.LoginModule;
+import org.argeo.api.acr.ldap.LdapAttr;
import org.argeo.api.cms.CmsConstants;
import org.argeo.api.cms.CmsLog;
-import org.argeo.cms.internal.osgi.NodeUserAdmin;
+import org.argeo.cms.directory.ldap.IpaUtils;
import org.argeo.cms.internal.runtime.CmsContextImpl;
-import org.argeo.cms.security.CryptoKeyring;
-import org.argeo.osgi.useradmin.AuthenticatingUser;
-import org.argeo.osgi.useradmin.IpaUtils;
-import org.argeo.osgi.useradmin.TokenUtils;
-import org.argeo.util.naming.LdapAttrs;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.FrameworkUtil;
-import org.osgi.framework.ServiceReference;
+import org.argeo.cms.osgi.useradmin.AuthenticatingUser;
+import org.argeo.cms.osgi.useradmin.TokenUtils;
import org.osgi.service.useradmin.Authorization;
import org.osgi.service.useradmin.Group;
import org.osgi.service.useradmin.User;
private CallbackHandler callbackHandler;
private Map<String, Object> sharedState = null;
- private List<String> indexedUserProperties = Arrays
- .asList(new String[] { LdapAttrs.mail.name(), LdapAttrs.uid.name(), LdapAttrs.authPassword.name() });
+ private List<String> indexedUserProperties = Arrays.asList(new String[] { LdapAttr.mail.name(), LdapAttr.uid.name(),
+ LdapAttr.employeeNumber.name(), LdapAttr.authPassword.name() });
// private state
- private BundleContext bc;
+// private BundleContext bc;
private User authenticatedUser = null;
private Locale locale;
Map<String, ?> options) {
this.subject = subject;
try {
- bc = FrameworkUtil.getBundle(UserAdminLoginModule.class).getBundleContext();
+// bc = FrameworkUtil.getBundle(UserAdminLoginModule.class).getBundleContext();
this.callbackHandler = callbackHandler;
this.sharedState = (Map<String, Object>) sharedState;
} catch (Exception e) {
@Override
public boolean login() throws LoginException {
- UserAdmin userAdmin = CmsContextImpl.getUserAdmin();
+ UserAdmin userAdmin = CmsContextImpl.getCmsContext().getUserAdmin();
final String username;
final char[] password;
Object certificateChain = null;
username = (String) sharedState.get(CmsAuthUtils.SHARED_STATE_NAME);
password = (char[]) sharedState.get(CmsAuthUtils.SHARED_STATE_PWD);
// // TODO locale?
+ } else if (sharedState.containsKey(CmsAuthUtils.SHARED_STATE_NAME)
+ && sharedState.containsKey(CmsAuthUtils.SHARED_STATE_SPNEGO_TOKEN)) {
+ // SPNEGO login has succeeded, that's enough for us at this stage
+ return true;
} else if (sharedState.containsKey(CmsAuthUtils.SHARED_STATE_NAME)
&& sharedState.containsKey(CmsAuthUtils.SHARED_STATE_CERTIFICATE_CHAIN)) {
String certDn = (String) sharedState.get(CmsAuthUtils.SHARED_STATE_NAME);
-// LdapName ldapName;
-// try {
-// ldapName = new LdapName(certificateName);
-// } catch (InvalidNameException e) {
-// e.printStackTrace();
-// return false;
-// }
-// username = ldapName.getRdn(ldapName.size() - 1).getValue().toString();
username = certDn;
certificateChain = sharedState.get(CmsAuthUtils.SHARED_STATE_CERTIFICATE_CHAIN);
password = null;
username = (String) sharedState.get(CmsAuthUtils.SHARED_STATE_NAME);
password = null;
preauth = true;
-// } else if (singleUser) {
-// username = OsUserUtils.getOsUsername();
-// password = null;
-// // TODO retrieve from http session
-// locale = Locale.getDefault();
} else {
// ask for username and password
return true;// expect Kerberos
if (password != null) {
+ // TODO disabling bind for the time being,
+ // as it requires authorisations to be set at LDAP level
+ boolean tryBind = false;
// try bind first
- try {
- AuthenticatingUser authenticatingUser = new AuthenticatingUser(user.getName(), password);
- bindAuthorization = userAdmin.getAuthorization(authenticatingUser);
- // TODO check tokens as well
- if (bindAuthorization != null) {
- authenticatedUser = user;
- return true;
+ if (tryBind)
+ try {
+ AuthenticatingUser authenticatingUser = new AuthenticatingUser(user.getName(), password);
+ bindAuthorization = userAdmin.getAuthorization(authenticatingUser);
+ // TODO check tokens as well
+ if (bindAuthorization != null) {
+ authenticatedUser = user;
+ return true;
+ }
+ } catch (Exception e) {
+ // silent
+ if (log.isTraceEnabled())
+ log.trace("Bind failed", e);
}
- } catch (Exception e) {
- // silent
- if (log.isTraceEnabled())
- log.trace("Bind failed", e);
- }
// works only if a connection password is provided
if (!user.hasCredential(null, password)) {
// if (singleUser) {
// OsUserUtils.loginAsSystemUser(subject);
// }
- UserAdmin userAdmin = CmsContextImpl.getUserAdmin();
+ UserAdmin userAdmin = CmsContextImpl.getCmsContext().getUserAdmin();
Authorization authorization;
if (callbackHandler == null) {// anonymous
authorization = userAdmin.getAuthorization(null);
throw new LoginException("Kerberos login " + authenticatingUser.getName()
+ " is inconsistent with user admin login " + authenticatedUser.getName());
}
+ if (log.isTraceEnabled())
+ log.trace("Retrieve authorization for " + authenticatingUser + "... ");
authorization = Subject.doAs(subject, new PrivilegedAction<Authorization>() {
@Override
CmsAuthUtils.addAuthorization(subject, authorization);
// Unlock keyring (underlying login to the JCR repository)
- char[] password = (char[]) sharedState.get(CmsAuthUtils.SHARED_STATE_PWD);
- if (password != null) {
- ServiceReference<CryptoKeyring> keyringSr = bc.getServiceReference(CryptoKeyring.class);
- if (keyringSr != null) {
- CryptoKeyring keyring = bc.getService(keyringSr);
- Subject.doAs(subject, new PrivilegedAction<Void>() {
-
- @Override
- public Void run() {
- try {
- keyring.unlock(password);
- } catch (Exception e) {
- e.printStackTrace();
- log.warn("Could not unlock keyring with the password provided by " + authorization.getName()
- + ": " + e.getMessage());
- }
- return null;
- }
-
- });
- }
- }
+// char[] password = (char[]) sharedState.get(CmsAuthUtils.SHARED_STATE_PWD);
+// if (password != null) {
+// ServiceReference<CryptoKeyring> keyringSr = bc.getServiceReference(CryptoKeyring.class);
+// if (keyringSr != null) {
+// CryptoKeyring keyring = bc.getService(keyringSr);
+// Subject.doAs(subject, new PrivilegedAction<Void>() {
+//
+// @Override
+// public Void run() {
+// try {
+// keyring.unlock(password);
+// } catch (Exception e) {
+// e.printStackTrace();
+// log.warn("Could not unlock keyring with the password provided by " + authorization.getName()
+// + ": " + e.getMessage());
+// }
+// return null;
+// }
+//
+// });
+// }
+// }
// Register CmsSession with initial subject
CmsAuthUtils.registerSessionAuthorization(request, subject, authorization, locale);
- if (log.isDebugEnabled())
- log.debug("Logged in to CMS: " + subject);
+ if (log.isDebugEnabled()) {
+ StringBuilder msg = new StringBuilder();
+ msg.append("Logged in to CMS: " + authorization.getName() + "(" + authorization + ")\n");
+ for (Principal principal : subject.getPrincipals()) {
+ msg.append(" Principal: " + principal.getName()).append(" (")
+ .append(principal.getClass().getSimpleName()).append(")\n");
+ }
+ for (Object credential : subject.getPublicCredentials()) {
+ msg.append(" Public Credential: " + credential).append(" (")
+ .append(credential.getClass().getSimpleName()).append(")\n");
+ }
+ log.debug(msg);
+ }
+// if (log.isTraceEnabled())
+// log.trace(" Subject: " + subject);
return true;
}
import javax.naming.ldap.LdapName;
import javax.naming.ldap.Rdn;
+import org.argeo.api.acr.ldap.LdapAttr;
import org.argeo.api.cms.CmsConstants;
-import org.argeo.util.naming.LdapAttrs;
+import org.argeo.cms.CurrentUser;
import org.osgi.service.useradmin.Role;
import org.osgi.service.useradmin.User;
import org.osgi.service.useradmin.UserAdmin;
// CURRENTUSER HELPERS
/** Checks if current user is the same as the passed one */
public static boolean isCurrentUser(User user) {
- String userUsername = getProperty(user, LdapAttrs.DN);
+ String userUsername = getProperty(user, LdapAttr.DN);
LdapName userLdapName = getLdapName(userUsername);
LdapName selfUserName = getCurrentUserLdapName();
return userLdapName.equals(selfUserName);
/** Retrieves the current logged-in user common name */
public final static String getCommonName(User user) {
- return getProperty(user, LdapAttrs.cn.name());
+ return getProperty(user, LdapAttr.cn.name());
}
// OTHER USERS HELPERS
public static String getUserLocalId(String dn) {
LdapName ldapName = getLdapName(dn);
Rdn last = ldapName.getRdn(ldapName.size() - 1);
- if (last.getType().toLowerCase().equals(LdapAttrs.uid.name())
- || last.getType().toLowerCase().equals(LdapAttrs.cn.name()))
+ if (last.getType().toLowerCase().equals(LdapAttr.uid.name())
+ || last.getType().toLowerCase().equals(LdapAttr.cn.name()))
return (String) last.getValue();
else
throw new IllegalArgumentException("Cannot retrieve user local id, non valid dn: " + dn);
*/
public static String getUserDisplayName(UserAdmin userAdmin, String dn) {
Role user = userAdmin.getRole(dn);
- String dName;
if (user == null)
- dName = getUserLocalId(dn);
- else {
- dName = getProperty(user, LdapAttrs.displayName.name());
- if (isEmpty(dName))
- dName = getProperty(user, LdapAttrs.cn.name());
- if (isEmpty(dName))
- dName = getUserLocalId(dn);
- }
+ return getUserLocalId(dn);
+ return getUserDisplayName(user);
+ }
+
+ public static String getUserDisplayName(Role user) {
+ String dName = getProperty(user, LdapAttr.displayName.name());
+ if (isEmpty(dName))
+ dName = getProperty(user, LdapAttr.cn.name());
+ if (isEmpty(dName))
+ dName = getProperty(user, LdapAttr.uid.name());
+ if (isEmpty(dName))
+ dName = getUserLocalId(user.getName());
return dName;
}
if (user == null)
return null;
else
- return getProperty(user, LdapAttrs.mail.name());
+ return getProperty(user, LdapAttr.mail.name());
}
// LDAP NAMES HELPERS
}
/**
- * Simply retrieves a LDAP name from a {@link LdapAttrs.DN} with no exception
+ * Simply retrieves a LDAP name from a {@link LdapAttr.DN} with no exception
*/
private static LdapName getLdapName(String dn) {
try {
/** Simply retrieves a display name of the relevant domain */
public final static String getDomainName(User user) {
String dn = user.getName();
- if (dn.endsWith(CmsConstants.ROLES_BASEDN))
+ if (dn.endsWith(CmsConstants.SYSTEM_ROLES_BASEDN))
return "System roles";
if (dn.endsWith(CmsConstants.TOKENS_BASEDN))
return "Tokens";
int i = 0;
loop: while (i < rdns.size()) {
Rdn currrRdn = rdns.get(i);
- if (LdapAttrs.uid.name().equals(currrRdn.getType()) || LdapAttrs.cn.name().equals(currrRdn.getType())
- || LdapAttrs.ou.name().equals(currrRdn.getType()))
+ if (LdapAttr.uid.name().equals(currrRdn.getType()) || LdapAttr.cn.name().equals(currrRdn.getType())
+ || LdapAttr.ou.name().equals(currrRdn.getType()))
break loop;
else {
String currVal = (String) currrRdn.getValue();
--- /dev/null
+package org.argeo.cms.client;
+
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.net.http.HttpResponse.BodyHandler;
+import java.net.http.HttpResponse.BodyHandlers;
+import java.net.http.WebSocket;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.security.KeyManagementException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.Collection;
+import java.util.concurrent.CompletableFuture;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+
+import org.argeo.cms.auth.ConsoleCallbackHandler;
+import org.argeo.cms.auth.RemoteAuthUtils;
+import org.argeo.cms.http.HttpHeader;
+
+/** Utility to connect to a remote CMS node. */
+public class CmsClient {
+ public final static String CLIENT_LOGIN_CONTEXT = "CLIENT";
+
+ private URI uri;
+
+ private HttpClient httpClient;
+ private String gssToken;
+
+ public CmsClient(URI uri) {
+ this.uri = uri;
+ }
+
+ public void login() {
+ String server = uri.getHost();
+
+ URL jaasUrl = CmsClient.class.getResource("jaas-client-ipa.cfg");
+ System.setProperty("java.security.auth.login.config", jaasUrl.toExternalForm());
+ try {
+ LoginContext lc = new LoginContext(CLIENT_LOGIN_CONTEXT, new ConsoleCallbackHandler());
+ lc.login();
+ gssToken = RemoteAuthUtils.createGssToken(lc.getSubject(), "HTTP", server);
+ } catch (LoginException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } finally {
+
+ }
+ }
+
+ public String getAsString() {
+ return getAsString(uri);
+ }
+
+ public String getAsString(URI uri) {
+ uri = normalizeUri(uri);
+ try {
+ HttpClient httpClient = getHttpClient();
+
+ HttpRequest request = HttpRequest.newBuilder().uri(uri) //
+ .header(HttpHeader.AUTHORIZATION.getHeaderName(), HttpHeader.NEGOTIATE + " " + getGssToken()) //
+ .build();
+ BodyHandler<String> bodyHandler = BodyHandlers.ofString();
+ HttpResponse<String> response = httpClient.send(request, bodyHandler);
+ return response.body();
+// int responseCode = response.statusCode();
+// System.exit(responseCode);
+ } catch (IOException | InterruptedException e) {
+ throw new RuntimeException("Cannot read " + uri + " as a string", e);
+ }
+ }
+
+ protected URI normalizeUri(URI uri) {
+ if (uri.getHost() != null)
+ return uri;
+ try {
+ String path = uri.getPath();
+ if (path.startsWith("/")) {// absolute
+ return new URI(this.uri.getScheme(), this.uri.getUserInfo(), this.uri.getHost(), this.uri.getPort(),
+ path, uri.getQuery(), uri.getFragment());
+ } else {
+ String thisUriStr = this.uri.toString();
+ if (!thisUriStr.endsWith("/"))
+ thisUriStr = thisUriStr + "/";
+ return URI.create(thisUriStr + path);
+ }
+ } catch (URISyntaxException e) {
+ throw new IllegalArgumentException("Cannot interpret " + uri, e);
+ }
+ }
+
+ public URI getUri() {
+ return uri;
+ }
+
+ String getGssToken() {
+ return gssToken;
+ }
+
+ public HttpClient getHttpClient() {
+ if (httpClient == null) {
+ login();
+ HttpClient client = HttpClient.newBuilder() //
+ .sslContext(ipaSslContext()) //
+ .version(HttpClient.Version.HTTP_1_1) //
+ .build();
+ httpClient = client;
+ }
+ return httpClient;
+ }
+
+ public CompletableFuture<WebSocket> newWebSocket(WebSocket.Listener listener) {
+ return newWebSocket(uri, listener);
+ }
+
+ public CompletableFuture<WebSocket> newWebSocket(URI uri, WebSocket.Listener listener) {
+ CompletableFuture<WebSocket> ws = getHttpClient().newWebSocketBuilder()
+ .header(HttpHeader.AUTHORIZATION.getHeaderName(), HttpHeader.NEGOTIATE + " " + getGssToken())
+ .buildAsync(uri, listener);
+ return ws;
+ }
+
+ @SuppressWarnings("unchecked")
+ protected SSLContext ipaSslContext() {
+ try {
+ final Collection<X509Certificate> certificates;
+ Path caCertificatePath = Paths.get("/etc/ipa/ca.crt");
+ if (Files.exists(caCertificatePath)) {
+ CertificateFactory certificateFactory = CertificateFactory.getInstance("X509");
+ try (BufferedInputStream in = new BufferedInputStream(Files.newInputStream(caCertificatePath))) {
+ certificates = (Collection<X509Certificate>) certificateFactory.generateCertificates(in);
+ }
+ } else {
+ certificates = null;
+ }
+ TrustManager[] noopTrustManager = new TrustManager[] { new X509TrustManager() {
+ public void checkClientTrusted(X509Certificate[] xcs, String string) {
+ }
+
+ public void checkServerTrusted(X509Certificate[] xcs, String string) {
+ }
+
+ public X509Certificate[] getAcceptedIssuers() {
+ if (certificates == null)
+ return null;
+ return certificates.toArray(new X509Certificate[certificates.size()]);
+ }
+ } };
+
+ SSLContext sc = SSLContext.getInstance("ssl");
+ sc.init(null, noopTrustManager, null);
+ return sc;
+ } catch (KeyManagementException | NoSuchAlgorithmException | CertificateException | IOException e) {
+ throw new IllegalStateException("Cannot create SSL context ", e);
+ }
+ }
+
+}
--- /dev/null
+package org.argeo.cms.client;
+
+import java.net.URI;
+import java.net.http.WebSocket;
+import java.nio.ByteBuffer;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.ExecutionException;
+
+/** Tests connectivity to the web socket server. */
+public class WebSocketEventClient implements Runnable {
+
+ private final URI uri;
+
+ private WebSocket webSocket;
+
+ private CmsClient cmsClient;
+
+ public WebSocketEventClient(URI uri) {
+ this.uri = uri;
+ cmsClient = new CmsClient(uri);
+ }
+
+ @Override
+ public void run() {
+ try {
+ CompletableFuture<WebSocket> ws = cmsClient.newWebSocket(new WsEventListener());
+
+ WebSocket webSocket = ws.get();
+ webSocket.request(Long.MAX_VALUE);
+
+ Runtime.getRuntime().addShutdownHook(new Thread(() -> webSocket.sendClose(WebSocket.NORMAL_CLOSURE, "")));
+
+ while (!webSocket.isInputClosed()) {
+ webSocket.sendPing(ByteBuffer.allocate(0));
+ Thread.sleep(10000);
+ }
+ } catch (InterruptedException e) {
+ if (webSocket != null)
+ webSocket.sendClose(WebSocket.NORMAL_CLOSURE, "");
+ } catch (ExecutionException e) {
+ throw new RuntimeException("Cannot listent to " + uri, e.getCause());
+ }
+ }
+
+ private class WsEventListener implements WebSocket.Listener {
+ public CompletionStage<?> onText(WebSocket webSocket, CharSequence message, boolean last) {
+ System.out.println(message);
+ CompletionStage<String> res = CompletableFuture.completedStage(message.toString());
+ return res;
+ }
+
+ @Override
+ public CompletionStage<?> onPong(WebSocket webSocket, ByteBuffer message) {
+ // System.out.println("Pong received.");
+ return null;
+ }
+
+ }
+}
--- /dev/null
+package org.argeo.cms.client;
+
+import java.math.RoundingMode;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.WebSocket;
+import java.nio.ByteBuffer;
+import java.text.DecimalFormat;
+import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.ExecutionException;
+
+/** Tests connectivity to the web socket server. */
+public class WebSocketPing implements Runnable {
+ private final static int PING_FRAME_SIZE = 125;
+ private final static DecimalFormat decimalFormat = new DecimalFormat("0.0");
+ static {
+ decimalFormat.setRoundingMode(RoundingMode.HALF_UP);
+ }
+
+ private final URI uri;
+ private final UUID uuid;
+
+ private WebSocket webSocket;
+
+ public WebSocketPing(URI uri) {
+ this.uri = uri;
+ this.uuid = UUID.randomUUID();
+ }
+
+ @Override
+ public void run() {
+ try {
+ WebSocket.Listener listener = new WebSocket.Listener() {
+
+ @Override
+ public CompletionStage<?> onPong(WebSocket webSocket, ByteBuffer message) {
+ long msb = message.getLong();
+ long lsb = message.getLong();
+ long end = System.nanoTime();
+ if (msb != uuid.getMostSignificantBits() || lsb != uuid.getLeastSignificantBits())
+ return null; // ignore
+ long begin = message.getLong();
+ double durationNs = end - begin;
+ double durationMs = durationNs / 1000000;
+ int size = message.remaining() + (3 * Long.BYTES);
+ System.out.println(
+ size + " bytes from " + uri + ": time=" + decimalFormat.format(durationMs) + " ms");
+ return null;
+ }
+
+ };
+
+ HttpClient client = HttpClient.newHttpClient();
+ CompletableFuture<WebSocket> ws = client.newWebSocketBuilder().buildAsync(uri, listener);
+ webSocket = ws.get();
+ webSocket.request(Long.MAX_VALUE);
+
+ Runtime.getRuntime().addShutdownHook(new Thread(() -> webSocket.sendClose(WebSocket.NORMAL_CLOSURE, "")));
+
+ while (!webSocket.isInputClosed()) {
+ long begin = System.nanoTime();
+ ByteBuffer buffer = ByteBuffer.allocate(PING_FRAME_SIZE);
+ buffer.putLong(uuid.getMostSignificantBits());
+ buffer.putLong(uuid.getLeastSignificantBits());
+ buffer.putLong(begin);
+ buffer.flip();
+ webSocket.sendPing(buffer);
+ Thread.sleep(1000);
+ }
+ } catch (InterruptedException e) {
+ if (webSocket != null)
+ webSocket.sendClose(WebSocket.NORMAL_CLOSURE, "");
+ } catch (ExecutionException e) {
+ throw new RuntimeException("Cannot ping " + uri, e.getCause());
+ }
+ }
+
+// public static void main(String[] args) throws Exception {
+// if (args.length == 0) {
+// System.err.println("usage: java " + WsPing.class.getName() + " <url>");
+// System.exit(1);
+// return;
+// }
+// URI uri = URI.create(args[0]);
+// new WsPing(uri).run();
+// }
+
+}
--- /dev/null
+CLIENT {
+ com.sun.security.auth.module.Krb5LoginModule required
+ useTicketCache=true;
+};
--- /dev/null
+package org.argeo.cms.dav;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.Authenticator;
+import java.net.PasswordAuthentication;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpRequest.BodyPublishers;
+import java.net.http.HttpResponse;
+import java.net.http.HttpResponse.BodyHandler;
+import java.net.http.HttpResponse.BodyHandlers;
+import java.util.Iterator;
+
+import javax.xml.namespace.QName;
+
+import org.argeo.cms.http.HttpHeader;
+import org.argeo.cms.http.HttpMethod;
+import org.argeo.cms.http.HttpStatus;
+
+public class DavClient {
+
+ private HttpClient httpClient;
+
+ public DavClient() {
+ httpClient = HttpClient.newBuilder() //
+// .sslContext(insecureContext()) //
+ .version(HttpClient.Version.HTTP_1_1) //
+ .authenticator(new Authenticator() {
+
+ @Override
+ protected PasswordAuthentication getPasswordAuthentication() {
+ return new PasswordAuthentication("root", "demo".toCharArray());
+ }
+
+ }) //
+ .build();
+ }
+
+ public void setProperty(String url, QName key, String value) {
+ try {
+ String body = """
+ <?xml version="1.0" encoding="utf-8" ?>
+ <D:propertyupdate xmlns:D="DAV:"
+ """ //
+ + "xmlns:" + key.getPrefix() + "=\"" + key.getNamespaceURI() + "\">" + //
+ """
+ <D:set>
+ <D:prop>
+ """ //
+ + "<" + key.getPrefix() + ":" + key.getLocalPart() + ">" + value + "</" + key.getPrefix() + ":"
+ + key.getLocalPart() + ">" + //
+ """
+ </D:prop>
+ </D:set>
+ </D:propertyupdate>
+ """;
+ System.out.println(body);
+ HttpRequest request = HttpRequest.newBuilder().uri(URI.create(url)) //
+ .header(HttpHeader.DEPTH.getHeaderName(), DavDepth.DEPTH_1.getValue()) //
+ .method(HttpMethod.PROPPATCH.name(), BodyPublishers.ofString(body)) //
+ .build();
+ BodyHandler<String> bodyHandler = BodyHandlers.ofString();
+ HttpResponse<String> response = httpClient.send(request, bodyHandler);
+ System.out.println(response.body());
+ } catch (IOException | InterruptedException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+
+ public Iterator<DavResponse> listChildren(URI uri) {
+ try {
+ String body = """
+ <?xml version="1.0" encoding="utf-8" ?>
+ <D:propfind xmlns:D="DAV:">
+ <D:propname/>
+ </D:propfind>""";
+ HttpRequest request = HttpRequest.newBuilder().uri(uri) //
+ .header(HttpHeader.DEPTH.getHeaderName(), DavDepth.DEPTH_1.getValue()) //
+ .method(HttpMethod.PROPFIND.name(), BodyPublishers.ofString(body)) //
+ .build();
+
+ HttpResponse<String> responseStr = httpClient.send(request, BodyHandlers.ofString());
+ System.out.println(responseStr.body());
+
+ HttpResponse<InputStream> response = httpClient.send(request, BodyHandlers.ofInputStream());
+ MultiStatusReader msReader = new MultiStatusReader(response.body(), uri.getPath());
+ return msReader;
+ } catch (IOException | InterruptedException e) {
+ throw new IllegalStateException("Cannot list children of " + uri, e);
+ }
+
+ }
+
+ public boolean exists(URI uri) {
+ try {
+ HttpRequest request = HttpRequest.newBuilder().uri(uri) //
+ .header(HttpHeader.DEPTH.getHeaderName(), DavDepth.DEPTH_0.getValue()) //
+ .method(HttpMethod.HEAD.name(), BodyPublishers.noBody()) //
+ .build();
+ BodyHandler<String> bodyHandler = BodyHandlers.ofString();
+ HttpResponse<String> response = httpClient.send(request, bodyHandler);
+ System.out.println(response.body());
+ int responseStatusCode = response.statusCode();
+ if (responseStatusCode == HttpStatus.NOT_FOUND.getCode())
+ return false;
+ if (responseStatusCode >= 200 && responseStatusCode < 300)
+ return true;
+ throw new IllegalStateException(
+ "Cannot check whether " + uri + " exists: Unknown response status code " + responseStatusCode);
+ } catch (IOException | InterruptedException e) {
+ throw new IllegalStateException("Cannot check whether " + uri + " exists", e);
+ }
+
+ }
+
+ public DavResponse get(URI uri) {
+ try {
+ String body = """
+ <?xml version="1.0" encoding="utf-8" ?>
+ <D:propfind xmlns:D="DAV:">
+ <D:allprop/>
+ </D:propfind>""";
+ HttpRequest request = HttpRequest.newBuilder().uri(uri) //
+ .header(HttpHeader.DEPTH.getHeaderName(), DavDepth.DEPTH_0.getValue()) //
+ .method(HttpMethod.PROPFIND.name(), BodyPublishers.ofString(body)) //
+ .build();
+
+// HttpResponse<String> responseStr = httpClient.send(request, BodyHandlers.ofString());
+// System.out.println(responseStr.body());
+
+ HttpResponse<InputStream> response = httpClient.send(request, BodyHandlers.ofInputStream());
+ MultiStatusReader msReader = new MultiStatusReader(response.body());
+ if (!msReader.hasNext())
+ throw new IllegalArgumentException(uri + " does not exist");
+ return msReader.next();
+ } catch (IOException | InterruptedException e) {
+ throw new IllegalStateException("Cannot list children of " + uri, e);
+ }
+
+ }
+
+ public static void main(String[] args) {
+ DavClient davClient = new DavClient();
+// Iterator<DavResponse> responses = davClient
+// .listChildren(URI.create("http://localhost/unstable/a2/org.argeo.tp.sdk/"));
+ Iterator<DavResponse> responses = davClient
+ .listChildren(URI.create("http://root:demo@localhost:7070/api/acr/srv/example"));
+ while (responses.hasNext()) {
+ DavResponse response = responses.next();
+ System.out.println(response.getHref() + (response.isCollection() ? " (collection)" : ""));
+ //System.out.println(" " + response.getPropertyNames(HttpStatus.OK));
+
+ }
+// davClient.setProperty("http://localhost/unstable/a2/org.argeo.tp.sdk/org.opentest4j.1.2.jar",
+// CrName.uuid.qName(), UUID.randomUUID().toString());
+
+ }
+
+}
--- /dev/null
+package org.argeo.cms.dav;
+
+import org.argeo.cms.http.HttpHeader;
+
+import com.sun.net.httpserver.HttpExchange;
+
+public enum DavDepth {
+ DEPTH_0("0"), DEPTH_1("1"), DEPTH_INFINITY("infinity");
+
+ private final String value;
+
+ private DavDepth(String value) {
+ this.value = value;
+ }
+
+ @Override
+ public String toString() {
+ return getValue();
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public static DavDepth fromHttpExchange(HttpExchange httpExchange) {
+ String value = httpExchange.getRequestHeaders().getFirst(HttpHeader.DEPTH.getHeaderName());
+ if (value == null)
+ return null;
+ DavDepth depth = switch (value) {
+ case "0" -> DEPTH_0;
+ case "1" -> DEPTH_1;
+ case "infinity" -> DEPTH_INFINITY;
+ default -> throw new IllegalArgumentException("Unexpected value: " + value);
+ };
+ return depth;
+ }
+}
--- /dev/null
+package org.argeo.cms.dav;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.StringJoiner;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.Consumer;
+
+import javax.xml.namespace.NamespaceContext;
+
+import org.argeo.api.acr.ContentNotFoundException;
+import org.argeo.cms.http.HttpHeader;
+import org.argeo.cms.http.HttpMethod;
+import org.argeo.cms.http.HttpStatus;
+import org.argeo.cms.http.server.HttpServerUtils;
+
+import com.sun.net.httpserver.HttpExchange;
+import com.sun.net.httpserver.HttpHandler;
+
+/**
+ * Centralise patterns which are not ACR specific. Not really meant as a
+ * framework for building WebDav servers, but rather to make upper-level of
+ * ACR-specific code more readable and maintainable.
+ */
+public abstract class DavHttpHandler implements HttpHandler {
+
+ @Override
+ public void handle(HttpExchange exchange) throws IOException {
+ String subPath = HttpServerUtils.subPath(exchange);
+ String method = exchange.getRequestMethod();
+ try {
+ if (HttpMethod.GET.name().equals(method)) {
+ handleGET(exchange, subPath);
+ } else if (HttpMethod.OPTIONS.name().equals(method)) {
+ handleOPTIONS(exchange, subPath);
+ exchange.sendResponseHeaders(HttpStatus.NO_CONTENT.getCode(), -1);
+ } else if (HttpMethod.PROPFIND.name().equals(method)) {
+ DavDepth depth = DavDepth.fromHttpExchange(exchange);
+ if (depth == null) {
+ // default, as per http://www.webdav.org/specs/rfc4918.html#METHOD_PROPFIND
+ depth = DavDepth.DEPTH_INFINITY;
+ }
+ DavPropfind davPropfind;
+ try (InputStream in = exchange.getRequestBody()) {
+ davPropfind = DavPropfind.load(depth, in);
+ }
+ MultiStatusWriter multiStatusWriter = new MultiStatusWriter(exchange.getProtocol());
+ CompletableFuture<Void> published = handlePROPFIND(exchange, subPath, davPropfind, multiStatusWriter);
+ exchange.sendResponseHeaders(HttpStatus.MULTI_STATUS.getCode(), 0l);
+ NamespaceContext namespaceContext = getNamespaceContext(exchange, subPath);
+ try (OutputStream out = exchange.getResponseBody()) {
+ multiStatusWriter.process(namespaceContext, out, published.minimalCompletionStage(),
+ davPropfind.isPropname());
+ }
+ } else {
+ throw new IllegalArgumentException("Unsupported method " + method);
+ }
+ } catch (ContentNotFoundException e) {
+ exchange.sendResponseHeaders(HttpStatus.NOT_FOUND.getCode(), -1);
+ }
+ // TODO return a structured error message
+ catch (UnsupportedOperationException e) {
+ e.printStackTrace();
+ exchange.sendResponseHeaders(HttpStatus.NOT_IMPLEMENTED.getCode(), -1);
+ } catch (Exception e) {
+ exchange.sendResponseHeaders(HttpStatus.INTERNAL_SERVER_ERROR.getCode(), -1);
+ }
+
+ }
+
+ protected abstract NamespaceContext getNamespaceContext(HttpExchange httpExchange, String path);
+
+ protected abstract CompletableFuture<Void> handlePROPFIND(HttpExchange exchange, String path,
+ DavPropfind davPropfind, Consumer<DavResponse> consumer) throws IOException;
+
+ protected abstract void handleGET(HttpExchange exchange, String path) throws IOException;
+
+ protected void handleOPTIONS(HttpExchange exchange, String path) throws IOException {
+ exchange.getResponseHeaders().set(HttpHeader.DAV.getHeaderName(), "1, 3");
+ StringJoiner methods = new StringJoiner(",");
+ methods.add(HttpMethod.OPTIONS.name());
+ methods.add(HttpMethod.HEAD.name());
+ methods.add(HttpMethod.GET.name());
+ methods.add(HttpMethod.POST.name());
+ methods.add(HttpMethod.PUT.name());
+ methods.add(HttpMethod.PROPFIND.name());
+ // TODO :
+ methods.add(HttpMethod.PROPPATCH.name());
+ methods.add(HttpMethod.MKCOL.name());
+ methods.add(HttpMethod.DELETE.name());
+ methods.add(HttpMethod.MOVE.name());
+ methods.add(HttpMethod.COPY.name());
+
+ exchange.getResponseHeaders().add(HttpHeader.ALLOW.getHeaderName(), methods.toString());
+ }
+
+}
--- /dev/null
+package org.argeo.cms.dav;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.xml.namespace.QName;
+import javax.xml.stream.FactoryConfigurationError;
+import javax.xml.stream.XMLInputFactory;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamReader;
+
+public class DavPropfind {
+ private DavDepth depth;
+ private boolean propname = false;
+ private boolean allprop = false;
+ private List<QName> props = new ArrayList<>();
+
+ public DavPropfind(DavDepth depth) {
+ this.depth = depth;
+ }
+
+ public boolean isPropname() {
+ return propname;
+ }
+
+ public void setPropname(boolean propname) {
+ this.propname = propname;
+ }
+
+ public boolean isAllprop() {
+ return allprop;
+ }
+
+ public void setAllprop(boolean allprop) {
+ this.allprop = allprop;
+ }
+
+ public List<QName> getProps() {
+ return props;
+ }
+
+ public DavDepth getDepth() {
+ return depth;
+ }
+
+ public static DavPropfind load(DavDepth depth, InputStream in) throws IOException {
+ try {
+ DavPropfind res = null;
+ XMLInputFactory inputFactory = XMLInputFactory.newFactory();
+ XMLStreamReader reader = inputFactory.createXMLStreamReader(in);
+ while (reader.hasNext()) {
+ reader.next();
+ if (reader.isStartElement()) {
+ QName name = reader.getName();
+// System.out.println(name);
+ DavXmlElement davXmlElement = DavXmlElement.toEnum(name);
+ if (davXmlElement != null) {
+ switch (davXmlElement) {
+ case propfind:
+ res = new DavPropfind(depth);
+ break;
+ case allprop:
+ res.setAllprop(true);
+ break;
+ case propname:
+ res.setPropname(true);
+ case prop:
+ // ignore
+ case include:
+ // ignore
+ break;
+ default:
+ // TODO check that the format is really respected
+ res.getProps().add(reader.getName());
+ }
+ }
+ }
+ }
+
+ // checks
+ if (res.isPropname()) {
+ if (!res.getProps().isEmpty() || res.isAllprop())
+ throw new IllegalArgumentException("Cannot set other values if propname is set");
+ }
+ return res;
+ } catch (FactoryConfigurationError | XMLStreamException e) {
+ throw new RuntimeException("Cannot load propfind", e);
+ }
+ }
+}
--- /dev/null
+package org.argeo.cms.dav;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
+
+import javax.xml.namespace.QName;
+
+import org.argeo.cms.http.HttpStatus;
+
+/** The WebDav response for a given resource. */
+public class DavResponse {
+ final static String MOD_DAV_NAMESPACE = "http://apache.org/dav/props/";
+
+ private String href;
+ private boolean collection;
+ private Map<HttpStatus, Set<QName>> propertyNames = new TreeMap<>();
+ private Map<QName, String> properties = new HashMap<>();
+ private List<QName> resourceTypes = new ArrayList<>();
+
+ public Map<QName, String> getProperties() {
+ return properties;
+ }
+
+ public void setHref(String href) {
+ this.href = href;
+ }
+
+ public String getHref() {
+ return href;
+ }
+
+ public boolean isCollection() {
+ return collection;
+ }
+
+ public void setCollection(boolean collection) {
+ this.collection = collection;
+ }
+
+ public List<QName> getResourceTypes() {
+ return resourceTypes;
+ }
+
+ public Set<QName> getPropertyNames(HttpStatus status) {
+ if (!propertyNames.containsKey(status))
+ propertyNames.put(status, new TreeSet<>(DavXmlElement.QNAME_COMPARATOR));
+ return propertyNames.get(status);
+ }
+
+ public Set<HttpStatus> getStatuses() {
+ return propertyNames.keySet();
+ }
+
+}
--- /dev/null
+package org.argeo.cms.dav;
+
+import java.util.Comparator;
+import java.util.Objects;
+
+import javax.xml.namespace.QName;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamWriter;
+
+import org.argeo.api.acr.QNamed;
+
+enum DavXmlElement implements QNamed {
+ response, //
+ multistatus, //
+ href, //
+ /** MUST be the same as DName.collection */
+ collection, //
+ prop, //
+ resourcetype, //
+
+ // propfind
+ propfind, //
+ allprop, //
+ propname, //
+ include, //
+ propstat, //
+ status, //
+
+ // locking
+ lockscope, //
+ locktype, //
+ supportedlock, //
+ lockentry, //
+ lockdiscovery, //
+ write, //
+ shared, //
+ exclusive, //
+ ;
+
+ final static String WEBDAV_NAMESPACE_URI = "DAV:";
+ final static String WEBDAV_DEFAULT_PREFIX = "D";
+
+ final static Comparator<QName> QNAME_COMPARATOR = new Comparator<QName>() {
+
+ @Override
+ public int compare(QName qn1, QName qn2) {
+ if (Objects.equals(qn1.getNamespaceURI(), qn2.getNamespaceURI())) {// same namespace
+ return qn1.getLocalPart().compareTo(qn2.getLocalPart());
+ } else {
+ return qn1.getNamespaceURI().compareTo(qn2.getNamespaceURI());
+ }
+ }
+
+ };
+
+// private final QName value;
+//
+// private DavXmlElement() {
+// this.value = new ContentName(getNamespace(), localName(), RuntimeNamespaceContext.getNamespaceContext());
+// }
+//
+// @Override
+// public QName qName() {
+// return value;
+// }
+
+ @Override
+ public String getNamespace() {
+ return WEBDAV_NAMESPACE_URI;
+ }
+
+ @Override
+ public String getDefaultPrefix() {
+ return WEBDAV_DEFAULT_PREFIX;
+ }
+
+ public static DavXmlElement toEnum(QName name) {
+ for (DavXmlElement e : values()) {
+ if (e.qName().equals(name))
+ return e;
+ }
+ return null;
+ }
+
+ public void setSimpleValue(XMLStreamWriter xsWriter, String value) throws XMLStreamException {
+ if (value == null) {
+ emptyElement(xsWriter);
+ return;
+ }
+ startElement(xsWriter);
+ xsWriter.writeCharacters(value);
+ xsWriter.writeEndElement();
+ }
+
+ public void emptyElement(XMLStreamWriter xsWriter) throws XMLStreamException {
+ xsWriter.writeEmptyElement(WEBDAV_NAMESPACE_URI, name());
+ }
+
+ public void startElement(XMLStreamWriter xsWriter) throws XMLStreamException {
+ xsWriter.writeStartElement(WEBDAV_NAMESPACE_URI, name());
+ }
+}
--- /dev/null
+package org.argeo.cms.dav;
+
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ForkJoinPool;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import javax.xml.namespace.QName;
+import javax.xml.stream.FactoryConfigurationError;
+import javax.xml.stream.XMLInputFactory;
+import javax.xml.stream.XMLStreamConstants;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamReader;
+
+import org.argeo.cms.http.HttpStatus;
+
+/**
+ * Asynchronously iterate over the response statuses of the response to a
+ * PROPFIND request.
+ */
+class MultiStatusReader implements Iterator<DavResponse> {
+ private CompletableFuture<Boolean> empty = new CompletableFuture<Boolean>();
+ private AtomicBoolean processed = new AtomicBoolean(false);
+
+ private BlockingQueue<DavResponse> queue = new ArrayBlockingQueue<>(64);
+
+ private final String ignoredHref;
+
+ public MultiStatusReader(InputStream in) {
+ this(in, null);
+ }
+
+ /** Typically ignoring self */
+ public MultiStatusReader(InputStream in, String ignoredHref) {
+ this.ignoredHref = ignoredHref;
+ ForkJoinPool.commonPool().execute(() -> process(in));
+ }
+
+ protected void process(InputStream in) {
+ try {
+ XMLInputFactory inputFactory = XMLInputFactory.newFactory();
+ XMLStreamReader reader = inputFactory.createXMLStreamReader(in, StandardCharsets.UTF_8.name());
+
+ DavResponse currentResponse = null;
+ boolean collectiongProperties = false;
+ Set<QName> currentPropertyNames = null;
+ HttpStatus currentStatus = null;
+
+ final QName COLLECTION = DavXmlElement.collection.qName(); // optimisation
+ elements: while (reader.hasNext()) {
+ reader.next();
+ if (reader.isStartElement()) {
+ QName name = reader.getName();
+// System.out.println(name);
+ DavXmlElement davXmlElement = DavXmlElement.toEnum(name);
+ if (davXmlElement != null) {
+ switch (davXmlElement) {
+ case response:
+ currentResponse = new DavResponse();
+ break;
+ case href:
+ assert currentResponse != null;
+ while (reader.hasNext() && !reader.hasText())
+ reader.next();
+ String href = reader.getText();
+ currentResponse.setHref(href);
+ break;
+// case collection:
+// currentResponse.setCollection(true);
+// break;
+ case status:
+ reader.next();
+ String statusLine = reader.getText();
+ currentStatus = HttpStatus.parseStatusLine(statusLine);
+ break;
+ case prop:
+ collectiongProperties = true;
+ currentPropertyNames = new HashSet<>();
+ break;
+ case resourcetype:
+ while (reader.hasNext()) {
+ int event = reader.nextTag();
+ QName resourceType = reader.getName();
+ if (event == XMLStreamConstants.END_ELEMENT && name.equals(resourceType))
+ break;
+ assert currentResponse != null;
+ if (event == XMLStreamConstants.START_ELEMENT) {
+ if (COLLECTION.equals(resourceType))
+ currentResponse.setCollection(true);
+ else
+ currentResponse.getResourceTypes().add(resourceType);
+ }
+ }
+ break;
+ default:
+ // ignore
+ }
+ } else {
+ if (collectiongProperties) {
+ String value = null;
+ // TODO deal with complex properties
+ readProperty: while (reader.hasNext()) {
+ reader.next();
+ if (reader.getEventType() == XMLStreamConstants.END_ELEMENT)
+ break readProperty;
+ if (reader.getEventType() == XMLStreamConstants.CHARACTERS)
+ value = reader.getText();
+ }
+
+ if (name.getNamespaceURI().equals(DavResponse.MOD_DAV_NAMESPACE))
+ continue elements; // skip mod_dav properties
+
+ assert currentResponse != null;
+ currentPropertyNames.add(name);
+ if (value != null)
+ currentResponse.getProperties().put(name, value);
+
+ }
+ }
+ } else if (reader.isEndElement()) {
+ QName name = reader.getName();
+// System.out.println(name);
+ DavXmlElement davXmlElement = DavXmlElement.toEnum(name);
+ if (davXmlElement != null)
+ switch (davXmlElement) {
+ case propstat:
+ currentResponse.getPropertyNames(currentStatus).addAll(currentPropertyNames);
+ currentPropertyNames = null;
+ break;
+ case response:
+ assert currentResponse != null;
+ if (ignoredHref == null || !ignoredHref.equals(currentResponse.getHref())) {
+ if (!empty.isDone())
+ empty.complete(false);
+ publish(currentResponse);
+ }
+ case prop:
+ collectiongProperties = false;
+ break;
+ default:
+ // ignore
+ }
+ }
+ }
+
+ if (!empty.isDone())
+ empty.complete(true);
+ } catch (FactoryConfigurationError | XMLStreamException e) {
+ empty.completeExceptionally(e);
+ throw new IllegalStateException("Cannot process DAV response", e);
+ } finally {
+ processed();
+ }
+ }
+
+ protected synchronized void publish(DavResponse response) {
+ try {
+ queue.put(response);
+ } catch (InterruptedException e) {
+ throw new IllegalStateException("Cannot put response " + response, e);
+ } finally {
+ notifyAll();
+ }
+ }
+
+ protected synchronized void processed() {
+ processed.set(true);
+ notifyAll();
+ }
+
+ @Override
+ public synchronized boolean hasNext() {
+ try {
+ if (empty.get())
+ return false;
+ while (!processed.get() && queue.isEmpty()) {
+ wait();
+ }
+ if (!queue.isEmpty())
+ return true;
+ if (processed.get())
+ return false;
+ throw new IllegalStateException("Cannot determine hasNext");
+ } catch (InterruptedException | ExecutionException e) {
+ throw new IllegalStateException("Cannot determine hasNext", e);
+ } finally {
+ // notifyAll();
+ }
+ }
+
+ @Override
+ public synchronized DavResponse next() {
+ try {
+ if (!hasNext())
+ throw new IllegalStateException("No fursther items are available");
+
+ DavResponse response = queue.take();
+ return response;
+ } catch (InterruptedException e) {
+ throw new IllegalStateException("Cannot get next", e);
+ } finally {
+ // notifyAll();
+ }
+ }
+
+}
--- /dev/null
+package org.argeo.cms.dav;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Consumer;
+
+import javax.xml.XMLConstants;
+import javax.xml.namespace.NamespaceContext;
+import javax.xml.namespace.QName;
+import javax.xml.stream.FactoryConfigurationError;
+import javax.xml.stream.XMLOutputFactory;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamWriter;
+
+import org.argeo.cms.http.HttpStatus;
+
+class MultiStatusWriter implements Consumer<DavResponse> {
+ private BlockingQueue<DavResponse> queue = new ArrayBlockingQueue<>(64);
+
+// private OutputStream out;
+
+ private Thread processingThread;
+
+ private AtomicBoolean done = new AtomicBoolean(false);
+
+ private AtomicBoolean polling = new AtomicBoolean();
+
+ private String protocol;
+
+ public MultiStatusWriter(String protocol) {
+ this.protocol = protocol;
+ }
+
+ public void process(NamespaceContext namespaceContext, OutputStream out, CompletionStage<Void> published,
+ boolean propname) throws IOException {
+ published.thenRun(() -> allPublished());
+ processingThread = Thread.currentThread();
+// this.out = out;
+
+ try {
+ XMLOutputFactory xmlOutputFactory = XMLOutputFactory.newFactory();
+ XMLStreamWriter xsWriter = xmlOutputFactory.createXMLStreamWriter(out, StandardCharsets.UTF_8.name());
+ xsWriter.setNamespaceContext(namespaceContext);
+ xsWriter.setDefaultNamespace(DavXmlElement.WEBDAV_NAMESPACE_URI);
+
+ xsWriter.writeStartDocument();
+ DavXmlElement.multistatus.startElement(xsWriter);
+ xsWriter.writeDefaultNamespace(DavXmlElement.WEBDAV_NAMESPACE_URI);
+
+ poll: while (!(done.get() && queue.isEmpty())) {
+ DavResponse davResponse;
+ try {
+ polling.set(true);
+ davResponse = queue.poll(10, TimeUnit.MILLISECONDS);
+ if (davResponse == null)
+ continue poll;
+ //System.err.println(davResponse.getHref());
+ } catch (InterruptedException e) {
+ //System.err.println(e);
+ continue poll;
+ } finally {
+ polling.set(false);
+ }
+
+ writeDavResponse(xsWriter, davResponse, propname);
+ }
+
+ xsWriter.writeEndElement();// multistatus
+ xsWriter.writeEndDocument();
+ xsWriter.close();
+ out.close();
+ } catch (FactoryConfigurationError | XMLStreamException e) {
+ synchronized (this) {
+ processingThread = null;
+ }
+ }
+ }
+
+ protected void writeDavResponse(XMLStreamWriter xsWriter, DavResponse davResponse, boolean propname)
+ throws XMLStreamException {
+ Set<String> namespaces = new HashSet<>();
+ for (HttpStatus status : davResponse.getStatuses())
+ for (QName key : davResponse.getPropertyNames(status)) {
+ if (key.getNamespaceURI().equals(DavXmlElement.WEBDAV_NAMESPACE_URI))
+ continue; // skip
+ if (key.getNamespaceURI().equals(XMLConstants.W3C_XML_SCHEMA_NS_URI))
+ continue; // skip
+ namespaces.add(key.getNamespaceURI());
+ }
+ DavXmlElement.response.startElement(xsWriter);
+ // namespaces
+ for (String ns : namespaces)
+ xsWriter.writeNamespace(xsWriter.getNamespaceContext().getPrefix(ns), ns);
+
+ DavXmlElement.href.setSimpleValue(xsWriter, davResponse.getHref());
+
+ {
+ for (HttpStatus status : davResponse.getStatuses()) {
+ DavXmlElement.propstat.startElement(xsWriter);
+ {
+ DavXmlElement.prop.startElement(xsWriter);
+
+ // resourcetype
+ if (HttpStatus.OK.equals(status))
+ if (propname) {
+ DavXmlElement.resourcetype.emptyElement(xsWriter);
+ } else {
+ if (!davResponse.getResourceTypes().isEmpty() || davResponse.isCollection()) {
+ DavXmlElement.resourcetype.startElement(xsWriter);
+ if (davResponse.isCollection())
+ DavXmlElement.collection.emptyElement(xsWriter);
+ for (QName resourceType : davResponse.getResourceTypes()) {
+ xsWriter.writeEmptyElement(resourceType.getNamespaceURI(),
+ resourceType.getLocalPart());
+ }
+ xsWriter.writeEndElement();// resource type
+ }
+ }
+
+ properties: for (QName key : davResponse.getPropertyNames(status)) {
+ if (DavXmlElement.resourcetype.qName().equals(key))
+ continue properties;
+
+ if (propname) {
+ xsWriter.writeEmptyElement(key.getNamespaceURI(), key.getLocalPart());
+ } else {
+ xsWriter.writeStartElement(key.getNamespaceURI(), key.getLocalPart());
+ xsWriter.writeCData(davResponse.getProperties().get(key));
+ xsWriter.writeEndElement();
+ }
+ }
+ xsWriter.writeEndElement();// prop
+ }
+ DavXmlElement.status.setSimpleValue(xsWriter, status.getStatusLine(protocol));
+ xsWriter.writeEndElement();// propstat
+ }
+ }
+ xsWriter.writeEndElement();// response
+ }
+
+ @Override
+ public void accept(DavResponse davResponse) {
+ try {
+ queue.put(davResponse);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+
+ protected synchronized void allPublished() {
+ done.set(true);
+ if (processingThread != null && queue.isEmpty() && polling.get()) {
+ // we only interrupt if the queue is already processed
+ // so as not to interrupt I/O
+ processingThread.interrupt();
+ }
+ }
+
+}
--- /dev/null
+package org.argeo.cms.directory.ldap;
+
+import static org.argeo.cms.directory.ldap.LdapNameUtils.toLdapName;
+
+import java.io.File;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Arrays;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Locale;
+import java.util.Optional;
+import java.util.StringJoiner;
+
+import javax.naming.Context;
+import javax.naming.InvalidNameException;
+import javax.naming.NameNotFoundException;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.BasicAttributes;
+import javax.naming.ldap.LdapName;
+import javax.naming.ldap.Rdn;
+import javax.transaction.xa.XAResource;
+
+import org.argeo.api.acr.ldap.LdapAttr;
+import org.argeo.api.acr.ldap.LdapObj;
+import org.argeo.api.cms.directory.CmsDirectory;
+import org.argeo.api.cms.directory.HierarchyUnit;
+import org.argeo.api.cms.transaction.WorkControl;
+import org.argeo.api.cms.transaction.WorkingCopyXaResource;
+import org.argeo.api.cms.transaction.XAResourceProvider;
+import org.argeo.cms.osgi.useradmin.OsUserDirectory;
+import org.argeo.cms.runtime.DirectoryConf;
+
+/** A {@link CmsDirectory} based either on LDAP or LDIF. */
+public abstract class AbstractLdapDirectory implements CmsDirectory, XAResourceProvider {
+ protected static final String SHARED_STATE_USERNAME = "javax.security.auth.login.name";
+ protected static final String SHARED_STATE_PASSWORD = "javax.security.auth.login.password";
+
+ private final LdapName baseDn;
+ private final Hashtable<String, Object> configProperties;
+ private final Rdn userBaseRdn, groupBaseRdn, systemRoleBaseRdn;
+ private final String userObjectClass, groupObjectClass;
+ private String memberAttributeId = "member";
+
+ private final boolean readOnly;
+ private final boolean disabled;
+ private final String uri;
+
+ private String forcedPassword;
+
+ private final boolean scoped;
+
+ private List<String> credentialAttributeIds = Arrays
+ .asList(new String[] { LdapAttr.userPassword.name(), LdapAttr.authPassword.name() });
+
+ private WorkControl transactionControl;
+ private WorkingCopyXaResource<LdapEntryWorkingCopy> xaResource;
+
+ private LdapDirectoryDao directoryDao;
+
+ /** Whether the the directory has is authenticated via a service user. */
+ private boolean authenticated = false;
+
+ public AbstractLdapDirectory(URI uriArg, Dictionary<String, ?> props, boolean scoped) {
+ this.configProperties = new Hashtable<String, Object>();
+ for (Enumeration<String> keys = props.keys(); keys.hasMoreElements();) {
+ String key = keys.nextElement();
+ configProperties.put(key, props.get(key));
+ }
+
+ String baseDnStr = DirectoryConf.baseDn.getValue(configProperties);
+ if (baseDnStr == null)
+ throw new IllegalArgumentException("Base DN must be specified: " + configProperties);
+ baseDn = toLdapName(baseDnStr);
+ this.scoped = scoped;
+
+ if (uriArg != null) {
+ uri = uriArg.toString();
+ // uri from properties is ignored
+ } else {
+ String uriStr = DirectoryConf.uri.getValue(configProperties);
+ if (uriStr == null)
+ uri = null;
+ else
+ uri = uriStr;
+ }
+
+ forcedPassword = DirectoryConf.forcedPassword.getValue(configProperties);
+
+ userObjectClass = DirectoryConf.userObjectClass.getValue(configProperties);
+ groupObjectClass = DirectoryConf.groupObjectClass.getValue(configProperties);
+
+ String userBase = DirectoryConf.userBase.getValue(configProperties);
+ String groupBase = DirectoryConf.groupBase.getValue(configProperties);
+ String systemRoleBase = DirectoryConf.systemRoleBase.getValue(configProperties);
+ try {
+// baseDn = new LdapName(UserAdminConf.baseDn.getValue(properties));
+ userBaseRdn = new Rdn(userBase);
+// userBaseDn = new LdapName(userBase + "," + baseDn);
+ groupBaseRdn = new Rdn(groupBase);
+// groupBaseDn = new LdapName(groupBase + "," + baseDn);
+ systemRoleBaseRdn = new Rdn(systemRoleBase);
+ } catch (InvalidNameException e) {
+ throw new IllegalArgumentException(
+ "Badly formated base DN " + DirectoryConf.baseDn.getValue(configProperties), e);
+ }
+
+ // read only
+ String readOnlyStr = DirectoryConf.readOnly.getValue(configProperties);
+ if (readOnlyStr == null) {
+ readOnly = readOnlyDefault(uri);
+ configProperties.put(DirectoryConf.readOnly.name(), Boolean.toString(readOnly));
+ } else
+ readOnly = Boolean.parseBoolean(readOnlyStr);
+
+ // disabled
+ String disabledStr = DirectoryConf.disabled.getValue(configProperties);
+ if (disabledStr != null)
+ disabled = Boolean.parseBoolean(disabledStr);
+ else
+ disabled = false;
+ if (!getRealm().isEmpty()) {
+ // IPA multiple LDAP causes URI parsing to fail
+ // TODO manage generic redundant LDAP case
+ directoryDao = new LdapDao(this);
+ } else {
+ if (uri != null) {
+ URI u = URI.create(uri);
+ if (DirectoryConf.SCHEME_LDAP.equals(u.getScheme())
+ || DirectoryConf.SCHEME_LDAPS.equals(u.getScheme())) {
+ directoryDao = new LdapDao(this);
+ authenticated = configProperties.get(Context.SECURITY_PRINCIPAL) != null;
+ } else if (DirectoryConf.SCHEME_FILE.equals(u.getScheme())) {
+ directoryDao = new LdifDao(this);
+ authenticated = true;
+ } else if (DirectoryConf.SCHEME_OS.equals(u.getScheme())) {
+ directoryDao = new OsUserDirectory(this);
+ authenticated = true;
+ // singleUser = true;
+ } else {
+ throw new IllegalArgumentException("Unsupported scheme " + u.getScheme());
+ }
+ } else {
+ // in memory
+ directoryDao = new LdifDao(this);
+ }
+ }
+ if (directoryDao != null)
+ xaResource = new WorkingCopyXaResource<>(directoryDao);
+ }
+
+ /*
+ * INITIALISATION
+ */
+
+ public void init() {
+ getDirectoryDao().init();
+ }
+
+ public void destroy() {
+ getDirectoryDao().destroy();
+ }
+
+ /*
+ * CREATION
+ */
+ protected abstract LdapEntry newUser(LdapName name);
+
+ protected abstract LdapEntry newGroup(LdapName name);
+
+ /*
+ * EDITION
+ */
+
+ public boolean isEditing() {
+ return xaResource.wc() != null;
+ }
+
+ public LdapEntryWorkingCopy getWorkingCopy() {
+ LdapEntryWorkingCopy wc = xaResource.wc();
+ if (wc == null)
+ return null;
+ return wc;
+ }
+
+ public void checkEdit() {
+ if (xaResource.wc() == null) {
+ try {
+ transactionControl.getWorkContext().registerXAResource(xaResource, null);
+ } catch (Exception e) {
+ throw new IllegalStateException("Cannot enlist " + xaResource, e);
+ }
+ } else {
+ }
+ }
+
+ public void setTransactionControl(WorkControl transactionControl) {
+ this.transactionControl = transactionControl;
+ }
+
+ public XAResource getXaResource() {
+ return xaResource;
+ }
+
+ public boolean removeEntry(LdapName dn) {
+ checkEdit();
+ LdapEntryWorkingCopy wc = getWorkingCopy();
+ boolean actuallyDeleted;
+ if (getDirectoryDao().entryExists(dn) || wc.getNewData().containsKey(dn)) {
+ LdapEntry user = doGetRole(dn);
+ wc.getDeletedData().put(dn, user);
+ actuallyDeleted = true;
+ } else {// just removing from groups (e.g. system roles)
+ actuallyDeleted = false;
+ }
+ for (LdapName groupDn : getDirectoryDao().getDirectGroups(dn)) {
+ LdapEntry group = doGetRole(groupDn);
+ group.getAttributes().get(getMemberAttributeId()).remove(dn.toString());
+ }
+ return actuallyDeleted;
+ }
+
+ /*
+ * RETRIEVAL
+ */
+
+ protected LdapEntry doGetRole(LdapName dn) {
+ LdapEntryWorkingCopy wc = getWorkingCopy();
+ LdapEntry user;
+ try {
+ user = getDirectoryDao().doGetEntry(dn);
+ } catch (NameNotFoundException e) {
+ user = null;
+ }
+ if (wc != null) {
+ if (user == null && wc.getNewData().containsKey(dn))
+ user = wc.getNewData().get(dn);
+ else if (wc.getDeletedData().containsKey(dn))
+ user = null;
+ }
+ return user;
+ }
+
+ protected void collectGroups(LdapEntry user, List<LdapEntry> allRoles) {
+ Attributes attrs = user.getAttributes();
+ // TODO centralize attribute name
+ Attribute memberOf = attrs.get(LdapAttr.memberOf.name());
+ // if user belongs to this directory, we only check memberOf
+ if (memberOf != null && user.getDn().startsWith(getBaseDn())) {
+ try {
+ NamingEnumeration<?> values = memberOf.getAll();
+ while (values.hasMore()) {
+ Object value = values.next();
+ LdapName groupDn = new LdapName(value.toString());
+ LdapEntry group = doGetRole(groupDn);
+ if (group != null) {
+ allRoles.add(group);
+ } else {
+ // user doesn't have the right to retrieve role, but we know it exists
+ // otherwise memberOf would not work
+ group = newGroup(groupDn);
+ allRoles.add(group);
+ }
+ }
+ } catch (NamingException e) {
+ throw new IllegalStateException("Cannot get memberOf groups for " + user, e);
+ }
+ } else {
+ directGroups: for (LdapName groupDn : getDirectoryDao().getDirectGroups(user.getDn())) {
+ LdapEntry group = doGetRole(groupDn);
+ if (group != null) {
+ if (allRoles.contains(group)) {
+ // important in order to avoi loops
+ continue directGroups;
+ }
+ allRoles.add(group);
+ collectGroups(group, allRoles);
+ }
+ }
+ }
+ }
+
+ /*
+ * HIERARCHY
+ */
+ @Override
+ public HierarchyUnit getHierarchyUnit(String path) {
+ LdapName dn = pathToName(path);
+ return directoryDao.doGetHierarchyUnit(dn);
+ }
+
+ @Override
+ public Iterable<HierarchyUnit> getDirectHierarchyUnits(boolean functionalOnly) {
+ return directoryDao.doGetDirectHierarchyUnits(baseDn, functionalOnly);
+ }
+
+ @Override
+ public HierarchyUnit getDirectChild(Type type) {
+ // TODO factorise with hierarchy unit?
+ return switch (type) {
+ case ROLES -> getDirectoryDao().doGetHierarchyUnit((LdapName) getBaseDn().add(getSystemRoleBaseRdn()));
+ case PEOPLE -> getDirectoryDao().doGetHierarchyUnit((LdapName) getBaseDn().add(getUserBaseRdn()));
+ case GROUPS -> getDirectoryDao().doGetHierarchyUnit((LdapName) getBaseDn().add(getGroupBaseRdn()));
+ case FUNCTIONAL -> throw new IllegalArgumentException("Type must be a technical type");
+ };
+ }
+
+ @Override
+ public String getHierarchyUnitName() {
+ return getName();
+ }
+
+ @Override
+ public String getHierarchyUnitLabel(Locale locale) {
+ String key = LdapNameUtils.getLastRdn(getBaseDn()).getType();
+ Object value = LdapEntry.getLocalized(asLdapEntry().getProperties(), key, locale);
+ if (value == null)
+ value = getHierarchyUnitName();
+ assert value != null;
+ return value.toString();
+ }
+
+ @Override
+ public HierarchyUnit getParent() {
+ return null;
+ }
+
+ @Override
+ public boolean isType(Type type) {
+ return Type.FUNCTIONAL.equals(type);
+ }
+
+ @Override
+ public CmsDirectory getDirectory() {
+ return this;
+ }
+
+ @Override
+ public HierarchyUnit createHierarchyUnit(String path) {
+ checkEdit();
+ LdapEntryWorkingCopy wc = getWorkingCopy();
+ LdapName dn = pathToName(path);
+ if ((getDirectoryDao().entryExists(dn) && !wc.getDeletedData().containsKey(dn))
+ || wc.getNewData().containsKey(dn))
+ throw new IllegalArgumentException("Already a hierarchy unit " + path);
+ BasicAttributes attrs = new BasicAttributes(true);
+ attrs.put(LdapAttr.objectClass.name(), LdapObj.organizationalUnit.name());
+ Rdn nameRdn = dn.getRdn(dn.size() - 1);
+ // TODO deal with multiple attr RDN
+ attrs.put(nameRdn.getType(), nameRdn.getValue());
+ wc.getModifiedData().put(dn, attrs);
+ LdapHierarchyUnit newHierarchyUnit = new LdapHierarchyUnit(this, dn);
+ wc.getNewData().put(dn, newHierarchyUnit);
+ return newHierarchyUnit;
+ }
+
+ /*
+ * PATHS
+ */
+
+ @Override
+ public String getBase() {
+ return getBaseDn().toString();
+ }
+
+ @Override
+ public String getName() {
+ return nameToSimple(getBaseDn(), ".");
+ }
+
+ protected String nameToRelativePath(LdapName dn) {
+ LdapName name = LdapNameUtils.relativeName(getBaseDn(), dn);
+ return nameToSimple(name, "/");
+ }
+
+ protected String nameToSimple(LdapName name, String separator) {
+ StringJoiner path = new StringJoiner(separator);
+ for (int i = 0; i < name.size(); i++) {
+ path.add(name.getRdn(i).getValue().toString());
+ }
+ return path.toString();
+
+ }
+
+ protected LdapName pathToName(String path) {
+ try {
+ LdapName name = (LdapName) getBaseDn().clone();
+ String[] segments = path.split("/");
+ Rdn parentRdn = null;
+ // segments[0] is the directory itself
+ for (int i = 0; i < segments.length; i++) {
+ String segment = segments[i];
+ // TODO make attr names configurable ?
+ String attr = getDirectory().getRealm().isPresent()/* IPA */ ? LdapAttr.cn.name() : LdapAttr.ou.name();
+ if (parentRdn != null) {
+ if (getUserBaseRdn().equals(parentRdn))
+ attr = LdapAttr.uid.name();
+ else if (getGroupBaseRdn().equals(parentRdn))
+ attr = LdapAttr.cn.name();
+ else if (getSystemRoleBaseRdn().equals(parentRdn))
+ attr = LdapAttr.cn.name();
+ }
+ Rdn rdn = new Rdn(attr, segment);
+ name.add(rdn);
+ parentRdn = rdn;
+ }
+ return name;
+ } catch (InvalidNameException e) {
+ throw new IllegalStateException("Cannot convert " + path + " to LDAP name", e);
+ }
+
+ }
+
+ /*
+ * UTILITIES
+ */
+ protected boolean isExternal(LdapName name) {
+ return !name.startsWith(baseDn);
+ }
+
+ protected static boolean hasObjectClass(Attributes attrs, LdapObj objectClass) {
+ return hasObjectClass(attrs, objectClass.name());
+ }
+
+ protected static boolean hasObjectClass(Attributes attrs, String objectClass) {
+ try {
+ Attribute attr = attrs.get(LdapAttr.objectClass.name());
+ NamingEnumeration<?> en = attr.getAll();
+ while (en.hasMore()) {
+ String v = en.next().toString();
+ if (v.equalsIgnoreCase(objectClass))
+ return true;
+
+ }
+ return false;
+ } catch (NamingException e) {
+ throw new IllegalStateException("Cannot search for objectClass " + objectClass, e);
+ }
+ }
+
+ private static boolean readOnlyDefault(String uriStr) {
+ if (uriStr == null)
+ return true;
+ /// TODO make it more generic
+ URI uri;
+ try {
+ uri = new URI(uriStr.split(" ")[0]);
+ } catch (URISyntaxException e) {
+ throw new IllegalArgumentException(e);
+ }
+ if (uri.getScheme() == null)
+ return false;// assume relative file to be writable
+ if (uri.getScheme().equals(DirectoryConf.SCHEME_FILE)) {
+ File file = new File(uri);
+ if (file.exists())
+ return !file.canWrite();
+ else
+ return !file.getParentFile().canWrite();
+ } else if (uri.getScheme().equals(DirectoryConf.SCHEME_LDAP)) {
+ if (uri.getAuthority() != null)// assume writable if authenticated
+ return false;
+ } else if (uri.getScheme().equals(DirectoryConf.SCHEME_OS)) {
+ return true;
+ }
+ return true;// read only by default
+ }
+
+ /*
+ * AS AN ENTRY
+ */
+ public LdapEntry asLdapEntry() {
+ try {
+ return directoryDao.doGetEntry(baseDn);
+ } catch (NameNotFoundException e) {
+ throw new IllegalStateException("Cannot get " + baseDn + " entry", e);
+ }
+ }
+
+ public Dictionary<String, Object> getProperties() {
+ return asLdapEntry().getProperties();
+ }
+
+ /*
+ * ACCESSORS
+ */
+ @Override
+ public Optional<String> getRealm() {
+ Object realm = configProperties.get(DirectoryConf.realm.name());
+ if (realm == null)
+ return Optional.empty();
+ return Optional.of(realm.toString());
+ }
+
+ public LdapName getBaseDn() {
+ return (LdapName) baseDn.clone();
+ }
+
+ public boolean isReadOnly() {
+ return readOnly;
+ }
+
+ public boolean isDisabled() {
+ return disabled;
+ }
+
+ public boolean isAuthenticated() {
+ return authenticated;
+ }
+
+ public Rdn getUserBaseRdn() {
+ return userBaseRdn;
+ }
+
+ public Rdn getGroupBaseRdn() {
+ return groupBaseRdn;
+ }
+
+ public Rdn getSystemRoleBaseRdn() {
+ return systemRoleBaseRdn;
+ }
+
+// public Dictionary<String, Object> getConfigProperties() {
+// return configProperties;
+// }
+
+ public Dictionary<String, Object> cloneConfigProperties() {
+ return new Hashtable<>(configProperties);
+ }
+
+ public String getForcedPassword() {
+ return forcedPassword;
+ }
+
+ public boolean isScoped() {
+ return scoped;
+ }
+
+ public List<String> getCredentialAttributeIds() {
+ return credentialAttributeIds;
+ }
+
+ public String getUri() {
+ return uri;
+ }
+
+ public LdapDirectoryDao getDirectoryDao() {
+ return directoryDao;
+ }
+
+ /** dn can be null, in that case a default should be returned. */
+ public String getUserObjectClass() {
+ return userObjectClass;
+ }
+
+ public String getGroupObjectClass() {
+ return groupObjectClass;
+ }
+
+ public String getMemberAttributeId() {
+ return memberAttributeId;
+ }
+
+ /*
+ * OBJECT METHODS
+ */
+
+ @Override
+ public int hashCode() {
+ return baseDn.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return "Directory " + baseDn.toString();
+ }
+
+}
--- /dev/null
+package org.argeo.cms.directory.ldap;
+
+import javax.naming.ldap.LdapName;
+
+/** Base class for LDAP/LDIF directory DAOs. */
+public abstract class AbstractLdapDirectoryDao implements LdapDirectoryDao {
+
+ private AbstractLdapDirectory directory;
+
+ public AbstractLdapDirectoryDao(AbstractLdapDirectory directory) {
+ this.directory = directory;
+ }
+
+ public AbstractLdapDirectory getDirectory() {
+ return directory;
+ }
+
+ @Override
+ public LdapEntryWorkingCopy newWorkingCopy() {
+ return new LdapEntryWorkingCopy();
+ }
+
+ @Override
+ public LdapEntry newUser(LdapName name) {
+ return getDirectory().newUser(name);
+ }
+
+ @Override
+ public LdapEntry newGroup(LdapName name) {
+ return getDirectory().newGroup(name);
+ }
+
+}
--- /dev/null
+package org.argeo.cms.directory.ldap;
+
+import java.util.Dictionary;
+import java.util.Enumeration;
+
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.BasicAttribute;
+
+public class AttributesDictionary extends Dictionary<String, Object> {
+ private final Attributes attributes;
+
+ /** The provided attributes is wrapped, not copied. */
+ public AttributesDictionary(Attributes attributes) {
+ if (attributes == null)
+ throw new IllegalArgumentException("Attributes cannot be null");
+ this.attributes = attributes;
+ }
+
+ @Override
+ public int size() {
+ return attributes.size();
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return attributes.size() == 0;
+ }
+
+ @Override
+ public Enumeration<String> keys() {
+ NamingEnumeration<String> namingEnumeration = attributes.getIDs();
+ return new Enumeration<String>() {
+
+ @Override
+ public boolean hasMoreElements() {
+ return namingEnumeration.hasMoreElements();
+ }
+
+ @Override
+ public String nextElement() {
+ return namingEnumeration.nextElement();
+ }
+
+ };
+ }
+
+ @Override
+ public Enumeration<Object> elements() {
+ NamingEnumeration<String> namingEnumeration = attributes.getIDs();
+ return new Enumeration<Object>() {
+
+ @Override
+ public boolean hasMoreElements() {
+ return namingEnumeration.hasMoreElements();
+ }
+
+ @Override
+ public Object nextElement() {
+ String key = namingEnumeration.nextElement();
+ return get(key);
+ }
+
+ };
+ }
+
+ @Override
+ /** @returns a <code>String</code> or <code>String[]</code> */
+ public Object get(Object key) {
+ try {
+ if (key == null)
+ throw new IllegalArgumentException("Key cannot be null");
+ Attribute attr = attributes.get(key.toString());
+ if (attr == null)
+ return null;
+ if (attr.size() == 0)
+ throw new IllegalStateException("There must be at least one value");
+ else if (attr.size() == 1) {
+ return attr.get().toString();
+ } else {// multiple
+ String[] res = new String[attr.size()];
+ for (int i = 0; i < attr.size(); i++) {
+ Object value = attr.get();
+ if (value == null)
+ throw new RuntimeException("Values cannot be null");
+ res[i] = attr.get(i).toString();
+ }
+ return res;
+ }
+ } catch (NamingException e) {
+ throw new RuntimeException("Cannot get value for " + key, e);
+ }
+ }
+
+ @Override
+ public Object put(String key, Object value) {
+ if (key == null)
+ throw new IllegalArgumentException("Key cannot be null");
+ if (value == null)
+ throw new IllegalArgumentException("Value cannot be null");
+
+ Object oldValue = get(key);
+ Attribute attr = attributes.get(key);
+ if (attr == null) {
+ attr = new BasicAttribute(key);
+ attributes.put(attr);
+ }
+
+ if (value instanceof String[]) {
+ String[] values = (String[]) value;
+ // clean additional values
+ for (int i = values.length; i < attr.size(); i++)
+ attr.remove(i);
+ // set values
+ for (int i = 0; i < values.length; i++) {
+ attr.set(i, values[i]);
+ }
+ } else {
+ if (attr.size() > 1)
+ throw new IllegalArgumentException("Attribute " + key + " is multi-valued");
+ if (attr.size() == 1) {
+ try {
+ if (!attr.get(0).equals(value))
+ attr.set(0, value.toString());
+ } catch (NamingException e) {
+ throw new RuntimeException("Cannot check existing value", e);
+ }
+ } else {
+ attr.add(value.toString());
+ }
+ }
+ return oldValue;
+ }
+
+ @Override
+ public Object remove(Object key) {
+ if (key == null)
+ throw new IllegalArgumentException("Key cannot be null");
+ Object oldValue = get(key);
+ if (oldValue == null)
+ return null;
+ return attributes.remove(key.toString());
+ }
+
+ /**
+ * Copy the <b>content</b> of an {@link Attributes} to the provided
+ * {@link Dictionary}.
+ */
+ public static void copy(Attributes attributes, Dictionary<String, Object> dictionary) {
+ AttributesDictionary ad = new AttributesDictionary(attributes);
+ Enumeration<String> keys = ad.keys();
+ while (keys.hasMoreElements()) {
+ String key = keys.nextElement();
+ dictionary.put(key, ad.get(key));
+ }
+ }
+
+ /**
+ * Copy a {@link Dictionary} into an {@link Attributes}.
+ */
+ public static void copy(Dictionary<String, Object> dictionary, Attributes attributes) {
+ AttributesDictionary ad = new AttributesDictionary(attributes);
+ Enumeration<String> keys = dictionary.keys();
+ while (keys.hasMoreElements()) {
+ String key = keys.nextElement();
+ ad.put(key, dictionary.get(key));
+ }
+ }
+}
--- /dev/null
+package org.argeo.cms.directory.ldap;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.StringTokenizer;
+
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+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.UnsupportedCallbackException;
+
+import org.argeo.api.acr.ldap.LdapAttr;
+
+/** LDAP authPassword field according to RFC 3112 */
+public class AuthPassword implements CallbackHandler {
+ private final String authScheme;
+ private final String authInfo;
+ private final String authValue;
+
+ public AuthPassword(String value) {
+ StringTokenizer st = new StringTokenizer(value, "$");
+ // TODO make it more robust, deal with bad formatting
+ this.authScheme = st.nextToken().trim();
+ this.authInfo = st.nextToken().trim();
+ this.authValue = st.nextToken().trim();
+
+ String expectedAuthScheme = getExpectedAuthScheme();
+ if (expectedAuthScheme != null && !authScheme.equals(expectedAuthScheme))
+ throw new IllegalArgumentException(
+ "Auth scheme " + authScheme + " is not compatible with " + expectedAuthScheme);
+ }
+
+ protected AuthPassword(String authInfo, String authValue) {
+ this.authScheme = getExpectedAuthScheme();
+ if (authScheme == null)
+ throw new IllegalArgumentException("Expected auth scheme cannot be null");
+ this.authInfo = authInfo;
+ this.authValue = authValue;
+ }
+
+ protected AuthPassword(AuthPassword authPassword) {
+ this.authScheme = authPassword.getAuthScheme();
+ this.authInfo = authPassword.getAuthInfo();
+ this.authValue = authPassword.getAuthValue();
+ }
+
+ protected String getExpectedAuthScheme() {
+ return null;
+ }
+
+ protected boolean matchAuthValue(Object object) {
+ return authValue.equals(object.toString());
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof AuthPassword))
+ return false;
+ AuthPassword authPassword = (AuthPassword) obj;
+ return authScheme.equals(authPassword.authScheme) && authInfo.equals(authPassword.authInfo)
+ && authValue.equals(authValue);
+ }
+
+ public boolean keyEquals(AuthPassword authPassword) {
+ return authScheme.equals(authPassword.authScheme) && authInfo.equals(authPassword.authInfo);
+ }
+
+ @Override
+ public int hashCode() {
+ return authValue.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return toAuthPassword();
+ }
+
+ public final String toAuthPassword() {
+ return getAuthScheme() + '$' + authInfo + '$' + authValue;
+ }
+
+ public String getAuthScheme() {
+ return authScheme;
+ }
+
+ public String getAuthInfo() {
+ return authInfo;
+ }
+
+ public String getAuthValue() {
+ return authValue;
+ }
+
+ public static AuthPassword matchAuthValue(Attributes attributes, char[] value) {
+ try {
+ Attribute authPassword = attributes.get(LdapAttr.authPassword.name());
+ if (authPassword != null) {
+ NamingEnumeration<?> values = authPassword.getAll();
+ while (values.hasMore()) {
+ Object val = values.next();
+ AuthPassword token = new AuthPassword(val.toString());
+ String auth;
+ if (Arrays.binarySearch(value, '$') >= 0) {
+ auth = token.authInfo + '$' + token.authValue;
+ } else {
+ auth = token.authValue;
+ }
+ if (Arrays.equals(auth.toCharArray(), value))
+ return token;
+ // if (token.matchAuthValue(value))
+ // return token;
+ }
+ }
+ return null;
+ } catch (NamingException e) {
+ throw new IllegalStateException("Cannot check attribute", e);
+ }
+ }
+
+ public static boolean remove(Attributes attributes, AuthPassword value) {
+ Attribute authPassword = attributes.get(LdapAttr.authPassword.name());
+ return authPassword.remove(value.toAuthPassword());
+ }
+
+ @Override
+ public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
+ for (Callback callback : callbacks) {
+ if (callback instanceof NameCallback)
+ ((NameCallback) callback).setName(toAuthPassword());
+ else if (callback instanceof PasswordCallback)
+ ((PasswordCallback) callback).setPassword(getAuthValue().toCharArray());
+ }
+ }
+
+}
--- /dev/null
+package org.argeo.cms.directory.ldap;
+
+import static java.nio.charset.StandardCharsets.US_ASCII;
+
+import java.math.BigInteger;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Base64;
+import java.util.Collections;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Objects;
+import java.util.StringJoiner;
+
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.BasicAttribute;
+import javax.naming.ldap.LdapName;
+
+import org.argeo.api.acr.ldap.LdapAttr;
+import org.argeo.api.acr.ldap.LdapObj;
+import org.argeo.api.cms.directory.DirectoryDigestUtils;
+
+/** An entry in an LDAP (or LDIF) directory. */
+public class DefaultLdapEntry implements LdapEntry {
+ private final AbstractLdapDirectory directory;
+
+ private final LdapName dn;
+
+ private AttributeDictionary properties;
+ private AttributeDictionary credentials;
+
+// private String primaryObjectClass;
+// private List<String> objectClasses = new ArrayList<>();
+
+ protected DefaultLdapEntry(AbstractLdapDirectory directory, LdapName dn) {
+ Objects.requireNonNull(directory);
+ Objects.requireNonNull(dn);
+ this.directory = directory;
+ this.dn = dn;
+
+ // Object classes
+// Objects.requireNonNull(initialAttributes);
+// try {
+// NamingEnumeration<?> en = initialAttributes.get(LdapAttrs.objectClass.name()).getAll();
+// String first = null;
+// attrs: while (en.hasMore()) {
+// String v = en.next().toString();
+// if (v.equalsIgnoreCase(LdapObjs.top.name()))
+// continue attrs;
+// if (first == null)
+// first = v;
+// if (v.equalsIgnoreCase(getDirectory().getUserObjectClass()))
+// primaryObjectClass = getDirectory().getUserObjectClass();
+// else if (v.equalsIgnoreCase(getDirectory().getGroupObjectClass()))
+// primaryObjectClass = getDirectory().getGroupObjectClass();
+// objectClasses.add(v);
+// }
+// if (primaryObjectClass == null) {
+// if (first == null)
+// throw new IllegalStateException("Could not find primary object class");
+// primaryObjectClass = first;
+// }
+// } catch (NamingException e) {
+// throw new IllegalStateException("Cannot find object classes", e);
+// }
+
+ }
+
+ @Override
+ public LdapName getDn() {
+ // always return a copy since LdapName is mutable
+ return (LdapName) dn.clone();
+ }
+
+ public synchronized Attributes getAttributes() {
+ return isEditing() ? getModifiedAttributes() : getDirectory().getDirectoryDao().doGetAttributes(dn);
+ }
+
+ @Override
+ public List<LdapName> getReferences(String attributeId) {
+ Attribute memberAttribute = getAttributes().get(attributeId);
+ if (memberAttribute == null)
+ return new ArrayList<LdapName>();
+ try {
+ List<LdapName> roles = new ArrayList<LdapName>();
+ NamingEnumeration<?> values = memberAttribute.getAll();
+ while (values.hasMore()) {
+ LdapName dn = new LdapName(values.next().toString());
+ roles.add(dn);
+ }
+ return roles;
+ } catch (NamingException e) {
+ throw new IllegalStateException("Cannot get members", e);
+ }
+
+ }
+
+ /** Should only be called from working copy thread. */
+ protected synchronized Attributes getModifiedAttributes() {
+ assert getWc() != null;
+ return getWc().getModifiedData().get(getDn());
+ }
+
+ protected synchronized boolean isEditing() {
+ return getWc() != null && getModifiedAttributes() != null;
+ }
+
+ private synchronized LdapEntryWorkingCopy getWc() {
+ return directory.getWorkingCopy();
+ }
+
+ protected synchronized void startEditing() {
+// if (frozen)
+// throw new IllegalStateException("Cannot edit frozen view");
+ if (directory.isReadOnly())
+ throw new IllegalStateException("User directory is read-only");
+ assert getModifiedAttributes() == null;
+ getWc().startEditing(this);
+ // modifiedAttributes = (Attributes) publishedAttributes.clone();
+ }
+
+ public synchronized void publishAttributes(Attributes modifiedAttributes) {
+// publishedAttributes = modifiedAttributes;
+ }
+
+ /*
+ * PROPERTIES
+ */
+ @Override
+ public Dictionary<String, Object> getProperties() {
+ if (properties == null)
+ properties = new AttributeDictionary(false);
+ return properties;
+ }
+
+ public Dictionary<String, Object> getCredentials() {
+ if (credentials == null)
+ credentials = new AttributeDictionary(true);
+ return credentials;
+ }
+
+ /*
+ * CREDENTIALS
+ */
+ @Override
+ public boolean hasCredential(String key, Object value) {
+ if (key == null) {
+ // TODO check other sources (like PKCS12)
+ // String pwd = new String((char[]) value);
+ // authPassword (RFC 312 https://tools.ietf.org/html/rfc3112)
+ char[] password = DirectoryDigestUtils.bytesToChars(value);
+
+ if (getDirectory().getForcedPassword() != null
+ && getDirectory().getForcedPassword().equals(new String(password)))
+ return true;
+
+ AuthPassword authPassword = AuthPassword.matchAuthValue(getAttributes(), password);
+ if (authPassword != null) {
+ if (authPassword.getAuthScheme().equals(SharedSecret.X_SHARED_SECRET)) {
+ SharedSecret onceToken = new SharedSecret(authPassword);
+ if (onceToken.isExpired()) {
+ // AuthPassword.remove(getAttributes(), onceToken);
+ return false;
+ } else {
+ // boolean wasRemoved = AuthPassword.remove(getAttributes(), onceToken);
+ return true;
+ }
+ // TODO delete expired tokens?
+ } else {
+ // TODO implement SHA
+ throw new UnsupportedOperationException(
+ "Unsupported authPassword scheme " + authPassword.getAuthScheme());
+ }
+ }
+
+ // Regular password
+// byte[] hashedPassword = hash(password, DigestUtils.PASSWORD_SCHEME_PBKDF2_SHA256);
+ if (hasCredential(LdapAttr.userPassword.name(), DirectoryDigestUtils.charsToBytes(password)))
+ return true;
+ return false;
+ }
+
+ Object storedValue = getCredentials().get(key);
+ if (storedValue == null || value == null)
+ return false;
+ if (!(value instanceof String || value instanceof byte[]))
+ return false;
+ if (storedValue instanceof String && value instanceof String)
+ return storedValue.equals(value);
+ if (storedValue instanceof byte[] && value instanceof byte[]) {
+ String storedBase64 = new String((byte[]) storedValue, US_ASCII);
+ String passwordScheme = null;
+ if (storedBase64.charAt(0) == '{') {
+ int index = storedBase64.indexOf('}');
+ if (index > 0) {
+ passwordScheme = storedBase64.substring(1, index);
+ String storedValueBase64 = storedBase64.substring(index + 1);
+ byte[] storedValueBytes = Base64.getDecoder().decode(storedValueBase64);
+ char[] passwordValue = DirectoryDigestUtils.bytesToChars((byte[]) value);
+ byte[] valueBytes;
+ if (DirectoryDigestUtils.PASSWORD_SCHEME_SHA.equals(passwordScheme)) {
+ valueBytes = DirectoryDigestUtils.toPasswordScheme(passwordScheme, passwordValue, null, null,
+ null);
+ } else if (DirectoryDigestUtils.PASSWORD_SCHEME_PBKDF2_SHA256.equals(passwordScheme)) {
+ // see https://www.thesubtlety.com/post/a-389-ds-pbkdf2-password-checker/
+ byte[] iterationsArr = Arrays.copyOfRange(storedValueBytes, 0, 4);
+ BigInteger iterations = new BigInteger(iterationsArr);
+ byte[] salt = Arrays.copyOfRange(storedValueBytes, iterationsArr.length,
+ iterationsArr.length + 64);
+ byte[] keyArr = Arrays.copyOfRange(storedValueBytes, iterationsArr.length + salt.length,
+ storedValueBytes.length);
+ int keyLengthBits = keyArr.length * 8;
+ valueBytes = DirectoryDigestUtils.toPasswordScheme(passwordScheme, passwordValue, salt,
+ iterations.intValue(), keyLengthBits);
+ } else {
+ throw new UnsupportedOperationException("Unknown password scheme " + passwordScheme);
+ }
+ return Arrays.equals(storedValueBytes, valueBytes);
+ }
+ }
+ }
+// if (storedValue instanceof byte[] && value instanceof byte[]) {
+// return Arrays.equals((byte[]) storedValue, (byte[]) value);
+// }
+ return false;
+ }
+
+ /** Hash the password */
+ private static byte[] sha1hash(char[] password) {
+ byte[] hashedPassword = ("{SHA}" + Base64.getEncoder()
+ .encodeToString(DirectoryDigestUtils.sha1(DirectoryDigestUtils.charsToBytes(password))))
+ .getBytes(StandardCharsets.UTF_8);
+ return hashedPassword;
+ }
+
+ public AbstractLdapDirectory getDirectory() {
+ return directory;
+ }
+
+ public LdapDirectoryDao getDirectoryDao() {
+ return directory.getDirectoryDao();
+ }
+
+ @Override
+ public int hashCode() {
+ return dn.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj instanceof LdapEntry) {
+ LdapEntry that = (LdapEntry) obj;
+ return this.dn.equals(that.getDn());
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return dn.toString();
+ }
+
+ private static boolean isAsciiPrintable(String str) {
+ if (str == null) {
+ return false;
+ }
+ int sz = str.length();
+ for (int i = 0; i < sz; i++) {
+ if (isAsciiPrintable(str.charAt(i)) == false) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private static boolean isAsciiPrintable(char ch) {
+ return ch >= 32 && ch < 127;
+ }
+
+ protected class AttributeDictionary extends Dictionary<String, Object> {
+ private final List<String> effectiveKeys = new ArrayList<String>();
+ private final List<String> attrFilter;
+ private final Boolean includeFilter;
+
+ public AttributeDictionary(Boolean credentials) {
+ this.attrFilter = getDirectory().getCredentialAttributeIds();
+ this.includeFilter = credentials;
+ try {
+ NamingEnumeration<String> ids = getAttributes().getIDs();
+ while (ids.hasMore()) {
+ String id = ids.next();
+ if (credentials && attrFilter.contains(id))
+ effectiveKeys.add(id);
+ else if (!credentials && !attrFilter.contains(id))
+ effectiveKeys.add(id);
+ }
+ } catch (NamingException e) {
+ throw new IllegalStateException("Cannot initialise attribute dictionary", e);
+ }
+ if (!credentials)
+ effectiveKeys.add(LdapAttr.objectClasses.name());
+ }
+
+ @Override
+ public int size() {
+ return effectiveKeys.size();
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return effectiveKeys.size() == 0;
+ }
+
+ @Override
+ public Enumeration<String> keys() {
+ return Collections.enumeration(effectiveKeys);
+ }
+
+ @Override
+ public Enumeration<Object> elements() {
+ final Iterator<String> it = effectiveKeys.iterator();
+ return new Enumeration<Object>() {
+
+ @Override
+ public boolean hasMoreElements() {
+ return it.hasNext();
+ }
+
+ @Override
+ public Object nextElement() {
+ String key = it.next();
+ return get(key);
+ }
+
+ };
+ }
+
+ @Override
+ public Object get(Object key) {
+ try {
+ Attribute attr = !key.equals(LdapAttr.objectClasses.name()) ? getAttributes().get(key.toString())
+ : getAttributes().get(LdapAttr.objectClass.name());
+ if (attr == null)
+ return null;
+ Object value = attr.get();
+ if (value instanceof byte[]) {
+ if (key.equals(LdapAttr.userPassword.name()))
+ // TODO other cases (certificates, images)
+ return value;
+ value = new String((byte[]) value, StandardCharsets.UTF_8);
+ }
+ if (attr.size() == 1)
+ return value;
+ // special case for object class
+ if (key.equals(LdapAttr.objectClass.name())) {
+ // TODO support multiple object classes
+ NamingEnumeration<?> en = attr.getAll();
+ String first = null;
+ attrs: while (en.hasMore()) {
+ String v = en.next().toString();
+ if (v.equalsIgnoreCase(LdapObj.top.name()))
+ continue attrs;
+ if (first == null)
+ first = v;
+ if (v.equalsIgnoreCase(getDirectory().getUserObjectClass()))
+ return getDirectory().getUserObjectClass();
+ else if (v.equalsIgnoreCase(getDirectory().getGroupObjectClass()))
+ return getDirectory().getGroupObjectClass();
+ }
+ if (first != null)
+ return first;
+ throw new IllegalStateException("Cannot find objectClass in " + value);
+ } else {
+ NamingEnumeration<?> en = attr.getAll();
+ StringJoiner values = new StringJoiner("\n");
+ while (en.hasMore()) {
+ String v = en.next().toString();
+ values.add(v);
+ }
+ return values.toString();
+ }
+// else
+// return value;
+ } catch (NamingException e) {
+ throw new IllegalStateException("Cannot get value for attribute " + key, e);
+ }
+ }
+
+ @Override
+ public Object put(String key, Object value) {
+ Objects.requireNonNull(value, "Value for key " + key + " is null");
+ try {
+ if (key == null) {
+ // FIXME remove this "feature", a key should be specified
+ // TODO persist to other sources (like PKCS12)
+ char[] password = DirectoryDigestUtils.bytesToChars(value);
+ byte[] hashedPassword = sha1hash(password);
+ return put(LdapAttr.userPassword.name(), hashedPassword);
+ }
+ if (key.startsWith("X-")) {
+ return put(LdapAttr.authPassword.name(), value);
+ }
+
+ // start editing
+ getDirectory().checkEdit();
+ if (!isEditing())
+ startEditing();
+
+ // object classes special case.
+ if (key.equals(LdapAttr.objectClasses.name())) {
+ Attribute attribute = new BasicAttribute(LdapAttr.objectClass.name());
+ String[] objectClasses = value.toString().split("\n");
+ for (String objectClass : objectClasses) {
+ if (objectClass.trim().equals(""))
+ continue;
+ attribute.add(objectClass);
+ }
+ Attribute previousAttribute = getModifiedAttributes().put(attribute);
+ if (previousAttribute != null)
+ return previousAttribute.get();
+ else
+ return null;
+ }
+
+ if (!(value instanceof String || value instanceof byte[]))
+ throw new IllegalArgumentException("Value must be String or byte[]");
+
+ if (includeFilter && !attrFilter.contains(key))
+ throw new IllegalArgumentException("Key " + key + " not included");
+ else if (!includeFilter && attrFilter.contains(key))
+ throw new IllegalArgumentException("Key " + key + " excluded");
+
+ Attribute attribute = getModifiedAttributes().get(key.toString());
+ // if (attribute == null) // block unit tests
+ attribute = new BasicAttribute(key.toString());
+ if (value instanceof String && !isAsciiPrintable(((String) value)))
+ attribute.add(((String) value).getBytes(StandardCharsets.UTF_8));
+ else
+ attribute.add(value);
+ Attribute previousAttribute = getModifiedAttributes().put(attribute);
+ if (previousAttribute != null)
+ return previousAttribute.get();
+ else
+ return null;
+ } catch (NamingException e) {
+ throw new IllegalStateException("Cannot get value for attribute " + key, e);
+ }
+ }
+
+ @Override
+ public Object remove(Object key) {
+ getDirectory().checkEdit();
+ if (!isEditing())
+ startEditing();
+
+ if (includeFilter && !attrFilter.contains(key))
+ throw new IllegalArgumentException("Key " + key + " not included");
+ else if (!includeFilter && attrFilter.contains(key))
+ throw new IllegalArgumentException("Key " + key + " excluded");
+
+ try {
+ Attribute attr = getModifiedAttributes().remove(key.toString());
+ if (attr != null)
+ return attr.get();
+ else
+ return null;
+ } catch (NamingException e) {
+ throw new IllegalStateException("Cannot remove attribute " + key, e);
+ }
+ }
+
+ }
+
+}
--- /dev/null
+package org.argeo.cms.directory.ldap;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.StringJoiner;
+
+import javax.naming.InvalidNameException;
+import javax.naming.ldap.LdapName;
+
+import org.argeo.api.acr.ldap.LdapAttr;
+import org.argeo.cms.dns.DnsBrowser;
+import org.argeo.cms.runtime.DirectoryConf;
+
+/** Free IPA specific conventions. */
+public class IpaUtils {
+ public final static String IPA_USER_BASE = "cn=users";
+ public final static String IPA_GROUP_BASE = "cn=groups";
+ public final static String IPA_ROLE_BASE = "cn=roles";
+ public final static String IPA_SERVICE_BASE = "cn=services";
+
+ public final static String IPA_ACCOUNTS_BASE = "cn=accounts";
+
+ private final static String KRB_PRINCIPAL_NAME = LdapAttr.krbPrincipalName.name().toLowerCase();
+
+ public final static String IPA_USER_DIRECTORY_CONFIG = DirectoryConf.userBase + "=" + IPA_USER_BASE + "&"
+ + DirectoryConf.groupBase + "=" + IPA_GROUP_BASE + "&" + DirectoryConf.systemRoleBase + "=" + IPA_ROLE_BASE
+ + "&" + DirectoryConf.readOnly + "=true";
+
+ @Deprecated
+ static String domainToUserDirectoryConfigPath(String realm) {
+ return domainToBaseDn(realm) + "?" + IPA_USER_DIRECTORY_CONFIG + "&" + DirectoryConf.realm.name() + "=" + realm;
+ }
+
+ public static void addIpaConfig(String realm, Dictionary<String, Object> properties) {
+ properties.put(DirectoryConf.baseDn.name(), domainToBaseDn(realm));
+ properties.put(DirectoryConf.realm.name(), realm);
+ properties.put(DirectoryConf.userBase.name(), IPA_USER_BASE);
+ properties.put(DirectoryConf.groupBase.name(), IPA_GROUP_BASE);
+ properties.put(DirectoryConf.systemRoleBase.name(), IPA_ROLE_BASE);
+ properties.put(DirectoryConf.readOnly.name(), Boolean.TRUE.toString());
+ }
+
+ public static String domainToBaseDn(String domain) {
+ String[] dcs = domain.split("\\.");
+ StringJoiner sj = new StringJoiner(",");
+ for (int i = 0; i < dcs.length; i++) {
+ String dc = dcs[i];
+ sj.add(LdapAttr.dc.name() + '=' + dc.toLowerCase());
+ }
+ return IPA_ACCOUNTS_BASE + ',' + sj.toString();
+ }
+
+ public static LdapName kerberosToDn(String kerberosName) {
+ String[] kname = kerberosName.split("@");
+ String username = kname[0];
+ String baseDn = domainToBaseDn(kname[1]);
+ String dn;
+ if (!username.contains("/"))
+ dn = LdapAttr.uid + "=" + username + "," + IPA_USER_BASE + "," + baseDn;
+ else
+ dn = KRB_PRINCIPAL_NAME + "=" + kerberosName + "," + IPA_SERVICE_BASE + "," + baseDn;
+ try {
+ return new LdapName(dn);
+ } catch (InvalidNameException e) {
+ throw new IllegalArgumentException("Badly formatted name for " + kerberosName + ": " + dn);
+ }
+ }
+
+ private IpaUtils() {
+
+ }
+
+ public static String kerberosDomainFromDns() {
+ String kerberosDomain;
+ try (DnsBrowser dnsBrowser = new DnsBrowser()) {
+ // TODO retrieve hostname from CMS config
+ InetAddress localhost = InetAddress.getLocalHost();
+ String hostname = localhost.getHostName();
+ int dotIndex = hostname.indexOf('.');
+ if (dotIndex <= 0) {
+ hostname = localhost.getCanonicalHostName();
+ dotIndex = hostname.indexOf('.');
+ if (dotIndex <= 0)
+ throw new IllegalArgumentException(
+ "Cannot extract DNS zone from hostname " + hostname + " (" + localhost + ")");
+ }
+ String dnsZone = hostname.substring(dotIndex + 1);
+ kerberosDomain = dnsBrowser.getRecord("_kerberos." + dnsZone, "TXT");
+ return kerberosDomain;
+ } catch (IOException e) {
+ throw new IllegalStateException("Cannot determine Kerberos domain from DNS", e);
+ }
+
+ }
+
+ public static Dictionary<String, Object> convertIpaUri(URI uri) {
+ String path = uri.getPath();
+ String kerberosRealm;
+ if (path == null || path.length() <= 1) {
+ kerberosRealm = kerberosDomainFromDns();
+ } else {
+ kerberosRealm = path.substring(1);
+ }
+
+ if (kerberosRealm == null)
+ throw new IllegalStateException("No Kerberos domain available for " + uri);
+ // TODO intergrate CA certificate in truststore
+ // String schemeToUse = SCHEME_LDAPS;
+ String schemeToUse = DirectoryConf.SCHEME_LDAP;
+ List<String> ldapHosts;
+ String ldapHostsStr = uri.getHost();
+ if (ldapHostsStr == null || ldapHostsStr.trim().equals("")) {
+ try (DnsBrowser dnsBrowser = new DnsBrowser()) {
+ ldapHosts = dnsBrowser.getSrvRecordsAsHosts("_ldap._tcp." + kerberosRealm.toLowerCase(),
+ schemeToUse.equals(DirectoryConf.SCHEME_LDAP) ? true : false);
+ if (ldapHosts == null || ldapHosts.size() == 0) {
+ throw new IllegalStateException("Cannot configure LDAP for IPA " + uri);
+ } else {
+ ldapHostsStr = ldapHosts.get(0);
+ }
+ } catch (IOException e) {
+ throw new IllegalStateException("Cannot convert IPA uri " + uri, e);
+ }
+ } else {
+ ldapHosts = new ArrayList<>();
+ ldapHosts.add(ldapHostsStr);
+ }
+
+ StringBuilder uriStr = new StringBuilder();
+ try {
+ for (String host : ldapHosts) {
+ URI convertedUri = new URI(schemeToUse + "://" + host + "/");
+ uriStr.append(convertedUri).append(' ');
+ }
+ } catch (URISyntaxException e) {
+ throw new IllegalStateException("Cannot convert IPA uri " + uri, e);
+ }
+
+ Hashtable<String, Object> res = new Hashtable<>();
+ res.put(DirectoryConf.uri.name(), uriStr.toString());
+ addIpaConfig(kerberosRealm, res);
+ return res;
+ }
+}
--- /dev/null
+package org.argeo.cms.directory.ldap;
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+
+import javax.naming.CommunicationException;
+import javax.naming.Context;
+import javax.naming.NameNotFoundException;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.DirContext;
+import javax.naming.directory.SearchControls;
+import javax.naming.directory.SearchResult;
+import javax.naming.ldap.InitialLdapContext;
+import javax.naming.ldap.LdapName;
+
+import org.argeo.api.acr.ldap.LdapAttr;
+import org.argeo.api.cms.transaction.WorkingCopy;
+
+/** A synchronized wrapper for a single {@link InitialLdapContext}. */
+// TODO implement multiple contexts and connection pooling.
+public class LdapConnection {
+ private InitialLdapContext initialLdapContext = null;
+
+ public LdapConnection(String url, Dictionary<String, ?> properties) {
+ try {
+ Hashtable<String, Object> connEnv = new Hashtable<String, Object>();
+ connEnv.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
+ connEnv.put(Context.PROVIDER_URL, url);
+ connEnv.put("java.naming.ldap.attributes.binary", LdapAttr.userPassword.name());
+ // use pooling in order to avoid connection timeout
+// connEnv.put("com.sun.jndi.ldap.connect.pool", "true");
+// connEnv.put("com.sun.jndi.ldap.connect.pool.timeout", 300000);
+
+ initialLdapContext = new InitialLdapContext(connEnv, null);
+ // StartTlsResponse tls = (StartTlsResponse) ctx
+ // .extendedOperation(new StartTlsRequest());
+ // tls.negotiate();
+ Object securityAuthentication = properties.get(Context.SECURITY_AUTHENTICATION);
+ if (securityAuthentication != null)
+ initialLdapContext.addToEnvironment(Context.SECURITY_AUTHENTICATION, securityAuthentication);
+ else
+ initialLdapContext.addToEnvironment(Context.SECURITY_AUTHENTICATION, "simple");
+ Object principal = properties.get(Context.SECURITY_PRINCIPAL);
+ if (principal != null) {
+ initialLdapContext.addToEnvironment(Context.SECURITY_PRINCIPAL, principal.toString());
+ Object creds = properties.get(Context.SECURITY_CREDENTIALS);
+ if (creds != null) {
+ initialLdapContext.addToEnvironment(Context.SECURITY_CREDENTIALS, creds.toString());
+ }
+ }
+ } catch (NamingException e) {
+ throw new IllegalStateException("Cannot connect to LDAP", e);
+ }
+
+ }
+
+ public void init() {
+
+ }
+
+ public void destroy() {
+ try {
+ // tls.close();
+ initialLdapContext.close();
+ initialLdapContext = null;
+ } catch (NamingException e) {
+ e.printStackTrace();
+ }
+ }
+
+ protected InitialLdapContext getLdapContext() {
+ return initialLdapContext;
+ }
+
+ protected void reconnect() throws NamingException {
+ initialLdapContext.reconnect(initialLdapContext.getConnectControls());
+ }
+
+ public synchronized NamingEnumeration<SearchResult> search(LdapName searchBase, String searchFilter,
+ SearchControls searchControls) throws NamingException {
+ NamingEnumeration<SearchResult> results;
+ try {
+ results = getLdapContext().search(searchBase, searchFilter, searchControls);
+ } catch (CommunicationException e) {
+ reconnect();
+ results = getLdapContext().search(searchBase, searchFilter, searchControls);
+ }
+ return results;
+ }
+
+ public synchronized Attributes getAttributes(LdapName name) throws NamingException {
+ try {
+ return getLdapContext().getAttributes(name);
+ } catch (CommunicationException e) {
+ reconnect();
+ return getLdapContext().getAttributes(name);
+ }
+ }
+
+ public synchronized boolean entryExists(LdapName name) throws NamingException {
+ String[] noAttrOID = new String[] { "1.1" };
+ try {
+ getLdapContext().getAttributes(name, noAttrOID);
+ return true;
+ } catch (CommunicationException e) {
+ reconnect();
+ getLdapContext().getAttributes(name, noAttrOID);
+ return true;
+ } catch (NameNotFoundException e) {
+ return false;
+ }
+ }
+
+ public synchronized void prepareChanges(WorkingCopy<?, ?, LdapName> wc) throws NamingException {
+ // make sure connection will work
+ reconnect();
+
+ // delete
+ for (LdapName dn : wc.getDeletedData().keySet()) {
+ if (!entryExists(dn))
+ throw new IllegalStateException("User to delete no found " + dn);
+ }
+ // add
+ for (LdapName dn : wc.getNewData().keySet()) {
+ if (entryExists(dn))
+ throw new IllegalStateException("User to create found " + dn);
+ }
+ // modify
+ for (LdapName dn : wc.getModifiedData().keySet()) {
+ if (!wc.getNewData().containsKey(dn) && !entryExists(dn))
+ throw new IllegalStateException("User to modify not found " + dn);
+ }
+
+ }
+
+// protected boolean entryExists(LdapName dn) throws NamingException {
+// try {
+// return getAttributes(dn).size() != 0;
+// } catch (NameNotFoundException e) {
+// return false;
+// }
+// }
+
+ public synchronized void commitChanges(LdapEntryWorkingCopy wc) throws NamingException {
+ // delete
+ for (LdapName dn : wc.getDeletedData().keySet()) {
+ getLdapContext().destroySubcontext(dn);
+ }
+ // add
+ for (LdapName dn : wc.getNewData().keySet()) {
+ LdapEntry user = wc.getNewData().get(dn);
+ getLdapContext().createSubcontext(dn, user.getAttributes());
+ }
+ // modify
+ for (LdapName dn : wc.getModifiedData().keySet()) {
+ Attributes modifiedAttrs = wc.getModifiedData().get(dn);
+ getLdapContext().modifyAttributes(dn, DirContext.REPLACE_ATTRIBUTE, modifiedAttrs);
+ }
+ }
+}
--- /dev/null
+package org.argeo.cms.directory.ldap;
+
+import static org.argeo.api.acr.ldap.LdapAttr.objectClass;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.naming.AuthenticationNotSupportedException;
+import javax.naming.Binding;
+import javax.naming.InvalidNameException;
+import javax.naming.NameNotFoundException;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.BasicAttributes;
+import javax.naming.directory.SearchControls;
+import javax.naming.directory.SearchResult;
+import javax.naming.ldap.LdapName;
+import javax.naming.ldap.Rdn;
+
+import org.argeo.api.acr.ldap.LdapAttr;
+import org.argeo.api.acr.ldap.LdapObj;
+import org.argeo.api.cms.directory.HierarchyUnit;
+
+/** A user admin based on a LDAP server. */
+public class LdapDao extends AbstractLdapDirectoryDao {
+ private LdapConnection ldapConnection;
+
+ public LdapDao(AbstractLdapDirectory directory) {
+ super(directory);
+ }
+
+ @Override
+ public void init() {
+ ldapConnection = new LdapConnection(getDirectory().getUri().toString(), getDirectory().cloneConfigProperties());
+ }
+
+ public void destroy() {
+ ldapConnection.destroy();
+ }
+
+ @Override
+ public boolean checkConnection() {
+ try {
+ return ldapConnection.entryExists(getDirectory().getBaseDn());
+ } catch (NamingException e) {
+ return false;
+ }
+ }
+
+ @Override
+ public boolean entryExists(LdapName dn) {
+ try {
+ return ldapConnection.entryExists(dn);
+ } catch (NameNotFoundException e) {
+ return false;
+ } catch (NamingException e) {
+ throw new IllegalStateException("Cannot check " + dn, e);
+ }
+ }
+
+ @Override
+ public LdapEntry doGetEntry(LdapName name) throws NameNotFoundException {
+// if (!entryExists(name))
+// throw new NameNotFoundException(name + " was not found in " + getDirectory().getBaseDn());
+ try {
+ Attributes attrs = ldapConnection.getAttributes(name);
+
+ LdapEntry res;
+ Rdn technicalRdn = LdapNameUtils.getParentRdn(name);
+ if (getDirectory().getGroupBaseRdn().equals(technicalRdn)) {
+ if (attrs.size() == 0) {// exists but not accessible
+ attrs = new BasicAttributes();
+ attrs.put(LdapAttr.objectClass.name(), LdapObj.top.name());
+ attrs.put(LdapAttr.objectClass.name(), getDirectory().getGroupObjectClass());
+ }
+ res = newGroup(name);
+ } else if (getDirectory().getSystemRoleBaseRdn().equals(technicalRdn)) {
+ if (attrs.size() == 0) {// exists but not accessible
+ attrs = new BasicAttributes();
+ attrs.put(LdapAttr.objectClass.name(), LdapObj.top.name());
+ attrs.put(LdapAttr.objectClass.name(), getDirectory().getGroupObjectClass());
+ }
+ res = newGroup(name);
+ } else if (getDirectory().getUserBaseRdn().equals(technicalRdn)) {
+ if (attrs.size() == 0) {// exists but not accessible
+ attrs = new BasicAttributes();
+ attrs.put(LdapAttr.objectClass.name(), LdapObj.top.name());
+ attrs.put(LdapAttr.objectClass.name(), getDirectory().getUserObjectClass());
+ }
+ res = newUser(name);
+ } else {
+ res = new DefaultLdapEntry(getDirectory(), name);
+ }
+ return res;
+ } catch (NameNotFoundException e) {
+ throw e;
+ } catch (NamingException e) {
+ throw new IllegalStateException("Cannot retrieve entry " + name, e);
+ }
+ }
+
+ @Override
+ public Attributes doGetAttributes(LdapName name) {
+ try {
+ Attributes attrs = ldapConnection.getAttributes(name);
+ return attrs;
+ } catch (NamingException e) {
+ throw new IllegalStateException("Cannot get attributes for " + name);
+ }
+ }
+
+ @Override
+ public List<LdapEntry> doGetEntries(LdapName searchBase, String f, boolean deep) {
+ ArrayList<LdapEntry> res = new ArrayList<>();
+ try {
+ String searchFilter = f != null ? f.toString()
+ : "(|(" + objectClass.name() + "=" + getDirectory().getUserObjectClass() + ")(" + objectClass.name()
+ + "=" + getDirectory().getGroupObjectClass() + "))";
+ SearchControls searchControls = new SearchControls();
+ // only attribute needed is objectClass
+ searchControls.setReturningAttributes(new String[] { objectClass.name() });
+ // FIXME make one level consistent with deep
+ searchControls.setSearchScope(deep ? SearchControls.SUBTREE_SCOPE : SearchControls.ONELEVEL_SCOPE);
+
+ // LdapName searchBase = getBaseDn();
+ NamingEnumeration<SearchResult> results = ldapConnection.search(searchBase, searchFilter, searchControls);
+
+ results: while (results.hasMoreElements()) {
+ SearchResult searchResult = results.next();
+ Attributes attrs = searchResult.getAttributes();
+ Attribute objectClassAttr = attrs.get(objectClass.name());
+ LdapName dn = toDn(searchBase, searchResult);
+ LdapEntry role;
+ if (objectClassAttr.contains(getDirectory().getGroupObjectClass())
+ || objectClassAttr.contains(getDirectory().getGroupObjectClass().toLowerCase()))
+ role = newGroup(dn);
+ else if (objectClassAttr.contains(getDirectory().getUserObjectClass())
+ || objectClassAttr.contains(getDirectory().getUserObjectClass().toLowerCase()))
+ role = newUser(dn);
+ else {
+// log.warn("Unsupported LDAP type for " + searchResult.getName());
+ continue results;
+ }
+ res.add(role);
+ }
+ return res;
+ } catch (AuthenticationNotSupportedException e) {
+ // ignore (typically an unsupported anonymous bind)
+ // TODO better logging
+ return res;
+ } catch (NamingException e) {
+ throw new IllegalStateException("Cannot get roles for filter " + f, e);
+ }
+ }
+
+ private LdapName toDn(LdapName baseDn, Binding binding) throws InvalidNameException {
+ return new LdapName(binding.isRelative() ? binding.getName() + "," + baseDn : binding.getName());
+ }
+
+ @Override
+ public List<LdapName> getDirectGroups(LdapName dn) {
+ List<LdapName> directGroups = new ArrayList<LdapName>();
+ try {
+ String searchFilter = "(&(" + objectClass + "=" + getDirectory().getGroupObjectClass() + ")("
+ + getDirectory().getMemberAttributeId() + "=" + dn + "))";
+
+ SearchControls searchControls = new SearchControls();
+ searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
+
+ LdapName searchBase = getDirectory().getBaseDn();
+ NamingEnumeration<SearchResult> results = ldapConnection.search(searchBase, searchFilter, searchControls);
+
+ while (results.hasMoreElements()) {
+ SearchResult searchResult = (SearchResult) results.nextElement();
+ directGroups.add(toDn(searchBase, searchResult));
+ }
+ return directGroups;
+ } catch (NamingException e) {
+ throw new IllegalStateException("Cannot populate direct members of " + dn, e);
+ }
+ }
+
+ @Override
+ public void prepare(LdapEntryWorkingCopy wc) {
+ try {
+ ldapConnection.prepareChanges(wc);
+ } catch (NamingException e) {
+ throw new IllegalStateException("Cannot prepare LDAP", e);
+ }
+ }
+
+ @Override
+ public void commit(LdapEntryWorkingCopy wc) {
+ try {
+ ldapConnection.commitChanges(wc);
+ } catch (NamingException e) {
+ throw new IllegalStateException("Cannot commit LDAP", e);
+ }
+ }
+
+ @Override
+ public void rollback(LdapEntryWorkingCopy wc) {
+ // prepare not impacting
+ }
+
+ /*
+ * HIERARCHY
+ */
+
+ @Override
+ public Iterable<HierarchyUnit> doGetDirectHierarchyUnits(LdapName searchBase, boolean functionalOnly) {
+ List<HierarchyUnit> res = new ArrayList<>();
+ try {
+ String structuralFilter = functionalOnly ? ""
+ : "(" + getDirectory().getUserBaseRdn() + ")(" + getDirectory().getGroupBaseRdn() + ")("
+ + getDirectory().getSystemRoleBaseRdn() + ")";
+ String searchFilter = "(|(" + objectClass + "=" + LdapObj.organizationalUnit.name() + ")(" + objectClass
+ + "=" + LdapObj.organization.name() + ")" + structuralFilter + ")";
+
+ SearchControls searchControls = new SearchControls();
+ searchControls.setSearchScope(SearchControls.ONELEVEL_SCOPE);
+ // no attributes needed
+ searchControls.setReturningAttributes(new String[0]);
+
+ NamingEnumeration<SearchResult> results = ldapConnection.search(searchBase, searchFilter, searchControls);
+
+ while (results.hasMoreElements()) {
+ SearchResult searchResult = (SearchResult) results.nextElement();
+ LdapName dn = toDn(searchBase, searchResult);
+// Attributes attrs = searchResult.getAttributes();
+ LdapHierarchyUnit hierarchyUnit = new LdapHierarchyUnit(getDirectory(), dn);
+ if (functionalOnly) {
+ if (hierarchyUnit.isFunctional())
+ res.add(hierarchyUnit);
+ } else {
+ res.add(hierarchyUnit);
+ }
+ }
+ return res;
+ } catch (NamingException e) {
+ throw new IllegalStateException("Cannot get direct hierarchy units ", e);
+ }
+ }
+
+ @Override
+ public HierarchyUnit doGetHierarchyUnit(LdapName dn) {
+ try {
+ if (getDirectory().getBaseDn().equals(dn))
+ return getDirectory();
+ if (!dn.startsWith(getDirectory().getBaseDn()))
+ throw new IllegalArgumentException(dn + " does not start with base DN " + getDirectory().getBaseDn());
+ if (!ldapConnection.entryExists(dn))
+ return null;
+ return new LdapHierarchyUnit(getDirectory(), dn);
+ } catch (NameNotFoundException e) {
+ return null;
+ } catch (NamingException e) {
+ throw new IllegalStateException("Cannot get hierarchy unit " + dn, e);
+ }
+ }
+
+}
--- /dev/null
+package org.argeo.cms.directory.ldap;
+
+import java.util.List;
+
+import javax.naming.NameNotFoundException;
+import javax.naming.directory.Attributes;
+import javax.naming.ldap.LdapName;
+
+import org.argeo.api.cms.directory.HierarchyUnit;
+import org.argeo.api.cms.transaction.WorkingCopyProcessor;
+
+/** Low-level access to an LDAP/LDIF directory. */
+public interface LdapDirectoryDao extends WorkingCopyProcessor<LdapEntryWorkingCopy> {
+ boolean checkConnection();
+
+ boolean entryExists(LdapName dn);
+
+ LdapEntry doGetEntry(LdapName name) throws NameNotFoundException;
+
+ Attributes doGetAttributes(LdapName name);
+
+ List<LdapEntry> doGetEntries(LdapName searchBase, String filter, boolean deep);
+
+ List<LdapName> getDirectGroups(LdapName dn);
+
+ Iterable<HierarchyUnit> doGetDirectHierarchyUnits(LdapName searchBase, boolean functionalOnly);
+
+ HierarchyUnit doGetHierarchyUnit(LdapName dn);
+
+ LdapEntry newUser(LdapName name);
+
+ LdapEntry newGroup(LdapName name);
+
+ void init();
+
+ void destroy();
+}
--- /dev/null
+package org.argeo.cms.directory.ldap;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Dictionary;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+import java.util.StringJoiner;
+import java.util.TreeSet;
+
+import javax.naming.directory.Attributes;
+import javax.naming.ldap.LdapName;
+
+import org.argeo.api.acr.ldap.LdapAttr;
+
+/** An LDAP entry. */
+public interface LdapEntry {
+ LdapName getDn();
+
+ Attributes getAttributes();
+
+ void publishAttributes(Attributes modifiedAttributes);
+
+ List<LdapName> getReferences(String attributeId);
+
+ Dictionary<String, Object> getProperties();
+
+ boolean hasCredential(String key, Object value);
+
+ /*
+ * UTILITIES
+ */
+ /**
+ * Convert a collection of object classes to the format expected by an LDAP
+ * backend.
+ */
+ public static void addObjectClasses(Dictionary<String, Object> properties, Collection<String> objectClasses) {
+ String value = properties.get(LdapAttr.objectClasses.name()).toString();
+ Set<String> currentObjectClasses = new TreeSet<>(Arrays.asList(value.toString().split("\n")));
+ currentObjectClasses.addAll(objectClasses);
+ StringJoiner values = new StringJoiner("\n");
+ currentObjectClasses.forEach((s) -> values.add(s));
+ properties.put(LdapAttr.objectClasses.name(), values.toString());
+ }
+
+ public static Object getLocalized(Dictionary<String, Object> properties, String key, Locale locale) {
+ if (locale == null)
+ return null;
+ Object value = null;
+ value = properties.get(key + ";lang-" + locale.getLanguage() + "-" + locale.getCountry());
+ if (value == null)
+ value = properties.get(key + ";lang-" + locale.getLanguage());
+ return value;
+ }
+
+ public static String toLocalizedKey(String key, Locale locale) {
+ String country = locale.getCountry();
+ if ("".equals(country)) {
+ return key + ";lang-" + locale.getLanguage();
+ } else {
+ return key + ";lang-" + locale.getLanguage() + "-" + locale.getCountry();
+ }
+ }
+}
--- /dev/null
+package org.argeo.cms.directory.ldap;
+
+import javax.naming.directory.Attributes;
+import javax.naming.ldap.LdapName;
+
+import org.argeo.api.cms.transaction.AbstractWorkingCopy;
+
+/** Working copy for a user directory being edited. */
+public class LdapEntryWorkingCopy extends AbstractWorkingCopy<LdapEntry, Attributes, LdapName> {
+ @Override
+ protected LdapName getId(LdapEntry entry) {
+ return (LdapName) entry.getDn().clone();
+ }
+
+ @Override
+ protected Attributes cloneAttributes(LdapEntry entry) {
+ return (Attributes) entry.getAttributes().clone();
+ }
+}
--- /dev/null
+package org.argeo.cms.directory.ldap;
+
+import java.util.Locale;
+
+import javax.naming.ldap.LdapName;
+import javax.naming.ldap.Rdn;
+
+import org.argeo.api.cms.directory.HierarchyUnit;
+
+/** LDIF/LDAP based implementation of {@link HierarchyUnit}. */
+public class LdapHierarchyUnit extends DefaultLdapEntry implements HierarchyUnit {
+// private final boolean functional;
+
+ private final Type type;
+
+ public LdapHierarchyUnit(AbstractLdapDirectory directory, LdapName dn) {
+ super(directory, dn);
+
+ Rdn rdn = LdapNameUtils.getLastRdn(dn);
+ if (directory.getUserBaseRdn().equals(rdn))
+ type = Type.PEOPLE;
+ else if (directory.getGroupBaseRdn().equals(rdn))
+ type = Type.GROUPS;
+ else if (directory.getSystemRoleBaseRdn().equals(rdn))
+ type = Type.ROLES;
+ else
+ type = Type.FUNCTIONAL;
+// functional = !(directory.getUserBaseRdn().equals(rdn) || directory.getGroupBaseRdn().equals(rdn)
+// || directory.getSystemRoleBaseRdn().equals(rdn));
+ }
+
+ @Override
+ public HierarchyUnit getParent() {
+ return getDirectoryDao().doGetHierarchyUnit(LdapNameUtils.getParent(getDn()));
+ }
+
+ @Override
+ public Iterable<HierarchyUnit> getDirectHierarchyUnits(boolean functionalOnly) {
+ return getDirectoryDao().doGetDirectHierarchyUnits(getDn(), functionalOnly);
+ }
+
+ @Override
+ public HierarchyUnit getDirectChild(Type type) {
+ return switch (type) {
+ case ROLES ->
+ getDirectoryDao().doGetHierarchyUnit((LdapName) getDn().add(getDirectory().getSystemRoleBaseRdn()));
+ case PEOPLE -> getDirectoryDao().doGetHierarchyUnit((LdapName) getDn().add(getDirectory().getUserBaseRdn()));
+ case GROUPS -> getDirectoryDao().doGetHierarchyUnit((LdapName) getDn().add(getDirectory().getGroupBaseRdn()));
+ case FUNCTIONAL -> throw new IllegalArgumentException("Type must be a technical type");
+ };
+ }
+
+ @Override
+ public boolean isType(Type type) {
+ return this.type.equals(type);
+ }
+
+ @Override
+ public String getHierarchyUnitName() {
+ String name = LdapNameUtils.getLastRdnValue(getDn());
+ // TODO check ou, o, etc.
+ return name;
+ }
+
+ @Override
+ public String getHierarchyUnitLabel(Locale locale) {
+ String key = LdapNameUtils.getLastRdn(getDn()).getType();
+ Object value = LdapEntry.getLocalized(getProperties(), key, locale);
+ if (value == null)
+ value = getHierarchyUnitName();
+ assert value != null;
+ return value.toString();
+ }
+
+ @Override
+ public String getBase() {
+ return getDn().toString();
+ }
+
+ @Override
+ public String toString() {
+ return "Hierarchy Unit " + getDn().toString();
+ }
+
+}
--- /dev/null
+package org.argeo.cms.directory.ldap;
+
+import javax.naming.InvalidNameException;
+import javax.naming.ldap.LdapName;
+import javax.naming.ldap.Rdn;
+
+/** Utilities to simplify using {@link LdapName}. */
+public class LdapNameUtils {
+
+ public static LdapName relativeName(LdapName prefix, LdapName dn) {
+ try {
+ if (!dn.startsWith(prefix))
+ throw new IllegalArgumentException("Prefix " + prefix + " not consistent with " + dn);
+ LdapName res = (LdapName) dn.clone();
+ for (int i = 0; i < prefix.size(); i++) {
+ res.remove(0);
+ }
+ return res;
+ } catch (InvalidNameException e) {
+ throw new IllegalStateException("Cannot find realtive name", e);
+ }
+ }
+
+ public static LdapName getParent(LdapName dn) {
+ try {
+ LdapName parent = (LdapName) dn.clone();
+ parent.remove(parent.size() - 1);
+ return parent;
+ } catch (InvalidNameException e) {
+ throw new IllegalArgumentException("Cannot get parent of " + dn, e);
+ }
+ }
+
+ public static Rdn getParentRdn(LdapName dn) {
+ if (dn.size() < 2)
+ throw new IllegalArgumentException(dn + " has no parent");
+ Rdn parentRdn = dn.getRdn(dn.size() - 2);
+ return parentRdn;
+ }
+
+ public static LdapName toLdapName(String distinguishedName) {
+ try {
+ return new LdapName(distinguishedName);
+ } catch (InvalidNameException e) {
+ throw new IllegalArgumentException("Cannot parse " + distinguishedName + " as LDAP name", e);
+ }
+ }
+
+ public static Rdn getLastRdn(LdapName dn) {
+ return dn.getRdn(dn.size() - 1);
+ }
+
+ public static String getLastRdnAsString(LdapName dn) {
+ return getLastRdn(dn).toString();
+ }
+
+ public static String getLastRdnValue(String dn) {
+ return getLastRdnValue(toLdapName(dn));
+ }
+
+ public static String getLastRdnValue(LdapName dn) {
+ return getLastRdn(dn).getValue().toString();
+ }
+
+ /** singleton */
+ private LdapNameUtils() {
+
+ }
+}
--- /dev/null
+package org.argeo.cms.directory.ldap;
+
+import static org.argeo.api.acr.ldap.LdapAttr.objectClass;
+import static org.argeo.api.acr.ldap.LdapObj.inetOrgPerson;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.NavigableMap;
+import java.util.Objects;
+import java.util.Set;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+import javax.naming.NameNotFoundException;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attributes;
+import javax.naming.ldap.LdapName;
+
+import org.argeo.api.acr.ldap.LdapObj;
+import org.argeo.api.cms.directory.HierarchyUnit;
+import org.osgi.framework.Filter;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.service.useradmin.Role;
+
+/** A user admin based on a LDIF files. */
+public class LdifDao extends AbstractLdapDirectoryDao {
+ private NavigableMap<LdapName, LdapEntry> entries = new TreeMap<>();
+ private NavigableMap<LdapName, LdapHierarchyUnit> hierarchy = new TreeMap<>();
+
+ private NavigableMap<LdapName, Attributes> values = new TreeMap<>();
+
+ public LdifDao(AbstractLdapDirectory directory) {
+ super(directory);
+ }
+
+ public void init() {
+ String uri = getDirectory().getUri();
+ if (uri == null)
+ return;
+ try {
+ URI u = new URI(uri);
+ if (u.getScheme().equals("file")) {
+ File file = new File(u);
+ if (!file.exists())
+ return;
+ }
+ load(u.toURL().openStream());
+ } catch (IOException | URISyntaxException e) {
+ throw new IllegalStateException("Cannot open URL " + getDirectory().getUri(), e);
+ }
+ }
+
+ public void save() {
+ if (getDirectory().getUri() == null)
+ return; // ignore
+ if (getDirectory().isReadOnly())
+ throw new IllegalStateException(
+ "Cannot save LDIF user admin: " + getDirectory().getUri() + " is read-only");
+ try (FileOutputStream out = new FileOutputStream(new File(new URI(getDirectory().getUri())))) {
+ save(out);
+ } catch (IOException | URISyntaxException e) {
+ throw new IllegalStateException("Cannot save user admin to " + getDirectory().getUri(), e);
+ }
+ }
+
+ public void save(OutputStream out) throws IOException {
+ try {
+ LdifWriter ldifWriter = new LdifWriter(out);
+ for (LdapName name : hierarchy.keySet())
+ ldifWriter.writeEntry(name, hierarchy.get(name).getAttributes());
+ for (LdapName name : entries.keySet())
+ ldifWriter.writeEntry(name, entries.get(name).getAttributes());
+ } finally {
+ out.close();
+ }
+ }
+
+ public void load(InputStream in) {
+ try {
+ entries.clear();
+ hierarchy.clear();
+
+ LdifParser ldifParser = new LdifParser();
+ SortedMap<LdapName, Attributes> allEntries = ldifParser.read(in);
+ for (LdapName key : allEntries.keySet()) {
+ Attributes attributes = allEntries.get(key);
+ // check for inconsistency
+ Set<String> lowerCase = new HashSet<String>();
+ NamingEnumeration<String> ids = attributes.getIDs();
+ while (ids.hasMoreElements()) {
+ String id = ids.nextElement().toLowerCase();
+ if (lowerCase.contains(id))
+ throw new IllegalStateException(key + " has duplicate id " + id);
+ lowerCase.add(id);
+ }
+
+ values.put(key, attributes);
+
+ // analyse object classes
+ NamingEnumeration<?> objectClasses = attributes.get(objectClass.name()).getAll();
+ // System.out.println(key);
+ objectClasses: while (objectClasses.hasMore()) {
+ String objectClass = objectClasses.next().toString();
+ // System.out.println(" " + objectClass);
+ if (objectClass.toLowerCase().equals(inetOrgPerson.name().toLowerCase())) {
+ entries.put(key, newUser(key));
+ break objectClasses;
+ } else if (objectClass.toLowerCase().equals(getDirectory().getGroupObjectClass().toLowerCase())) {
+ entries.put(key, newGroup(key));
+ break objectClasses;
+ } else if (objectClass.equalsIgnoreCase(LdapObj.organizationalUnit.name())) {
+ // TODO skip if it does not contain groups or users
+ hierarchy.put(key, new LdapHierarchyUnit(getDirectory(), key));
+ break objectClasses;
+ }
+ }
+ }
+
+ } catch (NamingException | IOException e) {
+ throw new IllegalStateException("Cannot load user admin service from LDIF", e);
+ }
+ }
+
+ public void destroy() {
+// if (users == null || groups == null)
+ if (entries == null)
+ throw new IllegalStateException("User directory " + getDirectory().getBaseDn() + " is already destroyed");
+// users = null;
+// groups = null;
+ entries = null;
+ }
+
+ /*
+ * USER ADMIN
+ */
+
+ @Override
+ public LdapEntry doGetEntry(LdapName key) throws NameNotFoundException {
+ if (entries.containsKey(key))
+ return entries.get(key);
+ throw new NameNotFoundException(key + " not persisted");
+ }
+
+ @Override
+ public Attributes doGetAttributes(LdapName name) {
+ if (!values.containsKey(name))
+ throw new IllegalStateException(name + " doe not exist in " + getDirectory().getBaseDn());
+ return values.get(name);
+ }
+
+ @Override
+ public boolean checkConnection() {
+ return true;
+ }
+
+ @Override
+ public boolean entryExists(LdapName dn) {
+ return entries.containsKey(dn);// || groups.containsKey(dn);
+ }
+
+ @Override
+ public List<LdapEntry> doGetEntries(LdapName searchBase, String f, boolean deep) {
+ Objects.requireNonNull(searchBase);
+ ArrayList<LdapEntry> res = new ArrayList<>();
+ if (f == null && deep && getDirectory().getBaseDn().equals(searchBase)) {
+ res.addAll(entries.values());
+ } else {
+ filterRoles(entries, searchBase, f, deep, res);
+ }
+ return res;
+ }
+
+ private void filterRoles(SortedMap<LdapName, ? extends LdapEntry> map, LdapName searchBase, String f, boolean deep,
+ List<LdapEntry> res) {
+ // FIXME get rid of OSGi references
+ try {
+ // TODO reduce map with search base ?
+ Filter filter = f != null ? FrameworkUtil.createFilter(f) : null;
+ roles: for (LdapEntry user : map.values()) {
+ LdapName dn = user.getDn();
+ if (dn.startsWith(searchBase)) {
+ if (!deep && dn.size() != (searchBase.size() + 1))
+ continue roles;
+ if (filter == null)
+ res.add(user);
+ else {
+ if (user instanceof Role) {
+ if (filter.match(((Role) user).getProperties()))
+ res.add(user);
+ }
+ }
+ }
+ }
+ } catch (InvalidSyntaxException e) {
+ throw new IllegalArgumentException("Cannot create filter " + f, e);
+ }
+
+ }
+
+ @Override
+ public List<LdapName> getDirectGroups(LdapName dn) {
+ List<LdapName> directGroups = new ArrayList<LdapName>();
+ entries: for (LdapName name : entries.keySet()) {
+ LdapEntry group;
+ try {
+ LdapEntry entry = doGetEntry(name);
+ if (AbstractLdapDirectory.hasObjectClass(entry.getAttributes(), getDirectory().getGroupObjectClass())) {
+ group = entry;
+ } else {
+ continue entries;
+ }
+ } catch (NameNotFoundException e) {
+ throw new IllegalArgumentException("Group " + dn + " not found", e);
+ }
+ if (group.getReferences(getDirectory().getMemberAttributeId()).contains(dn)) {
+ directGroups.add(group.getDn());
+ }
+ }
+ return directGroups;
+ }
+
+ @Override
+ public void prepare(LdapEntryWorkingCopy wc) {
+ // delete
+ for (LdapName dn : wc.getDeletedData().keySet()) {
+ if (entries.containsKey(dn))
+ entries.remove(dn);
+ else
+ throw new IllegalStateException("User to delete not found " + dn);
+ }
+ // add
+ for (LdapName dn : wc.getNewData().keySet()) {
+ LdapEntry user = (LdapEntry) wc.getNewData().get(dn);
+ if (entries.containsKey(dn))
+ throw new IllegalStateException("User to create found " + dn);
+ entries.put(dn, user);
+ }
+ // modify
+ for (LdapName dn : wc.getModifiedData().keySet()) {
+ Attributes modifiedAttrs = wc.getModifiedData().get(dn);
+ LdapEntry user;
+ try {
+ user = doGetEntry(dn);
+ } catch (NameNotFoundException e) {
+ throw new IllegalStateException("User to modify no found " + dn, e);
+ }
+ if (user == null)
+ throw new IllegalStateException("User to modify no found " + dn);
+ user.publishAttributes(modifiedAttrs);
+ }
+ }
+
+ @Override
+ public void commit(LdapEntryWorkingCopy wc) {
+ save();
+ }
+
+ @Override
+ public void rollback(LdapEntryWorkingCopy wc) {
+ init();
+ }
+
+ /*
+ * HIERARCHY
+ */
+ @Override
+ public HierarchyUnit doGetHierarchyUnit(LdapName dn) {
+ if (getDirectory().getBaseDn().equals(dn))
+ return getDirectory();
+ return hierarchy.get(dn);
+ }
+
+ @Override
+ public Iterable<HierarchyUnit> doGetDirectHierarchyUnits(LdapName searchBase, boolean functionalOnly) {
+ List<HierarchyUnit> res = new ArrayList<>();
+ for (LdapName n : hierarchy.keySet()) {
+ if (n.size() == searchBase.size() + 1) {
+ if (n.startsWith(searchBase)) {
+ HierarchyUnit hu = hierarchy.get(n);
+ if (functionalOnly) {
+ if (hu.isFunctional())
+ res.add(hu);
+ } else {
+ res.add(hu);
+ }
+ }
+ }
+ }
+ return res;
+ }
+
+ public void scope(LdifDao scoped) {
+ scoped.entries = Collections.unmodifiableNavigableMap(entries);
+ }
+}
--- /dev/null
+package org.argeo.cms.directory.ldap;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Base64;
+import java.util.List;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+import javax.naming.InvalidNameException;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.BasicAttribute;
+import javax.naming.directory.BasicAttributes;
+import javax.naming.ldap.LdapName;
+import javax.naming.ldap.Rdn;
+
+import org.argeo.api.acr.ldap.LdapAttr;
+
+/** Basic LDIF parser. */
+public class LdifParser {
+ private final static Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
+
+ protected Attributes addAttributes(SortedMap<LdapName, Attributes> res, int lineNumber, LdapName currentDn,
+ Attributes currentAttributes) {
+ try {
+ Rdn nameRdn = currentDn.getRdn(currentDn.size() - 1);
+ Attribute nameAttr = currentAttributes.get(nameRdn.getType());
+ if (nameAttr == null)
+ currentAttributes.put(nameRdn.getType(), nameRdn.getValue());
+ else if (!nameAttr.get().equals(nameRdn.getValue()))
+ throw new IllegalStateException(
+ "Attribute " + nameAttr.getID() + "=" + nameAttr.get() + " not consistent with DN " + currentDn
+ + " (shortly before line " + lineNumber + " in LDIF file)");
+ Attributes previous = res.put(currentDn, currentAttributes);
+ return previous;
+ } catch (NamingException e) {
+ throw new IllegalStateException("Cannot add " + currentDn, e);
+ }
+ }
+
+ /** With UTF-8 charset */
+ public SortedMap<LdapName, Attributes> read(InputStream in) throws IOException {
+ try (Reader reader = new InputStreamReader(in, DEFAULT_CHARSET)) {
+ return read(reader);
+ } finally {
+ try {
+ in.close();
+ } catch (IOException e) {
+ // silent
+ }
+ }
+ }
+
+ /** Will close the reader. */
+ public SortedMap<LdapName, Attributes> read(Reader reader) throws IOException {
+ SortedMap<LdapName, Attributes> res = new TreeMap<LdapName, Attributes>();
+ try {
+ List<String> lines = new ArrayList<>();
+ try (BufferedReader br = new BufferedReader(reader)) {
+ String line;
+ while ((line = br.readLine()) != null) {
+ lines.add(line);
+ }
+ }
+ if (lines.size() == 0)
+ return res;
+ // add an empty new line since the last line is not checked
+ if (!lines.get(lines.size() - 1).equals(""))
+ lines.add("");
+
+ LdapName currentDn = null;
+ Attributes currentAttributes = null;
+ StringBuilder currentEntry = new StringBuilder();
+
+ readLines: for (int lineNumber = 0; lineNumber < lines.size(); lineNumber++) {
+ String line = lines.get(lineNumber);
+ boolean isLastLine = false;
+ if (lineNumber == lines.size() - 1)
+ isLastLine = true;
+ if (line.startsWith(" ")) {
+ currentEntry.append(line.substring(1));
+ if (!isLastLine)
+ continue readLines;
+ }
+
+ if (currentEntry.length() != 0 || isLastLine) {
+ // read previous attribute
+ StringBuilder attrId = new StringBuilder(8);
+ boolean isBase64 = false;
+ readAttrId: for (int i = 0; i < currentEntry.length(); i++) {
+ char c = currentEntry.charAt(i);
+ if (c == ':') {
+ if (i + 1 < currentEntry.length() && currentEntry.charAt(i + 1) == ':')
+ isBase64 = true;
+ currentEntry.delete(0, i + (isBase64 ? 2 : 1));
+ break readAttrId;
+ } else {
+ attrId.append(c);
+ }
+ }
+
+ String attributeId = attrId.toString();
+ // TODO should we really trim the end of the string as well?
+ String cleanValueStr = currentEntry.toString().trim();
+ Object attributeValue = isBase64 ? Base64.getDecoder().decode(cleanValueStr) : cleanValueStr;
+
+ // manage DN attributes
+ if (attributeId.equals(LdapAttr.DN) || isLastLine) {
+ if (currentDn != null) {
+ //
+ // ADD
+ //
+ Attributes previous = addAttributes(res, lineNumber, currentDn, currentAttributes);
+ if (previous != null) {
+// log.warn("There was already an entry with DN " + currentDn
+// + ", which has been discarded by a subsequent one.");
+ }
+ }
+
+ if (attributeId.equals(LdapAttr.DN))
+ try {
+ currentDn = new LdapName(attributeValue.toString());
+ currentAttributes = new BasicAttributes(true);
+ } catch (InvalidNameException e) {
+// log.error(attributeValue + " not a valid DN, skipping the entry.");
+ currentDn = null;
+ currentAttributes = null;
+ }
+ }
+
+ // store attribute
+ if (currentAttributes != null) {
+ Attribute attribute = currentAttributes.get(attributeId);
+ if (attribute == null) {
+ attribute = new BasicAttribute(attributeId);
+ currentAttributes.put(attribute);
+ }
+ attribute.add(attributeValue);
+ }
+ currentEntry = new StringBuilder();
+ }
+ currentEntry.append(line);
+ }
+ } finally {
+ try {
+ reader.close();
+ } catch (IOException e) {
+ // silent
+ }
+ }
+ return res;
+ }
+}
\ No newline at end of file
--- /dev/null
+package org.argeo.cms.directory.ldap;
+
+import static org.argeo.api.acr.ldap.LdapAttr.DN;
+import static org.argeo.api.acr.ldap.LdapAttr.member;
+import static org.argeo.api.acr.ldap.LdapAttr.objectClass;
+import static org.argeo.api.acr.ldap.LdapAttr.uniqueMember;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+import java.util.Map;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.naming.ldap.LdapName;
+import javax.naming.ldap.Rdn;
+
+/** Basic LDIF writer */
+public class LdifWriter {
+ private final static Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
+ private final Writer writer;
+
+ /** Writer must be closed by caller */
+ public LdifWriter(Writer writer) {
+ this.writer = writer;
+ }
+
+ /** Stream must be closed by caller */
+ public LdifWriter(OutputStream out) {
+ this(new OutputStreamWriter(out, DEFAULT_CHARSET));
+ }
+
+ public void writeEntry(LdapName name, Attributes attributes) throws IOException {
+ try {
+ // check consistency
+ Rdn nameRdn = name.getRdn(name.size() - 1);
+ Attribute nameAttr = attributes.get(nameRdn.getType());
+ if (!nameAttr.get().equals(nameRdn.getValue()))
+ throw new IllegalArgumentException(
+ "Attribute " + nameAttr.getID() + "=" + nameAttr.get() + " not consistent with DN " + name);
+
+ writer.append(DN + ": ").append(name.toString()).append('\n');
+ Attribute objectClassAttr = attributes.get(objectClass.name());
+ if (objectClassAttr != null)
+ writeAttribute(objectClassAttr);
+ attributes: for (NamingEnumeration<? extends Attribute> attrs = attributes.getAll(); attrs.hasMore();) {
+ Attribute attribute = attrs.next();
+ if (attribute.getID().equals(DN) || attribute.getID().equals(objectClass.name()))
+ continue attributes;// skip DN attribute
+ if (attribute.getID().equals(member.name()) || attribute.getID().equals(uniqueMember.name()))
+ continue attributes;// skip member and uniqueMember attributes, so that they are always written last
+ writeAttribute(attribute);
+ }
+ // write member and uniqueMember attributes last
+ for (NamingEnumeration<? extends Attribute> attrs = attributes.getAll(); attrs.hasMore();) {
+ Attribute attribute = attrs.next();
+ if (attribute.getID().equals(member.name()) || attribute.getID().equals(uniqueMember.name()))
+ writeMemberAttribute(attribute);
+ }
+ writer.append('\n');
+ writer.flush();
+ } catch (NamingException e) {
+ throw new IllegalStateException("Cannot write LDIF", e);
+ }
+ }
+
+ public void write(Map<LdapName, Attributes> entries) throws IOException {
+ for (LdapName dn : entries.keySet())
+ writeEntry(dn, entries.get(dn));
+ }
+
+ protected void writeAttribute(Attribute attribute) throws NamingException, IOException {
+ for (NamingEnumeration<?> attrValues = attribute.getAll(); attrValues.hasMore();) {
+ Object value = attrValues.next();
+ if (value instanceof byte[]) {
+ String encoded = Base64.getEncoder().encodeToString((byte[]) value);
+ writer.append(attribute.getID()).append(":: ").append(encoded).append('\n');
+ } else {
+ writer.append(attribute.getID()).append(": ").append(value.toString()).append('\n');
+ }
+ }
+ }
+
+ protected void writeMemberAttribute(Attribute attribute) throws NamingException, IOException {
+ // Note: duplicate entries will be swallowed
+ SortedSet<String> values = new TreeSet<>();
+ for (NamingEnumeration<?> attrValues = attribute.getAll(); attrValues.hasMore();) {
+ String value = attrValues.next().toString();
+ values.add(value);
+ }
+
+ for (String value : values) {
+ writer.append(attribute.getID()).append(": ").append(value).append('\n');
+ }
+ }
+}
--- /dev/null
+package org.argeo.cms.directory.ldap;
+
+import java.time.Instant;
+import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
+
+import org.argeo.api.acr.ldap.NamingUtils;
+
+public class SharedSecret extends AuthPassword {
+ public final static String X_SHARED_SECRET = "X-SharedSecret";
+ private final Instant expiry;
+
+ public SharedSecret(String authInfo, String authValue) {
+ super(authInfo, authValue);
+ expiry = null;
+ }
+
+ public SharedSecret(AuthPassword authPassword) {
+ super(authPassword);
+ String authInfo = getAuthInfo();
+ if (authInfo.length() == 16) {
+ expiry = NamingUtils.ldapDateToInstant(authInfo);
+ } else {
+ expiry = null;
+ }
+ }
+
+ public SharedSecret(ZonedDateTime expiryTimestamp, String value) {
+ super(NamingUtils.instantToLdapDate(expiryTimestamp), value);
+ expiry = expiryTimestamp.withZoneSameInstant(ZoneOffset.UTC).toInstant();
+ }
+
+ public SharedSecret(int hours, String value) {
+ this(ZonedDateTime.now().plusHours(hours), value);
+ }
+
+ @Override
+ protected String getExpectedAuthScheme() {
+ return X_SHARED_SECRET;
+ }
+
+ public boolean isExpired() {
+ if (expiry == null)
+ return false;
+ return expiry.isBefore(Instant.now());
+ }
+
+}
--- /dev/null
+package org.argeo.cms.dns;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.Base64;
+import java.util.Collections;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.SortedSet;
+import java.util.StringJoiner;
+import java.util.TreeMap;
+import java.util.TreeSet;
+
+import javax.naming.Binding;
+import javax.naming.Context;
+import javax.naming.NameNotFoundException;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.DirContext;
+import javax.naming.directory.InitialDirContext;
+
+public class DnsBrowser implements Closeable {
+ private final DirContext initialCtx;
+
+ public DnsBrowser() {
+ this(new ArrayList<>());
+ }
+
+ public DnsBrowser(List<String> dnsServerUrls) {
+ try {
+ Objects.requireNonNull(dnsServerUrls);
+ Hashtable<String, Object> env = new Hashtable<>();
+ env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.dns.DnsContextFactory");
+ if (!dnsServerUrls.isEmpty()) {
+ boolean specified = false;
+ StringJoiner providerUrl = new StringJoiner(" ");
+ for (String dnsUrl : dnsServerUrls) {
+ if (dnsUrl != null) {
+ providerUrl.add(dnsUrl);
+ specified = true;
+ }
+ }
+ if (specified)
+ env.put(Context.PROVIDER_URL, providerUrl.toString());
+ }
+ initialCtx = new InitialDirContext(env);
+ } catch (NamingException e) {
+ throw new IllegalStateException("Cannot initialise DNS borowser.", e);
+ }
+ }
+
+ public Map<String, List<String>> getAllRecords(String name) {
+ try {
+ Map<String, List<String>> res = new TreeMap<>();
+ Attributes attrs = initialCtx.getAttributes(name);
+ NamingEnumeration<String> ids = attrs.getIDs();
+ while (ids.hasMore()) {
+ String recordType = ids.next();
+ List<String> lst = new ArrayList<String>();
+ res.put(recordType, lst);
+ Attribute attr = attrs.get(recordType);
+ addValues(attr, lst);
+ }
+ return Collections.unmodifiableMap(res);
+ } catch (NamingException e) {
+ throw new IllegalStateException("Cannot get allrecords of " + name, e);
+ }
+ }
+
+ /**
+ * Return a single record (typically A, AAAA, etc. or null if not available.
+ * Will fail if multiple records.
+ */
+ public String getRecord(String name, String recordType) {
+ try {
+ Attributes attrs = initialCtx.getAttributes(name, new String[] { recordType });
+ if (attrs.size() == 0)
+ return null;
+ Attribute attr = attrs.get(recordType);
+ if (attr.size() > 1)
+ throw new IllegalArgumentException("Multiple record type " + recordType);
+ assert attr.size() != 0;
+ Object value = attr.get();
+ assert value != null;
+ return value.toString();
+ } catch (NameNotFoundException e) {
+ return null;
+ } catch (NamingException e) {
+ throw new IllegalStateException("Cannot get DNS entry " + recordType + " of " + name, e);
+ }
+ }
+
+ /**
+ * Return records of a given type.
+ */
+ public List<String> getRecords(String name, String recordType) {
+ try {
+ List<String> res = new ArrayList<String>();
+ Attributes attrs = initialCtx.getAttributes(name, new String[] { recordType });
+ Attribute attr = attrs.get(recordType);
+ addValues(attr, res);
+ return res;
+ } catch (NamingException e) {
+ throw new IllegalStateException("Cannot get records " + recordType + " of " + name, e);
+ }
+ }
+
+ /** Ordered, with preferred first. */
+ public List<String> getSrvRecordsAsHosts(String name, boolean withPort) {
+ List<String> raw = getRecords(name, "SRV");
+ if (raw.size() == 0)
+ return null;
+ SortedSet<SrvRecord> res = new TreeSet<>();
+ for (int i = 0; i < raw.size(); i++) {
+ String record = raw.get(i);
+ String[] arr = record.split(" ");
+ Integer priority = Integer.parseInt(arr[0]);
+ Integer weight = Integer.parseInt(arr[1]);
+ Integer port = Integer.parseInt(arr[2]);
+ String hostname = arr[3];
+ SrvRecord order = new SrvRecord(priority, weight, port, hostname);
+ res.add(order);
+ }
+ List<String> lst = new ArrayList<>();
+ for (SrvRecord order : res) {
+ lst.add(order.toHost(withPort));
+ }
+ return Collections.unmodifiableList(lst);
+ }
+
+ private void addValues(Attribute attr, List<String> lst) throws NamingException {
+ NamingEnumeration<?> values = attr.getAll();
+ while (values.hasMore()) {
+ Object value = values.next();
+ if (value != null) {
+ if (value instanceof byte[]) {
+ String str = Base64.getEncoder().encodeToString((byte[]) value);
+ lst.add(str);
+ } else
+ lst.add(value.toString());
+ }
+ }
+
+ }
+
+ public List<String> listEntries(String name) {
+ try {
+ List<String> res = new ArrayList<String>();
+ NamingEnumeration<Binding> ne = initialCtx.listBindings(name);
+ while (ne.hasMore()) {
+ Binding b = ne.next();
+ res.add(b.getName());
+ }
+ return Collections.unmodifiableList(res);
+ } catch (NamingException e) {
+ throw new IllegalStateException("Cannot list entries of " + name, e);
+ }
+ }
+
+ @Override
+ public void close() throws IOException {
+ destroy();
+ }
+
+ public void destroy() {
+ try {
+ initialCtx.close();
+ } catch (NamingException e) {
+ // silent
+ }
+ }
+
+ public static void main(String[] args) {
+ if (args.length == 0) {
+ printUsage(System.err);
+ System.exit(1);
+ }
+ try (DnsBrowser dnsBrowser = new DnsBrowser()) {
+ String hostname = args[0];
+ String recordType = args.length > 1 ? args[1] : "A";
+ if (recordType.equals("*")) {
+ Map<String, List<String>> records = dnsBrowser.getAllRecords(hostname);
+ for (String type : records.keySet()) {
+ for (String record : records.get(type)) {
+ String typeLabel;
+ if ("44".equals(type))
+ typeLabel = "SSHFP";
+ else if ("46".equals(type))
+ typeLabel = "RRSIG";
+ else if ("48".equals(type))
+ typeLabel = "DNSKEY";
+ else
+ typeLabel = type;
+ System.out.println(typeLabel + "\t" + record);
+ }
+ }
+ } else {
+ System.out.println(dnsBrowser.getRecord(hostname, recordType));
+ }
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ public static void printUsage(PrintStream out) {
+ out.println("java org.argeo.naming.DnsBrowser <hostname> [<record type> | *]");
+ }
+
+}
\ No newline at end of file
--- /dev/null
+package org.argeo.cms.dns;
+
+class SrvRecord implements Comparable<SrvRecord> {
+ private final Integer priority;
+ private final Integer weight;
+ private final Integer port;
+ private final String hostname;
+
+ public SrvRecord(Integer priority, Integer weight, Integer port, String hostname) {
+ this.priority = priority;
+ this.weight = weight;
+ this.port = port;
+ this.hostname = hostname;
+ }
+
+ @Override
+ public int compareTo(SrvRecord other) {
+ // https: // en.wikipedia.org/wiki/SRV_record
+ if (priority != other.priority)
+ return priority - other.priority;
+ if (weight != other.weight)
+ return other.weight - other.weight;
+ String host = toHost(false);
+ String otherHost = other.toHost(false);
+ if (host.length() == otherHost.length())
+ return host.compareTo(otherHost);
+ else
+ return host.length() - otherHost.length();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof SrvRecord) {
+ SrvRecord other = (SrvRecord) obj;
+ return priority == other.priority && weight == other.weight && port == other.port
+ && hostname.equals(other.hostname);
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return priority + " " + weight;
+ }
+
+ public String toHost(boolean withPort) {
+ String hostStr = hostname;
+ if (hostname.charAt(hostname.length() - 1) == '.')
+ hostStr = hostname.substring(0, hostname.length() - 1);
+ return hostStr + (withPort ? ":" + port : "");
+ }
+}
--- /dev/null
+package org.argeo.cms.file;
+
+import java.io.IOException;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.StandardCopyOption;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.nio.file.attribute.FileTime;
+
+/** Synchronises two directory structures. */
+public class BasicSyncFileVisitor extends SimpleFileVisitor<Path> {
+ // TODO make it configurable
+ private boolean trace = false;
+
+ private final Path sourceBasePath;
+ private final Path targetBasePath;
+ private final boolean delete;
+ private final boolean recursive;
+
+ private SyncResult<Path> syncResult = new SyncResult<>();
+
+ public BasicSyncFileVisitor(Path sourceBasePath, Path targetBasePath, boolean delete, boolean recursive) {
+ this.sourceBasePath = sourceBasePath;
+ this.targetBasePath = targetBasePath;
+ this.delete = delete;
+ this.recursive = recursive;
+ }
+
+ @Override
+ public FileVisitResult preVisitDirectory(Path sourceDir, BasicFileAttributes attrs) throws IOException {
+ if (!recursive && !sourceDir.equals(sourceBasePath))
+ return FileVisitResult.SKIP_SUBTREE;
+ Path targetDir = toTargetPath(sourceDir);
+ Files.createDirectories(targetDir);
+ return FileVisitResult.CONTINUE;
+ }
+
+ @Override
+ public FileVisitResult postVisitDirectory(Path sourceDir, IOException exc) throws IOException {
+ if (delete) {
+ Path targetDir = toTargetPath(sourceDir);
+ for (Path targetPath : Files.newDirectoryStream(targetDir)) {
+ Path sourcePath = sourceDir.resolve(targetPath.getFileName());
+ if (!Files.exists(sourcePath)) {
+ try {
+ FsSyncUtils.delete(targetPath);
+ deleted(targetPath);
+ } catch (Exception e) {
+ deleteFailed(targetPath, exc);
+ }
+ }
+ }
+ }
+ return FileVisitResult.CONTINUE;
+ }
+
+ @Override
+ public FileVisitResult visitFile(Path sourceFile, BasicFileAttributes attrs) throws IOException {
+ Path targetFile = toTargetPath(sourceFile);
+ try {
+ if (!Files.exists(targetFile)) {
+ Files.copy(sourceFile, targetFile);
+ added(sourceFile, targetFile);
+ } else {
+ if (shouldOverwrite(sourceFile, targetFile)) {
+ Files.copy(sourceFile, targetFile, StandardCopyOption.REPLACE_EXISTING);
+ }
+ }
+ } catch (Exception e) {
+ copyFailed(sourceFile, targetFile, e);
+ }
+ return FileVisitResult.CONTINUE;
+ }
+
+ protected boolean shouldOverwrite(Path sourceFile, Path targetFile) throws IOException {
+ long sourceSize = Files.size(sourceFile);
+ long targetSize = Files.size(targetFile);
+ if (sourceSize != targetSize) {
+ return true;
+ }
+ FileTime sourceLastModif = Files.getLastModifiedTime(sourceFile);
+ FileTime targetLastModif = Files.getLastModifiedTime(targetFile);
+ if (sourceLastModif.compareTo(targetLastModif) > 0)
+ return true;
+ return shouldOverwriteLaterSameSize(sourceFile, targetFile);
+ }
+
+ protected boolean shouldOverwriteLaterSameSize(Path sourceFile, Path targetFile) {
+ return false;
+ }
+
+// @Override
+// public FileVisitResult visitFileFailed(Path sourceFile, IOException exc) throws IOException {
+// error("Cannot sync " + sourceFile, exc);
+// return FileVisitResult.CONTINUE;
+// }
+
+ private Path toTargetPath(Path sourcePath) {
+ Path relativePath = sourceBasePath.relativize(sourcePath);
+ Path targetPath = targetBasePath.resolve(relativePath.toString());
+ return targetPath;
+ }
+
+ public Path getSourceBasePath() {
+ return sourceBasePath;
+ }
+
+ public Path getTargetBasePath() {
+ return targetBasePath;
+ }
+
+ protected void added(Path sourcePath, Path targetPath) {
+ syncResult.getAdded().add(targetPath);
+ if (isTraceEnabled())
+ trace("Added " + sourcePath + " as " + targetPath);
+ }
+
+ protected void modified(Path sourcePath, Path targetPath) {
+ syncResult.getModified().add(targetPath);
+ if (isTraceEnabled())
+ trace("Overwritten from " + sourcePath + " to " + targetPath);
+ }
+
+ protected void copyFailed(Path sourcePath, Path targetPath, Exception e) {
+ syncResult.addError(sourcePath, targetPath, e);
+ if (isTraceEnabled())
+ error("Cannot copy " + sourcePath + " to " + targetPath, e);
+ }
+
+ protected void deleted(Path targetPath) {
+ syncResult.getDeleted().add(targetPath);
+ if (isTraceEnabled())
+ trace("Deleted " + targetPath);
+ }
+
+ protected void deleteFailed(Path targetPath, Exception e) {
+ syncResult.addError(null, targetPath, e);
+ if (isTraceEnabled())
+ error("Cannot delete " + targetPath, e);
+ }
+
+ /** Log error. */
+ protected void error(Object obj, Throwable e) {
+ System.err.println(obj);
+ e.printStackTrace();
+ }
+
+ protected boolean isTraceEnabled() {
+ return trace;
+ }
+
+ protected void trace(Object obj) {
+ System.out.println(obj);
+ }
+
+ public SyncResult<Path> getSyncResult() {
+ return syncResult;
+ }
+
+}
--- /dev/null
+package org.argeo.cms.file;
+
+import java.io.IOException;
+import java.math.BigInteger;
+import java.nio.MappedByteBuffer;
+import java.nio.channels.FileChannel;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Base64;
+import java.util.zip.Checksum;
+
+/** Allows to fine tune how files are read. */
+public class ChecksumFactory {
+ private int regionSize = 10 * 1024 * 1024;
+
+ public byte[] digest(Path path, final String algo) {
+ try {
+ final MessageDigest md = MessageDigest.getInstance(algo);
+ if (Files.isDirectory(path)) {
+ long begin = System.currentTimeMillis();
+ Files.walkFileTree(path, new SimpleFileVisitor<Path>() {
+
+ @Override
+ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
+ if (!Files.isDirectory(file)) {
+ byte[] digest = digest(file, algo);
+ md.update(digest);
+ }
+ return FileVisitResult.CONTINUE;
+ }
+
+ });
+ byte[] digest = md.digest();
+ long duration = System.currentTimeMillis() - begin;
+ System.out.println(printBase64Binary(digest) + " " + path + " (" + duration / 1000 + "s)");
+ return digest;
+ } else {
+ long begin = System.nanoTime();
+ long length = -1;
+ try (FileChannel channel = (FileChannel) Files.newByteChannel(path);) {
+ length = channel.size();
+ long cursor = 0;
+ while (cursor < length) {
+ long effectiveSize = Math.min(regionSize, length - cursor);
+ MappedByteBuffer mb = channel.map(FileChannel.MapMode.READ_ONLY, cursor, effectiveSize);
+ // md.update(mb);
+ byte[] buffer = new byte[1024];
+ while (mb.hasRemaining()) {
+ mb.get(buffer);
+ md.update(buffer);
+ }
+
+ // sub digest
+ // mb.flip();
+ // MessageDigest subMd =
+ // MessageDigest.getInstance(algo);
+ // subMd.update(mb);
+ // byte[] subDigest = subMd.digest();
+ // System.out.println(" -> " + cursor);
+ // System.out.println(IOUtils.encodeHexString(subDigest));
+ // System.out.println(new BigInteger(1,
+ // subDigest).toString(16));
+ // System.out.println(new BigInteger(1, subDigest)
+ // .toString(Character.MAX_RADIX));
+ // System.out.println(printBase64Binary(subDigest));
+
+ cursor = cursor + regionSize;
+ }
+ byte[] digest = md.digest();
+ long duration = System.nanoTime() - begin;
+ System.out.println(printBase64Binary(digest) + " " + path.getFileName() + " (" + duration / 1000000
+ + "ms, " + (length / 1024) + "kB, " + (length / (duration / 1000000)) * 1000 / (1024 * 1024)
+ + " MB/s)");
+ return digest;
+ }
+ }
+ } catch (NoSuchAlgorithmException | IOException e) {
+ throw new IllegalStateException("Cannot digest " + path, e);
+ }
+ }
+
+ /** Whether the file should be mapped. */
+ protected boolean mapFile(FileChannel fileChannel) throws IOException {
+ long size = fileChannel.size();
+ if (size > (regionSize / 10))
+ return true;
+ return false;
+ }
+
+ public long checksum(Path path, Checksum crc) {
+ final int bufferSize = 2 * 1024 * 1024;
+ long begin = System.currentTimeMillis();
+ try (FileChannel channel = (FileChannel) Files.newByteChannel(path);) {
+ byte[] bytes = new byte[bufferSize];
+ long length = channel.size();
+ long cursor = 0;
+ while (cursor < length) {
+ long effectiveSize = Math.min(regionSize, length - cursor);
+ MappedByteBuffer mb = channel.map(FileChannel.MapMode.READ_ONLY, cursor, effectiveSize);
+ int nGet;
+ while (mb.hasRemaining()) {
+ nGet = Math.min(mb.remaining(), bufferSize);
+ mb.get(bytes, 0, nGet);
+ crc.update(bytes, 0, nGet);
+ }
+ cursor = cursor + regionSize;
+ }
+ return crc.getValue();
+ } catch (IOException e) {
+ throw new IllegalStateException("Cannot checksum " + path, e);
+ } finally {
+ long duration = System.currentTimeMillis() - begin;
+ System.out.println(duration / 1000 + "s");
+ }
+ }
+
+ public static void main(String... args) {
+ ChecksumFactory cf = new ChecksumFactory();
+ // Path path =
+ // Paths.get("/home/mbaudier/apache-maven-3.2.3-bin.tar.gz");
+ Path path;
+ if (args.length > 0) {
+ path = Paths.get(args[0]);
+ } else {
+ path = Paths.get("/home/mbaudier/Downloads/torrents/CentOS-7-x86_64-DVD-1503-01/"
+ + "CentOS-7-x86_64-DVD-1503-01.iso");
+ }
+ // long adler = cf.checksum(path, new Adler32());
+ // System.out.format("Adler=%d%n", adler);
+ // long crc = cf.checksum(path, new CRC32());
+ // System.out.format("CRC=%d%n", crc);
+ String algo = "SHA1";
+ byte[] digest = cf.digest(path, algo);
+ System.out.println(algo + " " + printBase64Binary(digest));
+ System.out.println(algo + " " + new BigInteger(1, digest).toString(16));
+ // String sha1 = printBase64Binary(cf.digest(path, "SHA1"));
+ // System.out.format("SHA1=%s%n", sha1);
+ }
+
+ private static String printBase64Binary(byte[] arr) {
+ return Base64.getEncoder().encodeToString(arr);
+ }
+}
--- /dev/null
+package org.argeo.cms.file;
+
+import java.io.IOException;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+
+public class FsSyncUtils {
+ /** Sync a source path with a target path. */
+ public static void sync(Path sourceBasePath, Path targetBasePath) {
+ sync(sourceBasePath, targetBasePath, false);
+ }
+
+ /** Sync a source path with a target path. */
+ public static void sync(Path sourceBasePath, Path targetBasePath, boolean delete) {
+ sync(new BasicSyncFileVisitor(sourceBasePath, targetBasePath, delete, true));
+ }
+
+ public static void sync(BasicSyncFileVisitor syncFileVisitor) {
+ try {
+ Files.walkFileTree(syncFileVisitor.getSourceBasePath(), syncFileVisitor);
+ } catch (Exception e) {
+ throw new RuntimeException("Cannot sync " + syncFileVisitor.getSourceBasePath() + " with "
+ + syncFileVisitor.getTargetBasePath(), e);
+ }
+ }
+
+ /**
+ * Deletes this path, recursively if needed. Does nothing if the path does not
+ * exist.
+ */
+ public static void delete(Path path) {
+ try {
+ if (!Files.exists(path))
+ return;
+ Files.walkFileTree(path, new SimpleFileVisitor<Path>() {
+ @Override
+ public FileVisitResult postVisitDirectory(Path directory, IOException e) throws IOException {
+ if (e != null)
+ throw e;
+ Files.delete(directory);
+ return FileVisitResult.CONTINUE;
+ }
+
+ @Override
+ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
+ Files.delete(file);
+ return FileVisitResult.CONTINUE;
+ }
+ });
+ } catch (IOException e) {
+ throw new RuntimeException("Cannot delete " + path, e);
+ }
+ }
+
+ /** Singleton. */
+ private FsSyncUtils() {
+
+ }
+}
--- /dev/null
+package org.argeo.cms.file;
+
+import java.net.URI;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.spi.FileSystemProvider;
+import java.util.concurrent.Callable;
+
+/** Synchronises two paths. */
+public class PathSync implements Callable<SyncResult<Path>> {
+ private final URI sourceUri, targetUri;
+ private final boolean delete;
+ private final boolean recursive;
+
+ public PathSync(URI sourceUri, URI targetUri) {
+ this(sourceUri, targetUri, false, false);
+ }
+
+ public PathSync(URI sourceUri, URI targetUri, boolean delete, boolean recursive) {
+ this.sourceUri = sourceUri;
+ this.targetUri = targetUri;
+ this.delete = delete;
+ this.recursive = recursive;
+ }
+
+ @Override
+ public SyncResult<Path> call() {
+ try {
+ Path sourceBasePath = createPath(sourceUri);
+ Path targetBasePath = createPath(targetUri);
+ SyncFileVisitor syncFileVisitor = new SyncFileVisitor(sourceBasePath, targetBasePath, delete, recursive);
+ Files.walkFileTree(sourceBasePath, syncFileVisitor);
+ return syncFileVisitor.getSyncResult();
+ } catch (Exception e) {
+ throw new IllegalStateException("Cannot sync " + sourceUri + " to " + targetUri, e);
+ }
+ }
+
+ private Path createPath(URI uri) {
+ Path path;
+ if (uri.getScheme() == null) {
+ path = Paths.get(uri.getPath());
+ } else if (uri.getScheme().equals("file")) {
+ FileSystemProvider fsProvider = FileSystems.getDefault().provider();
+ path = fsProvider.getPath(uri);
+ } else if (uri.getScheme().equals("davex")) {
+ throw new UnsupportedOperationException();
+// FileSystemProvider fsProvider = new DavexFsProvider();
+// path = fsProvider.getPath(uri);
+// } else if (uri.getScheme().equals("sftp")) {
+// Sftp sftp = new Sftp(uri);
+// path = sftp.getBasePath();
+ } else
+ throw new IllegalArgumentException("URI scheme not supported for " + uri);
+ return path;
+ }
+}
--- /dev/null
+package org.argeo.cms.file;
+
+import java.nio.file.Path;
+import java.util.Objects;
+
+import org.argeo.api.cms.CmsLog;
+
+/** Synchronises two directory structures. */
+public class SyncFileVisitor extends BasicSyncFileVisitor {
+ private final static CmsLog log = CmsLog.getLog(SyncFileVisitor.class);
+
+ public SyncFileVisitor(Path sourceBasePath, Path targetBasePath, boolean delete, boolean recursive) {
+ super(sourceBasePath, targetBasePath, delete, recursive);
+ }
+
+ @Override
+ protected void error(Object obj, Throwable e) {
+ log.error(Objects.toString(obj), e);
+ }
+
+ @Override
+ protected boolean isTraceEnabled() {
+ return log.isTraceEnabled();
+ }
+
+ @Override
+ protected void trace(Object obj) {
+ log.error(Objects.toString(obj));
+ }
+}
--- /dev/null
+package org.argeo.cms.file;
+
+import java.time.Instant;
+import java.util.Set;
+import java.util.TreeSet;
+
+/** Describes what happendend during a sync operation. */
+public class SyncResult<T> {
+ private final Set<T> added = new TreeSet<>();
+ private final Set<T> modified = new TreeSet<>();
+ private final Set<T> deleted = new TreeSet<>();
+ private final Set<Error> errors = new TreeSet<>();
+
+ public Set<T> getAdded() {
+ return added;
+ }
+
+ public Set<T> getModified() {
+ return modified;
+ }
+
+ public Set<T> getDeleted() {
+ return deleted;
+ }
+
+ public Set<Error> getErrors() {
+ return errors;
+ }
+
+ public void addError(T sourcePath, T targetPath, Exception e) {
+ Error error = new Error(sourcePath, targetPath, e);
+ errors.add(error);
+ }
+
+ public boolean noModification() {
+ return modified.isEmpty() && deleted.isEmpty() && added.isEmpty();
+ }
+
+ @Override
+ public String toString() {
+ if (noModification())
+ return "No modification.";
+ StringBuffer sb = new StringBuffer();
+ for (T p : modified)
+ sb.append("MOD ").append(p).append('\n');
+ for (T p : deleted)
+ sb.append("DEL ").append(p).append('\n');
+ for (T p : added)
+ sb.append("ADD ").append(p).append('\n');
+ for (Error error : errors)
+ sb.append(error).append('\n');
+ return sb.toString();
+ }
+
+ public class Error implements Comparable<Error> {
+ private final T sourcePath;// if null this is a failed delete
+ private final T targetPath;
+ private final Exception exception;
+ private final Instant timestamp = Instant.now();
+
+ public Error(T sourcePath, T targetPath, Exception e) {
+ super();
+ this.sourcePath = sourcePath;
+ this.targetPath = targetPath;
+ this.exception = e;
+ }
+
+ public T getSourcePath() {
+ return sourcePath;
+ }
+
+ public T getTargetPath() {
+ return targetPath;
+ }
+
+ public Exception getException() {
+ return exception;
+ }
+
+ public Instant getTimestamp() {
+ return timestamp;
+ }
+
+ @Override
+ public int compareTo(Error o) {
+ return timestamp.compareTo(o.timestamp);
+ }
+
+ @Override
+ public int hashCode() {
+ return timestamp.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return "ERR " + timestamp + (sourcePath == null ? "Deletion failed" : "Copy failed " + sourcePath) + " "
+ + targetPath + " " + exception.getMessage();
+ }
+
+ }
+}
--- /dev/null
+package org.argeo.cms.file.provider;
+
+import java.io.IOException;
+import java.nio.file.attribute.FileAttributeView;
+import java.nio.file.attribute.FileStoreAttributeView;
+
+import org.argeo.api.acr.fs.AbstractFsStore;
+
+public class CmsFileStore extends AbstractFsStore {
+
+ @Override
+ public String name() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public String type() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public boolean isReadOnly() {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ @Override
+ public long getTotalSpace() throws IOException {
+ // TODO Auto-generated method stub
+ return 0;
+ }
+
+ @Override
+ public long getUsableSpace() throws IOException {
+ // TODO Auto-generated method stub
+ return 0;
+ }
+
+ @Override
+ public long getUnallocatedSpace() throws IOException {
+ // TODO Auto-generated method stub
+ return 0;
+ }
+
+ @Override
+ public boolean supportsFileAttributeView(Class<? extends FileAttributeView> type) {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ @Override
+ public boolean supportsFileAttributeView(String name) {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ @Override
+ public <V extends FileStoreAttributeView> V getFileStoreAttributeView(Class<V> type) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public Object getAttribute(String attribute) throws IOException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+}
--- /dev/null
+package org.argeo.cms.file.provider;
+
+import java.io.IOException;
+import java.nio.file.FileStore;
+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.Set;
+
+import org.argeo.api.acr.fs.AbstractFsSystem;
+
+public class CmsFileSystem extends AbstractFsSystem<CmsFileStore> {
+
+ @Override
+ public CmsFileStore getBaseFileStore() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public CmsFileStore getFileStore(String path) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public FileSystemProvider provider() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public void close() throws IOException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public boolean isOpen() {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ @Override
+ public boolean isReadOnly() {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ @Override
+ public String getSeparator() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public Iterable<Path> getRootDirectories() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public Iterable<FileStore> getFileStores() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public Set<String> supportedFileAttributeViews() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public Path getPath(String first, String... more) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public PathMatcher getPathMatcher(String syntaxAndPattern) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public UserPrincipalLookupService getUserPrincipalLookupService() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public WatchService newWatchService() throws IOException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+}
--- /dev/null
+package org.argeo.cms.file.provider;
+
+import java.io.IOException;
+import java.net.URI;
+import java.nio.channels.SeekableByteChannel;
+import java.nio.file.AccessMode;
+import java.nio.file.CopyOption;
+import java.nio.file.DirectoryStream;
+import java.nio.file.DirectoryStream.Filter;
+import java.nio.file.FileStore;
+import java.nio.file.FileSystem;
+import java.nio.file.LinkOption;
+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.Map;
+import java.util.Set;
+
+public class CmsFileSystemProvider extends FileSystemProvider {
+
+ @Override
+ public String getScheme() {
+ return "cms";
+ }
+
+ @Override
+ public FileSystem newFileSystem(URI uri, Map<String, ?> env) throws IOException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public FileSystem getFileSystem(URI uri) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public Path getPath(URI uri) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public SeekableByteChannel newByteChannel(Path path, Set<? extends OpenOption> options, FileAttribute<?>... attrs)
+ throws IOException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public DirectoryStream<Path> newDirectoryStream(Path dir, Filter<? super Path> filter) throws IOException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public void createDirectory(Path dir, FileAttribute<?>... attrs) throws IOException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void delete(Path path) throws IOException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void copy(Path source, Path target, CopyOption... options) throws IOException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void move(Path source, Path target, CopyOption... options) throws IOException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public boolean isSameFile(Path path, Path path2) throws IOException {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ @Override
+ public boolean isHidden(Path path) throws IOException {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ @Override
+ public FileStore getFileStore(Path path) throws IOException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public void checkAccess(Path path, AccessMode... modes) throws IOException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public <V extends FileAttributeView> V getFileAttributeView(Path path, Class<V> type, LinkOption... options) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public <A extends BasicFileAttributes> A readAttributes(Path path, Class<A> type, LinkOption... options)
+ throws IOException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public Map<String, Object> readAttributes(Path path, String attributes, LinkOption... options) throws IOException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public void setAttribute(Path path, String attribute, Object value, LinkOption... options) throws IOException {
+ // TODO Auto-generated method stub
+
+ }
+
+}
--- /dev/null
+package org.argeo.cms.file.provider;
+
+import org.argeo.api.acr.fs.AbstractFsPath;
+
+public class CmsPath extends AbstractFsPath<CmsFileSystem, CmsFileStore> {
+
+ public CmsPath(CmsFileSystem filesSystem, CmsFileStore fileStore, String[] segments, boolean absolute) {
+ super(filesSystem, fileStore, segments, absolute);
+ }
+
+ public CmsPath(CmsFileSystem filesSystem, String path) {
+ super(filesSystem, path);
+ }
+
+ @Override
+ protected AbstractFsPath<CmsFileSystem, CmsFileStore> newInstance(String path) {
+ return new CmsPath(getFileSystem(), path);
+ }
+
+ @Override
+ protected AbstractFsPath<CmsFileSystem, CmsFileStore> newInstance(String[] segments, boolean absolute) {
+ return new CmsPath(getFileSystem(), getFileStore(), segments, absolute);
+ }
+
+}
--- /dev/null
+package org.argeo.cms.http;
+
+/** Selection of standard or common HTTP headers (including WebDav). */
+public enum HttpHeader {
+ AUTHORIZATION("Authorization"), //
+ WWW_AUTHENTICATE("WWW-Authenticate"), //
+ ALLOW("Allow"), //
+ VIA("Via"), //
+
+ // WebDav
+ DAV("DAV"), //
+ DEPTH("Depth"), //
+
+ // Non-standard
+ X_FORWARDED_HOST("X-Forwarded-Host"), //
+ ;
+
+ public final static String BASIC = "Basic";
+ public final static String REALM = "realm";
+ public final static String NEGOTIATE = "Negotiate";
+
+ private final String name;
+
+ private HttpHeader(String headerName) {
+ this.name = headerName;
+ }
+
+ public String getHeaderName() {
+ return name;
+ }
+
+ @Override
+ public String toString() {
+ return getHeaderName();
+ }
+
+}
--- /dev/null
+package org.argeo.cms.http;
+
+/** Generic HTTP methods. */
+public enum HttpMethod {
+ OPTIONS, //
+ HEAD, //
+ GET, //
+ POST, //
+ PUT, //
+ DELETE, //
+
+ // WebDav
+ PROPFIND, //
+ PROPPATCH, //
+ MKCOL, //
+ MOVE, //
+ COPY, //
+ ;
+}
--- /dev/null
+package org.argeo.cms.http;
+
+/**
+ * Standard HTTP response status codes (including WebDav ones).
+ *
+ * @see "https://developer.mozilla.org/en-US/docs/Web/HTTP/Status"
+ */
+public enum HttpStatus {
+ // Successful responses (200–299)
+ OK(200, "OK"), //
+ NO_CONTENT(204, "No Content"), //
+ MULTI_STATUS(207, "Multi-Status"), // WebDav
+ // Client error responses (400–499)
+ UNAUTHORIZED(401, "Unauthorized"), //
+ FORBIDDEN(403, "Forbidden"), //
+ NOT_FOUND(404, "Not Found"), //
+ // Server error responses (500-599)
+ INTERNAL_SERVER_ERROR(500, "Internal Server Error"), //
+ NOT_IMPLEMENTED(501, "Not Implemented"), //
+ ;
+
+ private final int code;
+ private final String reasonPhrase;
+
+ HttpStatus(int statusCode, String reasonPhrase) {
+ this.code = statusCode;
+ this.reasonPhrase = reasonPhrase;
+ }
+
+ public int getCode() {
+ return code;
+ }
+
+ public String getReasonPhrase() {
+ return reasonPhrase;
+ }
+
+ /**
+ * The status line, as defined by RFC2616.
+ *
+ * @see "https://www.rfc-editor.org/rfc/rfc2616#section-6.1"
+ */
+ public String getStatusLine(String httpVersion) {
+ return httpVersion + " " + code + " " + reasonPhrase;
+ }
+
+ public static HttpStatus parseStatusLine(String statusLine) {
+ try {
+ String[] arr = statusLine.split(" ");
+ int code = Integer.parseInt(arr[1]);
+ for (HttpStatus status : values()) {
+ if (status.getCode() == code)
+ return status;
+ }
+ } catch (Exception e) {
+ throw new IllegalArgumentException("Invalid status line: " + statusLine, e);
+ }
+ throw new IllegalArgumentException("Unkown status code: " + statusLine);
+ }
+
+ @Override
+ public String toString() {
+ return code + " " + reasonPhrase;
+ }
+
+}
--- /dev/null
+package org.argeo.cms.http.server;
+
+import java.net.URI;
+import java.util.Objects;
+
+import com.sun.net.httpserver.HttpContext;
+import com.sun.net.httpserver.HttpExchange;
+
+/** HTTP utilities on the server-side. */
+public class HttpServerUtils {
+ private final static String SLASH = "/";
+
+ private static String extractPathWithingContext(HttpContext httpContext, String fullPath, boolean startWithSlash) {
+ Objects.requireNonNull(fullPath);
+ String contextPath = httpContext.getPath();
+ if (!fullPath.startsWith(contextPath))
+ throw new IllegalArgumentException(fullPath + " does not belong to context" + contextPath);
+ String path = fullPath.substring(contextPath.length());
+ // TODO optimise?
+ if (!startWithSlash && path.startsWith(SLASH)) {
+ path = path.substring(1);
+ } else if (startWithSlash && !path.startsWith(SLASH)) {
+ path = SLASH + path;
+ }
+ return path;
+ }
+
+ /** Path within the context, NOT starting with a slash. */
+ public static String relativize(HttpExchange exchange) {
+ URI uri = exchange.getRequestURI();
+ HttpContext httpContext = exchange.getHttpContext();
+ return extractPathWithingContext(httpContext, uri.getPath(), false);
+ }
+
+ /** Path within the context, starting with a slash. */
+ public static String subPath(HttpExchange exchange) {
+ URI uri = exchange.getRequestURI();
+ HttpContext httpContext = exchange.getHttpContext();
+ return extractPathWithingContext(httpContext, uri.getPath(), true);
+ }
+
+ /** singleton */
+ private HttpServerUtils() {
+
+ }
+}
package org.argeo.cms.internal.auth;
import java.io.Serializable;
-import java.security.AccessControlContext;
-import java.security.AccessController;
-import java.security.PrivilegedAction;
import java.time.ZonedDateTime;
import java.util.ArrayList;
-import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
-import java.util.Hashtable;
import java.util.List;
import java.util.Locale;
import java.util.Map;
-import java.util.Set;
+import java.util.Objects;
import java.util.UUID;
import java.util.function.Consumer;
-import javax.crypto.SecretKey;
-import javax.naming.InvalidNameException;
-import javax.naming.ldap.LdapName;
import javax.security.auth.Subject;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;
import javax.security.auth.x500.X500Principal;
import org.argeo.api.cms.CmsAuth;
+import org.argeo.api.cms.CmsConstants;
import org.argeo.api.cms.CmsLog;
import org.argeo.api.cms.CmsSession;
-import org.argeo.cms.security.NodeSecurityUtils;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.FrameworkUtil;
-import org.osgi.framework.InvalidSyntaxException;
-import org.osgi.framework.ServiceReference;
-import org.osgi.framework.ServiceRegistration;
+import org.argeo.cms.internal.runtime.CmsContextImpl;
import org.osgi.service.useradmin.Authorization;
/** Default CMS session implementation. */
public class CmsSessionImpl implements CmsSession, Serializable {
private static final long serialVersionUID = 1867719354246307225L;
- private final static BundleContext bc = FrameworkUtil.getBundle(CmsSessionImpl.class).getBundleContext();
private final static CmsLog log = CmsLog.getLog(CmsSessionImpl.class);
- // private final Subject initialSubject;
- private transient AccessControlContext accessControlContext;
+ private transient Subject subject;
private final UUID uuid;
private final String localSessionId;
private Authorization authorization;
- private final LdapName userDn;
+// private final LdapName userDn;
+ private final String userDn;
private final boolean anonymous;
private final ZonedDateTime creationTime;
private ZonedDateTime end;
private final Locale locale;
- private ServiceRegistration<CmsSession> serviceRegistration;
-
private Map<String, Object> views = new HashMap<>();
private List<Consumer<CmsSession>> onCloseCallbacks = Collections.synchronizedList(new ArrayList<>());
- public CmsSessionImpl(Subject initialSubject, Authorization authorization, Locale locale, String localSessionId) {
+ public CmsSessionImpl(UUID uuid, Subject initialSubject, Authorization authorization, Locale locale,
+ String localSessionId) {
+ Objects.requireNonNull(uuid);
+
this.creationTime = ZonedDateTime.now();
this.locale = locale;
- this.accessControlContext = Subject.doAs(initialSubject, new PrivilegedAction<AccessControlContext>() {
-
- @Override
- public AccessControlContext run() {
- return AccessController.getContext();
- }
-
- });
- // this.initialSubject = initialSubject;
+ this.subject = initialSubject;
this.localSessionId = localSessionId;
this.authorization = authorization;
- if (authorization.getName() != null)
- try {
- this.userDn = new LdapName(authorization.getName());
- this.anonymous = false;
- } catch (InvalidNameException e) {
- throw new IllegalArgumentException("Invalid user name " + authorization.getName(), e);
- }
- else {
- this.userDn = NodeSecurityUtils.ROLE_ANONYMOUS_NAME;
+ if (authorization.getName() != null) {
+ this.userDn = authorization.getName();
+ this.anonymous = false;
+ } else {
+ this.userDn = CmsConstants.ROLE_ANONYMOUS;
this.anonymous = true;
}
- this.uuid = UUID.randomUUID();
- // register as service
- Hashtable<String, String> props = new Hashtable<>();
- props.put(CmsSession.USER_DN, userDn.toString());
- props.put(CmsSession.SESSION_UUID, uuid.toString());
- props.put(CmsSession.SESSION_LOCAL_ID, localSessionId);
- serviceRegistration = bc.registerService(CmsSession.class, this, props);
+ this.uuid = uuid;
}
public void close() {
end = ZonedDateTime.now();
- serviceRegistration.unregister();
+ CmsContextImpl.getCmsContext().unregisterCmsSession(this);
+// serviceRegistration.unregister();
for (Consumer<CmsSession> onClose : onCloseCallbacks) {
onClose.accept(this);
try {
LoginContext lc;
if (isAnonymous()) {
- lc = new LoginContext(CmsAuth.LOGIN_CONTEXT_ANONYMOUS, getSubject());
+ lc = CmsAuth.ANONYMOUS.newLoginContext(getSubject());
} else {
- lc = new LoginContext(CmsAuth.LOGIN_CONTEXT_USER, getSubject());
+ lc = CmsAuth.USER.newLoginContext(getSubject());
}
lc.logout();
} catch (LoginException e) {
log.warn("Could not logout " + getSubject() + ": " + e);
} finally {
- accessControlContext = null;
+ subject = null;
}
log.debug("Closed " + this);
}
}
public Subject getSubject() {
- return Subject.getSubject(accessControlContext);
+ return subject;
}
- public Set<SecretKey> getSecretKeys() {
- checkValid();
- return getSubject().getPrivateCredentials(SecretKey.class);
- }
+// public Set<SecretKey> getSecretKeys() {
+// checkValid();
+// return getSubject().getPrivateCredentials(SecretKey.class);
+// }
@Override
public boolean isValid() {
}
@Override
- public LdapName getUserDn() {
+ public String getUserDn() {
return userDn;
}
}
public String toString() {
- return "CMS Session " + userDn + " local=" + localSessionId + ", uuid=" + uuid;
- }
-
- public static CmsSessionImpl getByLocalId(String localId) {
- Collection<ServiceReference<CmsSession>> sr;
- try {
- sr = bc.getServiceReferences(CmsSession.class, "(" + CmsSession.SESSION_LOCAL_ID + "=" + localId + ")");
- } catch (InvalidSyntaxException e) {
- throw new IllegalArgumentException("Cannot get CMS session for id " + localId, e);
- }
- ServiceReference<CmsSession> cmsSessionRef;
- if (sr.size() == 1) {
- cmsSessionRef = sr.iterator().next();
- return (CmsSessionImpl) bc.getService(cmsSessionRef);
- } else if (sr.size() == 0) {
- return null;
- } else
- throw new IllegalStateException(sr.size() + " CMS sessions registered for " + localId);
-
- }
-
- public static CmsSessionImpl getByUuid(Object uuid) {
- Collection<ServiceReference<CmsSession>> sr;
- try {
- sr = bc.getServiceReferences(CmsSession.class, "(" + CmsSession.SESSION_UUID + "=" + uuid + ")");
- } catch (InvalidSyntaxException e) {
- throw new IllegalArgumentException("Cannot get CMS session for uuid " + uuid, e);
- }
- ServiceReference<CmsSession> cmsSessionRef;
- if (sr.size() == 1) {
- cmsSessionRef = sr.iterator().next();
- return (CmsSessionImpl) bc.getService(cmsSessionRef);
- } else if (sr.size() == 0) {
- return null;
- } else
- throw new IllegalStateException(sr.size() + " CMS sessions registered for " + uuid);
-
- }
-
- public static void closeInvalidSessions() {
- Collection<ServiceReference<CmsSession>> srs;
- try {
- srs = bc.getServiceReferences(CmsSession.class, null);
- for (ServiceReference<CmsSession> sr : srs) {
- CmsSession cmsSession = bc.getService(sr);
- if (!cmsSession.isValid()) {
- ((CmsSessionImpl) cmsSession).close();
- if (log.isDebugEnabled())
- log.debug("Closed expired CMS session " + cmsSession);
- }
- }
- } catch (InvalidSyntaxException e) {
- throw new IllegalArgumentException("Cannot get CMS sessions", e);
- }
+ return "CMS Session " + userDn + " localId=" + localSessionId + ", uuid=" + uuid;
}
}
+++ /dev/null
-package org.argeo.cms.internal.auth;
-
-import static org.argeo.util.naming.LdapAttrs.cn;
-import static org.argeo.util.naming.LdapAttrs.description;
-import static org.argeo.util.naming.LdapAttrs.owner;
-
-import java.time.ZoneOffset;
-import java.time.ZonedDateTime;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Dictionary;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Hashtable;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.UUID;
-
-import javax.naming.InvalidNameException;
-import javax.naming.ldap.LdapName;
-import javax.security.auth.Subject;
-
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.cms.CmsUserManager;
-import org.argeo.cms.auth.CurrentUser;
-import org.argeo.cms.auth.UserAdminUtils;
-import org.argeo.osgi.transaction.WorkTransaction;
-import org.argeo.osgi.useradmin.TokenUtils;
-import org.argeo.osgi.useradmin.UserAdminConf;
-import org.argeo.osgi.useradmin.UserDirectory;
-import org.argeo.util.naming.LdapAttrs;
-import org.argeo.util.naming.NamingUtils;
-import org.argeo.util.naming.SharedSecret;
-import org.osgi.framework.InvalidSyntaxException;
-import org.osgi.service.useradmin.Authorization;
-import org.osgi.service.useradmin.Group;
-import org.osgi.service.useradmin.Role;
-import org.osgi.service.useradmin.User;
-import org.osgi.service.useradmin.UserAdmin;
-
-/**
- * Canonical implementation of the people {@link CmsUserManager}. Wraps
- * interaction with users and groups.
- *
- * In a *READ-ONLY* mode. We want to be able to:
- * <ul>
- * <li>Retrieve my user and corresponding information (main info,
- * groups...)</li>
- * <li>List all local groups (not the system roles)</li>
- * <li>If sufficient rights: retrieve a given user and its information</li>
- * </ul>
- */
-public class CmsUserManagerImpl implements CmsUserManager {
- private final static CmsLog log = CmsLog.getLog(CmsUserManagerImpl.class);
-
- private UserAdmin userAdmin;
-// private Map<String, String> serviceProperties;
- private WorkTransaction userTransaction;
-
- private Map<UserDirectory, Hashtable<String, Object>> userDirectories = Collections
- .synchronizedMap(new LinkedHashMap<>());
-
- @Override
- public String getMyMail() {
- return getUserMail(CurrentUser.getUsername());
- }
-
- @Override
- public Role[] getRoles(String filter) throws InvalidSyntaxException {
- return userAdmin.getRoles(filter);
- }
-
- // ALL USER: WARNING access to this will be later reduced
-
- /** Retrieve a user given his dn */
- public User getUser(String dn) {
- return (User) getUserAdmin().getRole(dn);
- }
-
- /** Can be a group or a user */
- public String getUserDisplayName(String dn) {
- // FIXME: during initialisation phase, the system logs "admin" as user
- // name rather than the corresponding dn
- if ("admin".equals(dn))
- return "System Administrator";
- else
- return UserAdminUtils.getUserDisplayName(getUserAdmin(), dn);
- }
-
- @Override
- public String getUserMail(String dn) {
- return UserAdminUtils.getUserMail(getUserAdmin(), dn);
- }
-
- /** Lists all roles of the given user */
- @Override
- public String[] getUserRoles(String dn) {
- Authorization currAuth = getUserAdmin().getAuthorization(getUser(dn));
- return currAuth.getRoles();
- }
-
- @Override
- public boolean isUserInRole(String userDn, String roleDn) {
- String[] roles = getUserRoles(userDn);
- for (String role : roles) {
- if (role.equalsIgnoreCase(roleDn))
- return true;
- }
- return false;
- }
-
- private final String[] knownProps = { LdapAttrs.cn.name(), LdapAttrs.sn.name(), LdapAttrs.givenName.name(),
- LdapAttrs.uid.name() };
-
- public Set<User> listUsersInGroup(String groupDn, String filter) {
- Group group = (Group) userAdmin.getRole(groupDn);
- if (group == null)
- throw new IllegalArgumentException("Group " + groupDn + " not found");
- Set<User> users = new HashSet<User>();
- addUsers(users, group, filter);
- return users;
- }
-
- /** Recursively add users to list */
- private void addUsers(Set<User> users, Group group, String filter) {
- Role[] roles = group.getMembers();
- for (Role role : roles) {
- if (role.getType() == Role.GROUP) {
- addUsers(users, (Group) role, filter);
- } else if (role.getType() == Role.USER) {
- if (match(role, filter))
- users.add((User) role);
- } else {
- // ignore
- }
- }
- }
-
- public List<User> listGroups(String filter, boolean includeUsers, boolean includeSystemRoles) {
- Role[] roles = null;
- try {
- roles = getUserAdmin().getRoles(filter);
- } catch (InvalidSyntaxException e) {
- throw new IllegalArgumentException("Unable to get roles with filter: " + filter, e);
- }
-
- List<User> users = new ArrayList<User>();
- for (Role role : roles) {
- if ((includeUsers && role.getType() == Role.USER || role.getType() == Role.GROUP) && !users.contains(role)
- && (includeSystemRoles || !role.getName().toLowerCase().endsWith(CmsConstants.ROLES_BASEDN))) {
- if (match(role, filter))
- users.add((User) role);
- }
- }
- return users;
- }
-
- private boolean match(Role role, String filter) {
- boolean doFilter = filter != null && !"".equals(filter);
- if (doFilter) {
- for (String prop : knownProps) {
- Object currProp = null;
- try {
- currProp = role.getProperties().get(prop);
- } catch (Exception e) {
- throw e;
- }
- if (currProp != null) {
- String currPropStr = ((String) currProp).toLowerCase();
- if (currPropStr.contains(filter.toLowerCase())) {
- return true;
- }
- }
- }
- return false;
- } else
- return true;
- }
-
- @Override
- public User getUserFromLocalId(String localId) {
- User user = getUserAdmin().getUser(LdapAttrs.uid.name(), localId);
- if (user == null)
- user = getUserAdmin().getUser(LdapAttrs.cn.name(), localId);
- return user;
- }
-
- @Override
- public String buildDefaultDN(String localId, int type) {
- return buildDistinguishedName(localId, getDefaultDomainName(), type);
- }
-
- @Override
- public String getDefaultDomainName() {
- Map<String, String> dns = getKnownBaseDns(true);
- if (dns.size() == 1)
- return dns.keySet().iterator().next();
- else
- throw new IllegalStateException("Current context contains " + dns.size() + " base dns: "
- + dns.keySet().toString() + ". Unable to chose a default one.");
- }
-
-// public Map<String, String> getKnownBaseDns(boolean onlyWritable) {
-// Map<String, String> dns = new HashMap<String, String>();
-// String[] propertyKeys = serviceProperties.keySet().toArray(new String[serviceProperties.size()]);
-// for (String uri : propertyKeys) {
-// 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 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());
-
- }
- return dns;
- }
-
- public String buildDistinguishedName(String localId, String baseDn, int type) {
- Map<String, String> dns = getKnownBaseDns(true);
- Dictionary<String, ?> props = UserAdminConf.uriAsProperties(dns.get(baseDn));
- String dn = null;
- if (Role.GROUP == type)
- dn = LdapAttrs.cn.name() + "=" + localId + "," + UserAdminConf.groupBase.getValue(props) + "," + baseDn;
- else if (Role.USER == type)
- dn = LdapAttrs.uid.name() + "=" + localId + "," + UserAdminConf.userBase.getValue(props) + "," + baseDn;
- else
- throw new IllegalStateException("Unknown role type. " + "Cannot deduce dn for " + localId);
- return dn;
- }
-
- @Override
- public void changeOwnPassword(char[] oldPassword, char[] newPassword) {
- String name = CurrentUser.getUsername();
- LdapName dn;
- try {
- dn = new LdapName(name);
- } catch (InvalidNameException e) {
- throw new IllegalArgumentException("Invalid user dn " + name, e);
- }
- User user = (User) userAdmin.getRole(dn.toString());
- if (!user.hasCredential(null, oldPassword))
- throw new IllegalArgumentException("Invalid password");
- if (Arrays.equals(newPassword, new char[0]))
- throw new IllegalArgumentException("New password empty");
- try {
- userTransaction.begin();
- user.getCredentials().put(null, newPassword);
- userTransaction.commit();
- } catch (Exception e) {
- try {
- userTransaction.rollback();
- } catch (Exception e1) {
- log.error("Could not roll back", e1);
- }
- if (e instanceof RuntimeException)
- throw (RuntimeException) e;
- else
- throw new RuntimeException("Cannot change password", e);
- }
- }
-
- public void resetPassword(String username, char[] newPassword) {
- LdapName dn;
- try {
- dn = new LdapName(username);
- } catch (InvalidNameException e) {
- throw new IllegalArgumentException("Invalid user dn " + username, e);
- }
- User user = (User) userAdmin.getRole(dn.toString());
- if (Arrays.equals(newPassword, new char[0]))
- throw new IllegalArgumentException("New password empty");
- try {
- userTransaction.begin();
- user.getCredentials().put(null, newPassword);
- userTransaction.commit();
- } catch (Exception e) {
- try {
- userTransaction.rollback();
- } catch (Exception e1) {
- log.error("Could not roll back", e1);
- }
- if (e instanceof RuntimeException)
- throw (RuntimeException) e;
- else
- throw new RuntimeException("Cannot change password", e);
- }
- }
-
- public String addSharedSecret(String email, int hours) {
- User user = (User) userAdmin.getUser(LdapAttrs.mail.name(), email);
- try {
- userTransaction.begin();
- String uuid = UUID.randomUUID().toString();
- SharedSecret sharedSecret = new SharedSecret(hours, uuid);
- user.getCredentials().put(SharedSecret.X_SHARED_SECRET, sharedSecret.toAuthPassword());
- String tokenStr = sharedSecret.getAuthInfo() + '$' + sharedSecret.getAuthValue();
- userTransaction.commit();
- return tokenStr;
- } catch (Exception e) {
- try {
- userTransaction.rollback();
- } catch (Exception e1) {
- log.error("Could not roll back", e1);
- }
- if (e instanceof RuntimeException)
- throw (RuntimeException) e;
- else
- throw new RuntimeException("Cannot change password", e);
- }
- }
-
- @Deprecated
- public String addSharedSecret(String username, String authInfo, String authToken) {
- try {
- userTransaction.begin();
- User user = (User) userAdmin.getRole(username);
- SharedSecret sharedSecret = new SharedSecret(authInfo, authToken);
- user.getCredentials().put(SharedSecret.X_SHARED_SECRET, sharedSecret.toAuthPassword());
- String tokenStr = sharedSecret.getAuthInfo() + '$' + sharedSecret.getAuthValue();
- userTransaction.commit();
- return tokenStr;
- } catch (Exception e1) {
- try {
- if (!userTransaction.isNoTransactionStatus())
- userTransaction.rollback();
- } catch (Exception e2) {
- if (log.isTraceEnabled())
- log.trace("Cannot rollback transaction", e2);
- }
- throw new RuntimeException("Cannot add shared secret", e1);
- }
- }
-
- @Override
- public void expireAuthToken(String token) {
- try {
- userTransaction.begin();
- String dn = cn + "=" + token + "," + CmsConstants.TOKENS_BASEDN;
- Group tokenGroup = (Group) userAdmin.getRole(dn);
- String ldapDate = NamingUtils.instantToLdapDate(ZonedDateTime.now(ZoneOffset.UTC));
- tokenGroup.getProperties().put(description.name(), ldapDate);
- userTransaction.commit();
- if (log.isDebugEnabled())
- log.debug("Token " + token + " expired.");
- } catch (Exception e1) {
- try {
- if (!userTransaction.isNoTransactionStatus())
- userTransaction.rollback();
- } catch (Exception e2) {
- if (log.isTraceEnabled())
- log.trace("Cannot rollback transaction", e2);
- }
- throw new RuntimeException("Cannot expire token", e1);
- }
- }
-
- @Override
- public void expireAuthTokens(Subject subject) {
- Set<String> tokens = TokenUtils.tokensUsed(subject, CmsConstants.TOKENS_BASEDN);
- for (String token : tokens)
- expireAuthToken(token);
- }
-
- @Override
- public void addAuthToken(String userDn, String token, Integer hours, String... roles) {
- addAuthToken(userDn, token, ZonedDateTime.now().plusHours(hours), roles);
- }
-
- @Override
- public void addAuthToken(String userDn, String token, ZonedDateTime expiryDate, String... roles) {
- try {
- userTransaction.begin();
- User user = (User) userAdmin.getRole(userDn);
- String tokenDn = cn + "=" + token + "," + CmsConstants.TOKENS_BASEDN;
- Group tokenGroup = (Group) userAdmin.createRole(tokenDn, Role.GROUP);
- if (roles != null)
- for (String role : roles) {
- Role r = userAdmin.getRole(role);
- if (r != null)
- tokenGroup.addMember(r);
- else {
- if (!role.equals(CmsConstants.ROLE_USER)) {
- throw new IllegalStateException(
- "Cannot add role " + role + " to token " + token + " for " + userDn);
- }
- }
- }
- tokenGroup.getProperties().put(owner.name(), user.getName());
- if (expiryDate != null) {
- String ldapDate = NamingUtils.instantToLdapDate(expiryDate);
- tokenGroup.getProperties().put(description.name(), ldapDate);
- }
- userTransaction.commit();
- } catch (Exception e1) {
- try {
- if (!userTransaction.isNoTransactionStatus())
- userTransaction.rollback();
- } catch (Exception e2) {
- if (log.isTraceEnabled())
- log.trace("Cannot rollback transaction", e2);
- }
- throw new RuntimeException("Cannot add token", e1);
- }
- }
-
-// public User createUserFromPerson(Node person) {
-// String email = JcrUtils.get(person, LdapAttrs.mail.property());
-// String dn = buildDefaultDN(email, Role.USER);
-// User user;
-// try {
-// userTransaction.begin();
-// user = (User) userAdmin.createRole(dn, Role.USER);
-// Dictionary<String, Object> userProperties = user.getProperties();
-// String name = JcrUtils.get(person, LdapAttrs.displayName.property());
-// userProperties.put(LdapAttrs.cn.name(), name);
-// userProperties.put(LdapAttrs.displayName.name(), name);
-// String givenName = JcrUtils.get(person, LdapAttrs.givenName.property());
-// String surname = JcrUtils.get(person, LdapAttrs.sn.property());
-// userProperties.put(LdapAttrs.givenName.name(), givenName);
-// userProperties.put(LdapAttrs.sn.name(), surname);
-// userProperties.put(LdapAttrs.mail.name(), email.toLowerCase());
-// userTransaction.commit();
-// } catch (Exception e) {
-// try {
-// userTransaction.rollback();
-// } catch (Exception e1) {
-// log.error("Could not roll back", e1);
-// }
-// if (e instanceof RuntimeException)
-// throw (RuntimeException) e;
-// else
-// throw new RuntimeException("Cannot create user", e);
-// }
-// return user;
-// }
-
- public UserAdmin getUserAdmin() {
- return userAdmin;
- }
-
-// public UserTransaction getUserTransaction() {
-// return userTransaction;
-// }
-
- /* DEPENDENCY INJECTION */
- public void setUserAdmin(UserAdmin userAdmin) {
- this.userAdmin = userAdmin;
-// this.serviceProperties = serviceProperties;
- }
-
- public void setUserTransaction(WorkTransaction userTransaction) {
- this.userTransaction = userTransaction;
- }
-
- public void addUserDirectory(UserDirectory userDirectory, Map<String, Object> properties) {
- userDirectories.put(userDirectory, new Hashtable<>(properties));
- }
-
- public void removeUserDirectory(UserDirectory userDirectory, Map<String, Object> properties) {
- userDirectories.remove(userDirectory);
- }
-
-}
+++ /dev/null
-package org.argeo.cms.internal.auth;
-
-import java.io.Console;
-import java.io.IOException;
-import java.io.PrintWriter;
-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;
-
-/** Callback handler to be used with a command line UI. */
-public class ConsoleCallbackHandler implements CallbackHandler {
-
- @Override
- public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
- Console console = System.console();
- if (console == null)
- throw new IllegalStateException("No console available");
-
- PrintWriter writer = console.writer();
- for (int i = 0; i < callbacks.length; i++) {
- if (callbacks[i] instanceof TextOutputCallback) {
- TextOutputCallback callback = (TextOutputCallback) callbacks[i];
- writer.write(callback.getMessage());
- } else if (callbacks[i] instanceof NameCallback) {
- NameCallback callback = (NameCallback) callbacks[i];
- writer.write(callback.getPrompt());
- if (callback.getDefaultName() != null)
- writer.write(" (" + callback.getDefaultName() + ")");
- writer.write(" : ");
- String answer = console.readLine();
- if (callback.getDefaultName() != null && answer.trim().equals(""))
- callback.setName(callback.getDefaultName());
- else
- callback.setName(answer);
- } else if (callbacks[i] instanceof PasswordCallback) {
- PasswordCallback callback = (PasswordCallback) callbacks[i];
- writer.write(callback.getPrompt());
- char[] answer = console.readPassword();
- callback.setPassword(answer);
- Arrays.fill(answer, ' ');
- }
-// else if (callbacks[i] instanceof LocaleChoice) {
-// LocaleChoice callback = (LocaleChoice) callbacks[i];
-// writer.write("Language");
-// writer.write("\n");
-// for (int j = 0; j < callback.getLocales().size(); j++) {
-// Locale locale = callback.getLocales().get(j);
-// writer.print(j + " : " + locale.getDisplayName() + "\n");
-// }
-// writer.write("(" + callback.getDefaultIndex() + ") : ");
-// String answer = console.readLine();
-// if (answer.trim().equals(""))
-// callback.setSelectedIndex(callback.getDefaultIndex());
-// else
-// callback.setSelectedIndex(new Integer(answer.trim()));
-// }
- }
- }
-
-}
package org.argeo.cms.internal.auth;
import java.security.Principal;
-import java.util.Collections;
-import java.util.Dictionary;
-import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
-import javax.naming.InvalidNameException;
-import javax.naming.ldap.LdapName;
+import javax.xml.namespace.QName;
+import org.argeo.cms.RoleNameUtils;
import org.osgi.service.useradmin.Authorization;
-import org.osgi.service.useradmin.Role;
/**
* A {@link Principal} which has been implied by an {@link Authorization}. If it
* identity is removed, the related {@link ImpliedByPrincipal}s can thus be
* removed.
*/
-public final class ImpliedByPrincipal implements Principal, Role {
- private final LdapName name;
- private Set<Principal> causes = new HashSet<Principal>();
+public final class ImpliedByPrincipal implements Principal {
+ private final String name;
+ private final QName roleName;
+ private final boolean systemRole;
+ private final String context;
- private int type = Role.ROLE;
+ private Set<Principal> causes = new HashSet<Principal>();
public ImpliedByPrincipal(String name, Principal userPrincipal) {
- try {
- this.name = new LdapName(name);
- } catch (InvalidNameException e) {
- throw new IllegalArgumentException("Badly formatted role name", e);
- }
- if (userPrincipal != null)
- causes.add(userPrincipal);
- }
-
- public ImpliedByPrincipal(LdapName name, Principal userPrincipal) {
this.name = name;
+ roleName = RoleNameUtils.getLastRdnAsName(name);
+ systemRole = RoleNameUtils.isSystemRole(roleName);
+ context = RoleNameUtils.getContext(name);
if (userPrincipal != null)
causes.add(userPrincipal);
}
public String getName() {
- return name.toString();
- }
-
- public boolean addMember(Principal user) {
- throw new UnsupportedOperationException();
- }
-
- public boolean removeMember(Principal user) {
- throw new UnsupportedOperationException();
- }
-
- public boolean isMember(Principal member) {
- return causes.contains(member);
- }
-
- public Enumeration<? extends Principal> members() {
- return Collections.enumeration(causes);
+ return name;
}
/*
- * USER ADMIN
+ * OBJECT
*/
- @Override
- /** Type of {@link Role}, if known. */
- public int getType() {
- return type;
+ public QName getRoleName() {
+ return roleName;
}
- @Override
- /** Not supported for the time being. */
- public Dictionary<String, Object> getProperties() {
- throw new UnsupportedOperationException();
+ public String getContext() {
+ return context;
}
- /*
- * OBJECT
- */
+ public boolean isSystemRole() {
+ return systemRole;
+ }
@Override
public int hashCode() {
@Override
public boolean equals(Object obj) {
- // if (this == obj)
- // return true;
if (obj instanceof ImpliedByPrincipal) {
ImpliedByPrincipal that = (ImpliedByPrincipal) obj;
// TODO check members too?
@Override
public String toString() {
- // return name.toString() + " implied by " + causes;
return name.toString();
}
}
--- /dev/null
+package org.argeo.cms.internal.auth;
+
+import java.util.Locale;
+import java.util.UUID;
+
+import javax.security.auth.Subject;
+
+import org.argeo.cms.auth.RemoteAuthRequest;
+import org.argeo.cms.auth.RemoteAuthSession;
+import org.osgi.service.useradmin.Authorization;
+
+/** CMS session implementation in a web context. */
+public class RemoteCmsSessionImpl extends CmsSessionImpl {
+ private static final long serialVersionUID = -5178883380637048025L;
+ private RemoteAuthSession httpSession;
+
+ public RemoteCmsSessionImpl(UUID uuid, Subject initialSubject, Authorization authorization, Locale locale,
+ RemoteAuthRequest request) {
+ super(uuid, initialSubject, authorization, locale,
+ request.getSession() != null ? request.getSession().getId() : null);
+ httpSession = request.getSession();
+ }
+
+ @Override
+ public boolean isValid() {
+ if (isClosed())
+ return false;
+ if (httpSession == null)
+ return true;
+ return httpSession.isValid();
+ }
+}
--- /dev/null
+package org.argeo.cms.internal.http;
+
+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.cms.CurrentUser;
+import org.argeo.cms.auth.RemoteAuthCallbackHandler;
+import org.argeo.cms.auth.RemoteAuthRequest;
+import org.argeo.cms.auth.RemoteAuthResponse;
+import org.argeo.cms.auth.RemoteAuthUtils;
+
+import com.sun.net.httpserver.Authenticator;
+import com.sun.net.httpserver.HttpExchange;
+import com.sun.net.httpserver.HttpPrincipal;
+
+/** An {@link Authenticator} implementation based on CMS authentication. */
+public class CmsAuthenticator extends Authenticator {
+ // TODO make it configurable
+ private final String httpAuthRealm = "Argeo";
+ private final boolean forceBasic = false;
+
+ @Override
+ public Result authenticate(HttpExchange exch) {
+ RemoteAuthHttpExchange remoteAuthExchange = new RemoteAuthHttpExchange(exch);
+ ClassLoader currentThreadContextClassLoader = Thread.currentThread().getContextClassLoader();
+ Thread.currentThread().setContextClassLoader(CmsAuthenticator.class.getClassLoader());
+ LoginContext lc;
+ try {
+ lc = CmsAuth.USER.newLoginContext(new RemoteAuthCallbackHandler(remoteAuthExchange, remoteAuthExchange));
+ lc.login();
+ } catch (LoginException e) {
+ if (authIsRequired(remoteAuthExchange, remoteAuthExchange)) {
+ int statusCode = RemoteAuthUtils.askForWwwAuth(remoteAuthExchange, remoteAuthExchange, httpAuthRealm,
+ forceBasic);
+ return new Authenticator.Retry(statusCode);
+
+ } else {
+ lc = RemoteAuthUtils.anonymousLogin(remoteAuthExchange, remoteAuthExchange);
+ }
+ if (lc == null)
+ return new Authenticator.Failure(403);
+ } finally {
+ Thread.currentThread().setContextClassLoader(currentThreadContextClassLoader);
+ }
+
+ Subject subject = lc.getSubject();
+
+ String username = CurrentUser.getUsername(subject);
+ HttpPrincipal httpPrincipal = new HttpPrincipal(username, httpAuthRealm);
+ return new Authenticator.Success(httpPrincipal);
+ }
+
+ protected boolean authIsRequired(RemoteAuthRequest remoteAuthRequest, RemoteAuthResponse remoteAuthResponse) {
+ return true;
+ }
+
+}
+++ /dev/null
-package org.argeo.cms.internal.http;
-
-/** Compatible with Jetty. */
-public interface InternalHttpConstants {
- static final String HTTP_ENABLED = "http.enabled";
- static final String HTTP_PORT = "http.port";
- static final String HTTP_HOST = "http.host";
- static final String HTTPS_ENABLED = "https.enabled";
- static final String HTTPS_HOST = "https.host";
- static final String HTTPS_PORT = "https.port";
- static final String SSL_KEYSTORE = "ssl.keystore";
- static final String SSL_PASSWORD = "ssl.password";
- static final String SSL_KEYPASSWORD = "ssl.keypassword";
- static final String SSL_NEEDCLIENTAUTH = "ssl.needclientauth";
- static final String SSL_WANTCLIENTAUTH = "ssl.wantclientauth";
- static final String SSL_PROTOCOL = "ssl.protocol";
- static final String SSL_ALGORITHM = "ssl.algorithm";
- static final String SSL_KEYSTORETYPE = "ssl.keystoretype";
- static final String JETTY_PROPERTY_PREFIX = "org.eclipse.equinox.http.jetty.";
- // Argeo specific
- static final String WEBSOCKET_ENABLED = "websocket.enabled";
-
-}
--- /dev/null
+package org.argeo.cms.internal.http;
+
+import org.argeo.cms.auth.RemoteAuthRequest;
+import org.argeo.cms.auth.RemoteAuthResponse;
+
+public class PublicCmsAuthenticator extends CmsAuthenticator {
+
+ @Override
+ protected boolean authIsRequired(RemoteAuthRequest remoteAuthRequest, RemoteAuthResponse remoteAuthResponse) {
+ return false;
+ }
+
+}
--- /dev/null
+package org.argeo.cms.internal.http;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.Objects;
+
+import org.argeo.cms.auth.RemoteAuthRequest;
+import org.argeo.cms.auth.RemoteAuthResponse;
+import org.argeo.cms.auth.RemoteAuthSession;
+
+import com.sun.net.httpserver.HttpExchange;
+
+public class RemoteAuthHttpExchange implements RemoteAuthRequest, RemoteAuthResponse {
+ private final HttpExchange httpExchange;
+ private RemoteAuthSession remoteAuthSession;
+
+ public RemoteAuthHttpExchange(HttpExchange httpExchange) {
+ this.httpExchange = httpExchange;
+ this.remoteAuthSession = (RemoteAuthSession) httpExchange.getAttribute(RemoteAuthSession.class.getName());
+ Objects.requireNonNull(this.remoteAuthSession);
+ }
+
+ @Override
+ public void setHeader(String headerName, String value) {
+ httpExchange.getResponseHeaders().put(headerName, Collections.singletonList(value));
+ }
+
+ @Override
+ public void addHeader(String headerName, String value) {
+ List<String> values = httpExchange.getResponseHeaders().getOrDefault(headerName, new ArrayList<>());
+ values.add(value);
+ }
+
+ @Override
+ public RemoteAuthSession getSession() {
+ return remoteAuthSession;
+ }
+
+ @Override
+ public RemoteAuthSession createSession() {
+ throw new UnsupportedOperationException("Cannot create remote session");
+ }
+
+ @Override
+ public Locale getLocale() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public Object getAttribute(String key) {
+ return httpExchange.getAttribute(key);
+ }
+
+ @Override
+ public void setAttribute(String key, Object object) {
+ httpExchange.setAttribute(key, object);
+ }
+
+ @Override
+ public String getHeader(String key) {
+ List<String> lst = httpExchange.getRequestHeaders().get(key);
+ if (lst == null || lst.size() == 0)
+ return null;
+ return lst.get(0);
+ }
+
+ @Override
+ public int getLocalPort() {
+ return httpExchange.getLocalAddress().getPort();
+ }
+
+ @Override
+ public String getRemoteAddr() {
+ return httpExchange.getRemoteAddress().getHostName();
+ }
+
+ @Override
+ public int getRemotePort() {
+ return httpExchange.getRemoteAddress().getPort();
+ }
+
+}
+++ /dev/null
-package org.argeo.cms.internal.http;
-
-import java.util.Locale;
-
-import javax.security.auth.Subject;
-
-import org.argeo.cms.auth.RemoteAuthRequest;
-import org.argeo.cms.auth.RemoteAuthSession;
-import org.argeo.cms.internal.auth.CmsSessionImpl;
-import org.osgi.service.useradmin.Authorization;
-
-/** CMS session implementation in a web context. */
-public class WebCmsSessionImpl extends CmsSessionImpl {
- private static final long serialVersionUID = -5178883380637048025L;
- private RemoteAuthSession httpSession;
-
- public WebCmsSessionImpl(Subject initialSubject, Authorization authorization, Locale locale,
- RemoteAuthRequest request) {
- super(initialSubject, authorization, locale, request.getSession().getId());
- httpSession = request.getSession();
- }
-
- @Override
- public boolean isValid() {
- if (isClosed())
- return false;
- return httpSession.isValid();
- }
-
- public static CmsSessionImpl getCmsSession(RemoteAuthRequest request) {
- return CmsSessionImpl.getByLocalId(request.getSession().getId());
- }
-}
+++ /dev/null
-package org.argeo.cms.internal.http.client;
-
-import org.apache.commons.httpclient.Credentials;
-import org.apache.commons.httpclient.auth.AuthScheme;
-import org.apache.commons.httpclient.auth.CredentialsNotAvailableException;
-import org.apache.commons.httpclient.auth.CredentialsProvider;
-
-/** SPNEGO credential provider */
-public class HttpCredentialProvider implements CredentialsProvider {
-
- @Override
- public Credentials getCredentials(AuthScheme scheme, String host, int port, boolean proxy)
- throws CredentialsNotAvailableException {
- if (scheme instanceof SpnegoAuthScheme)
- return new SpnegoCredentials();
- else
- throw new UnsupportedOperationException("Auth scheme " + scheme.getSchemeName() + " not supported");
- }
-
-}
+++ /dev/null
-package org.argeo.cms.internal.http.client;
-
-import java.net.URL;
-import java.security.PrivilegedExceptionAction;
-import java.util.ArrayList;
-import java.util.Base64;
-
-import javax.security.auth.Subject;
-import javax.security.auth.login.LoginContext;
-
-import org.apache.commons.httpclient.Credentials;
-import org.apache.commons.httpclient.HttpClient;
-import org.apache.commons.httpclient.HttpMethod;
-import org.apache.commons.httpclient.URIException;
-import org.apache.commons.httpclient.auth.AuthPolicy;
-import org.apache.commons.httpclient.auth.AuthScheme;
-import org.apache.commons.httpclient.auth.AuthenticationException;
-import org.apache.commons.httpclient.auth.CredentialsProvider;
-import org.apache.commons.httpclient.auth.MalformedChallengeException;
-import org.apache.commons.httpclient.methods.GetMethod;
-import org.apache.commons.httpclient.params.DefaultHttpParams;
-import org.apache.commons.httpclient.params.HttpParams;
-import org.argeo.cms.internal.runtime.KernelConstants;
-import org.ietf.jgss.GSSContext;
-import org.ietf.jgss.GSSException;
-import org.ietf.jgss.GSSManager;
-import org.ietf.jgss.GSSName;
-import org.ietf.jgss.Oid;
-
-/** Implementation of the SPNEGO auth scheme. */
-public class SpnegoAuthScheme implements AuthScheme {
-// private final static Log log = LogFactory.getLog(SpnegoAuthScheme.class);
-
- public static final String NAME = "Negotiate";
- private final static Oid KERBEROS_OID;
- static {
- try {
- KERBEROS_OID = new Oid("1.3.6.1.5.5.2");
- } catch (GSSException e) {
- throw new IllegalStateException("Cannot create Kerberos OID", e);
- }
- }
-
- private boolean complete = false;
- private String realm;
-
- @Override
- public void processChallenge(String challenge) throws MalformedChallengeException {
- // if(tokenStr!=null){
- // log.error("Received challenge while there is a token. Failing.");
- // complete = false;
- // }
-
- }
-
- @Override
- public String getSchemeName() {
- return NAME;
- }
-
- @Override
- public String getParameter(String name) {
- return null;
- }
-
- @Override
- public String getRealm() {
- return realm;
- }
-
- @Override
- public String getID() {
- return NAME;
- }
-
- @Override
- public boolean isConnectionBased() {
- return true;
- }
-
- @Override
- public boolean isComplete() {
- return complete;
- }
-
- @Override
- public String authenticate(Credentials credentials, String method, String uri) throws AuthenticationException {
- // log.debug("authenticate " + method + " " + uri);
- // return null;
- throw new UnsupportedOperationException();
- }
-
- @Override
- public String authenticate(Credentials credentials, HttpMethod method) throws AuthenticationException {
- GSSContext context = null;
- String tokenStr = null;
- String hostname;
- try {
- hostname = method.getURI().getHost();
- } catch (URIException e1) {
- throw new IllegalStateException("Cannot authenticate", e1);
- }
- String serverPrinc = KernelConstants.DEFAULT_KERBEROS_SERVICE + "@" + hostname;
-
- try {
- // Get service's principal name
- GSSManager manager = GSSManager.getInstance();
- GSSName serverName = manager.createName(serverPrinc, GSSName.NT_HOSTBASED_SERVICE, KERBEROS_OID);
-
- // Get the context for authentication
- context = manager.createContext(serverName, KERBEROS_OID, null, GSSContext.DEFAULT_LIFETIME);
- // context.requestMutualAuth(true); // Request mutual authentication
- // context.requestConf(true); // Request confidentiality
- context.requestCredDeleg(true);
-
- byte[] token = new byte[0];
-
- // token is ignored on the first call
- token = context.initSecContext(token, 0, token.length);
-
- // Send a token to the server if one was generated by
- // initSecContext
- if (token != null) {
- tokenStr = Base64.getEncoder().encodeToString(token);
- // complete=true;
- }
- return "Negotiate " + tokenStr;
- } catch (GSSException e) {
- complete = true;
- throw new AuthenticationException("Cannot authenticate to " + serverPrinc, e);
- }
- }
-
- public static void main(String[] args) {
- if (args.length == 0) {
- System.err.println("usage: java " + SpnegoAuthScheme.class.getName() + " <url>");
- System.exit(1);
- return;
- }
- String url = args[0];
-
- URL jaasUrl = SpnegoAuthScheme.class.getResource("jaas.cfg");
- System.setProperty("java.security.auth.login.config", jaasUrl.toExternalForm());
- try {
- LoginContext lc = new LoginContext("SINGLE_USER");
- lc.login();
-
- AuthPolicy.registerAuthScheme(SpnegoAuthScheme.NAME, SpnegoAuthScheme.class);
- HttpParams params = DefaultHttpParams.getDefaultParams();
- ArrayList<String> schemes = new ArrayList<>();
- schemes.add(SpnegoAuthScheme.NAME);
- params.setParameter(AuthPolicy.AUTH_SCHEME_PRIORITY, schemes);
- params.setParameter(CredentialsProvider.PROVIDER, new HttpCredentialProvider());
-
- int responseCode = Subject.doAs(lc.getSubject(), new PrivilegedExceptionAction<Integer>() {
- public Integer run() throws Exception {
- HttpClient httpClient = new HttpClient();
- return httpClient.executeMethod(new GetMethod(url));
- }
- });
- System.out.println("Reponse code: " + responseCode);
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
-
-}
+++ /dev/null
-package org.argeo.cms.internal.http.client;
-
-import org.apache.commons.httpclient.Credentials;
-
-public class SpnegoCredentials implements Credentials {
-
-}
+++ /dev/null
-SINGLE_USER {
- com.sun.security.auth.module.Krb5LoginModule optional
- principal="${user.name}"
- useTicketCache=true;
-};
import java.util.Dictionary;
import org.argeo.api.cms.CmsLog;
-import org.argeo.cms.ArgeoLogger;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
-import org.osgi.framework.ServiceReference;
import org.osgi.service.condpermadmin.BundleLocationCondition;
import org.osgi.service.condpermadmin.ConditionInfo;
import org.osgi.service.condpermadmin.ConditionalPermissionAdmin;
import org.osgi.service.condpermadmin.ConditionalPermissionInfo;
import org.osgi.service.condpermadmin.ConditionalPermissionUpdate;
-import org.osgi.service.http.HttpService;
-import org.osgi.service.log.LogReaderService;
import org.osgi.service.permissionadmin.PermissionInfo;
-import org.osgi.util.tracker.ServiceTracker;
/**
* Activates the kernel. Gives access to kernel information for the rest of the
public class CmsActivator implements BundleActivator {
private final static CmsLog log = CmsLog.getLog(CmsActivator.class);
-// private static Activator instance;
-
// TODO make it configurable
private boolean hardened = false;
private static BundleContext bundleContext;
- private LogReaderService logReaderService;
-
- private CmsOsgiLogger logger;
-// private CmsStateImpl nodeState;
-// private CmsDeploymentImpl nodeDeployment;
-// private CmsContextImpl nodeInstance;
-
-// private ServiceTracker<UserAdmin, NodeUserAdmin> userAdminSt;
-
-// static {
-// Bundle bundle = FrameworkUtil.getBundle(Activator.class);
-// if (bundle != null) {
-// bundleContext = bundle.getBundleContext();
-// }
-// }
-
void init() {
-// Runtime.getRuntime().addShutdownHook(new CmsShutdown());
-// instance = this;
-// this.bc = bundleContext;
- if (bundleContext != null)
- this.logReaderService = getService(LogReaderService.class);
- initArgeoLogger();
-// this.internalExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
-//
-// try {
-// initSecurity();
-//// initArgeoLogger();
-// initNode();
-//
-// if (log.isTraceEnabled())
-// log.trace("Kernel bundle started");
-// } catch (Throwable e) {
-// log.error("## FATAL: CMS activator failed", e);
-// }
}
void destroy() {
try {
-// if (nodeInstance != null)
-// nodeInstance.shutdown();
-// if (nodeDeployment != null)
-// nodeDeployment.shutdown();
-// if (nodeState != null)
-// nodeState.shutdown();
-//
-// if (userAdminSt != null)
-// userAdminSt.close();
-
-// internalExecutorService.shutdown();
-// instance = null;
bundleContext = null;
- this.logReaderService = null;
- // this.configurationAdmin = null;
+// this.logReaderService = null;
} catch (Exception e) {
log.error("CMS activator shutdown failed", e);
}
-
+
new GogoShellKiller().start();
}
- private void initSecurity() {
+ protected void initSecurity() {
// code-level permissions
String osgiSecurity = bundleContext.getProperty(Constants.FRAMEWORK_SECURITY);
if (osgiSecurity != null && Constants.FRAMEWORK_SECURITY_OSGI.equals(osgiSecurity)) {
}
- private void initArgeoLogger() {
- logger = new CmsOsgiLogger(logReaderService);
- if (bundleContext != null)
- bundleContext.registerService(ArgeoLogger.class, logger, null);
- }
-
-// private void initNode() throws IOException {
-// // Node state
-// nodeState = new CmsStateImpl();
-// registerService(CmsState.class, nodeState, null);
-//
-// // Node deployment
-// nodeDeployment = new CmsDeploymentImpl();
-//// registerService(NodeDeployment.class, nodeDeployment, null);
-//
-// // Node instance
-// nodeInstance = new CmsContextImpl();
-// registerService(CmsContext.class, nodeInstance, null);
-// }
-
public static <T> void registerService(Class<T> clss, T service, Dictionary<String, ?> properties) {
if (bundleContext != null) {
bundleContext.registerService(clss, service, properties);
@Override
public void start(BundleContext bc) throws Exception {
bundleContext = bc;
-// if (!bc.getBundle().equals(bundleContext.getBundle()))
-// throw new IllegalStateException(
-// "Bundle " + bc.getBundle() + " is not consistent with " + bundleContext.getBundle());
- init();
-// userAdminSt = new ServiceTracker<>(bundleContext, UserAdmin.class, null);
-// userAdminSt.open();
- ServiceTracker<?, ?> httpSt = new ServiceTracker<HttpService, HttpService>(bc, HttpService.class, null) {
-
- @Override
- public HttpService addingService(ServiceReference<HttpService> sr) {
- Object httpPort = sr.getProperty("http.port");
- Object httpsPort = sr.getProperty("https.port");
- log.info(httpPortsMsg(httpPort, httpsPort));
- close();
- return super.addingService(sr);
- }
- };
- httpSt.open();
- }
+ init();
- private String httpPortsMsg(Object httpPort, Object httpsPort) {
- return (httpPort != null ? "HTTP " + httpPort + " " : " ") + (httpsPort != null ? "HTTPS " + httpsPort : "");
}
@Override
public void stop(BundleContext bc) throws Exception {
-// if (!bc.getBundle().equals(bundleContext.getBundle()))
-// throw new IllegalStateException(
-// "Bundle " + bc.getBundle() + " is not consistent with " + bundleContext.getBundle());
+
destroy();
bundleContext = null;
}
-// private <T> T getService(Class<T> clazz) {
-// ServiceReference<T> sr = bundleContext.getServiceReference(clazz);
-// if (sr == null)
-// throw new IllegalStateException("No service available for " + clazz);
-// return bundleContext.getService(sr);
-// }
-
-// public static GSSCredential getAcceptorCredentials() {
-// return getNodeUserAdmin().getAcceptorCredentials();
-// }
-//
-// @Deprecated
-// public static boolean isSingleUser() {
-// return getNodeUserAdmin().isSingleUser();
-// }
-//
-// public static UserAdmin getUserAdmin() {
-// return (UserAdmin) getNodeUserAdmin();
-// }
-//
-// public static String getHttpProxySslHeader() {
-// return KernelUtils.getFrameworkProp(CmsConstants.HTTP_PROXY_SSL_DN);
-// }
-//
-// private static NodeUserAdmin getNodeUserAdmin() {
-// NodeUserAdmin res;
-// try {
-// res = instance.userAdminSt.waitForService(60000);
-// } catch (InterruptedException e) {
-// throw new IllegalStateException("Cannot retrieve Node user admin", e);
-// }
-// if (res == null)
-// throw new IllegalStateException("No Node user admin found");
-//
-// return res;
-// // ServiceReference<UserAdmin> sr =
-// // instance.bc.getServiceReference(UserAdmin.class);
-// // NodeUserAdmin userAdmin = (NodeUserAdmin) instance.bc.getService(sr);
-// // return userAdmin;
-//
-// }
-
-// public static ExecutorService getInternalExecutorService() {
-// return instance.internalExecutorService;
-// }
-
- // static CmsSecurity getCmsSecurity() {
- // return instance.nodeSecurity;
- // }
-
-// public String[] getLocales() {
-// // TODO optimize?
-// List<Locale> locales = CmsStateImpl.getNodeState().getLocales();
-// String[] res = new String[locales.size()];
-// for (int i = 0; i < locales.size(); i++)
-// res[i] = locales.get(i).toString();
-// return res;
-// }
-
public static BundleContext getBundleContext() {
return bundleContext;
}
package org.argeo.cms.internal.osgi;
-import java.io.IOException;
-import java.nio.file.FileSystems;
-import java.nio.file.Path;
-import java.nio.file.StandardWatchEventKinds;
-import java.nio.file.WatchEvent;
-import java.nio.file.WatchKey;
-import java.nio.file.WatchService;
import java.security.SignatureException;
-import java.util.ArrayList;
-import java.util.Collections;
import java.util.Enumeration;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.ListIterator;
-import java.util.Map;
-import java.util.Properties;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.LinkedBlockingQueue;
import org.argeo.api.cms.CmsConstants;
import org.argeo.api.cms.CmsLog;
-import org.argeo.cms.ArgeoLogListener;
-import org.argeo.cms.ArgeoLogger;
-import org.argeo.cms.CmsException;
-import org.argeo.cms.auth.CurrentUser;
-import org.argeo.cms.internal.runtime.KernelConstants;
-import org.argeo.osgi.useradmin.UserAdminConf;
+import org.argeo.cms.runtime.DirectoryConf;
import org.osgi.framework.Bundle;
import org.osgi.framework.Constants;
import org.osgi.framework.ServiceReference;
import org.osgi.service.log.LogListener;
import org.osgi.service.log.LogReaderService;
-/** Not meant to be used directly in standard log4j config */
-public class CmsOsgiLogger implements ArgeoLogger, LogListener {
- /** Internal debug for development purposes. */
- private static Boolean debug = false;
+/** Logs OSGi events. */
+public class CmsOsgiLogger implements LogListener {
+ private final static String WHITEBOARD_PATTERN_PROP = "osgi.http.whiteboard.servlet.pattern";
+ private final static String CONTEXT_NAME_PROP = "contextName";
+
+ private LogReaderService logReaderService;
- private Boolean disabled = false;
+// /** Internal debug for development purposes. */
+// private static Boolean debug = false;
- private String level = null;
+// private Boolean disabled = false;
+//
+// private String level = null;
// private Level log4jLevel = null;
- private Properties configuration;
-
- private AppenderImpl appender;
-
- private final List<ArgeoLogListener> everythingListeners = Collections
- .synchronizedList(new ArrayList<ArgeoLogListener>());
- private final List<ArgeoLogListener> allUsersListeners = Collections
- .synchronizedList(new ArrayList<ArgeoLogListener>());
- private final Map<String, List<ArgeoLogListener>> userListeners = Collections
- .synchronizedMap(new HashMap<String, List<ArgeoLogListener>>());
+// private Properties configuration;
- private BlockingQueue<LogEvent> events;
- private LogDispatcherThread logDispatcherThread = new LogDispatcherThread();
+// private AppenderImpl appender;
- private Integer maxLastEventsCount = 10 * 1000;
+// private BlockingQueue<LogEvent> events;
+// private LogDispatcherThread logDispatcherThread = new LogDispatcherThread();
- /** Marker to prevent stack overflow */
- private ThreadLocal<Boolean> dispatching = new ThreadLocal<Boolean>() {
+// private Integer maxLastEventsCount = 10 * 1000;
+//
+// /** Marker to prevent stack overflow */
+// private ThreadLocal<Boolean> dispatching = new ThreadLocal<Boolean>() {
+//
+// @Override
+// protected Boolean initialValue() {
+// return false;
+// }
+// };
- @Override
- protected Boolean initialValue() {
- return false;
- }
- };
+// public CmsOsgiLogger(LogReaderService lrs) {
+// }
- public CmsOsgiLogger(LogReaderService lrs) {
- if (lrs != null) {
- Enumeration<LogEntry> logEntries = lrs.getLog();
+ public void start() {
+ if (logReaderService != null) {
+ Enumeration<LogEntry> logEntries = logReaderService.getLog();
while (logEntries.hasMoreElements())
logged(logEntries.nextElement());
- lrs.addLogListener(this);
+ logReaderService.addLogListener(this);
// configure log4j watcher
// String log4jConfiguration = KernelUtils.getFrameworkProp("log4j.configuration");
// }
// }
}
+// try {
+//// events = new LinkedBlockingQueue<LogEvent>();
+////
+//// // if (layout != null)
+//// // setLayout(layout);
+//// // else
+//// // setLayout(new PatternLayout(pattern));
+////// appender = new AppenderImpl();
+//// reloadConfiguration();
+////// Logger.getRootLogger().addAppender(appender);
+////
+//// logDispatcherThread = new LogDispatcherThread();
+//// logDispatcherThread.start();
+// } catch (Exception e) {
+// throw new IllegalStateException("Cannot initialize log4j");
+// }
}
- public void init() {
- try {
- events = new LinkedBlockingQueue<LogEvent>();
-
- // if (layout != null)
- // setLayout(layout);
- // else
- // setLayout(new PatternLayout(pattern));
- appender = new AppenderImpl();
- reloadConfiguration();
-// Logger.getRootLogger().addAppender(appender);
-
- logDispatcherThread = new LogDispatcherThread();
- logDispatcherThread.start();
- } catch (Exception e) {
- throw new CmsException("Cannot initialize log4j");
- }
- }
-
- public void destroy() throws Exception {
-// Logger.getRootLogger().removeAppender(appender);
- allUsersListeners.clear();
- for (List<ArgeoLogListener> lst : userListeners.values())
- lst.clear();
- userListeners.clear();
-
- events.clear();
- events = null;
- logDispatcherThread.interrupt();
+ public void stop() throws Exception {
+// events.clear();
+// events = null;
+// logDispatcherThread.interrupt();
+ logReaderService.removeLogListener(this);
}
- // public void setLayout(Layout layout) {
- // this.layout = layout;
- // }
-
public String toString() {
return "Node Logger";
}
// sb.append(" " + Constants.SERVICE_PID + ": " + servicePid);
// }
// servlets
- Object whiteBoardPattern = sr.getProperty(KernelConstants.WHITEBOARD_PATTERN_PROP);
+ Object whiteBoardPattern = sr.getProperty(WHITEBOARD_PATTERN_PROP);
if (whiteBoardPattern != null) {
if (whiteBoardPattern instanceof String) {
- sb.append(" " + KernelConstants.WHITEBOARD_PATTERN_PROP + ": " + whiteBoardPattern);
+ sb.append(" " + WHITEBOARD_PATTERN_PROP + ": " + whiteBoardPattern);
} else {
- sb.append(" " + KernelConstants.WHITEBOARD_PATTERN_PROP + ": "
- + arrayToString((String[]) whiteBoardPattern));
+ sb.append(" " + WHITEBOARD_PATTERN_PROP + ": " + arrayToString((String[]) whiteBoardPattern));
}
}
// RWT
- Object contextName = sr.getProperty(KernelConstants.CONTEXT_NAME_PROP);
+ Object contextName = sr.getProperty(CONTEXT_NAME_PROP);
if (contextName != null)
- sb.append(" " + KernelConstants.CONTEXT_NAME_PROP + ": " + contextName);
+ sb.append(" " + CONTEXT_NAME_PROP + ": " + contextName);
// user directories
- Object baseDn = sr.getProperty(UserAdminConf.baseDn.name());
+ Object baseDn = sr.getProperty(DirectoryConf.baseDn.name());
if (baseDn != null)
- sb.append(" " + UserAdminConf.baseDn.name() + ": " + baseDn);
+ sb.append(" " + DirectoryConf.baseDn.name() + ": " + baseDn);
}
return sb.toString();
return false;
}
+ public void setLogReaderService(LogReaderService logReaderService) {
+ this.logReaderService = logReaderService;
+ }
+
+
//
// ARGEO LOGGER
//
- public synchronized void register(ArgeoLogListener listener, Integer numberOfPreviousEvents) {
- String username = CurrentUser.getUsername();
- if (username == null)
- throw new CmsException("Only authenticated users can register a log listener");
-
- if (!userListeners.containsKey(username)) {
- List<ArgeoLogListener> lst = Collections.synchronizedList(new ArrayList<ArgeoLogListener>());
- userListeners.put(username, lst);
- }
- userListeners.get(username).add(listener);
- List<LogEvent> lastEvents = logDispatcherThread.getLastEvents(username, numberOfPreviousEvents);
- for (LogEvent evt : lastEvents)
- dispatchEvent(listener, evt);
- }
-
- public synchronized void registerForAll(ArgeoLogListener listener, Integer numberOfPreviousEvents,
- boolean everything) {
- if (everything)
- everythingListeners.add(listener);
- else
- allUsersListeners.add(listener);
- List<LogEvent> lastEvents = logDispatcherThread.getLastEvents(null, numberOfPreviousEvents);
- for (LogEvent evt : lastEvents)
- if (everything || evt.getUsername() != null)
- dispatchEvent(listener, evt);
- }
-
- public synchronized void unregister(ArgeoLogListener listener) {
- String username = CurrentUser.getUsername();
- if (username == null)// FIXME
- return;
- if (!userListeners.containsKey(username))
- throw new IllegalStateException("No user listeners " + listener + " registered for user " + username);
- if (!userListeners.get(username).contains(listener))
- throw new IllegalStateException("No user listeners " + listener + " registered for user " + username);
- userListeners.get(username).remove(listener);
- if (userListeners.get(username).isEmpty())
- userListeners.remove(username);
-
- }
-
- public synchronized void unregisterForAll(ArgeoLogListener listener) {
- everythingListeners.remove(listener);
- allUsersListeners.remove(listener);
- }
-
- /** For development purpose, since using regular logging is not easy here */
- private static void stdOut(Object obj) {
- System.out.println(obj);
- }
-
- private static void stdErr(Object obj) {
- System.err.println(obj);
- }
-
- private static void debug(Object obj) {
- if (debug)
- System.out.println(obj);
- }
-
- private static boolean isInternalDebugEnabled() {
- return debug;
- }
+// public synchronized void register(ArgeoLogListener listener, Integer numberOfPreviousEvents) {
+// String username = CurrentUser.getUsername();
+// if (username == null)
+// throw new IllegalStateException("Only authenticated users can register a log listener");
+//
+// if (!userListeners.containsKey(username)) {
+// List<ArgeoLogListener> lst = Collections.synchronizedList(new ArrayList<ArgeoLogListener>());
+// userListeners.put(username, lst);
+// }
+// userListeners.get(username).add(listener);
+// List<LogEvent> lastEvents = logDispatcherThread.getLastEvents(username, numberOfPreviousEvents);
+// for (LogEvent evt : lastEvents)
+// dispatchEvent(listener, evt);
+// }
+//
+// public synchronized void registerForAll(ArgeoLogListener listener, Integer numberOfPreviousEvents,
+// boolean everything) {
+// if (everything)
+// everythingListeners.add(listener);
+// else
+// allUsersListeners.add(listener);
+// List<LogEvent> lastEvents = logDispatcherThread.getLastEvents(null, numberOfPreviousEvents);
+// for (LogEvent evt : lastEvents)
+// if (everything || evt.getUsername() != null)
+// dispatchEvent(listener, evt);
+// }
+//
+// public synchronized void unregister(ArgeoLogListener listener) {
+// String username = CurrentUser.getUsername();
+// if (username == null)// FIXME
+// return;
+// if (!userListeners.containsKey(username))
+// throw new IllegalStateException("No user listeners " + listener + " registered for user " + username);
+// if (!userListeners.get(username).contains(listener))
+// throw new IllegalStateException("No user listeners " + listener + " registered for user " + username);
+// userListeners.get(username).remove(listener);
+// if (userListeners.get(username).isEmpty())
+// userListeners.remove(username);
+//
+// }
+//
+// public synchronized void unregisterForAll(ArgeoLogListener listener) {
+// everythingListeners.remove(listener);
+// allUsersListeners.remove(listener);
+// }
+
+// /** For development purpose, since using regular logging is not easy here */
+// private static void stdOut(Object obj) {
+// System.out.println(obj);
+// }
+//
+// private static void stdErr(Object obj) {
+// System.err.println(obj);
+// }
+//
+// private static void debug(Object obj) {
+// if (debug)
+// System.out.println(obj);
+// }
+//
+// private static boolean isInternalDebugEnabled() {
+// return debug;
+// }
// public void setPattern(String pattern) {
// this.pattern = pattern;
// }
- public void setDisabled(Boolean disabled) {
- this.disabled = disabled;
- }
-
- public void setLevel(String level) {
- this.level = level;
- }
-
- public void setConfiguration(Properties configuration) {
- this.configuration = configuration;
- }
-
- public void updateConfiguration(Properties configuration) {
- setConfiguration(configuration);
- reloadConfiguration();
- }
-
- public Properties getConfiguration() {
- return configuration;
- }
-
- /**
- * Reloads configuration (if the configuration {@link Properties} is set)
- */
- protected void reloadConfiguration() {
- if (configuration != null) {
-// LogManager.resetConfiguration();
-// PropertyConfigurator.configure(configuration);
- }
- }
-
- protected synchronized void processLoggingEvent(LogEvent event) {
- if (disabled)
- return;
-
- if (dispatching.get())
- return;
-
- if (level != null && !level.trim().equals("")) {
-// if (log4jLevel == null || !log4jLevel.toString().equals(level))
-// try {
-// log4jLevel = Level.toLevel(level);
-// } catch (Exception e) {
-// System.err.println("Log4j level could not be set for level '" + level + "', resetting it to null.");
-// e.printStackTrace();
-// level = null;
-// }
+// public void setDisabled(Boolean disabled) {
+// this.disabled = disabled;
+// }
//
-// if (log4jLevel != null && !event.getLoggingEvent().getLevel().isGreaterOrEqual(log4jLevel)) {
-// return;
-// }
- }
-
- try {
- // admin listeners
- Iterator<ArgeoLogListener> everythingIt = everythingListeners.iterator();
- while (everythingIt.hasNext())
- dispatchEvent(everythingIt.next(), event);
-
- if (event.getUsername() != null) {
- Iterator<ArgeoLogListener> allUsersIt = allUsersListeners.iterator();
- while (allUsersIt.hasNext())
- dispatchEvent(allUsersIt.next(), event);
-
- if (userListeners.containsKey(event.getUsername())) {
- Iterator<ArgeoLogListener> userIt = userListeners.get(event.getUsername()).iterator();
- while (userIt.hasNext())
- dispatchEvent(userIt.next(), event);
- }
- }
- } catch (Exception e) {
- stdOut("Cannot process logging event");
- e.printStackTrace();
- }
- }
-
- protected void dispatchEvent(ArgeoLogListener logListener, LogEvent evt) {
-// LoggingEvent event = evt.getLoggingEvent();
-// logListener.appendLog(evt.getUsername(), event.getTimeStamp(), event.getLevel().toString(),
-// event.getLoggerName(), event.getThreadName(), event.getMessage(), event.getThrowableStrRep());
- }
+// public void setLevel(String level) {
+// this.level = level;
+// }
- private class AppenderImpl { // extends AppenderSkeleton {
- public boolean requiresLayout() {
- return false;
- }
+// public void setConfiguration(Properties configuration) {
+// this.configuration = configuration;
+// }
+//
+// public void updateConfiguration(Properties configuration) {
+// setConfiguration(configuration);
+// reloadConfiguration();
+// }
+//
+// public Properties getConfiguration() {
+// return configuration;
+// }
+//
+// /**
+// * Reloads configuration (if the configuration {@link Properties} is set)
+// */
+// protected void reloadConfiguration() {
+// if (configuration != null) {
+//// LogManager.resetConfiguration();
+//// PropertyConfigurator.configure(configuration);
+// }
+// }
- public void close() {
- }
+// protected synchronized void processLoggingEvent(LogEvent event) {
+// if (disabled)
+// return;
+//
+// if (dispatching.get())
+// return;
+//
+// if (level != null && !level.trim().equals("")) {
+//// if (log4jLevel == null || !log4jLevel.toString().equals(level))
+//// try {
+//// log4jLevel = Level.toLevel(level);
+//// } catch (Exception e) {
+//// System.err.println("Log4j level could not be set for level '" + level + "', resetting it to null.");
+//// e.printStackTrace();
+//// level = null;
+//// }
+////
+//// if (log4jLevel != null && !event.getLoggingEvent().getLevel().isGreaterOrEqual(log4jLevel)) {
+//// return;
+//// }
+// }
+//
+//// try {
+//// // admin listeners
+//// Iterator<ArgeoLogListener> everythingIt = everythingListeners.iterator();
+//// while (everythingIt.hasNext())
+//// dispatchEvent(everythingIt.next(), event);
+////
+//// if (event.getUsername() != null) {
+//// Iterator<ArgeoLogListener> allUsersIt = allUsersListeners.iterator();
+//// while (allUsersIt.hasNext())
+//// dispatchEvent(allUsersIt.next(), event);
+////
+//// if (userListeners.containsKey(event.getUsername())) {
+//// Iterator<ArgeoLogListener> userIt = userListeners.get(event.getUsername()).iterator();
+//// while (userIt.hasNext())
+//// dispatchEvent(userIt.next(), event);
+//// }
+//// }
+//// } catch (Exception e) {
+//// stdOut("Cannot process logging event");
+//// e.printStackTrace();
+//// }
+// }
+
+// protected void dispatchEvent(ArgeoLogListener logListener, LogEvent evt) {
+//// LoggingEvent event = evt.getLoggingEvent();
+//// logListener.appendLog(evt.getUsername(), event.getTimeStamp(), event.getLevel().toString(),
+//// event.getLoggerName(), event.getThreadName(), event.getMessage(), event.getThrowableStrRep());
+// }
+
+// private class AppenderImpl { // extends AppenderSkeleton {
+// public boolean requiresLayout() {
+// return false;
+// }
+//
+// public void close() {
+// }
+//
+//// @Override
+//// protected void append(LoggingEvent event) {
+//// if (events != null) {
+//// try {
+//// String username = CurrentUser.getUsername();
+//// events.put(new LogEvent(username, event));
+//// } catch (InterruptedException e) {
+//// // silent
+//// }
+//// }
+//// }
+//
+// }
-// @Override
-// protected void append(LoggingEvent event) {
-// if (events != null) {
+// private class LogDispatcherThread extends Thread {
+// /** encapsulated in order to simplify concurrency management */
+// private LinkedList<LogEvent> lastEvents = new LinkedList<LogEvent>();
+//
+// public LogDispatcherThread() {
+// super("Argeo Logging Dispatcher Thread");
+// }
+//
+// public void run() {
+// while (events != null) {
// try {
-// String username = CurrentUser.getUsername();
-// events.put(new LogEvent(username, event));
+// LogEvent loggingEvent = events.take();
+// processLoggingEvent(loggingEvent);
+// addLastEvent(loggingEvent);
// } catch (InterruptedException e) {
-// // silent
+// if (events == null)
+// return;
// }
// }
// }
+//
+// protected synchronized void addLastEvent(LogEvent loggingEvent) {
+// if (lastEvents.size() >= maxLastEventsCount)
+// lastEvents.poll();
+// lastEvents.add(loggingEvent);
+// }
+//
+// public synchronized List<LogEvent> getLastEvents(String username, Integer maxCount) {
+// LinkedList<LogEvent> evts = new LinkedList<LogEvent>();
+// ListIterator<LogEvent> it = lastEvents.listIterator(lastEvents.size());
+// int count = 0;
+// while (it.hasPrevious() && (count < maxCount)) {
+// LogEvent evt = it.previous();
+// if (username == null || username.equals(evt.getUsername())) {
+// evts.push(evt);
+// count++;
+// }
+// }
+// return evts;
+// }
+// }
- }
-
- private class LogDispatcherThread extends Thread {
- /** encapsulated in order to simplify concurrency management */
- private LinkedList<LogEvent> lastEvents = new LinkedList<LogEvent>();
-
- public LogDispatcherThread() {
- super("Argeo Logging Dispatcher Thread");
- }
-
- public void run() {
- while (events != null) {
- try {
- LogEvent loggingEvent = events.take();
- processLoggingEvent(loggingEvent);
- addLastEvent(loggingEvent);
- } catch (InterruptedException e) {
- if (events == null)
- return;
- }
- }
- }
-
- protected synchronized void addLastEvent(LogEvent loggingEvent) {
- if (lastEvents.size() >= maxLastEventsCount)
- lastEvents.poll();
- lastEvents.add(loggingEvent);
- }
-
- public synchronized List<LogEvent> getLastEvents(String username, Integer maxCount) {
- LinkedList<LogEvent> evts = new LinkedList<LogEvent>();
- ListIterator<LogEvent> it = lastEvents.listIterator(lastEvents.size());
- int count = 0;
- while (it.hasPrevious() && (count < maxCount)) {
- LogEvent evt = it.previous();
- if (username == null || username.equals(evt.getUsername())) {
- evts.push(evt);
- count++;
- }
- }
- return evts;
- }
- }
-
- private class LogEvent {
- private final String username;
-// private final LoggingEvent loggingEvent;
-
- public LogEvent(String username) {
- super();
- this.username = username;
-// this.loggingEvent = loggingEvent;
- }
-
-// @Override
-// public int hashCode() {
-// return loggingEvent.hashCode();
+// private class LogEvent {
+// private final String username;
+//// private final LoggingEvent loggingEvent;
+//
+// public LogEvent(String username) {
+// super();
+// this.username = username;
+//// this.loggingEvent = loggingEvent;
// }
//
-// @Override
-// public boolean equals(Object obj) {
-// return loggingEvent.equals(obj);
+//// @Override
+//// public int hashCode() {
+//// return loggingEvent.hashCode();
+//// }
+////
+//// @Override
+//// public boolean equals(Object obj) {
+//// return loggingEvent.equals(obj);
+//// }
+////
+//// @Override
+//// public String toString() {
+//// return username + "@ " + loggingEvent.toString();
+//// }
+//
+// public String getUsername() {
+// return username;
// }
//
-// @Override
-// public String toString() {
-// return username + "@ " + loggingEvent.toString();
+//// public LoggingEvent getLoggingEvent() {
+//// return loggingEvent;
+//// }
+//
+// }
+//
+// private class Log4jConfWatcherThread extends Thread {
+// private Path log4jConfigurationPath;
+//
+// public Log4jConfWatcherThread(Path log4jConfigurationPath) {
+// super("Log4j Configuration Watcher");
+// try {
+// this.log4jConfigurationPath = log4jConfigurationPath.toRealPath();
+// } catch (IOException e) {
+// this.log4jConfigurationPath = log4jConfigurationPath.toAbsolutePath();
+// stdOut("Cannot determine real path for " + log4jConfigurationPath + ": " + e.getMessage());
+// }
// }
-
- public String getUsername() {
- return username;
- }
-
-// public LoggingEvent getLoggingEvent() {
-// return loggingEvent;
+//
+// public void run() {
+// Path parentDir = log4jConfigurationPath.getParent();
+// try (final WatchService watchService = FileSystems.getDefault().newWatchService()) {
+// parentDir.register(watchService, StandardWatchEventKinds.ENTRY_MODIFY);
+// WatchKey wk;
+// watching: while ((wk = watchService.take()) != null) {
+// for (WatchEvent<?> event : wk.pollEvents()) {
+// final Path changed = (Path) event.context();
+// if (log4jConfigurationPath.equals(parentDir.resolve(changed))) {
+// if (isInternalDebugEnabled())
+// debug(log4jConfigurationPath + " has changed, reloading.");
+//// PropertyConfigurator.configure(log4jConfigurationPath.toUri().toURL());
+// }
+// }
+// // reset the key
+// boolean valid = wk.reset();
+// if (!valid) {
+// break watching;
+// }
+// }
+// } catch (IOException | InterruptedException e) {
+// stdErr("Log4j configuration watcher failed: " + e.getMessage());
+// }
// }
-
- }
-
- private class Log4jConfWatcherThread extends Thread {
- private Path log4jConfigurationPath;
-
- public Log4jConfWatcherThread(Path log4jConfigurationPath) {
- super("Log4j Configuration Watcher");
- try {
- this.log4jConfigurationPath = log4jConfigurationPath.toRealPath();
- } catch (IOException e) {
- this.log4jConfigurationPath = log4jConfigurationPath.toAbsolutePath();
- stdOut("Cannot determine real path for " + log4jConfigurationPath + ": " + e.getMessage());
- }
- }
-
- public void run() {
- Path parentDir = log4jConfigurationPath.getParent();
- try (final WatchService watchService = FileSystems.getDefault().newWatchService()) {
- parentDir.register(watchService, StandardWatchEventKinds.ENTRY_MODIFY);
- WatchKey wk;
- watching: while ((wk = watchService.take()) != null) {
- for (WatchEvent<?> event : wk.pollEvents()) {
- final Path changed = (Path) event.context();
- if (log4jConfigurationPath.equals(parentDir.resolve(changed))) {
- if (isInternalDebugEnabled())
- debug(log4jConfigurationPath + " has changed, reloading.");
-// PropertyConfigurator.configure(log4jConfigurationPath.toUri().toURL());
- }
- }
- // reset the key
- boolean valid = wk.reset();
- if (!valid) {
- break watching;
- }
- }
- } catch (IOException | InterruptedException e) {
- stdErr("Log4j configuration watcher failed: " + e.getMessage());
- }
- }
- }
+// }
}
+++ /dev/null
-package org.argeo.cms.internal.osgi;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.Writer;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.ArrayList;
-import java.util.Dictionary;
-import java.util.List;
-import java.util.SortedMap;
-import java.util.TreeMap;
-
-import javax.naming.InvalidNameException;
-import javax.naming.directory.Attribute;
-import javax.naming.directory.Attributes;
-import javax.naming.directory.BasicAttributes;
-import javax.naming.ldap.LdapName;
-import javax.naming.ldap.Rdn;
-
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.cms.internal.runtime.InitUtils;
-import org.argeo.cms.internal.runtime.KernelConstants;
-import org.argeo.cms.internal.runtime.KernelUtils;
-import org.argeo.osgi.useradmin.UserAdminConf;
-import org.argeo.util.naming.AttributesDictionary;
-import org.argeo.util.naming.LdifParser;
-import org.argeo.util.naming.LdifWriter;
-import org.osgi.framework.InvalidSyntaxException;
-import org.osgi.service.cm.Configuration;
-import org.osgi.service.cm.ConfigurationAdmin;
-import org.osgi.service.cm.ConfigurationEvent;
-import org.osgi.service.cm.ConfigurationListener;
-
-/** Manages the LDIF-based deployment configuration. */
-public class DeployConfig implements ConfigurationListener {
- private final CmsLog log = CmsLog.getLog(getClass());
-// private final BundleContext bc = FrameworkUtil.getBundle(getClass()).getBundleContext();
-
- private static Path deployConfigPath = KernelUtils.getOsgiInstancePath(KernelConstants.DEPLOY_CONFIG_PATH);
- private SortedMap<LdapName, Attributes> deployConfigs = new TreeMap<>();
-// private final DataModels dataModels;
-
- private boolean isFirstInit = false;
-
- private final static String ROLES = "roles";
-
- private ConfigurationAdmin configurationAdmin;
-
- public DeployConfig() {
-// this.dataModels = dataModels;
- // ConfigurationAdmin configurationAdmin =
-// // bc.getService(bc.getServiceReference(ConfigurationAdmin.class));
-// try {
-// if (!isInitialized()) { // first init
-// isFirstInit = true;
-// firstInit();
-// }
-// this.configurationAdmin = configurationAdmin;
-//// init(configurationAdmin, isClean, isFirstInit);
-// } catch (IOException e) {
-// throw new RuntimeException("Could not init deploy configs", e);
-// }
- // FIXME check race conditions during initialization
- // bc.registerService(ConfigurationListener.class, this, null);
- }
-
- private void firstInit() throws IOException {
- log.info("## FIRST INIT ##");
- Files.createDirectories(deployConfigPath.getParent());
-
- // FirstInit firstInit = new FirstInit();
- InitUtils.prepareFirstInitInstanceArea();
-
- if (!Files.exists(deployConfigPath))
- deployConfigs = new TreeMap<>();
- else// config file could have juste been copied by preparation
- try (InputStream in = Files.newInputStream(deployConfigPath)) {
- deployConfigs = new LdifParser().read(in);
- }
- save();
- }
-
- private void setFromFrameworkProperties(boolean isFirstInit) {
-
- // user admin
- List<Dictionary<String, Object>> userDirectoryConfigs = InitUtils.getUserDirectoryConfigs();
- if (userDirectoryConfigs.size() != 0) {
- List<String> activeCns = new ArrayList<>();
- for (int i = 0; i < userDirectoryConfigs.size(); i++) {
- Dictionary<String, Object> userDirectoryConfig = userDirectoryConfigs.get(i);
- String baseDn = (String) userDirectoryConfig.get(UserAdminConf.baseDn.name());
- String cn;
- if (CmsConstants.ROLES_BASEDN.equals(baseDn))
- cn = ROLES;
- else
- cn = UserAdminConf.baseDnHash(userDirectoryConfig);
- activeCns.add(cn);
- userDirectoryConfig.put(CmsConstants.CN, cn);
- putFactoryDeployConfig(CmsConstants.NODE_USER_ADMIN_PID, userDirectoryConfig);
- }
- // disable others
- LdapName userAdminFactoryName = serviceFactoryDn(CmsConstants.NODE_USER_ADMIN_PID);
- for (LdapName name : deployConfigs.keySet()) {
- if (name.startsWith(userAdminFactoryName) && !name.equals(userAdminFactoryName)) {
-// try {
- Attributes attrs = deployConfigs.get(name);
- String cn = name.getRdn(name.size() - 1).getValue().toString();
- if (!activeCns.contains(cn)) {
- attrs.put(UserAdminConf.disabled.name(), "true");
- }
-// } catch (Exception e) {
-// throw new CmsException("Cannot disable user directory " + name, e);
-// }
- }
- }
- }
-
- // http server
- Dictionary<String, Object> webServerConfig = InitUtils
- .getHttpServerConfig(getProps(KernelConstants.JETTY_FACTORY_PID, CmsConstants.DEFAULT));
- if (!webServerConfig.isEmpty()) {
- // TODO check for other customizers
-// webServerConfig.put("customizer.class", "org.argeo.equinox.jetty.CmsJettyCustomizer");
- putFactoryDeployConfig(KernelConstants.JETTY_FACTORY_PID, webServerConfig);
- }
-// LdapName defaultHttpServiceDn = serviceDn(KernelConstants.JETTY_FACTORY_PID, CmsConstants.DEFAULT);
-// if (deployConfigs.containsKey(defaultHttpServiceDn)) {
-// // remove old default configs since we have now to start Jetty servlet bridge
-// // indirectly
-// deployConfigs.remove(defaultHttpServiceDn);
-// }
-
- // SAVE
- save();
- //
-
-// Dictionary<String, Object> webServerConfig = InitUtils
-// .getHttpServerConfig(getProps(KernelConstants.JETTY_FACTORY_PID, CmsConstants.DEFAULT));
- }
-
- public void start() throws IOException {
- if (!isInitialized()) { // first init
- isFirstInit = true;
- firstInit();
- }
-
- boolean isClean;
- try {
- Configuration[] confs = configurationAdmin
- .listConfigurations("(service.factoryPid=" + CmsConstants.NODE_USER_ADMIN_PID + ")");
- isClean = confs == null || confs.length == 0;
- } catch (Exception e) {
- throw new IllegalStateException("Cannot analyse clean state", e);
- }
-
- try (InputStream in = Files.newInputStream(deployConfigPath)) {
- deployConfigs = new LdifParser().read(in);
- }
- if (isClean) {
- if (log.isDebugEnabled())
- log.debug("Clean state, loading from framework properties...");
- setFromFrameworkProperties(isFirstInit);
- loadConfigs();
- }
- // TODO check consistency if not clean
- }
-
- public void stop() {
-
- }
-
- public void loadConfigs() throws IOException {
- // FIXME make it more robust
- Configuration systemRolesConf = null;
- LdapName systemRolesDn;
- try {
- // FIXME make it more robust
- systemRolesDn = new LdapName("cn=roles,ou=org.argeo.api.userAdmin,ou=deploy,ou=node");
- } catch (InvalidNameException e) {
- throw new IllegalArgumentException(e);
- }
- deployConfigs: for (LdapName dn : deployConfigs.keySet()) {
- Rdn lastRdn = dn.getRdn(dn.size() - 1);
- LdapName prefix = (LdapName) dn.getPrefix(dn.size() - 1);
- if (prefix.toString().equals(CmsConstants.DEPLOY_BASEDN)) {
- if (lastRdn.getType().equals(CmsConstants.CN)) {
- // service
- String pid = lastRdn.getValue().toString();
- Configuration conf = configurationAdmin.getConfiguration(pid);
- AttributesDictionary dico = new AttributesDictionary(deployConfigs.get(dn));
- conf.update(dico);
- } else {
- // service factory definition
- }
- } else {
- Attributes config = deployConfigs.get(dn);
- Attribute disabled = config.get(UserAdminConf.disabled.name());
- if (disabled != null)
- continue deployConfigs;
- // service factory service
- Rdn beforeLastRdn = dn.getRdn(dn.size() - 2);
- assert beforeLastRdn.getType().equals(CmsConstants.OU);
- String factoryPid = beforeLastRdn.getValue().toString();
- Configuration conf = configurationAdmin.createFactoryConfiguration(factoryPid.toString(), null);
- if (systemRolesDn.equals(dn)) {
- systemRolesConf = configurationAdmin.createFactoryConfiguration(factoryPid.toString(), null);
- } else {
- AttributesDictionary dico = new AttributesDictionary(config);
- conf.update(dico);
- }
- }
- }
-
- // system roles must be last since it triggers node user admin publication
- if (systemRolesConf == null)
- throw new IllegalStateException("System roles are not configured.");
- systemRolesConf.update(new AttributesDictionary(deployConfigs.get(systemRolesDn)));
-
- }
-
- @Override
- public void configurationEvent(ConfigurationEvent event) {
- try {
- if (ConfigurationEvent.CM_UPDATED == event.getType()) {
- Configuration conf = configurationAdmin.getConfiguration(event.getPid(), null);
- LdapName serviceDn = null;
- String factoryPid = conf.getFactoryPid();
- if (factoryPid != null) {
- LdapName serviceFactoryDn = serviceFactoryDn(factoryPid);
- if (deployConfigs.containsKey(serviceFactoryDn)) {
- for (LdapName dn : deployConfigs.keySet()) {
- if (dn.startsWith(serviceFactoryDn)) {
- Rdn lastRdn = dn.getRdn(dn.size() - 1);
- assert lastRdn.getType().equals(CmsConstants.CN);
- Object value = conf.getProperties().get(lastRdn.getType());
- assert value != null;
- if (value.equals(lastRdn.getValue())) {
- serviceDn = dn;
- break;
- }
- }
- }
-
- Object cn = conf.getProperties().get(CmsConstants.CN);
- if (cn == null)
- throw new IllegalArgumentException("Properties must contain cn");
- if (serviceDn == null) {
- putFactoryDeployConfig(factoryPid, conf.getProperties());
- } else {
- Attributes attrs = deployConfigs.get(serviceDn);
- assert attrs != null;
- AttributesDictionary.copy(conf.getProperties(), attrs);
- }
- save();
- if (log.isDebugEnabled())
- log.debug("Updated deploy config " + serviceDn(factoryPid, cn.toString()));
- } else {
- // ignore non config-registered service factories
- }
- } else {
- serviceDn = serviceDn(event.getPid());
- if (deployConfigs.containsKey(serviceDn)) {
- Attributes attrs = deployConfigs.get(serviceDn);
- assert attrs != null;
- AttributesDictionary.copy(conf.getProperties(), attrs);
- save();
- if (log.isDebugEnabled())
- log.debug("Updated deploy config " + serviceDn);
- } else {
- // ignore non config-registered services
- }
- }
- }
- } catch (Exception e) {
- log.error("Could not handle configuration event", e);
- }
- }
-
- public void putFactoryDeployConfig(String factoryPid, Dictionary<String, Object> props) {
- Object cn = props.get(CmsConstants.CN);
- if (cn == null)
- throw new IllegalArgumentException("cn must be set in properties");
- LdapName serviceFactoryDn = serviceFactoryDn(factoryPid);
- if (!deployConfigs.containsKey(serviceFactoryDn))
- deployConfigs.put(serviceFactoryDn, new BasicAttributes(CmsConstants.OU, factoryPid));
- LdapName serviceDn = serviceDn(factoryPid, cn.toString());
- Attributes attrs = new BasicAttributes();
- AttributesDictionary.copy(props, attrs);
- deployConfigs.put(serviceDn, attrs);
- }
-
- void putDeployConfig(String servicePid, Dictionary<String, Object> props) {
- LdapName serviceDn = serviceDn(servicePid);
- Attributes attrs = new BasicAttributes(CmsConstants.CN, servicePid);
- AttributesDictionary.copy(props, attrs);
- deployConfigs.put(serviceDn, attrs);
- }
-
- public void save() {
- try (Writer writer = Files.newBufferedWriter(deployConfigPath)) {
- new LdifWriter(writer).write(deployConfigs);
- } catch (IOException e) {
- // throw new CmsException("Cannot save deploy configs", e);
- log.error("Cannot save deploy configs", e);
- }
- }
-
- public void setConfigurationAdmin(ConfigurationAdmin configurationAdmin) {
- this.configurationAdmin = configurationAdmin;
- }
-
- public boolean hasDomain() {
- Configuration[] configs;
- try {
- configs = configurationAdmin
- .listConfigurations("(service.factoryPid=" + CmsConstants.NODE_USER_ADMIN_PID + ")");
- } catch (IOException | InvalidSyntaxException e) {
- throw new IllegalStateException("Cannot list user directories", e);
- }
-
- boolean hasDomain = false;
- for (Configuration config : configs) {
- Object realm = config.getProperties().get(UserAdminConf.realm.name());
- if (realm != null) {
- log.debug("Found realm: " + realm);
- hasDomain = true;
- }
- }
- return hasDomain;
- }
-
- /*
- * UTILITIES
- */
- private LdapName serviceFactoryDn(String factoryPid) {
- try {
- return new LdapName(CmsConstants.OU + "=" + factoryPid + "," + CmsConstants.DEPLOY_BASEDN);
- } catch (InvalidNameException e) {
- throw new IllegalArgumentException("Cannot generate DN from " + factoryPid, e);
- }
- }
-
- private LdapName serviceDn(String servicePid) {
- try {
- return new LdapName(CmsConstants.CN + "=" + servicePid + "," + CmsConstants.DEPLOY_BASEDN);
- } catch (InvalidNameException e) {
- throw new IllegalArgumentException("Cannot generate DN from " + servicePid, e);
- }
- }
-
- private LdapName serviceDn(String factoryPid, String cn) {
- try {
- return (LdapName) serviceFactoryDn(factoryPid).add(new Rdn(CmsConstants.CN, cn));
- } catch (InvalidNameException e) {
- throw new IllegalArgumentException("Cannot generate DN from " + factoryPid + " and " + cn, e);
- }
- }
-
- public Dictionary<String, Object> getProps(String factoryPid, String cn) {
- Attributes attrs = deployConfigs.get(serviceDn(factoryPid, cn));
- if (attrs != null)
- return new AttributesDictionary(attrs);
- else
- return null;
- }
-
- private static boolean isInitialized() {
- return Files.exists(deployConfigPath);
- }
-
- public boolean isFirstInit() {
- return isFirstInit;
- }
-
-}
/**
* Workaround for killing Gogo shell by system shutdown.
*
- * @see https://issues.apache.org/jira/browse/FELIX-4208
+ * @see "https://issues.apache.org/jira/browse/FELIX-4208"
*/
class GogoShellKiller extends Thread {
+++ /dev/null
-package org.argeo.cms.internal.osgi;
-
-import java.io.IOException;
-import java.net.Inet6Address;
-import java.net.InetAddress;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.security.PrivilegedExceptionAction;
-import java.util.ArrayList;
-import java.util.Dictionary;
-import java.util.HashMap;
-import java.util.Hashtable;
-import java.util.Iterator;
-import java.util.Map;
-import java.util.Set;
-
-import javax.naming.ldap.LdapName;
-import javax.security.auth.Subject;
-import javax.security.auth.callback.Callback;
-import javax.security.auth.callback.CallbackHandler;
-import javax.security.auth.callback.NameCallback;
-import javax.security.auth.callback.UnsupportedCallbackException;
-import javax.security.auth.kerberos.KerberosPrincipal;
-import javax.security.auth.login.LoginContext;
-import javax.security.auth.login.LoginException;
-
-import org.apache.commons.httpclient.auth.AuthPolicy;
-import org.apache.commons.httpclient.auth.CredentialsProvider;
-import org.apache.commons.httpclient.params.DefaultHttpParams;
-import org.apache.commons.httpclient.params.HttpMethodParams;
-import org.apache.commons.httpclient.params.HttpParams;
-import org.argeo.api.cms.CmsAuth;
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.cms.internal.http.client.HttpCredentialProvider;
-import org.argeo.cms.internal.http.client.SpnegoAuthScheme;
-import org.argeo.cms.internal.runtime.KernelConstants;
-import org.argeo.cms.internal.runtime.KernelUtils;
-import org.argeo.osgi.transaction.WorkControl;
-import org.argeo.osgi.transaction.WorkTransaction;
-import org.argeo.osgi.useradmin.AbstractUserDirectory;
-import org.argeo.osgi.useradmin.AggregatingUserAdmin;
-import org.argeo.osgi.useradmin.LdapUserAdmin;
-import org.argeo.osgi.useradmin.LdifUserAdmin;
-import org.argeo.osgi.useradmin.OsUserDirectory;
-import org.argeo.osgi.useradmin.UserAdminConf;
-import org.argeo.osgi.useradmin.UserDirectory;
-import org.argeo.util.naming.DnsBrowser;
-import org.ietf.jgss.GSSCredential;
-import org.ietf.jgss.GSSException;
-import org.ietf.jgss.GSSManager;
-import org.ietf.jgss.GSSName;
-import org.ietf.jgss.Oid;
-import org.osgi.framework.Constants;
-import org.osgi.service.cm.ConfigurationException;
-import org.osgi.service.cm.ManagedServiceFactory;
-import org.osgi.service.useradmin.Authorization;
-import org.osgi.service.useradmin.Group;
-import org.osgi.service.useradmin.Role;
-import org.osgi.service.useradmin.UserAdmin;
-
-/**
- * Aggregates multiple {@link UserDirectory} and integrates them with system
- * roles.
- */
-public class NodeUserAdmin extends AggregatingUserAdmin implements ManagedServiceFactory, KernelConstants {
- private final static CmsLog log = CmsLog.getLog(NodeUserAdmin.class);
-
- // OSGi
- private Map<String, LdapName> pidToBaseDn = new HashMap<>();
-// private Map<String, ServiceRegistration<UserDirectory>> pidToServiceRegs = new HashMap<>();
-// private ServiceRegistration<UserAdmin> userAdminReg;
-
- // JTA
-// private final ServiceTracker<WorkControl, WorkControl> tmTracker;
- // private final String cacheName = UserDirectory.class.getName();
-
- // GSS API
- private Path nodeKeyTab = KernelUtils.getOsgiInstancePath(KernelConstants.NODE_KEY_TAB_PATH);
- private GSSCredential acceptorCredentials;
-
- private boolean singleUser = false;
-// private boolean systemRolesAvailable = false;
-
-// CmsUserManagerImpl userManager;
- private WorkControl transactionManager;
- private WorkTransaction userTransaction;
-
- public NodeUserAdmin() {
- super(CmsConstants.ROLES_BASEDN, CmsConstants.TOKENS_BASEDN);
-// BundleContext bc = Activator.getBundleContext();
-// if (bc != null) {
-// tmTracker = new ServiceTracker<>(bc, WorkControl.class, null) {
-//
-// @Override
-// public WorkControl addingService(ServiceReference<WorkControl> reference) {
-// WorkControl workControl = super.addingService(reference);
-// userManager = new CmsUserManagerImpl();
-// userManager.setUserAdmin(NodeUserAdmin.this);
-// // FIXME make it more robust
-// userManager.setUserTransaction((WorkTransaction) workControl);
-// bc.registerService(CmsUserManager.class, userManager, null);
-// return workControl;
-// }
-// };
-// tmTracker.open();
-// } else {
-// tmTracker = null;
-// }
- }
-
- public void start() {
- }
-
- public void stop() {
- }
-
- @Override
- public void updated(String pid, Dictionary<String, ?> properties) throws ConfigurationException {
- String uri = (String) properties.get(UserAdminConf.uri.name());
- Object realm = properties.get(UserAdminConf.realm.name());
- URI u;
- try {
- if (uri == null) {
- String baseDn = (String) properties.get(UserAdminConf.baseDn.name());
- u = KernelUtils.getOsgiInstanceUri(KernelConstants.DIR_NODE + '/' + baseDn + ".ldif");
- } else if (realm != null) {
- u = null;
- } else {
- u = new URI(uri);
- }
- } catch (URISyntaxException e) {
- throw new IllegalArgumentException("Badly formatted URI " + uri, e);
- }
-
- // Create
- AbstractUserDirectory userDirectory;
- if (realm != null || UserAdminConf.SCHEME_LDAP.equals(u.getScheme())
- || UserAdminConf.SCHEME_LDAPS.equals(u.getScheme())) {
- userDirectory = new LdapUserAdmin(properties);
- } else if (UserAdminConf.SCHEME_FILE.equals(u.getScheme())) {
- userDirectory = new LdifUserAdmin(u, properties);
- } else if (UserAdminConf.SCHEME_OS.equals(u.getScheme())) {
- userDirectory = new OsUserDirectory(u, properties);
- singleUser = true;
- } else {
- throw new IllegalArgumentException("Unsupported scheme " + u.getScheme());
- }
- LdapName baseDn = userDirectory.getBaseDn();
-
- // FIXME make updates more robust
- if (pidToBaseDn.containsValue(baseDn)) {
- if (log.isDebugEnabled())
- log.debug("Ignoring user directory update of " + baseDn);
- return;
- }
-
- addUserDirectory(userDirectory);
-
- // OSGi
- Hashtable<String, Object> regProps = new Hashtable<>();
- regProps.put(Constants.SERVICE_PID, pid);
- if (isSystemRolesBaseDn(baseDn))
- regProps.put(Constants.SERVICE_RANKING, Integer.MAX_VALUE);
- regProps.put(UserAdminConf.baseDn.name(), baseDn);
- // ServiceRegistration<UserDirectory> reg =
- // bc.registerService(UserDirectory.class, userDirectory, regProps);
- CmsActivator.getBundleContext().registerService(UserDirectory.class, userDirectory, regProps);
-// userManager.addUserDirectory(userDirectory, regProps);
- pidToBaseDn.put(pid, baseDn);
- // pidToServiceRegs.put(pid, reg);
-
- if (log.isDebugEnabled()) {
- log.debug("User directory " + userDirectory.getBaseDn() + (u != null ? " [" + u.getScheme() + "]" : "")
- + " enabled." + (realm != null ? " " + realm + " realm." : ""));
- }
-
- if (isSystemRolesBaseDn(baseDn)) {
- addStandardSystemRoles();
-
- // publishes itself as user admin only when system roles are available
- Dictionary<String, Object> userAdminregProps = new Hashtable<>();
- userAdminregProps.put(CmsConstants.CN, CmsConstants.DEFAULT);
- userAdminregProps.put(Constants.SERVICE_RANKING, Integer.MAX_VALUE);
- CmsActivator.getBundleContext().registerService(UserAdmin.class, this, userAdminregProps);
- }
-
-// if (isSystemRolesBaseDn(baseDn))
-// systemRolesAvailable = true;
-//
-// // start publishing only when system roles are available
-// if (systemRolesAvailable) {
-// // The list of baseDns is published as properties
-// // TODO clients should rather reference USerDirectory services
-// if (userAdminReg != null)
-// userAdminReg.unregister();
-// // register self as main user admin
-// Dictionary<String, Object> userAdminregProps = currentState();
-// userAdminregProps.put(NodeConstants.CN, NodeConstants.DEFAULT);
-// userAdminregProps.put(Constants.SERVICE_RANKING, Integer.MAX_VALUE);
-// userAdminReg = bc.registerService(UserAdmin.class, this, userAdminregProps);
-// }
- }
-
- private void addStandardSystemRoles() {
- // we assume UserTransaction is already available (TODO make it more robust)
- try {
- userTransaction.begin();
- Role adminRole = getRole(CmsConstants.ROLE_ADMIN);
- if (adminRole == null) {
- adminRole = createRole(CmsConstants.ROLE_ADMIN, Role.GROUP);
- }
- if (getRole(CmsConstants.ROLE_USER_ADMIN) == null) {
- Group userAdminRole = (Group) createRole(CmsConstants.ROLE_USER_ADMIN, Role.GROUP);
- userAdminRole.addMember(adminRole);
- }
- userTransaction.commit();
- } catch (Exception e) {
- try {
- userTransaction.rollback();
- } catch (Exception e1) {
- // silent
- }
- throw new IllegalStateException("Cannot add standard system roles", e);
- }
- }
-
- @Override
- public void deleted(String pid) {
- // assert pidToServiceRegs.get(pid) != null;
- assert pidToBaseDn.get(pid) != null;
- // pidToServiceRegs.remove(pid).unregister();
- LdapName baseDn = pidToBaseDn.remove(pid);
- removeUserDirectory(baseDn);
- }
-
- @Override
- public String getName() {
- return "Node User Admin";
- }
-
- @Override
- protected void addAbstractSystemRoles(Authorization rawAuthorization, Set<String> sysRoles) {
- if (rawAuthorization.getName() == null) {
- sysRoles.add(CmsConstants.ROLE_ANONYMOUS);
- } else {
- sysRoles.add(CmsConstants.ROLE_USER);
- }
- }
-
- protected void postAdd(AbstractUserDirectory userDirectory) {
- // JTA
-// WorkControl tm = tmTracker != null ? tmTracker.getService() : null;
-// if (tm == null)
-// throw new IllegalStateException("A JTA transaction manager must be available.");
- userDirectory.setTransactionControl(transactionManager);
-// if (tmTracker.getService() instanceof BitronixTransactionManager)
-// EhCacheXAResourceProducer.registerXAResource(cacheName, userDirectory.getXaResource());
-
- Object realm = userDirectory.getProperties().get(UserAdminConf.realm.name());
- if (realm != null) {
- if (Files.exists(nodeKeyTab)) {
- String servicePrincipal = getKerberosServicePrincipal(realm.toString());
- if (servicePrincipal != null) {
- CallbackHandler callbackHandler = new CallbackHandler() {
- @Override
- public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
- for (Callback callback : callbacks)
- if (callback instanceof NameCallback)
- ((NameCallback) callback).setName(servicePrincipal);
-
- }
- };
- try {
- LoginContext nodeLc = new LoginContext(CmsAuth.LOGIN_CONTEXT_NODE, callbackHandler);
- nodeLc.login();
- acceptorCredentials = logInAsAcceptor(nodeLc.getSubject(), servicePrincipal);
- } catch (LoginException e) {
- throw new IllegalStateException("Cannot log in kernel", e);
- }
- }
- }
-
- // Register client-side SPNEGO auth scheme
- AuthPolicy.registerAuthScheme(SpnegoAuthScheme.NAME, SpnegoAuthScheme.class);
- HttpParams params = DefaultHttpParams.getDefaultParams();
- ArrayList<String> schemes = new ArrayList<>();
- schemes.add(SpnegoAuthScheme.NAME);// SPNEGO preferred
- // schemes.add(AuthPolicy.BASIC);// incompatible with Basic
- params.setParameter(AuthPolicy.AUTH_SCHEME_PRIORITY, schemes);
- params.setParameter(CredentialsProvider.PROVIDER, new HttpCredentialProvider());
- params.setParameter(HttpMethodParams.COOKIE_POLICY, KernelConstants.COOKIE_POLICY_BROWSER_COMPATIBILITY);
- // params.setCookiePolicy(CookiePolicy.BROWSER_COMPATIBILITY);
- }
- }
-
- protected void preDestroy(AbstractUserDirectory userDirectory) {
-// if (tmTracker.getService() instanceof BitronixTransactionManager)
-// EhCacheXAResourceProducer.unregisterXAResource(cacheName, userDirectory.getXaResource());
-
- Object realm = userDirectory.getProperties().get(UserAdminConf.realm.name());
- if (realm != null) {
- if (acceptorCredentials != null) {
- try {
- acceptorCredentials.dispose();
- } catch (GSSException e) {
- // silent
- }
- acceptorCredentials = null;
- }
- }
- }
-
- private String getKerberosServicePrincipal(String realm) {
- String hostname;
- try (DnsBrowser dnsBrowser = new DnsBrowser()) {
- InetAddress localhost = InetAddress.getLocalHost();
- hostname = localhost.getHostName();
- String dnsZone = hostname.substring(hostname.indexOf('.') + 1);
- String ipfromDns = dnsBrowser.getRecord(hostname, localhost instanceof Inet6Address ? "AAAA" : "A");
- boolean consistentIp = localhost.getHostAddress().equals(ipfromDns);
- String kerberosDomain = dnsBrowser.getRecord("_kerberos." + dnsZone, "TXT");
- if (consistentIp && kerberosDomain != null && kerberosDomain.equals(realm) && Files.exists(nodeKeyTab)) {
- return KernelConstants.DEFAULT_KERBEROS_SERVICE + "/" + hostname + "@" + kerberosDomain;
- } else
- return null;
- } catch (Exception e) {
- log.warn("Exception when determining kerberos principal", e);
- return null;
- }
- }
-
- private GSSCredential logInAsAcceptor(Subject subject, String servicePrincipal) {
- // GSS
- Iterator<KerberosPrincipal> krb5It = subject.getPrincipals(KerberosPrincipal.class).iterator();
- if (!krb5It.hasNext())
- return null;
- KerberosPrincipal krb5Principal = null;
- while (krb5It.hasNext()) {
- KerberosPrincipal principal = krb5It.next();
- if (principal.getName().equals(servicePrincipal))
- krb5Principal = principal;
- }
-
- if (krb5Principal == null)
- return null;
-
- GSSManager manager = GSSManager.getInstance();
- try {
- GSSName gssName = manager.createName(krb5Principal.getName(), null);
- GSSCredential serverCredentials = Subject.doAs(subject, new PrivilegedExceptionAction<GSSCredential>() {
-
- @Override
- public GSSCredential run() throws GSSException {
- return manager.createCredential(gssName, GSSCredential.INDEFINITE_LIFETIME, KERBEROS_OID,
- GSSCredential.ACCEPT_ONLY);
-
- }
-
- });
- if (log.isDebugEnabled())
- log.debug("GSS acceptor configured for " + krb5Principal);
- return serverCredentials;
- } catch (Exception gsse) {
- throw new IllegalStateException("Cannot create acceptor credentials for " + krb5Principal, gsse);
- }
- }
-
- public GSSCredential getAcceptorCredentials() {
- return acceptorCredentials;
- }
-
- public boolean hasAcceptorCredentials() {
- return acceptorCredentials != null;
- }
-
- public boolean isSingleUser() {
- return singleUser;
- }
-
- public void setTransactionManager(WorkControl transactionManager) {
- this.transactionManager = transactionManager;
- }
-
- public void setUserTransaction(WorkTransaction userTransaction) {
- this.userTransaction = userTransaction;
- }
-
- /*
- * STATIC
- */
-
- public final static Oid KERBEROS_OID;
- static {
- try {
- KERBEROS_OID = new Oid("1.3.6.1.5.5.2");
- } catch (GSSException e) {
- throw new IllegalStateException("Cannot create Kerberos OID", e);
- }
- }
-}
--- /dev/null
+package org.argeo.cms.internal.runtime;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Collection;
+import java.util.Map;
+import java.util.Optional;
+import java.util.StringJoiner;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ForkJoinPool;
+import java.util.function.Consumer;
+
+import javax.xml.namespace.NamespaceContext;
+import javax.xml.namespace.QName;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.acr.ContentNotFoundException;
+import org.argeo.api.acr.ContentSession;
+import org.argeo.api.acr.DName;
+import org.argeo.api.acr.RuntimeNamespaceContext;
+import org.argeo.api.acr.spi.ProvidedRepository;
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.cms.auth.RemoteAuthUtils;
+import org.argeo.cms.dav.DavDepth;
+import org.argeo.cms.dav.DavHttpHandler;
+import org.argeo.cms.dav.DavPropfind;
+import org.argeo.cms.dav.DavResponse;
+import org.argeo.cms.http.HttpStatus;
+import org.argeo.cms.internal.http.RemoteAuthHttpExchange;
+import org.argeo.cms.util.StreamUtils;
+
+import com.sun.net.httpserver.HttpExchange;
+
+/** A partial WebDav implementation based on ACR. */
+public class CmsAcrHttpHandler extends DavHttpHandler {
+ private ProvidedRepository contentRepository;
+
+ @Override
+ protected NamespaceContext getNamespaceContext(HttpExchange httpExchange, String path) {
+ // TODO be smarter?
+ return RuntimeNamespaceContext.getNamespaceContext();
+ }
+
+ @Override
+ protected void handleGET(HttpExchange exchange, String path) throws IOException {
+ ContentSession session = RemoteAuthUtils.doAs(() -> contentRepository.get(),
+ new RemoteAuthHttpExchange(exchange));
+ if (!session.exists(path)) // not found
+ throw new ContentNotFoundException(session, path);
+ Content content = session.get(path);
+ Optional<Long> size = content.get(DName.getcontentlength, Long.class);
+ try (InputStream in = content.open(InputStream.class)) {
+ exchange.sendResponseHeaders(HttpStatus.OK.getCode(), size.orElse(0l));
+ StreamUtils.copy(in, exchange.getResponseBody());
+ } catch (IOException e) {
+ throw new RuntimeException("Cannot process " + path, e);
+ }
+ }
+
+ @Override
+ protected CompletableFuture<Void> handlePROPFIND(HttpExchange exchange, String path, DavPropfind davPropfind,
+ Consumer<DavResponse> consumer) throws IOException {
+ ContentSession session = RemoteAuthUtils.doAs(() -> contentRepository.get(),
+ new RemoteAuthHttpExchange(exchange));
+ if (!session.exists(path)) // not found
+ throw new ContentNotFoundException(session, path);
+ Content content = session.get(path);
+
+ CompletableFuture<Void> published = new CompletableFuture<Void>();
+ ForkJoinPool.commonPool().execute(() -> {
+ publishDavResponses(content, davPropfind, consumer);
+ published.complete(null);
+ });
+ return published;
+ }
+
+ protected void publishDavResponses(Content content, DavPropfind davPropfind, Consumer<DavResponse> consumer) {
+ publishDavResponse(content, davPropfind, consumer, 0);
+ }
+
+ protected void publishDavResponse(Content content, DavPropfind davPropfind, Consumer<DavResponse> consumer,
+ int currentDepth) {
+ DavResponse davResponse = new DavResponse();
+ String href = CmsConstants.PATH_API_ACR + content.getPath();
+ davResponse.setHref(href);
+ if (content.hasContentClass(DName.collection))
+ davResponse.setCollection(true);
+ if (davPropfind.isAllprop()) {
+ for (Map.Entry<QName, Object> entry : content.entrySet()) {
+ davResponse.getPropertyNames(HttpStatus.OK).add(entry.getKey());
+ processMapEntry(davResponse, entry.getKey(), entry.getValue());
+ }
+ davResponse.getResourceTypes().addAll(content.getContentClasses());
+ } else if (davPropfind.isPropname()) {
+ for (QName key : content.keySet()) {
+ davResponse.getPropertyNames(HttpStatus.OK).add(key);
+ }
+ } else {
+ for (QName key : davPropfind.getProps()) {
+ if (content.containsKey(key)) {
+ davResponse.getPropertyNames(HttpStatus.OK).add(key);
+ Object value = content.get(key);
+ processMapEntry(davResponse, key, value);
+ } else {
+ davResponse.getPropertyNames(HttpStatus.NOT_FOUND).add(key);
+ }
+ if (DName.resourcetype.qName().equals(key)) {
+ davResponse.getResourceTypes().addAll(content.getContentClasses());
+ }
+ }
+
+ }
+
+ consumer.accept(davResponse);
+
+ // recurse only on collections
+ if (content.hasContentClass(DName.collection)) {
+ if (davPropfind.getDepth() == DavDepth.DEPTH_INFINITY
+ || (davPropfind.getDepth() == DavDepth.DEPTH_1 && currentDepth == 0)) {
+ for (Content child : content) {
+ publishDavResponse(child, davPropfind, consumer, currentDepth + 1);
+ }
+ }
+ }
+ }
+
+ protected void processMapEntry(DavResponse davResponse, QName key, Object value) {
+ // ignore content classes
+ if (DName.resourcetype.qName().equals(key))
+ return;
+ String str;
+ if (value instanceof Collection) {
+ StringJoiner sj = new StringJoiner("\n");
+ for (Object v : (Collection<?>) value) {
+ sj.add(v.toString());
+ }
+ str = sj.toString();
+ } else {
+ str = value.toString();
+ }
+ davResponse.getProperties().put(key, str);
+
+ }
+
+ public void setContentRepository(ProvidedRepository contentRepository) {
+ this.contentRepository = contentRepository;
+ }
+
+}
package org.argeo.cms.internal.runtime;
-import static java.util.Locale.ENGLISH;
-
import java.lang.management.ManagementFactory;
+import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
import java.util.Locale;
+import java.util.Map;
+import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
-import org.argeo.api.cms.CmsConstants;
+import javax.security.auth.Subject;
+
import org.argeo.api.cms.CmsContext;
import org.argeo.api.cms.CmsDeployment;
+import org.argeo.api.cms.CmsEventBus;
import org.argeo.api.cms.CmsLog;
+import org.argeo.api.cms.CmsSession;
+import org.argeo.api.cms.CmsSessionId;
import org.argeo.api.cms.CmsState;
-import org.argeo.cms.LocaleUtils;
-import org.argeo.cms.internal.osgi.NodeUserAdmin;
+import org.argeo.api.uuid.UuidFactory;
+import org.argeo.cms.CmsDeployProperty;
+import org.argeo.cms.internal.auth.CmsSessionImpl;
import org.ietf.jgss.GSSCredential;
import org.osgi.service.useradmin.UserAdmin;
+/** Reference implementation of {@link CmsContext}. */
public class CmsContextImpl implements CmsContext {
+
private final CmsLog log = CmsLog.getLog(getClass());
-// private final BundleContext bc = FrameworkUtil.getBundle(getClass()).getBundleContext();
-// private EgoRepository egoRepository;
private static CompletableFuture<CmsContextImpl> instance = new CompletableFuture<CmsContextImpl>();
private CmsState cmsState;
private CmsDeployment cmsDeployment;
private UserAdmin userAdmin;
+ private UuidFactory uuidFactory;
+ private CmsEventBus cmsEventBus;
// i18n
private Locale defaultLocale;
private Long availableSince;
-// public CmsContextImpl() {
-// initTrackers();
-// }
+ // CMS sessions
+ private Map<UUID, CmsSessionImpl> cmsSessionsByUuid = new HashMap<>();
+ private Map<String, CmsSessionImpl> cmsSessionsByLocalId = new HashMap<>();
public void start() {
- Object defaultLocaleValue = KernelUtils.getFrameworkProp(CmsConstants.I18N_DEFAULT_LOCALE);
- defaultLocale = defaultLocaleValue != null ? new Locale(defaultLocaleValue.toString())
- : new Locale(ENGLISH.getLanguage());
- locales = LocaleUtils.asLocaleList(KernelUtils.getFrameworkProp(CmsConstants.I18N_LOCALES));
- // node repository
-// new ServiceTracker<Repository, Repository>(bc, Repository.class, null) {
-// @Override
-// public Repository addingService(ServiceReference<Repository> reference) {
-// Object cn = reference.getProperty(NodeConstants.CN);
-// if (cn != null && cn.equals(NodeConstants.EGO_REPOSITORY)) {
-//// egoRepository = (EgoRepository) bc.getService(reference);
-// if (log.isTraceEnabled())
-// log.trace("Home repository is available");
-// }
-// return super.addingService(reference);
-// }
-//
-// @Override
-// public void removedService(ServiceReference<Repository> reference, Repository service) {
-// super.removedService(reference, service);
-//// egoRepository = null;
-// }
-//
-// }.open();
-
- checkReadiness();
+ List<String> codes = CmsStateImpl.getDeployProperties(cmsState, CmsDeployProperty.LOCALE);
+ locales = getLocaleList(codes);
+ if (locales.size() == 0)
+ throw new IllegalStateException("At least one locale must be set");
+ defaultLocale = locales.get(0);
+ new Thread(() -> {
+ while (!checkReadiness()) {
+ try {
+ Thread.sleep(500);
+ } catch (InterruptedException e) {
+ }
+ }
+ }, "Check readiness").start();
setInstance(this);
}
* Checks whether the deployment is available according to expectations, and
* mark it as available.
*/
- private void checkReadiness() {
+ private boolean checkReadiness() {
if (isAvailable())
- return;
- if (cmsDeployment != null && userAdmin != null) {
+ return true;
+ if (cmsDeployment == null)
+ return false;
+
+ if (((CmsDeploymentImpl) cmsDeployment).allExpectedServicesAvailable() && userAdmin != null) {
String data = KernelUtils.getFrameworkProp(KernelUtils.OSGI_INSTANCE_AREA);
String state = KernelUtils.getFrameworkProp(KernelUtils.OSGI_CONFIGURATION_AREA);
availableSince = System.currentTimeMillis();
if (log.isTraceEnabled())
log.trace("Kernel initialization took " + initDuration + "ms");
tributeToFreeSoftware(initDuration);
+
+ return true;
} else {
- throw new IllegalStateException("Deployment is not available");
+ return false;
+ // throw new IllegalStateException("Deployment is not available");
}
}
throw new UnsupportedOperationException();
}
+ /** Returns null if argument is null. */
+ private static List<Locale> getLocaleList(List<String> codes) {
+ if (codes == null)
+ return null;
+ ArrayList<Locale> availableLocales = new ArrayList<Locale>();
+ for (String code : codes) {
+ if (code == null)
+ continue;
+ // variant not supported
+ int indexUnd = code.indexOf("_");
+ Locale locale;
+ if (indexUnd > 0) {
+ String language = code.substring(0, indexUnd);
+ String country = code.substring(indexUnd + 1);
+ locale = new Locale(language, country);
+ } else {
+ locale = new Locale(code);
+ }
+ availableLocales.add(locale);
+ }
+ return availableLocales;
+ }
+
public void setCmsDeployment(CmsDeployment cmsDeployment) {
this.cmsDeployment = cmsDeployment;
}
this.userAdmin = userAdmin;
}
+ public UuidFactory getUuidFactory() {
+ return uuidFactory;
+ }
+
+ public void setUuidFactory(UuidFactory uuidFactory) {
+ this.uuidFactory = uuidFactory;
+ }
+
@Override
public Locale getDefaultLocale() {
return defaultLocale;
}
+ @Override
+ public UUID timeUUID() {
+ return uuidFactory.timeUUID();
+ }
+
@Override
public List<Locale> getLocales() {
return locales;
}
@Override
- public synchronized Long getAvailableSince() {
+ public Long getAvailableSince() {
return availableSince;
}
- public synchronized boolean isAvailable() {
+ public boolean isAvailable() {
return availableSince != null;
}
+ public CmsState getCmsState() {
+ return cmsState;
+ }
+
+ @Override
+ public CmsEventBus getCmsEventBus() {
+ return cmsEventBus;
+ }
+
+ public void setCmsEventBus(CmsEventBus cmsEventBus) {
+ this.cmsEventBus = cmsEventBus;
+ }
+
/*
* STATIC
*/
- public synchronized static CmsContext getCmsContext() {
+ public static CmsContextImpl getCmsContext() {
return getInstance();
}
- /** Required by USER login module. */
- public synchronized static UserAdmin getUserAdmin() {
- return getInstance().userAdmin;
- }
-
/** Required by SPNEGO login module. */
- @Deprecated
- public synchronized static GSSCredential getAcceptorCredentials() {
- // FIXME find a cleaner way
- return ((NodeUserAdmin) getInstance().userAdmin).getAcceptorCredentials();
+ public GSSCredential getAcceptorCredentials() {
+ // TODO find a cleaner way
+ return ((CmsUserAdmin) userAdmin).getAcceptorCredentials();
}
- private synchronized static void setInstance(CmsContextImpl cmsContextImpl) {
+ private static void setInstance(CmsContextImpl cmsContextImpl) {
if (cmsContextImpl != null) {
if (instance.isDone())
throw new IllegalStateException("CMS Context is already set");
instance.complete(cmsContextImpl);
} else {
+ if (!instance.isDone())
+ instance.cancel(true);
instance = new CompletableFuture<CmsContextImpl>();
}
}
- private synchronized static CmsContextImpl getInstance() {
+ private static CmsContextImpl getInstance() {
try {
return instance.get();
} catch (InterruptedException | ExecutionException e) {
}
}
+ public UserAdmin getUserAdmin() {
+ return userAdmin;
+ }
+
+ /*
+ * CMS Sessions
+ */
+
+ @Override
+ public CmsSession getCmsSession(Subject subject) {
+ if (subject.getPrivateCredentials(CmsSessionId.class).isEmpty())
+ return null;
+ CmsSessionId cmsSessionId = subject.getPrivateCredentials(CmsSessionId.class).iterator().next();
+ return getCmsSessionByUuid(cmsSessionId.getUuid());
+ }
+
+ public void registerCmsSession(CmsSessionImpl cmsSession) {
+ if (cmsSessionsByUuid.containsKey(cmsSession.getUuid())
+ || cmsSessionsByLocalId.containsKey(cmsSession.getLocalId()))
+ throw new IllegalStateException("CMS session " + cmsSession + " is already registered.");
+ cmsSessionsByUuid.put(cmsSession.getUuid(), cmsSession);
+ cmsSessionsByLocalId.put(cmsSession.getLocalId(), cmsSession);
+ }
+
+ public void unregisterCmsSession(CmsSessionImpl cmsSession) {
+ if (!cmsSessionsByUuid.containsKey(cmsSession.getUuid())
+ || !cmsSessionsByLocalId.containsKey(cmsSession.getLocalId()))
+ throw new IllegalStateException("CMS session " + cmsSession + " is not registered.");
+ CmsSession removed = cmsSessionsByUuid.remove(cmsSession.getUuid());
+ assert removed == cmsSession;
+ cmsSessionsByLocalId.remove(cmsSession.getLocalId());
+ }
+
+ /**
+ * The {@link CmsSession} related to this UUID, or <code>null</null> if not
+ * registered.
+ */
+ public CmsSessionImpl getCmsSessionByUuid(UUID uuid) {
+ return cmsSessionsByUuid.get(uuid);
+ }
+
+ /**
+ * The {@link CmsSession} related to this local id, or <code>null</null> if not
+ * registered.
+ */
+ public CmsSessionImpl getCmsSessionByLocalId(String localId) {
+ return cmsSessionsByLocalId.get(localId);
+ }
+
}
package org.argeo.cms.internal.runtime;
-import java.io.IOException;
-import java.net.URL;
-import java.util.Dictionary;
+import static org.argeo.api.cms.CmsConstants.CONTEXT_PATH;
+import java.util.Map;
+import java.util.TreeMap;
+
+import org.argeo.api.cms.CmsConstants;
import org.argeo.api.cms.CmsDeployment;
import org.argeo.api.cms.CmsLog;
import org.argeo.api.cms.CmsState;
-import org.argeo.cms.internal.osgi.DeployConfig;
-import org.osgi.service.http.HttpService;
+import org.argeo.cms.CmsDeployProperty;
+import org.argeo.cms.CmsSshd;
+import org.argeo.cms.internal.http.CmsAuthenticator;
+import org.argeo.cms.internal.http.PublicCmsAuthenticator;
+
+import com.sun.net.httpserver.HttpContext;
+import com.sun.net.httpserver.HttpHandler;
+import com.sun.net.httpserver.HttpServer;
-/** Implementation of a CMS deployment. */
+/** Reference implementation of {@link CmsDeployment}. */
public class CmsDeploymentImpl implements CmsDeployment {
private final CmsLog log = CmsLog.getLog(getClass());
-// private final BundleContext bc = FrameworkUtil.getBundle(getClass()).getBundleContext();
-// private Long availableSince;
+ private CmsState cmsState;
- // Readiness
-// private boolean nodeAvailable = false;
-// private boolean userAdminAvailable = false;
+ // Expectations
private boolean httpExpected = false;
-// private boolean httpAvailable = false;
- private HttpService httpService;
+ private boolean sshdExpected = false;
- private CmsState cmsState;
- private DeployConfig deployConfig;
-
- public CmsDeploymentImpl() {
-// ServiceReference<NodeState> nodeStateSr = bc.getServiceReference(NodeState.class);
-// if (nodeStateSr == null)
-// throw new CmsException("No node state available");
+ // HTTP
+ private HttpServer httpServer;
+ private Map<String, HttpHandler> httpHandlers = new TreeMap<>();
+ private Map<String, CmsAuthenticator> httpAuthenticators = new TreeMap<>();
-// NodeState nodeState = bc.getService(nodeStateSr);
-// cleanState = nodeState.isClean();
+ // SSHD
+ private CmsSshd cmsSshd;
-// nodeHttp = new NodeHttp();
- initTrackers();
+ public void start() {
+ log.debug(() -> "CMS deployment available");
}
- private void initTrackers() {
-// ServiceTracker<?, ?> httpSt = new ServiceTracker<HttpService, HttpService>(bc, HttpService.class, null) {
-//
-// @Override
-// public HttpService addingService(ServiceReference<HttpService> sr) {
-// httpAvailable = true;
-// Object httpPort = sr.getProperty("http.port");
-// Object httpsPort = sr.getProperty("https.port");
-// log.info(httpPortsMsg(httpPort, httpsPort));
-// checkReadiness();
-// return super.addingService(sr);
-// }
-// };
-// // httpSt.open();
-// KernelUtils.asyncOpen(httpSt);
-
-// ServiceTracker<?, ?> userAdminSt = new ServiceTracker<UserAdmin, UserAdmin>(bc, UserAdmin.class, null) {
-// @Override
-// public UserAdmin addingService(ServiceReference<UserAdmin> reference) {
-// UserAdmin userAdmin = super.addingService(reference);
-// addStandardSystemRoles(userAdmin);
-// userAdminAvailable = true;
-// checkReadiness();
-// return userAdmin;
-// }
-// };
-// // userAdminSt.open();
-// KernelUtils.asyncOpen(userAdminSt);
-
-// ServiceTracker<?, ?> confAdminSt = new ServiceTracker<ConfigurationAdmin, ConfigurationAdmin>(bc,
-// ConfigurationAdmin.class, null) {
-// @Override
-// public ConfigurationAdmin addingService(ServiceReference<ConfigurationAdmin> reference) {
-// ConfigurationAdmin configurationAdmin = bc.getService(reference);
-//// boolean isClean;
-//// try {
-//// Configuration[] confs = configurationAdmin
-//// .listConfigurations("(service.factoryPid=" + CmsConstants.NODE_USER_ADMIN_PID + ")");
-//// isClean = confs == null || confs.length == 0;
-//// } catch (Exception e) {
-//// throw new IllegalStateException("Cannot analyse clean state", e);
-//// }
-// deployConfig = new DeployConfig(configurationAdmin, isClean);
-// Activator.registerService(CmsDeployment.class, CmsDeploymentImpl.this, null);
-//// JcrInitUtils.addToDeployment(CmsDeployment.this);
-// httpExpected = deployConfig.getProps(KernelConstants.JETTY_FACTORY_PID, "default") != null;
-// try {
-// Configuration[] configs = configurationAdmin
-// .listConfigurations("(service.factoryPid=" + CmsConstants.NODE_USER_ADMIN_PID + ")");
-//
-// boolean hasDomain = false;
-// for (Configuration config : configs) {
-// Object realm = config.getProperties().get(UserAdminConf.realm.name());
-// if (realm != null) {
-// log.debug("Found realm: " + realm);
-// hasDomain = true;
-// }
-// }
-// if (hasDomain) {
-// loadIpaJaasConfiguration();
-// }
-// } catch (Exception e) {
-// throw new IllegalStateException("Cannot initialize config", e);
-// }
-// return super.addingService(reference);
-// }
-// };
-// // confAdminSt.open();
-// KernelUtils.asyncOpen(confAdminSt);
+ public void stop() {
}
- public void start() {
- httpExpected = deployConfig.getProps(KernelConstants.JETTY_FACTORY_PID, "default") != null;
- if (deployConfig.hasDomain()) {
- loadIpaJaasConfiguration();
- }
-
-// while (!isHttpAvailableOrNotExpected()) {
-// try {
-// Thread.sleep(100);
-// } catch (InterruptedException e) {
-// log.error("Interrupted while waiting for http");
-// }
-// }
- }
+ public void setCmsState(CmsState cmsState) {
+ this.cmsState = cmsState;
- public void addFactoryDeployConfig(String factoryPid, Dictionary<String, Object> props) {
- deployConfig.putFactoryDeployConfig(factoryPid, props);
- deployConfig.save();
- try {
- deployConfig.loadConfigs();
- } catch (IOException e) {
- throw new IllegalStateException(e);
- }
- }
+ String httpPort = this.cmsState.getDeployProperty(CmsDeployProperty.HTTP_PORT.getProperty());
+ String httpsPort = this.cmsState.getDeployProperty(CmsDeployProperty.HTTPS_PORT.getProperty());
+ httpExpected = httpPort != null || httpsPort != null;
- public Dictionary<String, Object> getProps(String factoryPid, String cn) {
- return deployConfig.getProps(factoryPid, cn);
+ String sshdPort = this.cmsState.getDeployProperty(CmsDeployProperty.SSHD_PORT.getProperty());
+ sshdExpected = sshdPort != null;
}
-// private void addStandardSystemRoles(UserAdmin userAdmin) {
-// // we assume UserTransaction is already available (TODO make it more robust)
-// WorkTransaction userTransaction = bc.getService(bc.getServiceReference(WorkTransaction.class));
-// try {
-// userTransaction.begin();
-// Role adminRole = userAdmin.getRole(CmsConstants.ROLE_ADMIN);
-// if (adminRole == null) {
-// adminRole = userAdmin.createRole(CmsConstants.ROLE_ADMIN, Role.GROUP);
-// }
-// if (userAdmin.getRole(CmsConstants.ROLE_USER_ADMIN) == null) {
-// Group userAdminRole = (Group) userAdmin.createRole(CmsConstants.ROLE_USER_ADMIN, Role.GROUP);
-// userAdminRole.addMember(adminRole);
-// }
-// userTransaction.commit();
-// } catch (Exception e) {
-// try {
-// userTransaction.rollback();
-// } catch (Exception e1) {
-// // silent
-// }
-// throw new IllegalStateException("Cannot add standard system roles", e);
-// }
-// }
-
- public boolean isHttpAvailableOrNotExpected() {
- return (httpExpected ? httpService != null : true);
+ public void setHttpServer(HttpServer httpServer) {
+ this.httpServer = httpServer;
+ // create contexts whose handles had already been published
+ for (String contextPath : httpHandlers.keySet()) {
+ HttpHandler httpHandler = httpHandlers.get(contextPath);
+ CmsAuthenticator authenticator = httpAuthenticators.get(contextPath);
+ createHttpContext(contextPath, httpHandler, authenticator);
+ }
}
- private void loadIpaJaasConfiguration() {
- if (System.getProperty(KernelConstants.JAAS_CONFIG_PROP) == null) {
- String jaasConfig = KernelConstants.JAAS_CONFIG_IPA;
- URL url = getClass().getClassLoader().getResource(jaasConfig);
- KernelUtils.setJaasConfiguration(url);
- log.debug("Set IPA JAAS configuration.");
+ public void addHttpHandler(HttpHandler httpHandler, Map<String, String> properties) {
+ final String contextPath = properties.get(CONTEXT_PATH);
+ if (contextPath == null) {
+ log.warn("Property " + CONTEXT_PATH + " not set on HTTP handler " + properties + ". Ignoring it.");
+ return;
+ }
+ boolean isPublic = Boolean.parseBoolean(properties.get(CmsConstants.CONTEXT_PUBLIC));
+ CmsAuthenticator authenticator = isPublic ? new PublicCmsAuthenticator() : new CmsAuthenticator();
+ httpHandlers.put(contextPath, httpHandler);
+ httpAuthenticators.put(contextPath, authenticator);
+ if (httpServer == null) {
+ return;
+ } else {
+ createHttpContext(contextPath, httpHandler, authenticator);
}
}
- public void stop() {
-// if (nodeHttp != null)
-// nodeHttp.destroy();
-
-// try {
-// JettyConfigurator.stopServer(KernelConstants.DEFAULT_JETTY_SERVER);
-// } catch (Exception e) {
-// log.error("Cannot stop default Jetty server.", e);
-// }
-
- if (deployConfig != null) {
- deployConfig.save();
- // new Thread(() -> deployConfig.save(), "Save Argeo Deploy Config").start();
- }
+ public void createHttpContext(String contextPath, HttpHandler httpHandler, CmsAuthenticator authenticator) {
+ HttpContext httpContext = httpServer.createContext(contextPath);
+ // we want to set the authenticator BEFORE the handler actually becomes active
+ httpContext.setAuthenticator(authenticator);
+ httpContext.setHandler(httpHandler);
+ log.debug(() -> "Added handler " + contextPath + " : " + httpHandler.getClass().getName());
}
- public void setDeployConfig(DeployConfig deployConfig) {
- this.deployConfig = deployConfig;
+ public void removeHttpHandler(HttpHandler httpHandler, Map<String, String> properties) {
+ final String contextPath = properties.get(CmsConstants.CONTEXT_PATH);
+ if (contextPath == null)
+ return; // ignore silently
+ httpHandlers.remove(contextPath);
+ if (httpServer == null)
+ return;
+ httpServer.removeContext(contextPath);
+ log.debug(() -> "Removed handler " + contextPath + " : " + httpHandler.getClass().getName());
}
- public void setCmsState(CmsState cmsState) {
- this.cmsState = cmsState;
+ public boolean allExpectedServicesAvailable() {
+ if (httpExpected && httpServer == null)
+ return false;
+ if (sshdExpected && cmsSshd == null)
+ return false;
+ return true;
}
- public void setHttpService(HttpService httpService) {
- this.httpService = httpService;
+ public void setCmsSshd(CmsSshd cmsSshd) {
+ this.cmsSshd = cmsSshd;
}
}
--- /dev/null
+package org.argeo.cms.internal.runtime;
+
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.concurrent.Flow;
+import java.util.concurrent.Flow.Subscription;
+import java.util.concurrent.SubmissionPublisher;
+
+import org.argeo.api.cms.CmsEventBus;
+import org.argeo.api.cms.CmsEventSubscriber;
+import org.argeo.api.cms.CmsLog;
+
+/** {@link CmsEventBus} implementation based on {@link Flow}. */
+public class CmsEventBusImpl implements CmsEventBus {
+ private final CmsLog log = CmsLog.getLog(CmsEventBus.class);
+
+ private Map<String, SubmissionPublisher<Map<String, Object>>> topics = new TreeMap<>();
+
+ @Override
+ public void sendEvent(String topic, Map<String, Object> event) {
+ SubmissionPublisher<Map<String, Object>> publisher = topics.get(topic);
+ if (publisher == null)
+ return; // no one is interested
+ publisher.submit(event);
+ }
+
+ @Override
+ public void addEventSubscriber(String topic, CmsEventSubscriber subscriber) {
+ synchronized (topics) {
+ if (!topics.containsKey(topic))
+ topics.put(topic, new SubmissionPublisher<>());
+ }
+ SubmissionPublisher<Map<String, Object>> publisher = topics.get(topic);
+ CmsEventFlowSubscriber flowSubscriber = new CmsEventFlowSubscriber(topic, subscriber);
+ publisher.subscribe(flowSubscriber);
+ }
+
+ @Override
+ public void removeEventSubscriber(String topic, CmsEventSubscriber subscriber) {
+ SubmissionPublisher<Map<String, Object>> publisher = topics.get(topic);
+ if (publisher == null) {
+ log.error("There should be an event topic " + topic);
+ return;
+ }
+ for (Flow.Subscriber<? super Map<String, Object>> flowSubscriber : publisher.getSubscribers()) {
+ if (flowSubscriber instanceof CmsEventFlowSubscriber)
+ ((CmsEventFlowSubscriber) flowSubscriber).unsubscribe();
+ }
+ synchronized (topics) {
+ if (!publisher.hasSubscribers()) {
+ publisher.close();
+ topics.remove(topic);
+ }
+ }
+ }
+
+ /** A subscriber to a topic. */
+ static class CmsEventFlowSubscriber implements Flow.Subscriber<Map<String, Object>> {
+ private String topic;
+ private CmsEventSubscriber eventSubscriber;
+
+ private Subscription subscription;
+
+ public CmsEventFlowSubscriber(String topic, CmsEventSubscriber eventSubscriber) {
+ this.topic = topic;
+ this.eventSubscriber = eventSubscriber;
+ }
+
+ @Override
+ public void onSubscribe(Subscription subscription) {
+ this.subscription = subscription;
+ this.subscription.request(Long.MAX_VALUE);
+ }
+
+ @Override
+ public void onNext(Map<String, Object> item) {
+ eventSubscriber.onEvent(topic, item);
+ }
+
+ @Override
+ public void onError(Throwable throwable) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void onComplete() {
+ // TODO Auto-generated method stub
+
+ }
+
+ void unsubscribe() {
+ if (subscription != null)
+ subscription.cancel();
+ else
+ throw new IllegalStateException("No subscription to cancel");
+ }
+
+ }
+
+}
package org.argeo.cms.internal.runtime;
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.Reader;
import java.net.InetAddress;
import java.net.URL;
import java.net.UnknownHostException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.attribute.PosixFilePermission;
+import java.security.KeyStore;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.StringJoiner;
+import java.util.UUID;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ForkJoinPool;
+import java.util.concurrent.ForkJoinTask;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
import javax.security.auth.login.Configuration;
+import org.argeo.api.cms.CmsConstants;
import org.argeo.api.cms.CmsLog;
import org.argeo.api.cms.CmsState;
+import org.argeo.api.uuid.UuidFactory;
+import org.argeo.cms.CmsDeployProperty;
import org.argeo.cms.auth.ident.IdentClient;
-import org.osgi.framework.Constants;
+import org.argeo.cms.util.FsUtils;
/**
* Implementation of a {@link CmsState}, initialising the required services.
// REFERENCES
private Long availableSince;
- private String stateUuid;
+ private UUID uuid;
// private final boolean cleanState;
private String hostname;
+ private UuidFactory uuidFactory;
+
+ private final Map<CmsDeployProperty, String> deployPropertyDefaults;
+
+ public CmsStateImpl() {
+ this.deployPropertyDefaults = Collections.unmodifiableMap(createDeployPropertiesDefaults());
+ }
+
+ protected Map<CmsDeployProperty, String> createDeployPropertiesDefaults() {
+ Map<CmsDeployProperty, String> deployPropertyDefaults = new HashMap<>();
+ deployPropertyDefaults.put(CmsDeployProperty.NODE_INIT, "../../init");
+ deployPropertyDefaults.put(CmsDeployProperty.LOCALE, Locale.getDefault().toString());
+
+ // certificates
+ deployPropertyDefaults.put(CmsDeployProperty.SSL_KEYSTORETYPE, KernelConstants.PKCS12);
+ deployPropertyDefaults.put(CmsDeployProperty.SSL_PASSWORD, KernelConstants.DEFAULT_KEYSTORE_PASSWORD);
+ Path keyStorePath = getDataPath(KernelConstants.DEFAULT_KEYSTORE_PATH);
+ if (keyStorePath != null) {
+ deployPropertyDefaults.put(CmsDeployProperty.SSL_KEYSTORE, keyStorePath.toAbsolutePath().toString());
+ }
+
+ Path trustStorePath = getDataPath(KernelConstants.DEFAULT_TRUSTSTORE_PATH);
+ if (trustStorePath != null) {
+ deployPropertyDefaults.put(CmsDeployProperty.SSL_TRUSTSTORE, trustStorePath.toAbsolutePath().toString());
+ }
+ deployPropertyDefaults.put(CmsDeployProperty.SSL_TRUSTSTORETYPE, KernelConstants.PKCS12);
+ deployPropertyDefaults.put(CmsDeployProperty.SSL_TRUSTSTOREPASSWORD, KernelConstants.DEFAULT_KEYSTORE_PASSWORD);
+
+ // SSH
+ Path authorizedKeysPath = getDataPath(KernelConstants.NODE_SSHD_AUTHORIZED_KEYS_PATH);
+ if (authorizedKeysPath != null) {
+ deployPropertyDefaults.put(CmsDeployProperty.SSHD_AUTHORIZEDKEYS,
+ authorizedKeysPath.toAbsolutePath().toString());
+ }
+ return deployPropertyDefaults;
+ }
+
public void start() {
// Runtime.getRuntime().addShutdownHook(new CmsShutdown());
if (log.isTraceEnabled())
log.trace("CMS State started");
- this.stateUuid = KernelUtils.getFrameworkProp(Constants.FRAMEWORK_UUID);
+// String stateUuidStr = KernelUtils.getFrameworkProp(Constants.FRAMEWORK_UUID);
+// this.uuid = UUID.fromString(stateUuidStr);
+ this.uuid = uuidFactory.timeUUID();
// this.cleanState = stateUuid.equals(frameworkUuid);
- try {
- this.hostname = InetAddress.getLocalHost().getHostName();
- } catch (UnknownHostException e) {
- log.error("Cannot set hostname: " + e);
+
+ // hostname
+ this.hostname = getDeployProperty(CmsDeployProperty.HOST);
+ // TODO verify we have access to the IP address
+ if (hostname == null) {
+ final String LOCALHOST_IP = "::1";
+ ForkJoinTask<String> hostnameFJT = ForkJoinPool.commonPool().submit(() -> {
+ try {
+ String hostname = InetAddress.getLocalHost().getHostName();
+ return hostname;
+ } catch (UnknownHostException e) {
+ throw new IllegalStateException("Cannot get local hostname", e);
+ }
+ });
+ try {
+ this.hostname = hostnameFJT.get(5, TimeUnit.SECONDS);
+ } catch (InterruptedException | ExecutionException | TimeoutException e) {
+ this.hostname = LOCALHOST_IP;
+ log.warn("Could not get local hostname, using " + this.hostname);
+ }
}
availableSince = System.currentTimeMillis();
- if (log.isDebugEnabled())
+ if (log.isDebugEnabled()) {
// log.debug("## CMS starting... stateUuid=" + this.stateUuid + (cleanState ? "
// (clean state) " : " "));
- log.debug("## CMS starting... (" + stateUuid + ")");
+ StringJoiner sb = new StringJoiner("\n");
+ CmsDeployProperty[] deployProperties = CmsDeployProperty.values();
+ Arrays.sort(deployProperties, (o1, o2) -> o1.name().compareTo(o2.name()));
+ for (CmsDeployProperty deployProperty : deployProperties) {
+ List<String> values = getDeployProperties(deployProperty);
+ for (int i = 0; i < values.size(); i++) {
+ String value = values.get(i);
+ if (value != null) {
+ boolean isDefault = deployPropertyDefaults.containsKey(deployProperty)
+ && value.equals(deployPropertyDefaults.get(deployProperty));
+ String line = deployProperty.getProperty() + (i == 0 ? "" : "." + i) + "=" + value
+ + (isDefault ? " (default)" : "");
+ sb.add(line);
+ }
+ }
+ }
+ log.debug("## CMS starting... (" + uuid + ")\n" + sb + "\n");
+ }
-// initI18n();
-// initServices();
+ Path privateBase = getDataPath(KernelConstants.DIR_PRIVATE);
+ if (privateBase != null && !Files.exists(privateBase)) {// first init
+ firstInit();
+ Files.createDirectories(privateBase);
+ }
- } catch (RuntimeException e) {
- log.error("## FATAL: CMS activator failed", e);
+ } catch (RuntimeException | IOException e) {
+ log.error("## FATAL: CMS state failed", e);
}
}
private void initSecurity() {
- if (System.getProperty(KernelConstants.JAAS_CONFIG_PROP) == null) {
+ // private directory permissions
+ Path privateDir = KernelUtils.getOsgiInstancePath(KernelConstants.DIR_PRIVATE);
+ if (privateDir != null) {
+ // TODO rather check whether we can read and write
+ Set<PosixFilePermission> posixPermissions = new HashSet<>();
+ posixPermissions.add(PosixFilePermission.OWNER_READ);
+ posixPermissions.add(PosixFilePermission.OWNER_WRITE);
+ posixPermissions.add(PosixFilePermission.OWNER_EXECUTE);
+ try {
+ if (!Files.exists(privateDir))
+ Files.createDirectories(privateDir);
+ Files.setPosixFilePermissions(privateDir, posixPermissions);
+ } catch (IOException e) {
+ log.error("Cannot set permissions on " + privateDir, e);
+ }
+ }
+
+ if (getDeployProperty(CmsDeployProperty.JAVA_LOGIN_CONFIG) == null) {
String jaasConfig = KernelConstants.JAAS_CONFIG;
URL url = getClass().getResource(jaasConfig);
// System.setProperty(KernelConstants.JAAS_CONFIG_PROP,
}
// explicitly load JAAS configuration
Configuration.getConfiguration();
+
+ boolean initSsl = getDeployProperty(CmsDeployProperty.HTTPS_PORT) != null;
+ if (initSsl) {
+ initCertificates();
+ }
+ }
+
+ private void initCertificates() {
+ // server certificate
+ Path keyStorePath = Paths.get(getDeployProperty(CmsDeployProperty.SSL_KEYSTORE));
+ Path pemKeyPath = getDataPath(KernelConstants.DEFAULT_PEM_KEY_PATH);
+ Path pemCertPath = getDataPath(KernelConstants.DEFAULT_PEM_CERT_PATH);
+ char[] keyStorePassword = getDeployProperty(CmsDeployProperty.SSL_PASSWORD).toCharArray();
+
+ // Keystore
+ // if PEM files both exists, update the PKCS12 file
+ if (Files.exists(pemCertPath) && Files.exists(pemKeyPath)) {
+ // TODO check certificate update time? monitor changes?
+ KeyStore keyStore = PkiUtils.getKeyStore(keyStorePath, keyStorePassword,
+ getDeployProperty(CmsDeployProperty.SSL_KEYSTORETYPE));
+ try (Reader key = Files.newBufferedReader(pemKeyPath, StandardCharsets.US_ASCII);
+ BufferedInputStream cert = new BufferedInputStream(Files.newInputStream(pemCertPath));) {
+ PkiUtils.loadPrivateCertificatePem(keyStore, CmsConstants.NODE, key, keyStorePassword, cert);
+ Files.createDirectories(keyStorePath.getParent());
+ PkiUtils.saveKeyStore(keyStorePath, keyStorePassword, keyStore);
+ if (log.isDebugEnabled())
+ log.debug("PEM certificate stored in " + keyStorePath);
+ } catch (IOException e) {
+ log.error("Cannot read PEM files " + pemKeyPath + " and " + pemCertPath, e);
+ }
+ }
+
+ // Truststore
+ Path trustStorePath = Paths.get(getDeployProperty(CmsDeployProperty.SSL_TRUSTSTORE));
+ char[] trustStorePassword = getDeployProperty(CmsDeployProperty.SSL_TRUSTSTOREPASSWORD).toCharArray();
+
+ // IPA CA
+ Path ipaCaCertPath = Paths.get(KernelConstants.IPA_PEM_CA_CERT_PATH);
+ if (Files.exists(ipaCaCertPath)) {
+ KeyStore trustStore = PkiUtils.getKeyStore(trustStorePath, trustStorePassword,
+ getDeployProperty(CmsDeployProperty.SSL_TRUSTSTORETYPE));
+ try (BufferedInputStream cert = new BufferedInputStream(Files.newInputStream(ipaCaCertPath));) {
+ PkiUtils.loadTrustedCertificatePem(trustStore, trustStorePassword, cert);
+ Files.createDirectories(keyStorePath.getParent());
+ PkiUtils.saveKeyStore(trustStorePath, trustStorePassword, trustStore);
+ if (log.isDebugEnabled())
+ log.debug("IPA CA certificate stored in " + trustStorePath);
+ } catch (IOException e) {
+ log.error("Cannot trust CA certificate", e);
+ }
+ }
+
+// if (!Files.exists(keyStorePath))
+// PkiUtils.createSelfSignedKeyStore(keyStorePath, keyStorePassword, PkiUtils.PKCS12);
}
public void stop() {
if (log.isDebugEnabled())
- log.debug("CMS stopping... (" + this.stateUuid + ")");
+ log.debug("CMS stopping... (" + this.uuid + ")");
long duration = ((System.currentTimeMillis() - availableSince) / 1000) / 60;
log.info("## ARGEO CMS STOPPED after " + (duration / 60) + "h " + (duration % 60) + "min uptime ##");
}
+ private void firstInit() throws IOException {
+ log.info("## FIRST INIT ##");
+ List<String> nodeInits = getDeployProperties(CmsDeployProperty.NODE_INIT);
+// if (nodeInits == null)
+// nodeInits = "../../init";
+ CmsStateImpl.prepareFirstInitInstanceArea(nodeInits);
+ }
+
+ @Override
+ public String getDeployProperty(String property) {
+ CmsDeployProperty deployProperty = CmsDeployProperty.find(property);
+ if (deployProperty == null) {
+ // legacy
+ if (property.startsWith("argeo.node.")) {
+ return doGetDeployProperty(property);
+ }
+ if (property.equals("argeo.i18n.locales")) {
+ String value = doGetDeployProperty(property);
+ if (value != null) {
+ log.warn("Property " + property + " was ignored (value=" + value + ")");
+
+ }
+ return null;
+ }
+ throw new IllegalArgumentException("Unsupported deploy property " + property);
+ }
+ int index = CmsDeployProperty.getPropertyIndex(property);
+ return getDeployProperty(deployProperty, index);
+ }
+
+ @Override
+ public List<String> getDeployProperties(String property) {
+ CmsDeployProperty deployProperty = CmsDeployProperty.find(property);
+ if (deployProperty == null)
+ return new ArrayList<>();
+ return getDeployProperties(deployProperty);
+ }
+
+ public static List<String> getDeployProperties(CmsState cmsState, CmsDeployProperty deployProperty) {
+ return ((CmsStateImpl) cmsState).getDeployProperties(deployProperty);
+ }
+
+ public List<String> getDeployProperties(CmsDeployProperty deployProperty) {
+ List<String> res = new ArrayList<>(deployProperty.getMaxCount());
+ for (int i = 0; i < deployProperty.getMaxCount(); i++) {
+ // String propertyName = i == 0 ? deployProperty.getProperty() :
+ // deployProperty.getProperty() + "." + i;
+ String value = getDeployProperty(deployProperty, i);
+ res.add(i, value);
+ }
+ return res;
+ }
+
+ public static String getDeployProperty(CmsState cmsState, CmsDeployProperty deployProperty) {
+ return ((CmsStateImpl) cmsState).getDeployProperty(deployProperty);
+ }
+
+ public String getDeployProperty(CmsDeployProperty deployProperty) {
+ String value = getDeployProperty(deployProperty, 0);
+ return value;
+ }
+
+ public String getDeployProperty(CmsDeployProperty deployProperty, int index) {
+ String propertyName = deployProperty.getProperty() + (index == 0 ? "" : "." + index);
+ String value = doGetDeployProperty(propertyName);
+ if (value == null && index == 0) {
+ // try defaults
+ if (deployPropertyDefaults.containsKey(deployProperty)) {
+ value = deployPropertyDefaults.get(deployProperty);
+ if (deployProperty.isSystemPropertyOnly())
+ System.setProperty(deployProperty.getProperty(), value);
+ }
+
+ if (value == null) {
+ // try legacy properties
+ String legacyProperty = switch (deployProperty) {
+ case DIRECTORY -> "argeo.node.useradmin.uris";
+ case DB_URL -> "argeo.node.dburl";
+ case DB_USER -> "argeo.node.dbuser";
+ case DB_PASSWORD -> "argeo.node.dbpassword";
+ case HTTP_PORT -> "org.osgi.service.http.port";
+ case HTTPS_PORT -> "org.osgi.service.http.port.secure";
+ case HOST -> "org.eclipse.equinox.http.jetty.http.host";
+ case LOCALE -> "argeo.i18n.defaultLocale";
+
+ default -> null;
+ };
+ if (legacyProperty != null) {
+ value = doGetDeployProperty(legacyProperty);
+ if (value != null) {
+ log.warn("Retrieved deploy property " + deployProperty.getProperty()
+ + " through deprecated property " + legacyProperty);
+ }
+ }
+ }
+ }
+ if (index == 0 && deployProperty.isSystemPropertyOnly()) {
+ String systemPropertyValue = System.getProperty(deployProperty.getProperty());
+ if (!Objects.equals(value, systemPropertyValue))
+ throw new IllegalStateException(
+ "Property " + deployProperty + " must be a ssystem property, but its value is " + value
+ + ", while the system property value is " + systemPropertyValue);
+ }
+ return value != null ? value.toString() : null;
+ }
+
+ protected String getLegacyProperty(String legacyProperty, CmsDeployProperty deployProperty) {
+ String value = doGetDeployProperty(legacyProperty);
+ if (value != null) {
+ log.warn("Retrieved deploy property " + deployProperty.getProperty() + " through deprecated property "
+ + legacyProperty + ".");
+ }
+ return value;
+ }
+
+ protected String doGetDeployProperty(String property) {
+ return KernelUtils.getFrameworkProp(property);
+ }
+
+ @Override
+ public Path getDataPath(String relativePath) {
+ return KernelUtils.getOsgiInstancePath(relativePath);
+ }
@Override
public Long getAvailableSince() {
/*
* ACCESSORS
*/
+ @Override
+ public UUID getUuid() {
+ return uuid;
+ }
+
+ public void setUuidFactory(UuidFactory uuidFactory) {
+ this.uuidFactory = uuidFactory;
+ }
+
public String getHostname() {
return hostname;
}
+ /**
+ * Called before node initialisation, in order populate OSGi instance are with
+ * some files (typically LDIF, etc).
+ */
+ public static void prepareFirstInitInstanceArea(List<String> nodeInits) {
+
+ for (String nodeInit : nodeInits) {
+ if (nodeInit == null)
+ continue;
+
+ if (nodeInit.startsWith("http")) {
+ // TODO reconnect it
+ // registerRemoteInit(nodeInit);
+ } else {
+
+ // TODO use java.nio.file
+ Path initDir;
+ if (nodeInit.startsWith("."))
+ initDir = KernelUtils.getExecutionDir(nodeInit);
+ else
+ initDir = Paths.get(nodeInit);
+ // TODO also uncompress archives
+ if (Files.exists(initDir)) {
+ Path dataPath = KernelUtils.getOsgiInstancePath("");
+ FsUtils.copyDirectory(initDir, dataPath);
+ log.info("CMS initialized from " + initDir);
+ }
+ }
+ }
+ }
+
/*
* STATIC
*/
--- /dev/null
+package org.argeo.cms.internal.runtime;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.security.PrivilegedExceptionAction;
+import java.util.ArrayList;
+import java.util.Dictionary;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+
+import javax.security.auth.Subject;
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.NameCallback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+import javax.security.auth.kerberos.KerberosPrincipal;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+
+import org.argeo.api.cms.CmsAuth;
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.api.cms.CmsState;
+import org.argeo.api.cms.directory.UserDirectory;
+import org.argeo.api.cms.transaction.WorkControl;
+import org.argeo.api.cms.transaction.WorkTransaction;
+import org.argeo.cms.CmsDeployProperty;
+import org.argeo.cms.dns.DnsBrowser;
+import org.argeo.cms.osgi.useradmin.AggregatingUserAdmin;
+import org.argeo.cms.osgi.useradmin.DirectoryUserAdmin;
+import org.argeo.cms.runtime.DirectoryConf;
+import org.ietf.jgss.GSSCredential;
+import org.ietf.jgss.GSSException;
+import org.ietf.jgss.GSSManager;
+import org.ietf.jgss.GSSName;
+import org.ietf.jgss.Oid;
+import org.osgi.service.useradmin.Authorization;
+import org.osgi.service.useradmin.Group;
+import org.osgi.service.useradmin.Role;
+
+/**
+ * Aggregates multiple {@link UserDirectory} and integrates them with system
+ * roles.
+ */
+public class CmsUserAdmin extends AggregatingUserAdmin {
+ private final static CmsLog log = CmsLog.getLog(CmsUserAdmin.class);
+
+ // GSS API
+ private Path nodeKeyTab = KernelUtils.getOsgiInstancePath(KernelConstants.NODE_KEY_TAB_PATH);
+ private GSSCredential acceptorCredentials;
+
+ private boolean singleUser = false;
+
+ private WorkControl transactionManager;
+ private WorkTransaction userTransaction;
+
+ private CmsState cmsState;
+
+ public CmsUserAdmin() {
+ super(CmsConstants.SYSTEM_ROLES_BASEDN, CmsConstants.TOKENS_BASEDN);
+ }
+
+ public void start() {
+ super.start();
+ List<Dictionary<String, Object>> configs = getUserDirectoryConfigs();
+ for (Dictionary<String, Object> config : configs) {
+ enableUserDirectory(config);
+// if (userDirectory.getRealm().isPresent())
+// loadIpaJaasConfiguration();
+ }
+ log.debug(() -> "CMS user admin available");
+ }
+
+ public void stop() {
+// for (UserDirectory userDirectory : getUserDirectories()) {
+// removeUserDirectory(userDirectory);
+// }
+ super.stop();
+ }
+
+ protected List<Dictionary<String, Object>> getUserDirectoryConfigs() {
+ List<Dictionary<String, Object>> res = new ArrayList<>();
+ Path nodeBase = cmsState.getDataPath(KernelConstants.DIR_PRIVATE);
+ List<String> uris = new ArrayList<>();
+
+ // node roles
+ String nodeRolesUri = null;// getFrameworkProp(CmsConstants.ROLES_URI);
+ String baseNodeRoleDn = CmsConstants.SYSTEM_ROLES_BASEDN;
+ if (nodeRolesUri == null && nodeBase != null) {
+ nodeRolesUri = baseNodeRoleDn + ".ldif";
+ Path nodeRolesFile = nodeBase.resolve(nodeRolesUri);
+ if (!Files.exists(nodeRolesFile))
+ try {
+ Files.copy(CmsUserAdmin.class.getResourceAsStream(baseNodeRoleDn + ".ldif"), nodeRolesFile);
+ } catch (IOException e) {
+ throw new RuntimeException("Cannot copy demo resource", e);
+ }
+ // nodeRolesUri = nodeRolesFile.toURI().toString();
+ }
+ if (nodeRolesUri != null)
+ uris.add(nodeRolesUri);
+
+ // node tokens
+ String nodeTokensUri = null;// getFrameworkProp(CmsConstants.TOKENS_URI);
+ String baseNodeTokensDn = CmsConstants.TOKENS_BASEDN;
+ if (nodeTokensUri == null && nodeBase != null) {
+ nodeTokensUri = baseNodeTokensDn + ".ldif";
+ Path nodeTokensFile = nodeBase.resolve(nodeTokensUri);
+ if (!Files.exists(nodeTokensFile))
+ try {
+ Files.copy(CmsUserAdmin.class.getResourceAsStream(baseNodeTokensDn + ".ldif"), nodeTokensFile);
+ } catch (IOException e) {
+ throw new RuntimeException("Cannot copy demo resource", e);
+ }
+ // nodeRolesUri = nodeRolesFile.toURI().toString();
+ }
+ if (nodeTokensUri != null)
+ uris.add(nodeTokensUri);
+
+ // Business roles
+// String userAdminUris = getFrameworkProp(CmsConstants.USERADMIN_URIS);
+ List<String> userAdminUris = CmsStateImpl.getDeployProperties(cmsState, CmsDeployProperty.DIRECTORY);// getFrameworkProp(CmsConstants.USERADMIN_URIS);
+ for (String userAdminUri : userAdminUris) {
+ if (userAdminUri == null)
+ continue;
+// if (!userAdminUri.trim().equals(""))
+ uris.add(userAdminUri);
+ }
+
+ if (uris.size() == 0 && nodeBase != null) {
+ // TODO put this somewhere else
+ String demoBaseDn = "dc=example,dc=com";
+ String userAdminUri = demoBaseDn + ".ldif";
+ Path businessRolesFile = nodeBase.resolve(userAdminUri);
+ Path systemRolesFile = nodeBase.resolve("ou=roles,ou=node.ldif");
+ if (!Files.exists(businessRolesFile))
+ try {
+ Files.copy(CmsUserAdmin.class.getResourceAsStream(demoBaseDn + ".ldif"), businessRolesFile);
+ if (!Files.exists(systemRolesFile))
+ Files.copy(CmsUserAdmin.class.getResourceAsStream("example-ou=roles,ou=node.ldif"),
+ systemRolesFile);
+ } catch (IOException e) {
+ throw new RuntimeException("Cannot copy demo resources", e);
+ }
+ // userAdminUris = businessRolesFile.toURI().toString();
+ log.warn("## DEV Using dummy base DN " + demoBaseDn);
+ // TODO downgrade security level
+ }
+
+ // Interprets URIs
+ for (String uri : uris) {
+ URI u;
+ try {
+ u = new URI(uri);
+ if (u.getPath() == null)
+ throw new IllegalArgumentException(
+ "URI " + uri + " must have a path in order to determine base DN");
+ if (u.getScheme() == null) {
+ if (uri.startsWith("/") || uri.startsWith("./") || uri.startsWith("../"))
+ u = Paths.get(uri).toRealPath().toUri();
+ else if (!uri.contains("/")) {
+ // u = KernelUtils.getOsgiInstanceUri(KernelConstants.DIR_NODE + '/' + uri);
+ u = new URI(uri);
+ } else
+ throw new IllegalArgumentException("Cannot interpret " + uri + " as an uri");
+ } else if (u.getScheme().equals(DirectoryConf.SCHEME_FILE)) {
+ u = Paths.get(u).toRealPath().toUri();
+ }
+ } catch (Exception e) {
+ throw new RuntimeException("Cannot interpret " + uri + " as an uri", e);
+ }
+
+ try {
+ Dictionary<String, Object> properties = DirectoryConf.uriAsProperties(u.toString());
+ res.add(properties);
+ } catch (Exception e) {
+ log.error("Cannot load user directory " + u, e);
+ }
+ }
+
+ return res;
+ }
+
+ public UserDirectory enableUserDirectory(Dictionary<String, ?> properties) {
+ String uri = (String) properties.get(DirectoryConf.uri.name());
+ Object realm = properties.get(DirectoryConf.realm.name());
+ URI u;
+ try {
+ if (uri == null) {
+ String baseDn = (String) properties.get(DirectoryConf.baseDn.name());
+ u = KernelUtils.getOsgiInstanceUri(KernelConstants.DIR_PRIVATE + '/' + baseDn + ".ldif");
+ } else if (realm != null) {
+ u = null;
+ } else {
+ u = new URI(uri);
+ }
+ } catch (URISyntaxException e) {
+ throw new IllegalArgumentException("Badly formatted URI " + uri, e);
+ }
+
+ // Create
+ UserDirectory userDirectory = new DirectoryUserAdmin(u, properties);
+// if (realm != null || DirectoryConf.SCHEME_LDAP.equals(u.getScheme())
+// || DirectoryConf.SCHEME_LDAPS.equals(u.getScheme())) {
+// userDirectory = new LdapUserAdmin(properties);
+// } else if (DirectoryConf.SCHEME_FILE.equals(u.getScheme())) {
+// userDirectory = new LdifUserAdmin(u, properties);
+// } else if (DirectoryConf.SCHEME_OS.equals(u.getScheme())) {
+// userDirectory = new OsUserDirectory(u, properties);
+// singleUser = true;
+// } else {
+// throw new IllegalArgumentException("Unsupported scheme " + u.getScheme());
+// }
+ String basePath = userDirectory.getBase();
+
+ addUserDirectory(userDirectory);
+ if (isSystemRolesBaseDn(basePath)) {
+ addStandardSystemRoles();
+ }
+ if (log.isDebugEnabled()) {
+ log.debug("User directory " + userDirectory.getBase() + (u != null ? " [" + u.getScheme() + "]" : "")
+ + " enabled." + (realm != null ? " " + realm + " realm." : ""));
+ }
+ return userDirectory;
+ }
+
+ protected void addStandardSystemRoles() {
+ // we assume UserTransaction is already available (TODO make it more robust)
+ try {
+ userTransaction.begin();
+ Role adminRole = getRole(CmsConstants.ROLE_ADMIN);
+ if (adminRole == null) {
+ adminRole = createRole(CmsConstants.ROLE_ADMIN, Role.GROUP);
+ }
+ if (getRole(CmsConstants.ROLE_USER_ADMIN) == null) {
+ Group userAdminRole = (Group) createRole(CmsConstants.ROLE_USER_ADMIN, Role.GROUP);
+ userAdminRole.addMember(adminRole);
+ }
+ userTransaction.commit();
+ } catch (Exception e) {
+ try {
+ userTransaction.rollback();
+ } catch (Exception e1) {
+ // silent
+ }
+ throw new IllegalStateException("Cannot add standard system roles", e);
+ }
+ }
+
+ @Override
+ protected void addAbstractSystemRoles(Authorization rawAuthorization, Set<String> sysRoles) {
+ if (rawAuthorization.getName() == null) {
+ sysRoles.add(CmsConstants.ROLE_ANONYMOUS);
+ } else {
+ sysRoles.add(CmsConstants.ROLE_USER);
+ }
+ }
+
+ @Override
+ protected void postAdd(UserDirectory userDirectory) {
+ userDirectory.setTransactionControl(transactionManager);
+
+ Optional<String> realm = userDirectory.getRealm();
+ if (realm.isPresent()) {
+ loadIpaJaasConfiguration();
+ if (Files.exists(nodeKeyTab)) {
+ String servicePrincipal = getKerberosServicePrincipal(realm.get());
+ if (servicePrincipal != null) {
+ CallbackHandler callbackHandler = new CallbackHandler() {
+ @Override
+ public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
+ for (Callback callback : callbacks)
+ if (callback instanceof NameCallback)
+ ((NameCallback) callback).setName(servicePrincipal);
+
+ }
+ };
+ try {
+ LoginContext nodeLc = CmsAuth.NODE.newLoginContext(callbackHandler);
+ nodeLc.login();
+ acceptorCredentials = logInAsAcceptor(nodeLc.getSubject(), servicePrincipal);
+ } catch (LoginException e) {
+ throw new IllegalStateException("Cannot log in kernel", e);
+ }
+ }
+ }
+
+ }
+ }
+
+ @Override
+ protected void preDestroy(UserDirectory userDirectory) {
+ Optional<String> realm = userDirectory.getRealm();
+ if (realm.isPresent()) {
+ if (acceptorCredentials != null) {
+ try {
+ acceptorCredentials.dispose();
+ } catch (GSSException e) {
+ // silent
+ }
+ acceptorCredentials = null;
+ }
+ }
+ }
+
+ private void loadIpaJaasConfiguration() {
+ if (CmsStateImpl.getDeployProperty(cmsState, CmsDeployProperty.JAVA_LOGIN_CONFIG) == null) {
+ String jaasConfig = KernelConstants.JAAS_CONFIG_IPA;
+ URL url = getClass().getClassLoader().getResource(jaasConfig);
+ KernelUtils.setJaasConfiguration(url);
+ log.debug("Set IPA JAAS configuration.");
+ }
+ }
+
+ protected String getKerberosServicePrincipal(String realm) {
+ if (!Files.exists(nodeKeyTab))
+ return null;
+ List<String> dns = CmsStateImpl.getDeployProperties(cmsState, CmsDeployProperty.DNS);
+ String hostname = CmsStateImpl.getDeployProperty(cmsState, CmsDeployProperty.HOST);
+ try (DnsBrowser dnsBrowser = new DnsBrowser(dns)) {
+ hostname = hostname != null ? hostname : InetAddress.getLocalHost().getHostName();
+ String dnsZone = hostname.substring(hostname.indexOf('.') + 1);
+ String ipv4fromDns = dnsBrowser.getRecord(hostname, "A");
+ String ipv6fromDns = dnsBrowser.getRecord(hostname, "AAAA");
+ if (ipv4fromDns == null && ipv6fromDns == null)
+ throw new IllegalStateException("hostname " + hostname + " is not registered in DNS");
+ // boolean consistentIp = localhost.getHostAddress().equals(ipfromDns);
+ String kerberosDomain = dnsBrowser.getRecord("_kerberos." + dnsZone, "TXT");
+ if (kerberosDomain != null && kerberosDomain.equals(realm)) {
+ return KernelConstants.DEFAULT_KERBEROS_SERVICE + "/" + hostname + "@" + kerberosDomain;
+ } else
+ return null;
+ } catch (Exception e) {
+ log.warn("Exception when determining kerberos principal", e);
+ return null;
+ }
+ }
+
+ private GSSCredential logInAsAcceptor(Subject subject, String servicePrincipal) {
+ // not static because class is not supported by Android
+ final Oid KERBEROS_OID;
+ try {
+ KERBEROS_OID = new Oid("1.3.6.1.5.5.2");
+ } catch (GSSException e) {
+ throw new IllegalStateException("Cannot create Kerberos OID", e);
+ }
+ // GSS
+ Iterator<KerberosPrincipal> krb5It = subject.getPrincipals(KerberosPrincipal.class).iterator();
+ if (!krb5It.hasNext())
+ return null;
+ KerberosPrincipal krb5Principal = null;
+ while (krb5It.hasNext()) {
+ KerberosPrincipal principal = krb5It.next();
+ if (principal.getName().equals(servicePrincipal))
+ krb5Principal = principal;
+ }
+
+ if (krb5Principal == null)
+ return null;
+
+ GSSManager manager = GSSManager.getInstance();
+ try {
+ GSSName gssName = manager.createName(krb5Principal.getName(), null);
+ GSSCredential serverCredentials = Subject.doAs(subject, new PrivilegedExceptionAction<GSSCredential>() {
+
+ @Override
+ public GSSCredential run() throws GSSException {
+ return manager.createCredential(gssName, GSSCredential.INDEFINITE_LIFETIME, KERBEROS_OID,
+ GSSCredential.ACCEPT_ONLY);
+
+ }
+
+ });
+ if (log.isDebugEnabled())
+ log.debug("GSS acceptor configured for " + krb5Principal);
+ return serverCredentials;
+ } catch (Exception gsse) {
+ throw new IllegalStateException("Cannot create acceptor credentials for " + krb5Principal, gsse);
+ }
+ }
+
+ public GSSCredential getAcceptorCredentials() {
+ return acceptorCredentials;
+ }
+
+ public boolean hasAcceptorCredentials() {
+ return acceptorCredentials != null;
+ }
+
+ public boolean isSingleUser() {
+ return singleUser;
+ }
+
+ public void setTransactionManager(WorkControl transactionManager) {
+ this.transactionManager = transactionManager;
+ }
+
+ public void setUserTransaction(WorkTransaction userTransaction) {
+ this.userTransaction = userTransaction;
+ }
+
+ public void setCmsState(CmsState cmsState) {
+ this.cmsState = cmsState;
+ }
+
+}
--- /dev/null
+package org.argeo.cms.internal.runtime;
+
+import static org.argeo.api.acr.ldap.LdapAttr.cn;
+import static org.argeo.api.acr.ldap.LdapAttr.description;
+import static org.argeo.api.acr.ldap.LdapAttr.owner;
+
+import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Dictionary;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.NavigableMap;
+import java.util.Objects;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
+import java.util.UUID;
+
+import javax.naming.InvalidNameException;
+import javax.naming.ldap.LdapName;
+import javax.security.auth.Subject;
+import javax.xml.namespace.QName;
+
+import org.argeo.api.acr.NamespaceUtils;
+import org.argeo.api.acr.ldap.LdapAttr;
+import org.argeo.api.acr.ldap.NamingUtils;
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.api.cms.directory.CmsGroup;
+import org.argeo.api.cms.directory.CmsUser;
+import org.argeo.api.cms.directory.CmsUserManager;
+import org.argeo.api.cms.directory.HierarchyUnit;
+import org.argeo.api.cms.directory.UserDirectory;
+import org.argeo.api.cms.transaction.WorkTransaction;
+import org.argeo.cms.CurrentUser;
+import org.argeo.cms.auth.UserAdminUtils;
+import org.argeo.cms.directory.ldap.LdapEntry;
+import org.argeo.cms.directory.ldap.SharedSecret;
+import org.argeo.cms.osgi.useradmin.AggregatingUserAdmin;
+import org.argeo.cms.osgi.useradmin.TokenUtils;
+import org.argeo.cms.runtime.DirectoryConf;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.service.useradmin.Authorization;
+import org.osgi.service.useradmin.Group;
+import org.osgi.service.useradmin.Role;
+import org.osgi.service.useradmin.User;
+import org.osgi.service.useradmin.UserAdmin;
+
+/**
+ * Canonical implementation of the people {@link CmsUserManager}. Wraps
+ * interaction with users and groups.
+ *
+ * In a *READ-ONLY* mode. We want to be able to:
+ * <ul>
+ * <li>Retrieve my user and corresponding information (main info,
+ * groups...)</li>
+ * <li>List all local groups (not the system roles)</li>
+ * <li>If sufficient rights: retrieve a given user and its information</li>
+ * </ul>
+ */
+public class CmsUserManagerImpl implements CmsUserManager {
+ private final static CmsLog log = CmsLog.getLog(CmsUserManagerImpl.class);
+
+ private UserAdmin userAdmin;
+// private Map<String, String> serviceProperties;
+ private WorkTransaction userTransaction;
+
+ private final String[] knownProps = { LdapAttr.cn.name(), LdapAttr.sn.name(), LdapAttr.givenName.name(),
+ LdapAttr.uid.name() };
+
+// private Map<UserDirectory, Hashtable<String, Object>> userDirectories = Collections
+// .synchronizedMap(new LinkedHashMap<>());
+
+ private Set<UserDirectory> userDirectories = new HashSet<>();
+
+ public void start() {
+ log.debug(() -> "CMS user manager available");
+ }
+
+ public void stop() {
+
+ }
+
+ @Override
+ public String getMyMail() {
+ return getUserMail(CurrentUser.getUsername());
+ }
+
+ @Override
+ public Role[] getRoles(String filter) {
+ try {
+ return userAdmin.getRoles(filter);
+ } catch (InvalidSyntaxException e) {
+ throw new IllegalArgumentException("Invalid filter " + filter, e);
+ }
+ }
+
+ // ALL USER: WARNING access to this will be later reduced
+
+ /** Retrieve a user given his dn, or <code>null</code> if it doesn't exist. */
+ public CmsUser getUser(String dn) {
+ return (CmsUser) getUserAdmin().getRole(dn);
+ }
+
+ /** Can be a group or a user */
+ public String getUserDisplayName(String dn) {
+ // FIXME: during initialisation phase, the system logs "admin" as user
+ // name rather than the corresponding dn
+ if ("admin".equals(dn))
+ return "System Administrator";
+ else
+ return UserAdminUtils.getUserDisplayName(getUserAdmin(), dn);
+ }
+
+ @Override
+ public String getUserMail(String dn) {
+ return UserAdminUtils.getUserMail(getUserAdmin(), dn);
+ }
+
+ /** Lists all roles of the given user */
+ @Override
+ public String[] getUserRoles(String dn) {
+ Authorization currAuth = getUserAdmin().getAuthorization(getUser(dn));
+ return currAuth.getRoles();
+ }
+
+ @Override
+ public boolean isUserInRole(String userDn, String roleDn) {
+ String[] roles = getUserRoles(userDn);
+ for (String role : roles) {
+ if (role.equalsIgnoreCase(roleDn))
+ return true;
+ }
+ return false;
+ }
+
+ public Set<CmsUser> listUsersInGroup(String groupDn, String filter) {
+ Group group = (Group) userAdmin.getRole(groupDn);
+ if (group == null)
+ throw new IllegalArgumentException("Group " + groupDn + " not found");
+ Set<CmsUser> users = new HashSet<>();
+ addUsers(users, group, filter);
+ return users;
+ }
+
+// @Override
+// public Set<User> listAccounts(HierarchyUnit hierarchyUnit, boolean deep) {
+// if(!hierarchyUnit.isFunctional())
+// throw new IllegalArgumentException("Hierarchy unit "+hierarchyUnit.getBase()+" is not functional");
+// UserDirectory directory = (UserDirectory)hierarchyUnit.getDirectory();
+// Set<User> res = new HashSet<>();
+// for(HierarchyUnit technicalHu:hierarchyUnit.getDirectHierarchyUnits(false)) {
+// if(technicalHu.isFunctional())
+// continue;
+// for(Role role:directory.getHierarchyUnitRoles(technicalHu, null, false)) {
+// if(role)
+// }
+// }
+// return res;
+// }
+
+ /** Recursively add users to list */
+ private void addUsers(Set<CmsUser> users, Group group, String filter) {
+ Role[] roles = group.getMembers();
+ for (Role role : roles) {
+ if (role.getType() == Role.GROUP) {
+ addUsers(users, (CmsGroup) role, filter);
+ } else if (role.getType() == Role.USER) {
+ if (match(role, filter))
+ users.add((CmsUser) role);
+ } else {
+ // ignore
+ }
+ }
+ }
+
+ public List<CmsUser> listGroups(String filter, boolean includeUsers, boolean includeSystemRoles) {
+ Role[] roles = null;
+ try {
+ roles = getUserAdmin().getRoles(filter);
+ } catch (InvalidSyntaxException e) {
+ throw new IllegalArgumentException("Unable to get roles with filter: " + filter, e);
+ }
+
+ List<CmsUser> users = new ArrayList<>();
+ for (Role role : roles) {
+ if ((includeUsers && role.getType() == Role.USER || role.getType() == Role.GROUP) && !users.contains(role)
+ && (includeSystemRoles
+ || !role.getName().toLowerCase().endsWith(CmsConstants.SYSTEM_ROLES_BASEDN))) {
+ if (match(role, filter))
+ users.add((CmsUser) role);
+ }
+ }
+ return users;
+ }
+
+ private boolean match(Role role, String filter) {
+ boolean doFilter = filter != null && !"".equals(filter);
+ if (doFilter) {
+ for (String prop : knownProps) {
+ Object currProp = null;
+ try {
+ currProp = role.getProperties().get(prop);
+ } catch (Exception e) {
+ throw e;
+ }
+ if (currProp != null) {
+ String currPropStr = ((String) currProp).toLowerCase();
+ if (currPropStr.contains(filter.toLowerCase())) {
+ return true;
+ }
+ }
+ }
+ return false;
+ } else
+ return true;
+ }
+
+ @Override
+ public CmsUser getUserFromLocalId(String localId) {
+ CmsUser user = (CmsUser) getUserAdmin().getUser(LdapAttr.uid.name(), localId);
+ if (user == null)
+ user = (CmsUser) getUserAdmin().getUser(LdapAttr.cn.name(), localId);
+ return user;
+ }
+
+ @Override
+ public String buildDefaultDN(String localId, int type) {
+ return buildDistinguishedName(localId, getDefaultDomainName(), type);
+ }
+
+ /*
+ * EDITION
+ */
+ @Override
+ public CmsUser createUser(String username, Map<String, Object> properties, Map<String, Object> credentials) {
+ try {
+ userTransaction.begin();
+ CmsUser user = (CmsUser) userAdmin.createRole(username, Role.USER);
+ if (properties != null) {
+ for (String key : properties.keySet())
+ user.getProperties().put(key, properties.get(key));
+ }
+ if (credentials != null) {
+ for (String key : credentials.keySet())
+ user.getCredentials().put(key, credentials.get(key));
+ }
+ userTransaction.commit();
+ return user;
+ } catch (Exception e) {
+ try {
+ userTransaction.rollback();
+ } catch (Exception e1) {
+ log.error("Could not roll back", e1);
+ }
+ if (e instanceof RuntimeException)
+ throw (RuntimeException) e;
+ else
+ throw new RuntimeException("Cannot create user " + username, e);
+ }
+ }
+
+ @Override
+ public CmsGroup createGroup(String dn) {
+ try {
+ userTransaction.begin();
+ CmsGroup group = (CmsGroup) userAdmin.createRole(dn, Role.GROUP);
+ userTransaction.commit();
+ return group;
+ } catch (Exception e) {
+ try {
+ userTransaction.rollback();
+ } catch (Exception e1) {
+ log.error("Could not roll back", e1);
+ }
+ if (e instanceof RuntimeException)
+ throw (RuntimeException) e;
+ else
+ throw new RuntimeException("Cannot create group " + dn, e);
+ }
+ }
+
+ @Override
+ public CmsGroup getOrCreateGroup(HierarchyUnit groups, String commonName) {
+ String dn = LdapAttr.cn.name() + "=" + commonName + "," + groups.getBase();
+ CmsGroup group = (CmsGroup) getUserAdmin().getRole(dn);
+ if (group != null)
+ return group;
+ try {
+ userTransaction.begin();
+ group = (CmsGroup) userAdmin.createRole(dn, Role.GROUP);
+ userTransaction.commit();
+ return group;
+ } catch (Exception e) {
+ try {
+ userTransaction.rollback();
+ } catch (Exception e1) {
+ log.error("Could not roll back", e1);
+ }
+ if (e instanceof RuntimeException)
+ throw (RuntimeException) e;
+ else
+ throw new RuntimeException("Cannot create group " + commonName + " in " + groups, e);
+ }
+ }
+
+ @Override
+ public CmsGroup getOrCreateSystemRole(HierarchyUnit roles, QName systemRole) {
+ String dn = LdapAttr.cn.name() + "=" + NamespaceUtils.toPrefixedName(systemRole) + "," + roles.getBase();
+ CmsGroup group = (CmsGroup) getUserAdmin().getRole(dn);
+ if (group != null)
+ return group;
+ try {
+ userTransaction.begin();
+ group = (CmsGroup) userAdmin.createRole(dn, Role.GROUP);
+ userTransaction.commit();
+ return group;
+ } catch (Exception e) {
+ try {
+ userTransaction.rollback();
+ } catch (Exception e1) {
+ log.error("Could not roll back", e1);
+ }
+ if (e instanceof RuntimeException)
+ throw (RuntimeException) e;
+ else
+ throw new RuntimeException("Cannot create system role " + systemRole + " in " + roles, e);
+ }
+ }
+
+ @Override
+ public HierarchyUnit getOrCreateHierarchyUnit(UserDirectory directory, String path) {
+ HierarchyUnit hi = directory.getHierarchyUnit(path);
+ if (hi != null)
+ return hi;
+ try {
+ userTransaction.begin();
+ HierarchyUnit hierarchyUnit = directory.createHierarchyUnit(path);
+ userTransaction.commit();
+ return hierarchyUnit;
+ } catch (Exception e1) {
+ try {
+ if (!userTransaction.isNoTransactionStatus())
+ userTransaction.rollback();
+ } catch (Exception e2) {
+ if (log.isTraceEnabled())
+ log.trace("Cannot rollback transaction", e2);
+ }
+ throw new RuntimeException("Cannot create hierarchy unit " + path + " in directory " + directory, e1);
+ }
+ }
+
+ @Override
+ public void addObjectClasses(Role role, Set<String> objectClasses, Map<String, Object> additionalProperties) {
+ try {
+ userTransaction.begin();
+ LdapEntry.addObjectClasses(role.getProperties(), objectClasses);
+ for (String key : additionalProperties.keySet()) {
+ role.getProperties().put(key, additionalProperties.get(key));
+ }
+ userTransaction.commit();
+ } catch (Exception e1) {
+ try {
+ if (!userTransaction.isNoTransactionStatus())
+ userTransaction.rollback();
+ } catch (Exception e2) {
+ if (log.isTraceEnabled())
+ log.trace("Cannot rollback transaction", e2);
+ }
+ throw new RuntimeException("Cannot add object classes " + objectClasses + " to " + role, e1);
+ }
+ }
+
+ @Override
+ public void addObjectClasses(HierarchyUnit hierarchyUnit, Set<String> objectClasses,
+ Map<String, Object> additionalProperties) {
+ try {
+ userTransaction.begin();
+ LdapEntry.addObjectClasses(hierarchyUnit.getProperties(), objectClasses);
+ for (String key : additionalProperties.keySet()) {
+ hierarchyUnit.getProperties().put(key, additionalProperties.get(key));
+ }
+ userTransaction.commit();
+ } catch (Exception e1) {
+ try {
+ if (!userTransaction.isNoTransactionStatus())
+ userTransaction.rollback();
+ } catch (Exception e2) {
+ if (log.isTraceEnabled())
+ log.trace("Cannot rollback transaction", e2);
+ }
+ throw new RuntimeException("Cannot add object classes " + objectClasses + " to " + hierarchyUnit, e1);
+ }
+ }
+
+ @Override
+ public void edit(Runnable action) {
+ Objects.requireNonNull(action);
+ try {
+ userTransaction.begin();
+ action.run();
+ userTransaction.commit();
+ } catch (Exception e1) {
+ try {
+ if (!userTransaction.isNoTransactionStatus())
+ userTransaction.rollback();
+ } catch (Exception e2) {
+ if (log.isTraceEnabled())
+ log.trace("Cannot rollback transaction", e2);
+ }
+ throw new RuntimeException("Cannot edit", e1);
+ }
+ }
+
+ @Override
+ public void addMember(CmsGroup group, Role role) {
+ try {
+ userTransaction.begin();
+ group.addMember(role);
+ userTransaction.commit();
+ } catch (Exception e1) {
+ try {
+ if (!userTransaction.isNoTransactionStatus())
+ userTransaction.rollback();
+ } catch (Exception e2) {
+ if (log.isTraceEnabled())
+ log.trace("Cannot rollback transaction", e2);
+ }
+ throw new RuntimeException("Cannot add member " + role + " to group " + group, e1);
+ }
+ }
+
+ @Override
+ public void removeMember(CmsGroup group, Role role) {
+ try {
+ userTransaction.begin();
+ group.removeMember(role);
+ userTransaction.commit();
+ } catch (Exception e1) {
+ try {
+ if (!userTransaction.isNoTransactionStatus())
+ userTransaction.rollback();
+ } catch (Exception e2) {
+ if (log.isTraceEnabled())
+ log.trace("Cannot rollback transaction", e2);
+ }
+ throw new RuntimeException("Cannot remove member " + role + " from group " + group, e1);
+ }
+ }
+
+ @Override
+ public String getDefaultDomainName() {
+ Map<String, String> dns = getKnownBaseDns(true);
+ if (dns.size() == 1)
+ return dns.keySet().iterator().next();
+ else
+ throw new IllegalStateException("Current context contains " + dns.size() + " base dns: "
+ + dns.keySet().toString() + ". Unable to chose a default one.");
+ }
+
+ public Map<String, String> getKnownBaseDns(boolean onlyWritable) {
+ Map<String, String> dns = new HashMap<String, String>();
+ for (UserDirectory userDirectory : userDirectories) {
+ Boolean readOnly = userDirectory.isReadOnly();
+ String baseDn = userDirectory.getBase();
+
+ if (onlyWritable && readOnly)
+ continue;
+ if (baseDn.equalsIgnoreCase(CmsConstants.SYSTEM_ROLES_BASEDN))
+ continue;
+ if (baseDn.equalsIgnoreCase(CmsConstants.TOKENS_BASEDN))
+ continue;
+ dns.put(baseDn, DirectoryConf.propertiesAsUri(userDirectory.getProperties()).toString());
+
+ }
+ return dns;
+ }
+
+ public Set<UserDirectory> getUserDirectories() {
+ TreeSet<UserDirectory> res = new TreeSet<>((o1, o2) -> o1.getBase().compareTo(o2.getBase()));
+ res.addAll(userDirectories);
+ return res;
+ }
+
+ public String buildDistinguishedName(String localId, String baseDn, int type) {
+ Map<String, String> dns = getKnownBaseDns(true);
+ Dictionary<String, ?> props = DirectoryConf.uriAsProperties(dns.get(baseDn));
+ String dn = null;
+ if (Role.GROUP == type)
+ dn = LdapAttr.cn.name() + "=" + localId + "," + DirectoryConf.groupBase.getValue(props) + "," + baseDn;
+ else if (Role.USER == type)
+ dn = LdapAttr.uid.name() + "=" + localId + "," + DirectoryConf.userBase.getValue(props) + "," + baseDn;
+ else
+ throw new IllegalStateException("Unknown role type. " + "Cannot deduce dn for " + localId);
+ return dn;
+ }
+
+ @Override
+ public void changeOwnPassword(char[] oldPassword, char[] newPassword) {
+ String name = CurrentUser.getUsername();
+ LdapName dn;
+ try {
+ dn = new LdapName(name);
+ } catch (InvalidNameException e) {
+ throw new IllegalArgumentException("Invalid user dn " + name, e);
+ }
+ User user = (User) userAdmin.getRole(dn.toString());
+ if (!user.hasCredential(null, oldPassword))
+ throw new IllegalArgumentException("Invalid password");
+ if (Arrays.equals(newPassword, new char[0]))
+ throw new IllegalArgumentException("New password empty");
+ try {
+ userTransaction.begin();
+ user.getCredentials().put(null, newPassword);
+ userTransaction.commit();
+ } catch (Exception e) {
+ try {
+ userTransaction.rollback();
+ } catch (Exception e1) {
+ log.error("Could not roll back", e1);
+ }
+ if (e instanceof RuntimeException)
+ throw (RuntimeException) e;
+ else
+ throw new RuntimeException("Cannot change password", e);
+ }
+ }
+
+ public void resetPassword(String username, char[] newPassword) {
+ LdapName dn;
+ try {
+ dn = new LdapName(username);
+ } catch (InvalidNameException e) {
+ throw new IllegalArgumentException("Invalid user dn " + username, e);
+ }
+ User user = (User) userAdmin.getRole(dn.toString());
+ if (Arrays.equals(newPassword, new char[0]))
+ throw new IllegalArgumentException("New password empty");
+ try {
+ userTransaction.begin();
+ user.getCredentials().put(null, newPassword);
+ userTransaction.commit();
+ } catch (Exception e) {
+ try {
+ userTransaction.rollback();
+ } catch (Exception e1) {
+ log.error("Could not roll back", e1);
+ }
+ if (e instanceof RuntimeException)
+ throw (RuntimeException) e;
+ else
+ throw new RuntimeException("Cannot change password", e);
+ }
+ }
+
+ public String addSharedSecret(String email, int hours) {
+ User user = (User) userAdmin.getUser(LdapAttr.mail.name(), email);
+ try {
+ userTransaction.begin();
+ String uuid = UUID.randomUUID().toString();
+ SharedSecret sharedSecret = new SharedSecret(hours, uuid);
+ user.getCredentials().put(SharedSecret.X_SHARED_SECRET, sharedSecret.toAuthPassword());
+ String tokenStr = sharedSecret.getAuthInfo() + '$' + sharedSecret.getAuthValue();
+ userTransaction.commit();
+ return tokenStr;
+ } catch (Exception e) {
+ try {
+ userTransaction.rollback();
+ } catch (Exception e1) {
+ log.error("Could not roll back", e1);
+ }
+ if (e instanceof RuntimeException)
+ throw (RuntimeException) e;
+ else
+ throw new RuntimeException("Cannot change password", e);
+ }
+ }
+
+ @Deprecated
+ public String addSharedSecret(String username, String authInfo, String authToken) {
+ try {
+ userTransaction.begin();
+ User user = (User) userAdmin.getRole(username);
+ SharedSecret sharedSecret = new SharedSecret(authInfo, authToken);
+ user.getCredentials().put(SharedSecret.X_SHARED_SECRET, sharedSecret.toAuthPassword());
+ String tokenStr = sharedSecret.getAuthInfo() + '$' + sharedSecret.getAuthValue();
+ userTransaction.commit();
+ return tokenStr;
+ } catch (Exception e1) {
+ try {
+ if (!userTransaction.isNoTransactionStatus())
+ userTransaction.rollback();
+ } catch (Exception e2) {
+ if (log.isTraceEnabled())
+ log.trace("Cannot rollback transaction", e2);
+ }
+ throw new RuntimeException("Cannot add shared secret", e1);
+ }
+ }
+
+ @Override
+ public void expireAuthToken(String token) {
+ try {
+ userTransaction.begin();
+ String dn = cn + "=" + token + "," + CmsConstants.TOKENS_BASEDN;
+ Group tokenGroup = (Group) userAdmin.getRole(dn);
+ String ldapDate = NamingUtils.instantToLdapDate(ZonedDateTime.now(ZoneOffset.UTC));
+ tokenGroup.getProperties().put(description.name(), ldapDate);
+ userTransaction.commit();
+ if (log.isDebugEnabled())
+ log.debug("Token " + token + " expired.");
+ } catch (Exception e1) {
+ try {
+ if (!userTransaction.isNoTransactionStatus())
+ userTransaction.rollback();
+ } catch (Exception e2) {
+ if (log.isTraceEnabled())
+ log.trace("Cannot rollback transaction", e2);
+ }
+ throw new RuntimeException("Cannot expire token", e1);
+ }
+ }
+
+ @Override
+ public void expireAuthTokens(Subject subject) {
+ Set<String> tokens = TokenUtils.tokensUsed(subject, CmsConstants.TOKENS_BASEDN);
+ for (String token : tokens)
+ expireAuthToken(token);
+ }
+
+ @Override
+ public void addAuthToken(String userDn, String token, Integer hours, String... roles) {
+ addAuthToken(userDn, token, ZonedDateTime.now().plusHours(hours), roles);
+ }
+
+ @Override
+ public void addAuthToken(String userDn, String token, ZonedDateTime expiryDate, String... roles) {
+ try {
+ userTransaction.begin();
+ User user = (User) userAdmin.getRole(userDn);
+ String tokenDn = cn + "=" + token + "," + CmsConstants.TOKENS_BASEDN;
+ Group tokenGroup = (Group) userAdmin.createRole(tokenDn, Role.GROUP);
+ if (roles != null)
+ for (String role : roles) {
+ Role r = userAdmin.getRole(role);
+ if (r != null)
+ tokenGroup.addMember(r);
+ else {
+ if (!role.equals(CmsConstants.ROLE_USER)) {
+ throw new IllegalStateException(
+ "Cannot add role " + role + " to token " + token + " for " + userDn);
+ }
+ }
+ }
+ tokenGroup.getProperties().put(owner.name(), user.getName());
+ if (expiryDate != null) {
+ String ldapDate = NamingUtils.instantToLdapDate(expiryDate);
+ tokenGroup.getProperties().put(description.name(), ldapDate);
+ }
+ userTransaction.commit();
+ } catch (Exception e1) {
+ try {
+ if (!userTransaction.isNoTransactionStatus())
+ userTransaction.rollback();
+ } catch (Exception e2) {
+ if (log.isTraceEnabled())
+ log.trace("Cannot rollback transaction", e2);
+ }
+ throw new RuntimeException("Cannot add token", e1);
+ }
+ }
+
+ @Override
+ public UserDirectory getDirectory(Role user) {
+ String name = user.getName();
+ NavigableMap<String, UserDirectory> possible = new TreeMap<>();
+ for (UserDirectory userDirectory : userDirectories) {
+ if (name.endsWith(userDirectory.getBase())) {
+ possible.put(userDirectory.getBase(), userDirectory);
+ }
+ }
+ if (possible.size() == 0)
+ throw new IllegalStateException("No user directory found for user " + name);
+ return possible.lastEntry().getValue();
+ }
+
+// public User createUserFromPerson(Node person) {
+// String email = JcrUtils.get(person, LdapAttrs.mail.property());
+// String dn = buildDefaultDN(email, Role.USER);
+// User user;
+// try {
+// userTransaction.begin();
+// user = (User) userAdmin.createRole(dn, Role.USER);
+// Dictionary<String, Object> userProperties = user.getProperties();
+// String name = JcrUtils.get(person, LdapAttrs.displayName.property());
+// userProperties.put(LdapAttrs.cn.name(), name);
+// userProperties.put(LdapAttrs.displayName.name(), name);
+// String givenName = JcrUtils.get(person, LdapAttrs.givenName.property());
+// String surname = JcrUtils.get(person, LdapAttrs.sn.property());
+// userProperties.put(LdapAttrs.givenName.name(), givenName);
+// userProperties.put(LdapAttrs.sn.name(), surname);
+// userProperties.put(LdapAttrs.mail.name(), email.toLowerCase());
+// userTransaction.commit();
+// } catch (Exception e) {
+// try {
+// userTransaction.rollback();
+// } catch (Exception e1) {
+// log.error("Could not roll back", e1);
+// }
+// if (e instanceof RuntimeException)
+// throw (RuntimeException) e;
+// else
+// throw new RuntimeException("Cannot create user", e);
+// }
+// return user;
+// }
+
+ public UserAdmin getUserAdmin() {
+ return userAdmin;
+ }
+
+// public UserTransaction getUserTransaction() {
+// return userTransaction;
+// }
+
+ /* DEPENDENCY INJECTION */
+ public void setUserAdmin(UserAdmin userAdmin) {
+ this.userAdmin = userAdmin;
+
+ if (userAdmin instanceof AggregatingUserAdmin) {
+ userDirectories = ((AggregatingUserAdmin) userAdmin).getUserDirectories();
+ } else {
+ throw new IllegalArgumentException("Only " + AggregatingUserAdmin.class.getName() + " is supported.");
+ }
+
+// this.serviceProperties = serviceProperties;
+ }
+
+ public void setUserTransaction(WorkTransaction userTransaction) {
+ this.userTransaction = userTransaction;
+ }
+
+// public void addUserDirectory(UserDirectory userDirectory, Map<String, Object> properties) {
+// userDirectories.put(userDirectory, new Hashtable<>(properties));
+// }
+//
+// public void removeUserDirectory(UserDirectory userDirectory, Map<String, Object> properties) {
+// userDirectories.remove(userDirectory);
+// }
+
+}
--- /dev/null
+package org.argeo.cms.internal.runtime;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import org.argeo.api.cms.CmsLog;
+import org.argeo.api.cms.directory.CmsUserManager;
+import org.argeo.cms.acr.CmsContentRepository;
+import org.argeo.cms.acr.directory.DirectoryContentProvider;
+import org.argeo.cms.acr.fs.FsContentProvider;
+
+public class DeployedContentRepository extends CmsContentRepository {
+ private final static String ROOT_XML = "cr:root.xml";
+
+ private final static CmsLog log = CmsLog.getLog(DeployedContentRepository.class);
+
+ private CmsUserManager userManager;
+
+ @Override
+ public void start() {
+ long begin = System.currentTimeMillis();
+ try {
+ super.start();
+ Path rootXml = KernelUtils.getOsgiInstancePath(ROOT_XML);
+ initRootContentProvider(null);
+
+// Path srvPath = KernelUtils.getOsgiInstancePath(CmsConstants.SRV_WORKSPACE);
+// FsContentProvider srvContentProvider = new FsContentProvider("/" + CmsConstants.SRV_WORKSPACE, srvPath, false);
+// addProvider(srvContentProvider);
+
+ // run dir
+ Path runDirPath = KernelUtils.getOsgiInstancePath(CmsContentRepository.RUN_BASE);
+ if (runDirPath != null) {
+ Files.createDirectories(runDirPath);
+ FsContentProvider runContentProvider = new FsContentProvider(CmsContentRepository.RUN_BASE, runDirPath);
+ addProvider(runContentProvider);
+ }
+
+ // users
+ DirectoryContentProvider directoryContentProvider = new DirectoryContentProvider(
+ CmsContentRepository.DIRECTORY_BASE, userManager);
+ addProvider(directoryContentProvider);
+
+ // remote
+// DavContentProvider davContentProvider = new DavContentProvider("/srv",
+// URI.create("http://localhost/unstable/a2/"));
+// addProvider(davContentProvider);
+ } catch (IOException e) {
+ throw new IllegalStateException("Cannot start content repository", e);
+ }
+ long duration = System.currentTimeMillis() - begin;
+ log.debug(() -> "CMS content repository available (initialisation took " + duration + " ms)");
+ }
+
+ @Override
+ public void stop() {
+ super.stop();
+ }
+
+ public void setUserManager(CmsUserManager userManager) {
+ this.userManager = userManager;
+ }
+
+}
+++ /dev/null
-package org.argeo.cms.internal.runtime;
-
-import static org.argeo.cms.internal.runtime.KernelUtils.getFrameworkProp;
-
-import java.io.File;
-import java.io.FileFilter;
-import java.io.IOException;
-import java.io.Reader;
-import java.net.InetAddress;
-import java.net.URI;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.security.KeyStore;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Dictionary;
-import java.util.Hashtable;
-import java.util.List;
-
-import javax.security.auth.x500.X500Principal;
-
-import org.apache.commons.io.FileUtils;
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.cms.internal.http.InternalHttpConstants;
-import org.argeo.osgi.useradmin.UserAdminConf;
-
-/**
- * Interprets framework properties in order to generate the initial deploy
- * configuration.
- */
-public class InitUtils {
- private final static CmsLog log = CmsLog.getLog(InitUtils.class);
-
- /** Override the provided config with the framework properties */
- public static Dictionary<String, Object> getHttpServerConfig(Dictionary<String, Object> provided) {
- String httpPort = getFrameworkProp("org.osgi.service.http.port");
- String httpsPort = getFrameworkProp("org.osgi.service.http.port.secure");
- /// TODO make it more generic
- String httpHost = getFrameworkProp(
- InternalHttpConstants.JETTY_PROPERTY_PREFIX + InternalHttpConstants.HTTP_HOST);
- String httpsHost = getFrameworkProp(
- InternalHttpConstants.JETTY_PROPERTY_PREFIX + InternalHttpConstants.HTTPS_HOST);
- String webSocketEnabled = getFrameworkProp(
- InternalHttpConstants.JETTY_PROPERTY_PREFIX + InternalHttpConstants.WEBSOCKET_ENABLED);
-
- final Hashtable<String, Object> props = new Hashtable<String, Object>();
- // try {
- if (httpPort != null || httpsPort != null) {
- boolean httpEnabled = httpPort != null;
- props.put(InternalHttpConstants.HTTP_ENABLED, httpEnabled);
- boolean httpsEnabled = httpsPort != null;
- props.put(InternalHttpConstants.HTTPS_ENABLED, httpsEnabled);
-
- if (httpEnabled) {
- props.put(InternalHttpConstants.HTTP_PORT, httpPort);
- if (httpHost != null)
- props.put(InternalHttpConstants.HTTP_HOST, httpHost);
- }
-
- if (httpsEnabled) {
- props.put(InternalHttpConstants.HTTPS_PORT, httpsPort);
- if (httpsHost != null)
- props.put(InternalHttpConstants.HTTPS_HOST, httpsHost);
-
- // server certificate
- Path keyStorePath = KernelUtils.getOsgiInstancePath(KernelConstants.DEFAULT_KEYSTORE_PATH);
- Path pemKeyPath = KernelUtils.getOsgiInstancePath(KernelConstants.DEFAULT_PEM_KEY_PATH);
- Path pemCertPath = KernelUtils.getOsgiInstancePath(KernelConstants.DEFAULT_PEM_CERT_PATH);
- String keyStorePasswordStr = getFrameworkProp(
- InternalHttpConstants.JETTY_PROPERTY_PREFIX + InternalHttpConstants.SSL_PASSWORD);
- char[] keyStorePassword;
- if (keyStorePasswordStr == null)
- keyStorePassword = "changeit".toCharArray();
- else
- keyStorePassword = keyStorePasswordStr.toCharArray();
-
- // if PEM files both exists, update the PKCS12 file
- if (Files.exists(pemCertPath) && Files.exists(pemKeyPath)) {
- // TODO check certificate update time? monitor changes?
- KeyStore keyStore = PkiUtils.getKeyStore(keyStorePath, keyStorePassword, PkiUtils.PKCS12);
- try (Reader key = Files.newBufferedReader(pemKeyPath, StandardCharsets.US_ASCII);
- Reader cert = Files.newBufferedReader(pemCertPath, StandardCharsets.US_ASCII);) {
- PkiUtils.loadPem(keyStore, key, keyStorePassword, cert);
- PkiUtils.saveKeyStore(keyStorePath, keyStorePassword, keyStore);
- if (log.isDebugEnabled())
- log.debug("PEM certificate stored in " + keyStorePath);
- } catch (IOException e) {
- log.error("Cannot read PEM files " + pemKeyPath + " and " + pemCertPath, e);
- }
- }
-
- if (!Files.exists(keyStorePath))
- createSelfSignedKeyStore(keyStorePath, keyStorePassword, PkiUtils.PKCS12);
- props.put(InternalHttpConstants.SSL_KEYSTORETYPE, PkiUtils.PKCS12);
- props.put(InternalHttpConstants.SSL_KEYSTORE, keyStorePath.toString());
- props.put(InternalHttpConstants.SSL_PASSWORD, new String(keyStorePassword));
-
-// props.put(InternalHttpConstants.SSL_KEYSTORETYPE, "PKCS11");
-// props.put(InternalHttpConstants.SSL_KEYSTORE, "../../nssdb");
-// props.put(InternalHttpConstants.SSL_PASSWORD, keyStorePassword);
-
- // client certificate authentication
- String wantClientAuth = getFrameworkProp(
- InternalHttpConstants.JETTY_PROPERTY_PREFIX + InternalHttpConstants.SSL_WANTCLIENTAUTH);
- if (wantClientAuth != null)
- props.put(InternalHttpConstants.SSL_WANTCLIENTAUTH, Boolean.parseBoolean(wantClientAuth));
- String needClientAuth = getFrameworkProp(
- InternalHttpConstants.JETTY_PROPERTY_PREFIX + InternalHttpConstants.SSL_NEEDCLIENTAUTH);
- if (needClientAuth != null)
- props.put(InternalHttpConstants.SSL_NEEDCLIENTAUTH, Boolean.parseBoolean(needClientAuth));
- }
-
- // web socket
- if (webSocketEnabled != null && webSocketEnabled.equals("true"))
- props.put(InternalHttpConstants.WEBSOCKET_ENABLED, true);
-
- props.put(CmsConstants.CN, CmsConstants.DEFAULT);
- }
- return props;
- }
-
- public static List<Dictionary<String, Object>> getUserDirectoryConfigs() {
- List<Dictionary<String, Object>> res = new ArrayList<>();
- File nodeBaseDir = KernelUtils.getOsgiInstancePath(KernelConstants.DIR_NODE).toFile();
- List<String> uris = new ArrayList<>();
-
- // node roles
- String nodeRolesUri = getFrameworkProp(CmsConstants.ROLES_URI);
- String baseNodeRoleDn = CmsConstants.ROLES_BASEDN;
- if (nodeRolesUri == null) {
- nodeRolesUri = baseNodeRoleDn + ".ldif";
- File nodeRolesFile = new File(nodeBaseDir, nodeRolesUri);
- if (!nodeRolesFile.exists())
- try {
- FileUtils.copyInputStreamToFile(InitUtils.class.getResourceAsStream(baseNodeRoleDn + ".ldif"),
- nodeRolesFile);
- } catch (IOException e) {
- throw new RuntimeException("Cannot copy demo resource", e);
- }
- // nodeRolesUri = nodeRolesFile.toURI().toString();
- }
- uris.add(nodeRolesUri);
-
- // node tokens
- String nodeTokensUri = getFrameworkProp(CmsConstants.TOKENS_URI);
- String baseNodeTokensDn = CmsConstants.TOKENS_BASEDN;
- if (nodeTokensUri == null) {
- nodeTokensUri = baseNodeTokensDn + ".ldif";
- File nodeTokensFile = new File(nodeBaseDir, nodeTokensUri);
- if (!nodeTokensFile.exists())
- try {
- FileUtils.copyInputStreamToFile(InitUtils.class.getResourceAsStream(baseNodeTokensDn + ".ldif"),
- nodeTokensFile);
- } catch (IOException e) {
- throw new RuntimeException("Cannot copy demo resource", e);
- }
- // nodeRolesUri = nodeRolesFile.toURI().toString();
- }
- uris.add(nodeTokensUri);
-
- // Business roles
- String userAdminUris = getFrameworkProp(CmsConstants.USERADMIN_URIS);
- if (userAdminUris == null) {
- String demoBaseDn = "dc=example,dc=com";
- userAdminUris = demoBaseDn + ".ldif";
- File businessRolesFile = new File(nodeBaseDir, userAdminUris);
- File systemRolesFile = new File(nodeBaseDir, "ou=roles,ou=node.ldif");
- if (!businessRolesFile.exists())
- try {
- FileUtils.copyInputStreamToFile(InitUtils.class.getResourceAsStream(demoBaseDn + ".ldif"),
- businessRolesFile);
- if (!systemRolesFile.exists())
- FileUtils.copyInputStreamToFile(
- InitUtils.class.getResourceAsStream("example-ou=roles,ou=node.ldif"), systemRolesFile);
- } catch (IOException e) {
- throw new RuntimeException("Cannot copy demo resources", e);
- }
- // userAdminUris = businessRolesFile.toURI().toString();
- log.warn("## DEV Using dummy base DN " + demoBaseDn);
- // TODO downgrade security level
- }
- for (String userAdminUri : userAdminUris.split(" "))
- uris.add(userAdminUri);
-
- // Interprets URIs
- for (String uri : uris) {
- URI u;
- try {
- u = new URI(uri);
- if (u.getPath() == null)
- throw new IllegalArgumentException(
- "URI " + uri + " must have a path in order to determine base DN");
- if (u.getScheme() == null) {
- if (uri.startsWith("/") || uri.startsWith("./") || uri.startsWith("../"))
- u = new File(uri).getCanonicalFile().toURI();
- else if (!uri.contains("/")) {
- // u = KernelUtils.getOsgiInstanceUri(KernelConstants.DIR_NODE + '/' + uri);
- u = new URI(uri);
- } else
- throw new IllegalArgumentException("Cannot interpret " + uri + " as an uri");
- } else if (u.getScheme().equals(UserAdminConf.SCHEME_FILE)) {
- u = new File(u).getCanonicalFile().toURI();
- }
- } catch (Exception e) {
- throw new RuntimeException("Cannot interpret " + uri + " as an uri", e);
- }
- Dictionary<String, Object> properties = UserAdminConf.uriAsProperties(u.toString());
- res.add(properties);
- }
-
- return res;
- }
-
- /**
- * Called before node initialisation, in order populate OSGi instance are with
- * some files (typically LDIF, etc).
- */
- public static void prepareFirstInitInstanceArea() {
- String nodeInits = getFrameworkProp(CmsConstants.NODE_INIT);
- if (nodeInits == null)
- nodeInits = "../../init";
-
- for (String nodeInit : nodeInits.split(",")) {
-
- if (nodeInit.startsWith("http")) {
- // TODO reconnect it
- // registerRemoteInit(nodeInit);
- } else {
-
- // TODO use java.nio.file
- File initDir;
- if (nodeInit.startsWith("."))
- initDir = KernelUtils.getExecutionDir(nodeInit);
- else
- initDir = new File(nodeInit);
- // TODO also uncompress archives
- if (initDir.exists())
- try {
- FileUtils.copyDirectory(initDir, KernelUtils.getOsgiInstanceDir(), new FileFilter() {
-
- @Override
- public boolean accept(File pathname) {
- if (pathname.getName().equals(".svn") || pathname.getName().equals(".git"))
- return false;
- return true;
- }
- });
- log.info("CMS initialized from " + initDir.getCanonicalPath());
- } catch (IOException e) {
- throw new RuntimeException("Cannot initialize from " + initDir, e);
- }
- }
- }
- }
-
- private static void createSelfSignedKeyStore(Path keyStorePath, char[] keyStorePassword, String keyStoreType) {
- // for (Provider provider : Security.getProviders())
- // System.out.println(provider.getName());
-// File keyStoreFile = keyStorePath.toFile();
- char[] keyPwd = Arrays.copyOf(keyStorePassword, keyStorePassword.length);
- if (!Files.exists(keyStorePath)) {
- try {
- Files.createDirectories(keyStorePath.getParent());
- KeyStore keyStore = PkiUtils.getKeyStore(keyStorePath, keyStorePassword, keyStoreType);
- PkiUtils.generateSelfSignedCertificate(keyStore,
- new X500Principal("CN=" + InetAddress.getLocalHost().getHostName() + ",OU=UNSECURE,O=UNSECURE"),
- 1024, keyPwd);
- PkiUtils.saveKeyStore(keyStorePath, keyStorePassword, keyStore);
- if (log.isDebugEnabled())
- log.debug("Created self-signed unsecure keystore " + keyStorePath);
- } catch (Exception e) {
- try {
- if (Files.size(keyStorePath) == 0)
- Files.delete(keyStorePath);
- } catch (IOException e1) {
- // silent
- }
- log.error("Cannot create keystore " + keyStorePath, e);
- }
- } else {
- throw new IllegalStateException("Keystore " + keyStorePath + " already exists");
- }
- }
-
-}
import org.argeo.api.cms.CmsConstants;
/** Internal CMS constants. */
-public interface KernelConstants {
+interface KernelConstants {
// Directories
- String DIR_NODE = "node";
- String DIR_REPOS = "repos";
- String DIR_INDEXES = "indexes";
- String DIR_TRANSACTIONS = "transactions";
+ String DIR_PRIVATE = "private";
// 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";
+ String NODE_KEY_TAB_PATH = DIR_PRIVATE + "/krb5.keytab";
+ String NODE_SSHD_AUTHORIZED_KEYS_PATH = DIR_PRIVATE + "/authorized_keys";
// Security
String JAAS_CONFIG = "/org/argeo/cms/internal/runtime/jaas.cfg";
String JAAS_CONFIG_IPA = "/org/argeo/cms/internal/runtime/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";
+ String DEFAULT_KEYSTORE_PATH = DIR_PRIVATE + '/' + CmsConstants.NODE + ".p12";
+
+ String DEFAULT_TRUSTSTORE_PATH = DIR_PRIVATE + "/trusted.p12";
+
+ String DEFAULT_PEM_KEY_PATH = DIR_PRIVATE + '/' + CmsConstants.NODE + ".key";
+
+ String DEFAULT_PEM_CERT_PATH = DIR_PRIVATE + '/' + CmsConstants.NODE + ".crt";
+
+ String IPA_PEM_CA_CERT_PATH = "/etc/ipa/ca.crt";
+
+ String DEFAULT_KEYSTORE_PASSWORD = "changeit";
+
+ String PKCS12 = "PKCS12";
+
// 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 JETTY_FACTORY_PID = "org.argeo.equinox.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";
+ // String COOKIE_POLICY_BROWSER_COMPATIBILITY = "compatibility";
+
}
package org.argeo.cms.internal.runtime;
-import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.net.URI;
import org.argeo.cms.internal.osgi.CmsActivator;
/** Package utilities */
-public class KernelUtils implements KernelConstants {
+class KernelUtils implements KernelConstants {
final static String OSGI_INSTANCE_AREA = "osgi.instance.area";
final static String OSGI_CONFIGURATION_AREA = "osgi.configuration.area";
return asDictionary(props);
}
- static File getExecutionDir(String relativePath) {
- File executionDir = new File(getFrameworkProp("user.dir"));
+ static Path getExecutionDir(String relativePath) {
+ Path executionDir = Paths.get(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(CmsActivator.getBundleContext().getProperty(OSGI_INSTANCE_AREA).substring("file:".length()))
- .getAbsoluteFile();
+ return executionDir.resolve(relativePath);
}
public static Path getOsgiInstancePath(String relativePath) {
- return Paths.get(getOsgiInstanceUri(relativePath));
+ URI uri = getOsgiInstanceUri(relativePath);
+ if (uri == null) // no data area available
+ return null;
+ return Paths.get(uri);
}
public 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();
- }
+ if (osgiInstanceBaseUri == null) // no data area available
+ return null;
- static File getOsgiConfigurationFile(String relativePath) {
- try {
- return new File(new URI(CmsActivator.getBundleContext().getProperty(OSGI_CONFIGURATION_AREA) + relativePath))
- .getCanonicalFile();
- } catch (Exception e) {
- throw new IllegalArgumentException("Cannot get configuration file for " + relativePath, e);
- }
+ if (!osgiInstanceBaseUri.endsWith("/"))
+ osgiInstanceBaseUri = osgiInstanceBaseUri + "/";
+ return safeUri(osgiInstanceBaseUri + (relativePath != null ? relativePath : ""));
}
static String getFrameworkProp(String key, String def) {
return value;
}
- public static String getFrameworkProp(String key) {
+ 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) {
for (Object sysProp : new TreeSet<Object>(System.getProperties().keySet())) {
log.debug(sysProp + "=" + getFrameworkProp(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) {
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(NodeConstants.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);
-// }
-// }
-// }
-//
-// });
-// }
-
-// public 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 Activator.getBundleContext();
-// }
-
static boolean asBoolean(String value) {
if (value == null)
return false;
package org.argeo.cms.internal.runtime;
+import java.io.BufferedInputStream;
+import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
-import java.math.BigInteger;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.GeneralSecurityException;
-import java.security.KeyPair;
-import java.security.KeyPairGenerator;
+import java.security.KeyFactory;
import java.security.KeyStore;
+import java.security.KeyStore.TrustedCertificateEntry;
import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
-import java.security.SecureRandom;
-import java.security.Security;
-import java.security.cert.Certificate;
import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
-import java.util.Date;
-
-import javax.security.auth.x500.X500Principal;
-
-import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
-import org.bouncycastle.cert.X509CertificateHolder;
-import org.bouncycastle.cert.X509v3CertificateBuilder;
-import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
-import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
-import org.bouncycastle.jce.provider.BouncyCastleProvider;
-import org.bouncycastle.openssl.PEMParser;
-import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
-import org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8DecryptorProviderBuilder;
-import org.bouncycastle.operator.ContentSigner;
-import org.bouncycastle.operator.InputDecryptorProvider;
-import org.bouncycastle.operator.OperatorCreationException;
-import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
-import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo;
-import org.bouncycastle.pkcs.PKCSException;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.util.Base64;
+import java.util.Collection;
+import java.util.Objects;
/**
* Utilities around private keys and certificate, mostly wrapping BouncyCastle
* implementations.
*/
class PkiUtils {
- final static String PKCS12 = "PKCS12";
-
- private final static String SECURITY_PROVIDER;
- static {
- Security.addProvider(new BouncyCastleProvider());
- SECURITY_PROVIDER = "BC";
- }
-
- public static X509Certificate generateSelfSignedCertificate(KeyStore keyStore, X500Principal x500Principal,
- int keySize, char[] keyPassword) {
- try {
- KeyPairGenerator kpGen = KeyPairGenerator.getInstance("RSA", SECURITY_PROVIDER);
- kpGen.initialize(keySize, new SecureRandom());
- KeyPair pair = kpGen.generateKeyPair();
- Date notBefore = new Date(System.currentTimeMillis() - 10000);
- Date notAfter = new Date(System.currentTimeMillis() + 365 * 24L * 3600 * 1000);
- BigInteger serial = BigInteger.valueOf(System.currentTimeMillis());
- X509v3CertificateBuilder certGen = new JcaX509v3CertificateBuilder(x500Principal, serial, notBefore,
- notAfter, x500Principal, pair.getPublic());
- ContentSigner sigGen = new JcaContentSignerBuilder("SHA256WithRSAEncryption").setProvider(SECURITY_PROVIDER)
- .build(pair.getPrivate());
- X509Certificate cert = new JcaX509CertificateConverter().setProvider(SECURITY_PROVIDER)
- .getCertificate(certGen.build(sigGen));
- cert.checkValidity(new Date());
- cert.verify(cert.getPublicKey());
-
- keyStore.setKeyEntry(x500Principal.getName(), pair.getPrivate(), keyPassword, new Certificate[] { cert });
- return cert;
- } catch (GeneralSecurityException | OperatorCreationException e) {
- throw new RuntimeException("Cannot generate self-signed certificate", e);
- }
- }
-
public static KeyStore getKeyStore(Path keyStoreFile, char[] keyStorePassword, String keyStoreType) {
try {
- KeyStore store = KeyStore.getInstance(keyStoreType, SECURITY_PROVIDER);
+ KeyStore store = KeyStore.getInstance(keyStoreType);
if (Files.exists(keyStoreFile)) {
try (InputStream fis = Files.newInputStream(keyStoreFile)) {
store.load(fis, keyStorePassword);
}
}
-// public static byte[] pemToPKCS12(final String keyFile, final String cerFile, final String password)
-// throws Exception {
-// // Get the private key
-// FileReader reader = new FileReader(keyFile);
-//
-// PEMReader pem = new PemReader(reader, new PasswordFinder() {
-// @Override
-// public char[] getPassword() {
-// return password.toCharArray();
-// }
-// });
-//
-// PrivateKey key = ((KeyPair) pem.readObject()).getPrivate();
-//
-// pem.close();
-// reader.close();
-//
-// // Get the certificate
-// reader = new FileReader(cerFile);
-// pem = new PEMReader(reader);
-//
-// X509Certificate cert = (X509Certificate) pem.readObject();
-//
-// pem.close();
-// reader.close();
-//
-// // Put them into a PKCS12 keystore and write it to a byte[]
-// ByteArrayOutputStream bos = new ByteArrayOutputStream();
-// KeyStore ks = KeyStore.getInstance("PKCS12");
-// ks.load(null);
-// ks.setKeyEntry("alias", (Key) key, password.toCharArray(), new java.security.cert.Certificate[] { cert });
-// ks.store(bos, password.toCharArray());
-// bos.close();
-// return bos.toByteArray();
-// }
+ public static void loadPrivateCertificatePem(KeyStore keyStore, String alias, Reader key, char[] keyPassword,
+ BufferedInputStream cert) {
+ Objects.requireNonNull(keyStore);
+ Objects.requireNonNull(key);
+ try {
+ X509Certificate certificate = loadPemCertificate(cert);
+ PrivateKey privateKey = loadPemPrivateKey(key, keyPassword);
+ keyStore.setKeyEntry(alias, privateKey, keyPassword, new java.security.cert.Certificate[] { certificate });
+ } catch (KeyStoreException e) {
+ throw new RuntimeException("Cannot store PEM certificate", e);
+ }
+ }
- public static void loadPem(KeyStore keyStore, Reader key, char[] keyPassword, Reader cert) {
- PrivateKey privateKey = loadPemPrivateKey(key, keyPassword);
- X509Certificate certificate = loadPemCertificate(cert);
+ public static void loadTrustedCertificatePem(KeyStore keyStore, char[] keyStorePassword, BufferedInputStream cert) {
try {
- keyStore.setKeyEntry(certificate.getSubjectX500Principal().getName(), privateKey, keyPassword,
- new java.security.cert.Certificate[] { certificate });
+ X509Certificate certificate = loadPemCertificate(cert);
+ TrustedCertificateEntry trustedCertificateEntry = new TrustedCertificateEntry(certificate);
+ keyStore.setEntry(certificate.getSubjectX500Principal().getName(), trustedCertificateEntry, null);
} catch (KeyStoreException e) {
throw new RuntimeException("Cannot store PEM certificate", e);
}
}
public static PrivateKey loadPemPrivateKey(Reader reader, char[] keyPassword) {
- try (PEMParser pemParser = new PEMParser(reader)) {
- JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC");
- Object object = pemParser.readObject();
- PrivateKeyInfo privateKeyInfo;
- if (object instanceof PKCS8EncryptedPrivateKeyInfo) {
- if (keyPassword == null)
- throw new IllegalArgumentException("A key password is required");
- InputDecryptorProvider decProv = new JceOpenSSLPKCS8DecryptorProviderBuilder().build(keyPassword);
- privateKeyInfo = ((PKCS8EncryptedPrivateKeyInfo) object).decryptPrivateKeyInfo(decProv);
- } else if (object instanceof PrivateKeyInfo) {
- privateKeyInfo = (PrivateKeyInfo) object;
- } else {
- throw new IllegalArgumentException("Unsupported format for private key");
+ try {
+ StringBuilder key = new StringBuilder();
+ try (BufferedReader in = new BufferedReader(reader)) {
+ String line = in.readLine();
+ if (!"-----BEGIN PRIVATE KEY-----".equals(line))
+ throw new IllegalArgumentException("Not a PEM private key");
+ lines: while ((line = in.readLine()) != null) {
+ if ("-----END PRIVATE KEY-----".equals(line))
+ break lines;
+ key.append(line);
+ }
}
- return converter.getPrivateKey(privateKeyInfo);
- } catch (IOException | OperatorCreationException | PKCSException e) {
- throw new RuntimeException("Cannot read private key", e);
- }
- }
- public static X509Certificate loadPemCertificate(Reader reader) {
- try (PEMParser pemParser = new PEMParser(reader)) {
- X509CertificateHolder certHolder = (X509CertificateHolder) pemParser.readObject();
- X509Certificate cert = new JcaX509CertificateConverter().setProvider(SECURITY_PROVIDER)
- .getCertificate(certHolder);
- return cert;
- } catch (IOException | CertificateException e) {
- throw new RuntimeException("Cannot read private key", e);
- }
- }
+ byte[] encoded = Base64.getDecoder().decode(key.toString());
- public static void main(String[] args) throws Exception {
- final String ALGORITHM = "RSA";
- final String provider = "BC";
- SecureRandom secureRandom = new SecureRandom();
- long begin = System.currentTimeMillis();
- for (int i = 512; i < 1024; i = i + 2) {
- try {
- KeyPairGenerator keyGen = KeyPairGenerator.getInstance(ALGORITHM, provider);
- keyGen.initialize(i, secureRandom);
- keyGen.generateKeyPair();
- } catch (Exception e) {
- System.err.println(i + " : " + e.getMessage());
- }
+ KeyFactory keyFactory = KeyFactory.getInstance("RSA");
+ PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encoded);
+ return (RSAPrivateKey) keyFactory.generatePrivate(keySpec);
+ } catch (NoSuchAlgorithmException | InvalidKeySpecException | IOException e) {
+ throw new RuntimeException("Cannot load PEM key", e);
}
- System.out.println((System.currentTimeMillis() - begin) + " ms");
-
- // // String text = "a";
- // String text =
- // "testtesttesttesttesttesttesttesttesttesttesttesttesttesttest";
- // try {
- // System.out.println(text);
- // PrivateKey privateKey;
- // PublicKey publicKey;
- // char[] password = "changeit".toCharArray();
- // String alias = "CN=test";
- // KeyStore keyStore = KeyStore.getInstance("pkcs12");
- // File p12file = new File("test.p12");
- // p12file.delete();
- // if (!p12file.exists()) {
- // keyStore.load(null);
- // generateSelfSignedCertificate(keyStore, new X500Principal(alias),
- // 513, password);
- // try (OutputStream out = new FileOutputStream(p12file)) {
- // keyStore.store(out, password);
- // }
- // }
- // try (InputStream in = new FileInputStream(p12file)) {
- // keyStore.load(in, password);
- // privateKey = (PrivateKey) keyStore.getKey(alias, password);
- // publicKey = keyStore.getCertificateChain(alias)[0].getPublicKey();
- // }
- // // KeyPair key;
- // // final KeyPairGenerator keyGen =
- // // KeyPairGenerator.getInstance(ALGORITHM);
- // // keyGen.initialize(4096, new SecureRandom());
- // // long begin = System.currentTimeMillis();
- // // key = keyGen.generateKeyPair();
- // // System.out.println((System.currentTimeMillis() - begin) + " ms");
- // // keyStore.load(null);
- // // keyStore.setKeyEntry("test", key.getPrivate(), password, null);
- // // try(OutputStream out=new FileOutputStream(p12file)) {
- // // keyStore.store(out, password);
- // // }
- // // privateKey = key.getPrivate();
- // // publicKey = key.getPublic();
- //
- // Cipher encrypt = Cipher.getInstance(ALGORITHM);
- // encrypt.init(Cipher.ENCRYPT_MODE, publicKey);
- // byte[] encrypted = encrypt.doFinal(text.getBytes());
- // String encryptedBase64 =
- // Base64.getEncoder().encodeToString(encrypted);
- // System.out.println(encryptedBase64);
- // byte[] encryptedFromBase64 =
- // Base64.getDecoder().decode(encryptedBase64);
- //
- // Cipher decrypt = Cipher.getInstance(ALGORITHM);
- // decrypt.init(Cipher.DECRYPT_MODE, privateKey);
- // byte[] decrypted = decrypt.doFinal(encryptedFromBase64);
- // System.out.println(new String(decrypted));
- // } catch (Exception e) {
- // e.printStackTrace();
- // }
}
+ public static X509Certificate loadPemCertificate(BufferedInputStream in) {
+ try {
+ CertificateFactory certificateFactory = CertificateFactory.getInstance("X509");
+ @SuppressWarnings("unchecked")
+ Collection<X509Certificate> certificates = (Collection<X509Certificate>) certificateFactory
+ .generateCertificates(in);
+ if (certificates.isEmpty())
+ throw new IllegalArgumentException("No certificate found");
+ if (certificates.size() != 1)
+ throw new IllegalArgumentException(certificates.size() + " certificates found");
+ return certificates.iterator().next();
+ } catch (CertificateException e) {
+ throw new IllegalStateException("cannot load certifciate", e);
+ }
+ }
}
USER {
org.argeo.cms.auth.RemoteSessionLoginModule sufficient;
org.argeo.cms.auth.SpnegoLoginModule optional;
- com.sun.security.auth.module.Krb5LoginModule optional tryFirstPass=true;
- org.argeo.cms.auth.UserAdminLoginModule sufficient;
+ com.sun.security.auth.module.Krb5LoginModule optional
+ tryFirstPass=true
+ storeKey=true;
+ org.argeo.cms.auth.UserAdminLoginModule required;
};
ANONYMOUS {
NODE {
com.sun.security.auth.module.Krb5LoginModule optional
- keyTab="${osgi.instance.area}node/krb5.keytab"
+ keyTab="${osgi.instance.area}private/krb5.keytab"
useKeyTab=true
storeKey=true;
org.argeo.cms.auth.DataAdminLoginModule requisite;
SINGLE_USER {
com.sun.security.auth.module.Krb5LoginModule optional
- principal="${user.name}"
storeKey=true
- useTicketCache=true
- debug=true;
+ useTicketCache=true;
org.argeo.cms.auth.SingleUserLoginModule requisite;
};
USER {
org.argeo.cms.auth.RemoteSessionLoginModule sufficient;
org.argeo.cms.auth.IdentLoginModule optional;
- org.argeo.cms.auth.UserAdminLoginModule requisite;
+ org.argeo.cms.auth.UserAdminLoginModule required;
};
ANONYMOUS {
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
-import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.TreeSet;
import java.util.stream.Collectors;
-import org.apache.commons.io.IOUtils;
-import org.argeo.api.cms.CmsTheme;
+import org.argeo.api.cms.ux.CmsTheme;
+import org.argeo.cms.util.StreamUtils;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
* <code>/ ** /*.{png,gif,jpeg,...}</code>.<br>
*/
public class BundleCmsTheme implements CmsTheme {
- public final static String DEFAULT_CMS_THEME_BUNDLE = "org.argeo.theme.argeo2";
+// public final static String DEFAULT_CMS_THEME_BUNDLE = "org.argeo.theme.argeo2";
- public final static String CMS_THEME_PROPERTY = "argeo.cms.theme";
+// public final static String CMS_THEME_PROPERTY = "argeo.cms.theme";
+ @Deprecated
public final static String CMS_THEME_BUNDLE_PROPERTY = "argeo.cms.theme.bundle";
+ /** Declared theme ID, to be used by OSGi services to reference it as parent. */
+ public final static String THEME_ID_PROPERTY = "themeId";
+ public final static String SMALL_ICON_SIZE_PROPERTY = "smallIconSize";
+ public final static String BIG_ICON_SIZE_PROPERTY = "bigIconSize";
+
private final static String HEADER_CSS = "header.css";
private final static String FONTS_TXT = "fonts.txt";
private final static String BODY_HTML = "body.html";
private CmsTheme parentTheme;
private String themeId;
+ private String declaredThemeId;;
+
private Set<String> webCssPaths = new TreeSet<>();
private Set<String> rapCssPaths = new TreeSet<>();
private Set<String> swtCssPaths = new TreeSet<>();
// private String swtCssPath;
private Bundle themeBundle;
- private Integer defaultIconSize = 16;
+ private Integer smallIconSize = 16;
+ private Integer bigIconSize = 32;
public BundleCmsTheme() {
}
public void init(BundleContext bundleContext, Map<String, String> properties) {
+ declaredThemeId = properties.get(THEME_ID_PROPERTY);
+ if (properties.containsKey(SMALL_ICON_SIZE_PROPERTY))
+ smallIconSize = Integer.valueOf(properties.get(SMALL_ICON_SIZE_PROPERTY));
+ if (properties.containsKey(BIG_ICON_SIZE_PROPERTY))
+ smallIconSize = Integer.valueOf(properties.get(BIG_ICON_SIZE_PROPERTY));
+
initResources(bundleContext, null);
}
// swtCssPath = "/swt/";
// this.themeId = RWT.DEFAULT_THEME_ID;
this.themeId = themeBundle.getSymbolicName();
+ if (declaredThemeId != null && !declaredThemeId.equals(themeId))
+ throw new IllegalArgumentException(
+ "Declared theme id " + declaredThemeId + " is different from " + themeId);
+
webCssPaths = addCss(themeBundle, "/css/");
rapCssPaths = addCss(themeBundle, "/rap/");
swtCssPaths = addCss(themeBundle, "/swt/");
void loadBodyHtml(URL url) {
try (BufferedReader in = new BufferedReader(new InputStreamReader(url.openStream(), UTF_8))) {
- bodyHtml = IOUtils.toString(url, StandardCharsets.UTF_8);
+ bodyHtml = StreamUtils.toString(in);
} catch (IOException e) {
throw new IllegalArgumentException("Cannot load URL " + url, e);
}
}
@Override
- public Integer getDefaultIconSize() {
- return defaultIconSize;
+ public int getSmallIconSize() {
+ return smallIconSize;
+ }
+
+ @Override
+ public int getBigIconSize() {
+ return bigIconSize;
}
@Override
public InputStream loadPath(String path) throws IOException {
URL url = themeBundle.getResource(path);
- if (url == null)
- throw new IllegalArgumentException(
- "Path " + path + " not found in bundle " + themeBundle.getSymbolicName());
- return url.openStream();
+ if (url == null) {
+ if (parentTheme != null)
+ return parentTheme.loadPath(path);
+ else
+ throw new IllegalArgumentException(
+ "Path " + path + " not found in bundle " + themeBundle.getSymbolicName());
+ } else {
+ return url.openStream();
+ }
}
private static Bundle findThemeBundle(BundleContext bundleContext, String themeId) {
this.parentTheme = parentTheme;
}
+ public void setSmallIconSize(Integer smallIconSize) {
+ this.smallIconSize = smallIconSize;
+ }
+
+ public void setBigIconSize(Integer bigIconSize) {
+ this.bigIconSize = bigIconSize;
+ }
+
}
+++ /dev/null
-package org.argeo.cms.osgi;
-
-import java.util.Collection;
-
-import javax.security.auth.Subject;
-
-import org.argeo.api.cms.CmsSession;
-import org.argeo.api.cms.CmsSessionId;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.InvalidSyntaxException;
-import org.osgi.framework.ServiceReference;
-
-public class CmsOsgiUtils {
-
- /** @return The {@link CmsSession} for this {@link Subject} or null. */
- public static CmsSession getCmsSession(BundleContext bc, Subject subject) {
- if (subject.getPrivateCredentials(CmsSessionId.class).isEmpty())
- return null;
- CmsSessionId cmsSessionId = subject.getPrivateCredentials(CmsSessionId.class).iterator().next();
- String uuid = cmsSessionId.getUuid().toString();
- Collection<ServiceReference<CmsSession>> sr;
- try {
- sr = bc.getServiceReferences(CmsSession.class, "(" + CmsSession.SESSION_UUID + "=" + uuid + ")");
- } catch (InvalidSyntaxException e) {
- throw new IllegalArgumentException("Cannot get CMS session for uuid " + uuid, e);
- }
- ServiceReference<CmsSession> cmsSessionRef;
- if (sr.size() == 1) {
- cmsSessionRef = sr.iterator().next();
- return bc.getService(cmsSessionRef);
- } else if (sr.size() == 0) {
- return null;
- } else
- throw new IllegalStateException(sr.size() + " CMS sessions registered for " + uuid);
- }
-
- /** Singleton.*/
- private CmsOsgiUtils() {
- }
-}
--- /dev/null
+package org.argeo.cms.osgi;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.osgi.resource.Namespace;
+import org.osgi.resource.Requirement;
+import org.osgi.resource.Resource;
+
+/** Simplify filtering resources. */
+public class FilterRequirement implements Requirement {
+ private String namespace;
+ private String filter;
+
+ public FilterRequirement(String namespace, String filter) {
+ this.namespace = namespace;
+ this.filter = filter;
+ }
+
+ @Override
+ public Resource getResource() {
+ return null;
+ }
+
+ @Override
+ public String getNamespace() {
+ return namespace;
+ }
+
+ @Override
+ public Map<String, String> getDirectives() {
+ Map<String, String> directives = new HashMap<>();
+ directives.put(Namespace.REQUIREMENT_FILTER_DIRECTIVE, filter);
+ return directives;
+ }
+
+ @Override
+ public Map<String, Object> getAttributes() {
+ return new HashMap<>();
+ }
+
+}
--- /dev/null
+package org.argeo.cms.osgi.useradmin;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.security.auth.x500.X500Principal;
+
+import org.argeo.api.cms.directory.CmsAuthorization;
+import org.osgi.service.useradmin.Authorization;
+
+/** An {@link Authorization} which combines roles form various auth sources. */
+class AggregatingAuthorization implements CmsAuthorization {
+ private final String name;
+ private final String displayName;
+ private final Set<String> systemRoles;
+ private final Set<String> roles;
+
+ public AggregatingAuthorization(String name, String displayName, Set<String> systemRoles, String[] roles) {
+ this.name = new X500Principal(name).getName();
+ this.displayName = displayName;
+ this.systemRoles = Collections.unmodifiableSet(new HashSet<>(systemRoles));
+ Set<String> temp = new HashSet<>();
+ for (String role : roles) {
+ if (!temp.contains(role))
+ temp.add(role);
+ }
+ this.roles = Collections.unmodifiableSet(temp);
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public boolean hasRole(String name) {
+ if (systemRoles.contains(name))
+ return true;
+ if (roles.contains(name))
+ return true;
+ return false;
+ }
+
+ @Override
+ public String[] getRoles() {
+ int size = systemRoles.size() + roles.size();
+ List<String> res = new ArrayList<String>(size);
+ res.addAll(systemRoles);
+ res.addAll(roles);
+ return res.toArray(new String[size]);
+ }
+
+ @Override
+ public int hashCode() {
+ if (name == null)
+ return super.hashCode();
+ return name.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof Authorization))
+ return false;
+ Authorization that = (Authorization) obj;
+ if (name == null)
+ return that.getName() == null;
+ return name.equals(that.getName());
+ }
+
+ @Override
+ public String toString() {
+ return displayName;
+ }
+
+}
--- /dev/null
+package org.argeo.cms.osgi.useradmin;
+
+import static org.argeo.cms.osgi.useradmin.DirectoryUserAdmin.toLdapName;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.TreeSet;
+
+import javax.naming.InvalidNameException;
+import javax.naming.ldap.LdapName;
+
+import org.argeo.api.cms.directory.CmsUser;
+import org.argeo.api.cms.directory.UserDirectory;
+import org.argeo.cms.runtime.DirectoryConf;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.service.useradmin.Authorization;
+import org.osgi.service.useradmin.Group;
+import org.osgi.service.useradmin.Role;
+import org.osgi.service.useradmin.User;
+import org.osgi.service.useradmin.UserAdmin;
+
+/**
+ * Aggregates multiple {@link UserDirectory} and integrates them with system
+ * roles.
+ */
+public class AggregatingUserAdmin implements UserAdmin {
+ private final LdapName systemRolesBaseDn;
+ private final LdapName tokensBaseDn;
+
+ // DAOs
+ private DirectoryUserAdmin systemRoles = null;
+ private DirectoryUserAdmin tokens = null;
+ private Map<LdapName, DirectoryUserAdmin> businessRoles = new HashMap<LdapName, DirectoryUserAdmin>();
+
+ // TODO rather use an empty constructor and an init method
+ public AggregatingUserAdmin(String systemRolesBaseDn, String tokensBaseDn) {
+ try {
+ this.systemRolesBaseDn = new LdapName(systemRolesBaseDn);
+ if (tokensBaseDn != null)
+ this.tokensBaseDn = new LdapName(tokensBaseDn);
+ else
+ this.tokensBaseDn = null;
+ } catch (InvalidNameException e) {
+ throw new IllegalStateException("Cannot initialize " + AggregatingUserAdmin.class, e);
+ }
+ }
+
+ @Override
+ public Role createRole(String name, int type) {
+ return findUserAdmin(name).createRole(name, type);
+ }
+
+ @Override
+ public boolean removeRole(String name) {
+ boolean actuallyDeleted = findUserAdmin(name).removeRole(name);
+ systemRoles.removeRole(name);
+ return actuallyDeleted;
+ }
+
+ @Override
+ public Role getRole(String name) {
+ return findUserAdmin(name).getRole(name);
+ }
+
+ @Override
+ public Role[] getRoles(String filter) throws InvalidSyntaxException {
+ List<Role> res = new ArrayList<Role>();
+ for (UserAdmin userAdmin : businessRoles.values()) {
+ res.addAll(Arrays.asList(userAdmin.getRoles(filter)));
+ }
+ res.addAll(Arrays.asList(systemRoles.getRoles(filter)));
+ return res.toArray(new Role[res.size()]);
+ }
+
+ @Override
+ public User getUser(String key, String value) {
+ List<User> res = new ArrayList<User>();
+ for (UserAdmin userAdmin : businessRoles.values()) {
+ User u = userAdmin.getUser(key, value);
+ if (u != null)
+ res.add(u);
+ }
+ // Note: node roles cannot contain users, so it is not searched
+ return res.size() == 1 ? res.get(0) : null;
+ }
+
+ /** Builds an authorisation by scanning all referentials. */
+ @Override
+ public Authorization getAuthorization(User user) {
+ if (user == null) {// anonymous
+ return systemRoles.getAuthorization(null);
+ }
+ DirectoryUserAdmin userReferentialOfThisUser = findUserAdmin(user.getName());
+ Authorization rawAuthorization = userReferentialOfThisUser.getAuthorization(user);
+ User retrievedUser = (User) userReferentialOfThisUser.getRole(user.getName());
+ String usernameToUse;
+ String displayNameToUse;
+ if (user instanceof Group) {
+ // TODO check whether this is still working
+ String ownerDn = TokenUtils.userDn((Group) user);
+ if (ownerDn != null) {// tokens
+ UserAdmin ownerUserAdmin = findUserAdmin(ownerDn);
+ User ownerUser = (User) ownerUserAdmin.getRole(ownerDn);
+ usernameToUse = ownerDn;
+ displayNameToUse = LdifAuthorization.extractDisplayName(ownerUser);
+ } else {
+ usernameToUse = rawAuthorization.getName();
+ displayNameToUse = rawAuthorization.toString();
+ }
+ } else {// regular users
+ usernameToUse = rawAuthorization.getName();
+ displayNameToUse = rawAuthorization.toString();
+ }
+
+ // gather roles from other referentials
+ List<String> rawRoles = Arrays.asList(rawAuthorization.getRoles());
+ List<String> allRoles = new ArrayList<>(rawRoles);
+ for (LdapName otherBaseDn : businessRoles.keySet()) {
+ if (otherBaseDn.equals(userReferentialOfThisUser.getBaseDn()))
+ continue;
+ DirectoryUserAdmin otherUserAdmin = userAdminToUse(user, businessRoles.get(otherBaseDn));
+ if (otherUserAdmin == null)
+ continue;
+ for (String roleStr : rawRoles) {
+ User role = (User) findUserAdmin(roleStr).getRole(roleStr);
+ Authorization auth = otherUserAdmin.getAuthorization(role);
+ allRoles.addAll(Arrays.asList(auth.getRoles()));
+ }
+
+ }
+
+ // integrate system roles
+ final DirectoryUserAdmin userAdminToUse = userAdminToUse(retrievedUser, userReferentialOfThisUser);
+ Objects.requireNonNull(userAdminToUse);
+
+ try {
+ Set<String> sysRoles = new HashSet<String>();
+ for (String role : rawAuthorization.getRoles()) {
+ User userOrGroup = (User) userAdminToUse.getRole(role);
+ Authorization auth = systemRoles.getAuthorization(userOrGroup);
+ systemRoles: for (String systemRole : auth.getRoles()) {
+ if (role.equals(systemRole))
+ continue systemRoles;
+ sysRoles.add(systemRole);
+ }
+// sysRoles.addAll(Arrays.asList(auth.getRoles()));
+ }
+ addAbstractSystemRoles(rawAuthorization, sysRoles);
+ Authorization authorization = new AggregatingAuthorization(usernameToUse, displayNameToUse, sysRoles,
+ allRoles.toArray(new String[allRoles.size()]));
+ return authorization;
+ } finally {
+ if (userAdminToUse != null && userAdminToUse.isScoped()) {
+ userAdminToUse.destroy();
+ }
+ }
+ }
+
+ /** Decide whether to scope or not */
+ private DirectoryUserAdmin userAdminToUse(User user, DirectoryUserAdmin userAdmin) {
+ if (userAdmin.isAuthenticated())
+ return userAdmin;
+ if (user instanceof CmsUser) {
+ return userAdmin;
+ } else if (user instanceof AuthenticatingUser) {
+ return userAdmin.scope(user).orElse(null);
+ } else {
+ throw new IllegalArgumentException("Unsupported user type " + user.getClass());
+ }
+
+ }
+
+ /**
+ * Enrich with application-specific roles which are strictly programmatic, such
+ * as anonymous/user semantics.
+ */
+ protected void addAbstractSystemRoles(Authorization rawAuthorization, Set<String> sysRoles) {
+
+ }
+
+ //
+ // USER ADMIN AGGREGATOR
+ //
+ protected void addUserDirectory(UserDirectory ud) {
+ if (!(ud instanceof DirectoryUserAdmin))
+ throw new IllegalArgumentException("Only " + DirectoryUserAdmin.class.getName() + " is supported");
+ DirectoryUserAdmin userDirectory = (DirectoryUserAdmin) ud;
+ String basePath = userDirectory.getBase();
+ if (isSystemRolesBaseDn(basePath)) {
+ this.systemRoles = userDirectory;
+ systemRoles.setExternalRoles(this);
+ } else if (isTokensBaseDn(basePath)) {
+ this.tokens = userDirectory;
+ tokens.setExternalRoles(this);
+ } else {
+ LdapName baseDn = toLdapName(basePath);
+ if (businessRoles.containsKey(baseDn))
+ throw new IllegalStateException("There is already a user admin for " + baseDn);
+ businessRoles.put(baseDn, userDirectory);
+ }
+ userDirectory.init();
+ postAdd(userDirectory);
+ }
+
+ /** Called after a new user directory has been added */
+ protected void postAdd(UserDirectory userDirectory) {
+ }
+
+ private DirectoryUserAdmin findUserAdmin(String name) {
+ try {
+ return findUserAdmin(new LdapName(name));
+ } catch (InvalidNameException e) {
+ throw new IllegalArgumentException("Badly formatted name " + name, e);
+ }
+ }
+
+ private DirectoryUserAdmin findUserAdmin(LdapName name) {
+ if (name.startsWith(systemRolesBaseDn))
+ return systemRoles;
+ if (tokensBaseDn != null && name.startsWith(tokensBaseDn))
+ return tokens;
+ List<DirectoryUserAdmin> res = new ArrayList<>(1);
+ userDirectories: for (LdapName baseDn : businessRoles.keySet()) {
+ DirectoryUserAdmin userDirectory = businessRoles.get(baseDn);
+ if (name.startsWith(baseDn)) {
+ if (userDirectory.isDisabled())
+ continue userDirectories;
+// if (res.isEmpty()) {
+ res.add(userDirectory);
+// } else {
+// for (AbstractUserDirectory ud : res) {
+// LdapName bd = ud.getBaseDn();
+// if (userDirectory.getBaseDn().startsWith(bd)) {
+// // child user directory
+// }
+// }
+// }
+ }
+ }
+ if (res.size() == 0)
+ throw new IllegalStateException("Cannot find user admin for " + name);
+ if (res.size() > 1)
+ throw new IllegalStateException("Multiple user admin found for " + name);
+ return res.get(0);
+ }
+
+ protected boolean isSystemRolesBaseDn(String basePath) {
+ return toLdapName(basePath).equals(systemRolesBaseDn);
+ }
+
+ protected boolean isTokensBaseDn(String basePath) {
+ return tokensBaseDn != null && toLdapName(basePath).equals(tokensBaseDn);
+ }
+
+// protected Dictionary<String, Object> currentState() {
+// Dictionary<String, Object> res = new Hashtable<String, Object>();
+// // res.put(NodeConstants.CN, NodeConstants.DEFAULT);
+// for (LdapName name : businessRoles.keySet()) {
+// AbstractUserDirectory userDirectory = businessRoles.get(name);
+// String uri = UserAdminConf.propertiesAsUri(userDirectory.getProperties()).toString();
+// res.put(uri, "");
+// }
+// return res;
+// }
+
+ public void start() {
+ if (systemRoles == null) {
+ // TODO do we really need separate system roles?
+ Hashtable<String, Object> properties = new Hashtable<>();
+ properties.put(DirectoryConf.baseDn.name(), "ou=roles,ou=system");
+ systemRoles = new DirectoryUserAdmin(properties);
+ }
+ }
+
+ public void stop() {
+ for (LdapName name : businessRoles.keySet()) {
+ DirectoryUserAdmin userDirectory = businessRoles.get(name);
+ destroy(userDirectory);
+ }
+ businessRoles.clear();
+ businessRoles = null;
+ destroy(systemRoles);
+ systemRoles = null;
+ }
+
+ private void destroy(DirectoryUserAdmin userDirectory) {
+ preDestroy(userDirectory);
+ userDirectory.destroy();
+ }
+
+// protected void removeUserDirectory(UserDirectory userDirectory) {
+// LdapName baseDn = toLdapName(userDirectory.getContext());
+// businessRoles.remove(baseDn);
+// if (userDirectory instanceof DirectoryUserAdmin)
+// destroy((DirectoryUserAdmin) userDirectory);
+// }
+
+ @Deprecated
+ protected void removeUserDirectory(String basePath) {
+ if (isSystemRolesBaseDn(basePath))
+ throw new IllegalArgumentException("System roles cannot be removed ");
+ LdapName baseDn = toLdapName(basePath);
+ if (!businessRoles.containsKey(baseDn))
+ throw new IllegalStateException("No user directory registered for " + baseDn);
+ DirectoryUserAdmin userDirectory = businessRoles.remove(baseDn);
+ destroy(userDirectory);
+ }
+
+ /**
+ * Called before each user directory is destroyed, so that additional actions
+ * can be performed.
+ */
+ protected void preDestroy(UserDirectory userDirectory) {
+ }
+
+ public Set<UserDirectory> getUserDirectories() {
+ TreeSet<UserDirectory> res = new TreeSet<>((o1, o2) -> o1.getBase().compareTo(o2.getBase()));
+ res.addAll(businessRoles.values());
+ res.add(systemRoles);
+ return res;
+ }
+
+}
--- /dev/null
+package org.argeo.cms.osgi.useradmin;
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+
+import javax.naming.ldap.LdapName;
+
+import org.argeo.api.cms.directory.DirectoryDigestUtils;
+import org.osgi.service.useradmin.User;
+
+/**
+ * A special user type used during authentication in order to provide the
+ * credentials required for scoping the user admin.
+ */
+public class AuthenticatingUser implements User {
+ /** From com.sun.security.auth.module.*LoginModule */
+ public final static String SHARED_STATE_NAME = "javax.security.auth.login.name";
+ /** From com.sun.security.auth.module.*LoginModule */
+ public final static String SHARED_STATE_PWD = "javax.security.auth.login.password";
+
+ private final String name;
+ private final Dictionary<String, Object> credentials;
+
+ public AuthenticatingUser(LdapName name) {
+ if (name == null)
+ throw new NullPointerException("Provided name cannot be null.");
+ this.name = name.toString();
+ this.credentials = new Hashtable<>();
+ }
+
+ public AuthenticatingUser(String name, Dictionary<String, Object> credentials) {
+ this.name = name;
+ this.credentials = credentials;
+ }
+
+ public AuthenticatingUser(String name, char[] password) {
+ if (name == null)
+ throw new NullPointerException("Provided name cannot be null.");
+ this.name = name;
+ credentials = new Hashtable<>();
+ credentials.put(SHARED_STATE_NAME, name);
+ byte[] pwd = DirectoryDigestUtils.charsToBytes(password);
+ credentials.put(SHARED_STATE_PWD, pwd);
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public int getType() {
+ return User.USER;
+ }
+
+ @SuppressWarnings({ "rawtypes", "unchecked" })
+ @Override
+ public Dictionary getProperties() {
+ throw new UnsupportedOperationException();
+ }
+
+ @SuppressWarnings({ "rawtypes", "unchecked" })
+ @Override
+ public Dictionary getCredentials() {
+ return credentials;
+ }
+
+ @Override
+ public boolean hasCredential(String key, Object value) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int hashCode() {
+ return name.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return "Authenticating user " + name;
+ }
+
+}
--- /dev/null
+package org.argeo.cms.osgi.useradmin;
+
+import static org.argeo.api.acr.ldap.LdapAttr.objectClass;
+import static org.argeo.api.acr.ldap.LdapObj.extensibleObject;
+import static org.argeo.api.acr.ldap.LdapObj.inetOrgPerson;
+import static org.argeo.api.acr.ldap.LdapObj.organizationalPerson;
+import static org.argeo.api.acr.ldap.LdapObj.person;
+import static org.argeo.api.acr.ldap.LdapObj.top;
+
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Optional;
+
+import javax.naming.Context;
+import javax.naming.InvalidNameException;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.BasicAttribute;
+import javax.naming.directory.BasicAttributes;
+import javax.naming.ldap.LdapName;
+import javax.naming.ldap.Rdn;
+import javax.security.auth.Subject;
+import javax.security.auth.kerberos.KerberosTicket;
+
+import org.argeo.api.cms.directory.DirectoryDigestUtils;
+import org.argeo.api.cms.directory.CmsUser;
+import org.argeo.api.cms.directory.HierarchyUnit;
+import org.argeo.api.cms.directory.UserDirectory;
+import org.argeo.cms.directory.ldap.AbstractLdapDirectory;
+import org.argeo.cms.directory.ldap.LdapDao;
+import org.argeo.cms.directory.ldap.LdapEntry;
+import org.argeo.cms.directory.ldap.LdapEntryWorkingCopy;
+import org.argeo.cms.directory.ldap.LdapNameUtils;
+import org.argeo.cms.directory.ldap.LdifDao;
+import org.argeo.cms.runtime.DirectoryConf;
+import org.argeo.cms.util.CurrentSubject;
+import org.osgi.framework.Filter;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.service.useradmin.Authorization;
+import org.osgi.service.useradmin.Role;
+import org.osgi.service.useradmin.User;
+import org.osgi.service.useradmin.UserAdmin;
+
+/** Base class for a {@link UserDirectory}. */
+public class DirectoryUserAdmin extends AbstractLdapDirectory implements UserAdmin, UserDirectory {
+
+ private UserAdmin externalRoles;
+
+ // Transaction
+ public DirectoryUserAdmin(URI uriArg, Dictionary<String, ?> props) {
+ this(uriArg, props, false);
+ }
+
+ public DirectoryUserAdmin(URI uriArg, Dictionary<String, ?> props, boolean scoped) {
+ super(uriArg, props, scoped);
+ }
+
+ public DirectoryUserAdmin(Dictionary<String, ?> props) {
+ this(null, props);
+ }
+
+ /*
+ * ABSTRACT METHODS
+ */
+
+ protected Optional<DirectoryUserAdmin> scope(User user) {
+ if (getDirectoryDao() instanceof LdapDao) {
+ return scopeLdap(user);
+ } else if (getDirectoryDao() instanceof LdifDao) {
+ return scopeLdif(user);
+ } else {
+ throw new IllegalStateException("Unsupported DAO " + getDirectoryDao().getClass());
+ }
+ }
+
+ protected Optional<DirectoryUserAdmin> scopeLdap(User user) {
+ Dictionary<String, Object> credentials = user.getCredentials();
+ String username = (String) credentials.get(SHARED_STATE_USERNAME);
+ if (username == null)
+ username = user.getName();
+ Dictionary<String, Object> properties = cloneConfigProperties();
+ properties.put(Context.SECURITY_PRINCIPAL, username.toString());
+ Object pwdCred = credentials.get(SHARED_STATE_PASSWORD);
+ byte[] pwd = (byte[]) pwdCred;
+ if (pwd != null) {
+ char[] password = DirectoryDigestUtils.bytesToChars(pwd);
+ properties.put(Context.SECURITY_CREDENTIALS, new String(password));
+ } else {
+ properties.put(Context.SECURITY_AUTHENTICATION, "GSSAPI");
+ }
+ DirectoryUserAdmin scopedDirectory = new DirectoryUserAdmin(null, properties, true);
+ scopedDirectory.init();
+ // check connection
+ if (!scopedDirectory.getDirectoryDao().checkConnection())
+ return Optional.empty();
+ return Optional.of(scopedDirectory);
+ }
+
+ protected Optional<DirectoryUserAdmin> scopeLdif(User user) {
+ Dictionary<String, Object> credentials = user.getCredentials();
+ String username = (String) credentials.get(SHARED_STATE_USERNAME);
+ if (username == null)
+ username = user.getName();
+ Object pwdCred = credentials.get(SHARED_STATE_PASSWORD);
+ byte[] pwd = (byte[]) pwdCred;
+ if (pwd != null) {
+ char[] password = DirectoryDigestUtils.bytesToChars(pwd);
+ User directoryUser = (User) getRole(username);
+ if (!directoryUser.hasCredential(null, password))
+ throw new IllegalStateException("Invalid credentials");
+ } else {
+ throw new IllegalStateException("Password is required");
+ }
+ Dictionary<String, Object> properties = cloneConfigProperties();
+ properties.put(DirectoryConf.readOnly.name(), "true");
+ DirectoryUserAdmin scopedUserAdmin = new DirectoryUserAdmin(null, properties, true);
+ // FIXME do it better
+ ((LdifDao) getDirectoryDao()).scope((LdifDao) scopedUserAdmin.getDirectoryDao());
+ // no need to check authentication
+ scopedUserAdmin.init();
+ return Optional.of(scopedUserAdmin);
+ }
+
+ @Override
+ public String getRolePath(Role role) {
+ return nameToRelativePath(LdapNameUtils.toLdapName(role.getName()));
+ }
+
+ @Override
+ public String getRoleSimpleName(Role role) {
+ LdapName dn = LdapNameUtils.toLdapName(role.getName());
+ String name = LdapNameUtils.getLastRdnValue(dn);
+ return name;
+ }
+
+ @Override
+ public Role getRoleByPath(String path) {
+ LdapEntry entry = doGetRole(pathToName(path));
+ if (!(entry instanceof Role)) {
+ return null;
+// throw new IllegalStateException("Path must be a UserAdmin Role.");
+ } else {
+ return (Role) entry;
+ }
+ }
+
+ protected List<Role> getAllRoles(CmsUser user) {
+ List<Role> allRoles = new ArrayList<Role>();
+ if (user != null) {
+ collectRoles((LdapEntry) user, allRoles);
+ allRoles.add(user);
+ } else
+ collectAnonymousRoles(allRoles);
+ return allRoles;
+ }
+
+ private void collectRoles(LdapEntry user, List<Role> allRoles) {
+ List<LdapEntry> allEntries = new ArrayList<>();
+ LdapEntry entry = user;
+ collectGroups(entry, allEntries);
+ for (LdapEntry e : allEntries) {
+ if (e instanceof Role)
+ allRoles.add((Role) e);
+ }
+ }
+
+ private void collectAnonymousRoles(List<Role> allRoles) {
+ // TODO gather anonymous roles
+ }
+
+ // USER ADMIN
+ @Override
+ public Role getRole(String name) {
+ return (Role) doGetRole(toLdapName(name));
+ }
+
+ @Override
+ public Role[] getRoles(String filter) throws InvalidSyntaxException {
+ List<? extends Role> res = getRoles(getBaseDn(), filter, true);
+ return res.toArray(new Role[res.size()]);
+ }
+
+ List<CmsUser> getRoles(LdapName searchBase, String filter, boolean deep) throws InvalidSyntaxException {
+ LdapEntryWorkingCopy wc = getWorkingCopy();
+// Filter f = filter != null ? FrameworkUtil.createFilter(filter) : null;
+ List<LdapEntry> searchRes = getDirectoryDao().doGetEntries(searchBase, filter, deep);
+ List<CmsUser> res = new ArrayList<>();
+ for (LdapEntry entry : searchRes)
+ res.add((CmsUser) entry);
+ if (wc != null) {
+ for (Iterator<CmsUser> it = res.iterator(); it.hasNext();) {
+ CmsUser user = (CmsUser) it.next();
+ LdapName dn = LdapNameUtils.toLdapName(user.getName());
+ if (wc.getDeletedData().containsKey(dn))
+ it.remove();
+ }
+ Filter f = filter != null ? FrameworkUtil.createFilter(filter) : null;
+ for (LdapEntry ldapEntry : wc.getNewData().values()) {
+ CmsUser user = (CmsUser) ldapEntry;
+ if (f == null || f.match(user.getProperties()))
+ res.add(user);
+ }
+ // no need to check modified users,
+ // since doGetRoles was already based on the modified attributes
+ }
+ return res;
+ }
+
+ @Override
+ public User getUser(String key, String value) {
+ // TODO check value null or empty
+ List<CmsUser> collectedUsers = new ArrayList<CmsUser>();
+ if (key != null) {
+ doGetUser(key, value, collectedUsers);
+ } else {
+ throw new IllegalArgumentException("Key cannot be null");
+ }
+
+ if (collectedUsers.size() == 1) {
+ return collectedUsers.get(0);
+ } else if (collectedUsers.size() > 1) {
+ // log.warn(collectedUsers.size() + " users for " + (key != null ? key + "=" :
+ // "") + value);
+ }
+ return null;
+ }
+
+ protected void doGetUser(String key, String value, List<CmsUser> collectedUsers) {
+ String f = "(" + key + "=" + value + ")";
+ List<LdapEntry> users = getDirectoryDao().doGetEntries(getBaseDn(), f, true);
+ for (LdapEntry entry : users)
+ collectedUsers.add((CmsUser) entry);
+ }
+
+ @Override
+ public Authorization getAuthorization(User user) {
+ if (user == null) {// anonymous
+ return new LdifAuthorization(user, getAllRoles(null));
+ }
+ LdapName userName = toLdapName(user.getName());
+ if (isExternal(userName) && user instanceof LdapEntry) {
+ List<Role> allRoles = new ArrayList<Role>();
+ collectRoles((LdapEntry) user, allRoles);
+ return new LdifAuthorization(user, allRoles);
+ } else {
+
+ Subject currentSubject = CurrentSubject.current();
+ if (currentSubject != null //
+ && getRealm().isPresent() //
+ && !currentSubject.getPrivateCredentials(Authorization.class).isEmpty() //
+ && !currentSubject.getPrivateCredentials(KerberosTicket.class).isEmpty()) //
+ {
+ // TODO not only Kerberos but also bind scope with kept password ?
+ Authorization auth = currentSubject.getPrivateCredentials(Authorization.class).iterator().next();
+ // bind with authenticating user
+ DirectoryUserAdmin scopedUserAdmin = CurrentSubject.callAs(currentSubject, () -> {
+ return scope(new AuthenticatingUser(auth.getName(), new Hashtable<>())).orElseThrow();
+ });
+ return getAuthorizationFromScoped(scopedUserAdmin, user);
+ }
+
+ if (user instanceof CmsUser) {
+ return new LdifAuthorization(user, getAllRoles((CmsUser) user));
+ } else {
+ // bind with authenticating user
+ DirectoryUserAdmin scopedUserAdmin = scope(user).orElseThrow();
+ return getAuthorizationFromScoped(scopedUserAdmin, user);
+ }
+ }
+ }
+
+ private Authorization getAuthorizationFromScoped(DirectoryUserAdmin scopedUserAdmin, User user) {
+ try {
+ CmsUser directoryUser = (CmsUser) scopedUserAdmin.getRole(user.getName());
+ if (directoryUser == null)
+ throw new IllegalStateException("No scoped user found for " + user);
+ LdifAuthorization authorization = new LdifAuthorization(directoryUser,
+ scopedUserAdmin.getAllRoles(directoryUser));
+ return authorization;
+ } finally {
+ scopedUserAdmin.destroy();
+ }
+ }
+
+ @Override
+ public Role createRole(String name, int type) {
+ checkEdit();
+ LdapEntryWorkingCopy wc = getWorkingCopy();
+ LdapName dn = toLdapName(name);
+ if ((getDirectoryDao().entryExists(dn) && !wc.getDeletedData().containsKey(dn))
+ || wc.getNewData().containsKey(dn))
+ throw new IllegalArgumentException("Already a role " + name);
+ BasicAttributes attrs = new BasicAttributes(true);
+ // attrs.put(LdifName.dn.name(), dn.toString());
+ Rdn nameRdn = dn.getRdn(dn.size() - 1);
+ // TODO deal with multiple attr RDN
+ attrs.put(nameRdn.getType(), nameRdn.getValue());
+ if (wc.getDeletedData().containsKey(dn)) {
+ wc.getDeletedData().remove(dn);
+ wc.getModifiedData().put(dn, attrs);
+ return getRole(name);
+ } else {
+ wc.getModifiedData().put(dn, attrs);
+ LdapEntry newRole = doCreateRole(dn, type, attrs);
+ wc.getNewData().put(dn, newRole);
+ return (Role) newRole;
+ }
+ }
+
+ private LdapEntry doCreateRole(LdapName dn, int type, Attributes attrs) {
+ LdapEntry newRole;
+ BasicAttribute objClass = new BasicAttribute(objectClass.name());
+ if (type == Role.USER) {
+ String userObjClass = getUserObjectClass();
+ objClass.add(userObjClass);
+ if (inetOrgPerson.name().equals(userObjClass)) {
+ objClass.add(organizationalPerson.name());
+ objClass.add(person.name());
+ } else if (organizationalPerson.name().equals(userObjClass)) {
+ objClass.add(person.name());
+ }
+ objClass.add(top.name());
+ objClass.add(extensibleObject.name());
+ attrs.put(objClass);
+ newRole = newUser(dn);
+ } else if (type == Role.GROUP) {
+ String groupObjClass = getGroupObjectClass();
+ objClass.add(groupObjClass);
+ // objClass.add(LdifName.extensibleObject.name());
+ objClass.add(top.name());
+ attrs.put(objClass);
+ newRole = newGroup(dn);
+ } else
+ throw new IllegalArgumentException("Unsupported type " + type);
+ return newRole;
+ }
+
+ @Override
+ public boolean removeRole(String name) {
+ return removeEntry(LdapNameUtils.toLdapName(name));
+ }
+
+ /*
+ * HIERARCHY
+ */
+ @Override
+ public HierarchyUnit getHierarchyUnit(Role role) {
+ LdapName dn = LdapNameUtils.toLdapName(role.getName());
+ LdapName huDn = LdapNameUtils.getParent(dn);
+ HierarchyUnit hierarchyUnit = getDirectoryDao().doGetHierarchyUnit(huDn);
+ if (hierarchyUnit == null)
+ throw new IllegalStateException("No hierarchy unit found for " + role);
+ return hierarchyUnit;
+ }
+
+ @Override
+ public Iterable<? extends Role> getHierarchyUnitRoles(HierarchyUnit hierarchyUnit, String filter, boolean deep) {
+ LdapName dn = LdapNameUtils.toLdapName(hierarchyUnit.getBase());
+ try {
+ return getRoles(dn, filter, deep);
+ } catch (InvalidSyntaxException e) {
+ throw new IllegalArgumentException("Cannot filter " + filter + " " + dn, e);
+ }
+ }
+
+ /*
+ * ROLES CREATION
+ */
+ protected LdapEntry newUser(LdapName name) {
+ // TODO support devices, applications, etc.
+ return new LdifUser(this, name);
+ }
+
+ protected LdapEntry newGroup(LdapName name) {
+ return new LdifGroup(this, name);
+
+ }
+
+ // GETTERS
+ protected UserAdmin getExternalRoles() {
+ return externalRoles;
+ }
+
+ public void setExternalRoles(UserAdmin externalRoles) {
+ this.externalRoles = externalRoles;
+ }
+
+ /*
+ * STATIC UTILITIES
+ */
+ static LdapName toLdapName(String name) {
+ try {
+ return new LdapName(name);
+ } catch (InvalidNameException e) {
+ throw new IllegalArgumentException(name + " is not an LDAP name", e);
+ }
+ }
+}
--- /dev/null
+package org.argeo.cms.osgi.useradmin;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Dictionary;
+import java.util.List;
+
+import org.argeo.api.acr.ldap.LdapAttr;
+import org.osgi.service.useradmin.Authorization;
+import org.osgi.service.useradmin.Role;
+import org.osgi.service.useradmin.User;
+
+/** Basic authorization. */
+class LdifAuthorization implements Authorization {
+ private final String name;
+ private final String displayName;
+ private final List<String> allRoles;
+
+ public LdifAuthorization(User user, List<Role> allRoles) {
+ if (user == null) {
+ this.name = null;
+ this.displayName = "anonymous";
+ } else {
+ this.name = user.getName();
+ this.displayName = extractDisplayName(user);
+ }
+ // roles
+ String[] roles = new String[allRoles.size()];
+ for (int i = 0; i < allRoles.size(); i++) {
+ roles[i] = allRoles.get(i).getName();
+ }
+ this.allRoles = Collections.unmodifiableList(Arrays.asList(roles));
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public boolean hasRole(String name) {
+ return allRoles.contains(name);
+ }
+
+ @Override
+ public String[] getRoles() {
+ return allRoles.toArray(new String[allRoles.size()]);
+ }
+
+ @Override
+ public int hashCode() {
+ if (name == null)
+ return super.hashCode();
+ return name.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof Authorization))
+ return false;
+ Authorization that = (Authorization) obj;
+ if (name == null)
+ return that.getName() == null;
+ return name.equals(that.getName());
+ }
+
+ @Override
+ public String toString() {
+ return displayName;
+ }
+
+ final static String extractDisplayName(User user) {
+ Dictionary<String, Object> props = user.getProperties();
+ Object displayName = props.get(LdapAttr.displayName.name());
+ if (displayName == null)
+ displayName = props.get(LdapAttr.cn.name());
+ if (displayName == null)
+ displayName = props.get(LdapAttr.uid.name());
+ if (displayName == null)
+ displayName = user.getName();
+ if (displayName == null)
+ throw new IllegalStateException("Cannot set display name for " + user);
+ return displayName.toString();
+ }
+}
--- /dev/null
+package org.argeo.cms.osgi.useradmin;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.naming.InvalidNameException;
+import javax.naming.directory.Attribute;
+import javax.naming.ldap.LdapName;
+
+import org.argeo.api.cms.directory.CmsGroup;
+import org.argeo.cms.directory.ldap.AbstractLdapDirectory;
+import org.osgi.service.useradmin.Role;
+
+/** Directory group implementation */
+class LdifGroup extends LdifUser implements CmsGroup {
+ private final String memberAttributeId;
+
+ LdifGroup(AbstractLdapDirectory userAdmin, LdapName dn) {
+ super(userAdmin, dn);
+ memberAttributeId = userAdmin.getMemberAttributeId();
+ }
+
+ @Override
+ public boolean addMember(Role role) {
+ try {
+ Role foundRole = findRole(new LdapName(role.getName()));
+ if (foundRole == null)
+ throw new UnsupportedOperationException(
+ "Adding role " + role.getName() + " is unsupported within this context.");
+ } catch (InvalidNameException e) {
+ throw new IllegalArgumentException("Role name" + role.getName() + " is badly formatted");
+ }
+
+ getUserAdmin().checkEdit();
+ if (!isEditing())
+ startEditing();
+
+ Attribute member = getAttributes().get(memberAttributeId);
+ if (member != null) {
+ if (member.contains(role.getName()))
+ return false;
+ else
+ member.add(role.getName());
+ } else
+ getAttributes().put(memberAttributeId, role.getName());
+ return true;
+ }
+
+ @Override
+ public boolean addRequiredMember(Role role) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean removeMember(Role role) {
+ getUserAdmin().checkEdit();
+ if (!isEditing())
+ startEditing();
+
+ Attribute member = getAttributes().get(memberAttributeId);
+ if (member != null) {
+ if (!member.contains(role.getName()))
+ return false;
+ member.remove(role.getName());
+ return true;
+ } else
+ return false;
+ }
+
+ @Override
+ public Role[] getMembers() {
+ List<Role> directMembers = new ArrayList<Role>();
+ for (LdapName ldapName : getReferences(memberAttributeId)) {
+ Role role = findRole(ldapName);
+ if (role == null) {
+ throw new IllegalStateException("Role " + ldapName + " not found.");
+ }
+ directMembers.add(role);
+ }
+ return directMembers.toArray(new Role[directMembers.size()]);
+ }
+
+ /**
+ * Whether a role with this name can be found from this context.
+ *
+ * @return The related {@link Role} or <code>null</code>.
+ */
+ protected Role findRole(LdapName ldapName) {
+ Role role = getUserAdmin().getRole(ldapName.toString());
+ if (role == null) {
+ if (getUserAdmin().getExternalRoles() != null)
+ role = getUserAdmin().getExternalRoles().getRole(ldapName.toString());
+ }
+ return role;
+ }
+
+// @Override
+// public List<LdapName> getMemberNames() {
+// Attribute memberAttribute = getAttributes().get(memberAttributeId);
+// if (memberAttribute == null)
+// return new ArrayList<LdapName>();
+// try {
+// List<LdapName> roles = new ArrayList<LdapName>();
+// NamingEnumeration<?> values = memberAttribute.getAll();
+// while (values.hasMore()) {
+// LdapName dn = new LdapName(values.next().toString());
+// roles.add(dn);
+// }
+// return roles;
+// } catch (NamingException e) {
+// throw new IllegalStateException("Cannot get members", e);
+// }
+// }
+
+ @Override
+ public Role[] getRequiredMembers() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int getType() {
+ return GROUP;
+ }
+
+ protected DirectoryUserAdmin getUserAdmin() {
+ return (DirectoryUserAdmin) getDirectory();
+ }
+}
--- /dev/null
+package org.argeo.cms.osgi.useradmin;
+
+import javax.naming.ldap.LdapName;
+
+import org.argeo.api.cms.directory.CmsUser;
+import org.argeo.cms.directory.ldap.AbstractLdapDirectory;
+import org.argeo.cms.directory.ldap.DefaultLdapEntry;
+
+/** Directory user implementation */
+class LdifUser extends DefaultLdapEntry implements CmsUser {
+ LdifUser(AbstractLdapDirectory userAdmin, LdapName dn) {
+ super(userAdmin, dn);
+ }
+
+ @Override
+ public String getName() {
+ return getDn().toString();
+ }
+
+ @Override
+ public int getType() {
+ return USER;
+ }
+
+}
--- /dev/null
+package org.argeo.cms.osgi.useradmin;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.naming.NameNotFoundException;
+import javax.naming.NamingException;
+import javax.naming.directory.Attributes;
+import javax.naming.ldap.LdapName;
+
+import org.argeo.api.acr.ldap.LdapAttr;
+import org.argeo.api.cms.directory.HierarchyUnit;
+import org.argeo.cms.directory.ldap.AbstractLdapDirectory;
+import org.argeo.cms.directory.ldap.AbstractLdapDirectoryDao;
+import org.argeo.cms.directory.ldap.LdapEntry;
+import org.argeo.cms.directory.ldap.LdapEntryWorkingCopy;
+
+/** Pseudo user directory to be used when logging in as OS user. */
+public class OsUserDirectory extends AbstractLdapDirectoryDao {
+ private final String osUsername = System.getProperty("user.name");
+ private final LdapName osUserDn;
+ private final LdapEntry osUser;
+
+ public OsUserDirectory(AbstractLdapDirectory directory) {
+ super(directory);
+ try {
+ osUserDn = new LdapName(LdapAttr.uid.name() + "=" + osUsername + "," + directory.getUserBaseRdn() + ","
+ + directory.getBaseDn());
+// Attributes attributes = new BasicAttributes();
+// attributes.put(LdapAttrs.uid.name(), osUsername);
+ osUser = newUser(osUserDn);
+ } catch (NamingException e) {
+ throw new IllegalStateException("Cannot create system user", e);
+ }
+ }
+
+ @Override
+ public List<LdapName> getDirectGroups(LdapName dn) {
+ return new ArrayList<>();
+ }
+
+ @Override
+ public boolean entryExists(LdapName dn) {
+ return osUserDn.equals(dn);
+ }
+
+ @Override
+ public boolean checkConnection() {
+ return true;
+ }
+
+ @Override
+ public LdapEntry doGetEntry(LdapName key) throws NameNotFoundException {
+ if (osUserDn.equals(key))
+ return osUser;
+ else
+ throw new NameNotFoundException("Not an OS role");
+ }
+
+ @Override
+ public List<LdapEntry> doGetEntries(LdapName searchBase, String f, boolean deep) {
+ List<LdapEntry> res = new ArrayList<>();
+// if (f == null || f.match(osUser.getProperties()))
+ res.add(osUser);
+ return res;
+ }
+
+ @Override
+ public HierarchyUnit doGetHierarchyUnit(LdapName dn) {
+ return null;
+ }
+
+ @Override
+ public Iterable<HierarchyUnit> doGetDirectHierarchyUnits(LdapName searchBase, boolean functionalOnly) {
+ return new ArrayList<>();
+ }
+
+ public void prepare(LdapEntryWorkingCopy wc) {
+
+ }
+
+ public void commit(LdapEntryWorkingCopy wc) {
+
+ }
+
+ public void rollback(LdapEntryWorkingCopy wc) {
+
+ }
+
+ @Override
+ public void init() {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void destroy() {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public Attributes doGetAttributes(LdapName name) {
+ try {
+ return doGetEntry(name).getAttributes();
+ } catch (NameNotFoundException e) {
+ throw new IllegalStateException(name + " doe not exist in " + getDirectory().getBaseDn(), e);
+ }
+ }
+
+}
--- /dev/null
+package org.argeo.cms.osgi.useradmin;
+
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.security.NoSuchAlgorithmException;
+import java.security.URIParameter;
+
+import javax.security.auth.Subject;
+import javax.security.auth.login.Configuration;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+
+/** Log in based on JDK-provided OS integration. */
+public class OsUserUtils {
+ private final static String LOGIN_CONTEXT_USER_NIX = "USER_NIX";
+ private final static String LOGIN_CONTEXT_USER_NT = "USER_NT";
+
+ public static String getOsUsername() {
+ return System.getProperty("user.name");
+ }
+
+ public static LoginContext loginAsSystemUser(Subject subject) {
+ try {
+ URL jaasConfigurationUrl = OsUserUtils.class.getClassLoader()
+ .getResource("org/argeo/osgi/useradmin/jaas-os.cfg");
+ URIParameter uriParameter = new URIParameter(jaasConfigurationUrl.toURI());
+ Configuration jaasConfiguration = Configuration.getInstance("JavaLoginConfig", uriParameter);
+ LoginContext lc = new LoginContext(isWindows() ? LOGIN_CONTEXT_USER_NT : LOGIN_CONTEXT_USER_NIX, subject,
+ null, jaasConfiguration);
+ lc.login();
+ return lc;
+ } catch (URISyntaxException | NoSuchAlgorithmException | LoginException e) {
+ throw new RuntimeException("Cannot login as system user", e);
+ }
+ }
+
+ public static void main(String args[]) {
+ Subject subject = new Subject();
+ LoginContext loginContext = loginAsSystemUser(subject);
+ System.out.println(subject);
+ try {
+ loginContext.logout();
+ } catch (LoginException e) {
+ // silent
+ }
+ }
+
+ private static boolean isWindows() {
+ return System.getProperty("os.name").startsWith("Windows");
+ }
+
+ private OsUserUtils() {
+ }
+}
--- /dev/null
+package org.argeo.cms.osgi.useradmin;
+
+import static org.argeo.api.acr.ldap.LdapAttr.description;
+import static org.argeo.api.acr.ldap.LdapAttr.owner;
+
+import java.security.Principal;
+import java.time.Instant;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.naming.InvalidNameException;
+import javax.naming.ldap.LdapName;
+import javax.security.auth.Subject;
+
+import org.argeo.api.acr.ldap.NamingUtils;
+import org.osgi.service.useradmin.Group;
+
+/**
+ * Canonically implements the Argeo token conventions.
+ */
+public class TokenUtils {
+ public static Set<String> tokensUsed(Subject subject, String tokensBaseDn) {
+ Set<String> res = new HashSet<>();
+ for (Principal principal : subject.getPrincipals()) {
+ String name = principal.getName();
+ if (name.endsWith(tokensBaseDn)) {
+ try {
+ LdapName ldapName = new LdapName(name);
+ String token = ldapName.getRdn(ldapName.size()).getValue().toString();
+ res.add(token);
+ } catch (InvalidNameException e) {
+ throw new IllegalArgumentException("Invalid principal " + principal, e);
+ }
+ }
+ }
+ return res;
+ }
+
+ /** The user related to this token group */
+ public static String userDn(Group tokenGroup) {
+ return (String) tokenGroup.getProperties().get(owner.name());
+ }
+
+ public static boolean isExpired(Group tokenGroup) {
+ return isExpired(tokenGroup, Instant.now());
+
+ }
+
+ public static boolean isExpired(Group tokenGroup, Instant instant) {
+ String expiryDateStr = (String) tokenGroup.getProperties().get(description.name());
+ if (expiryDateStr != null) {
+ Instant expiryDate = NamingUtils.ldapDateToInstant(expiryDateStr);
+ if (expiryDate.isBefore(instant)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+// private final String token;
+//
+// public TokenUtils(String token) {
+// this.token = token;
+// }
+//
+// public String getToken() {
+// return token;
+// }
+//
+// @Override
+// public int hashCode() {
+// return token.hashCode();
+// }
+//
+// @Override
+// public boolean equals(Object obj) {
+// if ((obj instanceof TokenUtils) && ((TokenUtils) obj).token.equals(token))
+// return true;
+// return false;
+// }
+//
+// @Override
+// public String toString() {
+// return "Token #" + hashCode();
+// }
+
+}
--- /dev/null
+USER_NIX {
+ com.sun.security.auth.module.UnixLoginModule requisite;
+};
+
+USER_NT {
+ com.sun.security.auth.module.NTLoginModule requisite;
+};
+
--- /dev/null
+/** LDAP and LDIF based OSGi useradmin implementation. */
+package org.argeo.cms.osgi.useradmin;
\ No newline at end of file
--- /dev/null
+package org.argeo.cms.runtime;
+
+import java.net.InetAddress;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.UnknownHostException;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+
+import org.argeo.api.acr.ldap.NamingUtils;
+import org.argeo.api.cms.directory.DirectoryDigestUtils;
+import org.argeo.cms.directory.ldap.IpaUtils;
+
+/** Properties used to configure user admins. */
+public enum DirectoryConf {
+ /** Base DN (cannot be configured externally) */
+ baseDn(null),
+
+ /** URI of the underlying resource (cannot be configured externally) */
+ uri(null),
+
+ /** User objectClass */
+ userObjectClass("inetOrgPerson"),
+
+ /** Relative base DN for users */
+ userBase("ou=People"),
+
+ /** Groups objectClass */
+ groupObjectClass("groupOfNames"),
+
+ /** Relative base DN for users */
+ groupBase("ou=Groups"),
+
+ /** Relative base DN for users */
+ systemRoleBase("ou=Roles"),
+
+ /** Read-only source */
+ readOnly(null),
+
+ /** Disabled source */
+ disabled(null),
+
+ /** Authentication realm */
+ realm(null),
+
+ /** Override all passwords with this value (typically for testing purposes) */
+ forcedPassword(null);
+
+ public final static String FACTORY_PID = "org.argeo.osgi.useradmin.config";
+
+ public final static String SCHEME_LDAP = "ldap";
+ public final static String SCHEME_LDAPS = "ldaps";
+ public final static String SCHEME_FILE = "file";
+ public final static String SCHEME_OS = "os";
+ public final static String SCHEME_IPA = "ipa";
+
+ private final static String SECURITY_PRINCIPAL = "java.naming.security.principal";
+ private final static String SECURITY_CREDENTIALS = "java.naming.security.credentials";
+
+ /** The default value. */
+ private Object def;
+
+ DirectoryConf(Object def) {
+ this.def = def;
+ }
+
+ public Object getDefault() {
+ return def;
+ }
+
+ /**
+ * For use as Java property.
+ *
+ * @deprecated use {@link #name()} instead
+ */
+ @Deprecated
+ public String property() {
+ return name();
+ }
+
+ public String getValue(Dictionary<String, ?> properties) {
+ Object res = getRawValue(properties);
+ if (res == null)
+ return null;
+ return res.toString();
+ }
+
+ @SuppressWarnings("unchecked")
+ public <T> T getRawValue(Dictionary<String, ?> properties) {
+ Object res = properties.get(name());
+ if (res == null)
+ res = getDefault();
+ return (T) res;
+ }
+
+ /** @deprecated use {@link #valueOf(String)} instead */
+ @Deprecated
+ public static DirectoryConf local(String property) {
+ return DirectoryConf.valueOf(property);
+ }
+
+ /** Hides host and credentials. */
+ public static URI propertiesAsUri(Dictionary<String, ?> properties) {
+ StringBuilder query = new StringBuilder();
+
+ boolean first = true;
+// for (Enumeration<String> keys = properties.keys(); keys.hasMoreElements();) {
+// String key = keys.nextElement();
+// // TODO clarify which keys are relevant (list only the enum?)
+// if (!key.equals("service.factoryPid") && !key.equals("cn") && !key.equals("dn")
+// && !key.equals(Constants.SERVICE_PID) && !key.startsWith("java") && !key.equals(baseDn.name())
+// && !key.equals(uri.name()) && !key.equals(Constants.OBJECTCLASS)
+// && !key.equals(Constants.SERVICE_ID) && !key.equals("bundle.id")) {
+// if (first)
+// first = false;
+// else
+// query.append('&');
+// query.append(valueOf(key).name());
+// query.append('=').append(properties.get(key).toString());
+// }
+// }
+
+ keys: for (DirectoryConf key : DirectoryConf.values()) {
+ if (key.equals(baseDn) || key.equals(uri))
+ continue keys;
+ Object value = properties.get(key.name());
+ if (value == null)
+ continue keys;
+ if (first)
+ first = false;
+ else
+ query.append('&');
+ query.append(key.name());
+ query.append('=').append(value.toString());
+
+ }
+
+ Object bDnObj = properties.get(baseDn.name());
+ String bDn = bDnObj != null ? bDnObj.toString() : null;
+ try {
+ return new URI(null, null, bDn != null ? '/' + bDn : null, query.length() != 0 ? query.toString() : null,
+ null);
+ } catch (URISyntaxException e) {
+ throw new IllegalArgumentException("Cannot create URI from properties", e);
+ }
+ }
+
+ public static Dictionary<String, Object> uriAsProperties(String uriStr) {
+ try {
+ Hashtable<String, Object> res = new Hashtable<String, Object>();
+ URI u = new URI(uriStr);
+ String scheme = u.getScheme();
+ if (scheme != null && scheme.equals(SCHEME_IPA)) {
+ return IpaUtils.convertIpaUri(u);
+// scheme = u.getScheme();
+ }
+ String path = u.getPath();
+ // base DN
+ String bDn = path.substring(path.lastIndexOf('/') + 1, path.length());
+ if (bDn.equals("") && SCHEME_OS.equals(scheme)) {
+ bDn = getBaseDnFromHostname();
+ }
+
+ if (bDn.endsWith(".ldif"))
+ bDn = bDn.substring(0, bDn.length() - ".ldif".length());
+
+ // Normalize base DN as LDAP name
+// bDn = new LdapName(bDn).toString();
+
+ String principal = null;
+ String credentials = null;
+ if (scheme != null)
+ if (scheme.equals(SCHEME_LDAP) || scheme.equals(SCHEME_LDAPS)) {
+ // TODO additional checks
+ if (u.getUserInfo() != null) {
+ String[] userInfo = u.getUserInfo().split(":");
+ principal = userInfo.length > 0 ? userInfo[0] : null;
+ credentials = userInfo.length > 1 ? userInfo[1] : null;
+ }
+ } else if (scheme.equals(SCHEME_FILE)) {
+ } else if (scheme.equals(SCHEME_IPA)) {
+ } else if (scheme.equals(SCHEME_OS)) {
+ } else
+ throw new IllegalArgumentException("Unsupported scheme " + scheme);
+ Map<String, List<String>> query = NamingUtils.queryToMap(u);
+ for (String key : query.keySet()) {
+ DirectoryConf ldapProp = DirectoryConf.valueOf(key);
+ List<String> values = query.get(key);
+ if (values.size() == 1) {
+ res.put(ldapProp.name(), values.get(0));
+ } else {
+ throw new IllegalArgumentException("Only single values are supported");
+ }
+ }
+ res.put(baseDn.name(), bDn);
+ if (SCHEME_OS.equals(scheme))
+ res.put(readOnly.name(), "true");
+ if (principal != null)
+ res.put(SECURITY_PRINCIPAL, principal);
+ if (credentials != null)
+ res.put(SECURITY_CREDENTIALS, credentials);
+ if (scheme != null) {// relative URIs are dealt with externally
+ if (SCHEME_OS.equals(scheme)) {
+ res.put(uri.name(), SCHEME_OS + ":///");
+ } else {
+ URI bareUri = new URI(scheme, null, u.getHost(), u.getPort(),
+ scheme.equals(SCHEME_FILE) ? u.getPath() : null, null, null);
+ res.put(uri.name(), bareUri.toString());
+ }
+ }
+ return res;
+ } catch (URISyntaxException e) {
+ throw new IllegalArgumentException("Cannot convert " + uri + " to properties", e);
+ }
+ }
+
+ private static String getBaseDnFromHostname() {
+ String hostname;
+ try {
+ hostname = InetAddress.getLocalHost().getHostName();
+ } catch (UnknownHostException e) {
+ hostname = "localhost.localdomain";
+ }
+ int dotIdx = hostname.indexOf('.');
+ if (dotIdx >= 0) {
+ String domain = hostname.substring(dotIdx + 1, hostname.length());
+ String bDn = ("." + domain).replaceAll("\\.", ",dc=");
+ bDn = bDn.substring(1, bDn.length());
+ return bDn;
+ } else {
+ return "dc=" + hostname;
+ }
+ }
+
+ /**
+ * Hash the base DN in order to have a deterministic string to be used as a cn
+ * for the underlying user directory.
+ */
+ public static String baseDnHash(Dictionary<String, Object> properties) {
+ String bDn = (String) properties.get(baseDn.name());
+ if (bDn == null)
+ throw new IllegalStateException("No baseDn in " + properties);
+ return DirectoryDigestUtils.sha1str(bDn);
+ }
+}
--- /dev/null
+package org.argeo.cms.runtime;
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.concurrent.CompletableFuture;
+
+import org.argeo.api.acr.ContentRepository;
+import org.argeo.api.acr.spi.ProvidedRepository;
+import org.argeo.api.cms.CmsContext;
+import org.argeo.api.cms.CmsDeployment;
+import org.argeo.api.cms.CmsState;
+import org.argeo.api.cms.directory.CmsUserManager;
+import org.argeo.api.cms.transaction.SimpleTransactionManager;
+import org.argeo.api.cms.transaction.WorkControl;
+import org.argeo.api.cms.transaction.WorkTransaction;
+import org.argeo.api.register.Component;
+import org.argeo.api.register.ComponentRegister;
+import org.argeo.api.register.SimpleRegister;
+import org.argeo.api.uuid.UuidFactory;
+import org.argeo.cms.acr.CmsUuidFactory;
+import org.argeo.cms.internal.runtime.CmsContextImpl;
+import org.argeo.cms.internal.runtime.CmsDeploymentImpl;
+import org.argeo.cms.internal.runtime.CmsStateImpl;
+import org.argeo.cms.internal.runtime.CmsUserAdmin;
+import org.argeo.cms.internal.runtime.CmsUserManagerImpl;
+import org.argeo.cms.internal.runtime.DeployedContentRepository;
+import org.osgi.service.useradmin.UserAdmin;
+
+/**
+ * A CMS assembly which is programmatically defined, as an alternative to OSGi
+ * deployment. Useful for testing or AOT compilation.
+ */
+public class StaticCms {
+ private SimpleRegister register = new SimpleRegister();
+
+ private CompletableFuture<Void> stopped = new CompletableFuture<Void>();
+
+ public void start() {
+ // UID factory
+ CmsUuidFactory uuidFactory = new CmsUuidFactory();
+ Component<CmsUuidFactory> uuidFactoryC = new Component.Builder<>(uuidFactory) //
+ .addType(UuidFactory.class) //
+ .build(register);
+
+ // CMS State
+ CmsStateImpl cmsState = new CmsStateImpl();
+ Component<CmsStateImpl> cmsStateC = new Component.Builder<>(cmsState) //
+ .addType(CmsState.class) //
+ .addActivation(cmsState::start) //
+ .addDeactivation(cmsState::stop) //
+ .addDependency(uuidFactoryC.getType(UuidFactory.class), cmsState::setUuidFactory, null) //
+ .build(register);
+
+ // Transaction manager
+ SimpleTransactionManager transactionManager = new SimpleTransactionManager();
+ Component<SimpleTransactionManager> transactionManagerC = new Component.Builder<>(transactionManager) //
+ .addType(WorkControl.class) //
+ .addType(WorkTransaction.class) //
+ .build(register);
+
+ // User Admin
+ CmsUserAdmin userAdmin = new CmsUserAdmin();
+ Component<CmsUserAdmin> userAdminC = new Component.Builder<>(userAdmin) //
+ .addType(UserAdmin.class) //
+ .addActivation(userAdmin::start) //
+ .addDeactivation(userAdmin::stop) //
+ .addDependency(cmsStateC.getType(CmsState.class), userAdmin::setCmsState, null) //
+ .addDependency(transactionManagerC.getType(WorkControl.class), userAdmin::setTransactionManager, null) //
+ .addDependency(transactionManagerC.getType(WorkTransaction.class), userAdmin::setUserTransaction, null) //
+ .build(register);
+
+ // User manager
+ CmsUserManagerImpl userManager = new CmsUserManagerImpl();
+// for (UserDirectory userDirectory : userAdmin.getUserDirectories()) {
+// // FIXME deal with properties
+// userManager.addUserDirectory(userDirectory, new HashMap<>());
+// }
+ Component<CmsUserManagerImpl> userManagerC = new Component.Builder<>(userManager) //
+ .addType(CmsUserManager.class) //
+ .addActivation(userManager::start) //
+ .addDeactivation(userManager::stop) //
+ .addDependency(userAdminC.getType(UserAdmin.class), userManager::setUserAdmin, null) //
+ .addDependency(transactionManagerC.getType(WorkTransaction.class), userManager::setUserTransaction,
+ null) //
+ .build(register);
+
+ // Content Repository
+ DeployedContentRepository contentRepository = new DeployedContentRepository();
+ Component<DeployedContentRepository> contentRepositoryC = new Component.Builder<>(contentRepository) //
+ .addType(ProvidedRepository.class) //
+ .addType(ContentRepository.class) //
+ .addActivation(contentRepository::start) //
+ .addDeactivation(contentRepository::stop) //
+ .addDependency(cmsStateC.getType(CmsState.class), contentRepository::setCmsState, null) //
+ .addDependency(uuidFactoryC.getType(UuidFactory.class), contentRepository::setUuidFactory, null) //
+ .addDependency(userManagerC.getType(CmsUserManager.class), contentRepository::setUserManager, null) //
+ .build(register);
+
+ // CMS Deployment
+ CmsDeploymentImpl cmsDeployment = new CmsDeploymentImpl();
+ Component<CmsDeploymentImpl> cmsDeploymentC = new Component.Builder<>(cmsDeployment) //
+ .addType(CmsDeployment.class) //
+ .addActivation(cmsDeployment::start) //
+ .addDeactivation(cmsDeployment::stop) //
+ .addDependency(cmsStateC.getType(CmsState.class), cmsDeployment::setCmsState, null) //
+// .addDependency(deployConfigC.getType(DeployConfig.class), cmsDeployment::setDeployConfig, null) //
+ .build(register);
+
+ // CMS Context
+ CmsContextImpl cmsContext = new CmsContextImpl();
+ Component<CmsContextImpl> cmsContextC = new Component.Builder<>(cmsContext) //
+ .addType(CmsContext.class) //
+ .addActivation(cmsContext::start) //
+ .addDeactivation(cmsContext::stop) //
+ .addDependency(cmsStateC.getType(CmsState.class), cmsContext::setCmsState, null) //
+ .addDependency(cmsDeploymentC.getType(CmsDeployment.class), cmsContext::setCmsDeployment, null) //
+ .addDependency(userAdminC.getType(UserAdmin.class), cmsContext::setUserAdmin, null) //
+ .addDependency(uuidFactoryC.getType(UuidFactory.class), cmsContext::setUuidFactory, null) //
+// .addDependency(contentRepositoryC.getType(ProvidedRepository.class), cmsContext::setContentRepository,
+// null) //
+ .build(register);
+ assert cmsContextC.get() == cmsContext;
+
+ addComponents(register);
+
+ register.activate();
+
+ postActivation(register);
+ }
+
+ protected void addComponents(ComponentRegister register) {
+
+ }
+
+ protected void postActivation(ComponentRegister register) {
+
+ }
+
+ public ComponentRegister getComponentRegister() {
+ return register;
+ }
+
+ public void stop() {
+ if (register.isActive()) {
+ register.deactivate();
+ }
+ register.clear();
+ stopped.complete(null);
+ }
+
+ public void waitForStop() {
+ stopped.join();
+ }
+
+ public static void main(String[] args) {
+ if (args.length == 0) {
+ System.err.println("Usage: <data path>");
+ System.exit(1);
+ }
+ Path instancePath = Paths.get(args[0]);
+ System.setProperty("osgi.instance.area", instancePath.toUri().toString());
+
+ StaticCms staticCms = new StaticCms();
+ Runtime.getRuntime().addShutdownHook(new Thread(() -> staticCms.stop(), "Static CMS Shutdown"));
+ staticCms.start();
+ staticCms.waitForStop();
+ }
+
+}
+++ /dev/null
-package org.argeo.cms.security;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.CharArrayWriter;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.OutputStreamWriter;
-import java.io.Reader;
-import java.io.Writer;
-import java.security.AccessController;
-import java.security.Provider;
-import java.security.Security;
-import java.util.Arrays;
-import java.util.Iterator;
-
-import javax.crypto.SecretKey;
-import javax.security.auth.Subject;
-import javax.security.auth.callback.Callback;
-import javax.security.auth.callback.CallbackHandler;
-import javax.security.auth.callback.PasswordCallback;
-import javax.security.auth.callback.TextOutputCallback;
-import javax.security.auth.callback.UnsupportedCallbackException;
-import javax.security.auth.login.LoginContext;
-import javax.security.auth.login.LoginException;
-
-import org.apache.commons.io.IOUtils;
-import org.argeo.api.cms.CmsAuth;
-import org.argeo.cms.CmsException;
-
-/** username / password based keyring. TODO internationalize */
-public abstract class AbstractKeyring implements Keyring, CryptoKeyring {
- // public final static String DEFAULT_KEYRING_LOGIN_CONTEXT = "KEYRING";
-
- // private String loginContextName = DEFAULT_KEYRING_LOGIN_CONTEXT;
- private CallbackHandler defaultCallbackHandler;
-
- private String charset = "UTF-8";
-
- /**
- * Default provider is bouncy castle, in order to have consistent behaviour
- * across implementations
- */
- private String securityProviderName = "BC";
-
- /**
- * Whether the keyring has already been created in the past with a master
- * password
- */
- protected abstract Boolean isSetup();
-
- /**
- * Setup the keyring persistently, {@link #isSetup()} must return true
- * afterwards
- */
- protected abstract void setup(char[] password);
-
- /** Populates the key spec callback */
- protected abstract void handleKeySpecCallback(PBEKeySpecCallback pbeCallback);
-
- protected abstract void encrypt(String path, InputStream unencrypted);
-
- protected abstract InputStream decrypt(String path);
-
- /** Triggers lazy initialization */
- protected SecretKey getSecretKey(char[] password) {
- Subject subject = Subject.getSubject(AccessController.getContext());
- // we assume only one secrete key is available
- Iterator<SecretKey> iterator = subject.getPrivateCredentials(SecretKey.class).iterator();
- if (!iterator.hasNext() || password!=null) {// not initialized
- CallbackHandler callbackHandler = password == null ? new KeyringCallbackHandler()
- : new PasswordProvidedCallBackHandler(password);
- ClassLoader currentContextClassLoader = Thread.currentThread().getContextClassLoader();
- Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
- try {
- LoginContext loginContext = new LoginContext(CmsAuth.LOGIN_CONTEXT_KEYRING, subject,
- callbackHandler);
- loginContext.login();
- // FIXME will login even if password is wrong
- iterator = subject.getPrivateCredentials(SecretKey.class).iterator();
- return iterator.next();
- } catch (LoginException e) {
- throw new CmsException("Keyring login failed", e);
- } finally {
- Thread.currentThread().setContextClassLoader(currentContextClassLoader);
- }
-
- } else {
- SecretKey secretKey = iterator.next();
- if (iterator.hasNext())
- throw new CmsException("More than one secret key in private credentials");
- return secretKey;
- }
- }
-
- public InputStream getAsStream(String path) {
- return decrypt(path);
- }
-
- public void set(String path, InputStream in) {
- encrypt(path, in);
- }
-
- public char[] getAsChars(String path) {
- // InputStream in = getAsStream(path);
- // CharArrayWriter writer = null;
- // Reader reader = null;
- try (InputStream in = getAsStream(path);
- CharArrayWriter writer = new CharArrayWriter();
- Reader reader = new InputStreamReader(in, charset);) {
- IOUtils.copy(reader, writer);
- return writer.toCharArray();
- } catch (IOException e) {
- throw new CmsException("Cannot decrypt to char array", e);
- } finally {
- // IOUtils.closeQuietly(reader);
- // IOUtils.closeQuietly(in);
- // IOUtils.closeQuietly(writer);
- }
- }
-
- public void set(String path, char[] arr) {
- // ByteArrayOutputStream out = new ByteArrayOutputStream();
- // ByteArrayInputStream in = null;
- // Writer writer = null;
- try (ByteArrayOutputStream out = new ByteArrayOutputStream();
- Writer writer = new OutputStreamWriter(out, charset);) {
- // writer = new OutputStreamWriter(out, charset);
- writer.write(arr);
- writer.flush();
- // in = new ByteArrayInputStream(out.toByteArray());
- try (ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());) {
- set(path, in);
- }
- } catch (IOException e) {
- throw new CmsException("Cannot encrypt to char array", e);
- } finally {
- // IOUtils.closeQuietly(writer);
- // IOUtils.closeQuietly(out);
- // IOUtils.closeQuietly(in);
- }
- }
-
- public void unlock(char[] password) {
- if (!isSetup())
- setup(password);
- SecretKey secretKey = getSecretKey(password);
- if (secretKey == null)
- throw new CmsException("Could not unlock keyring");
- }
-
- protected Provider getSecurityProvider() {
- return Security.getProvider(securityProviderName);
- }
-
- public void setDefaultCallbackHandler(CallbackHandler defaultCallbackHandler) {
- this.defaultCallbackHandler = defaultCallbackHandler;
- }
-
- public void setCharset(String charset) {
- this.charset = charset;
- }
-
- public void setSecurityProviderName(String securityProviderName) {
- this.securityProviderName = securityProviderName;
- }
-
- // @Deprecated
- // protected static byte[] hash(char[] password, byte[] salt, Integer
- // iterationCount) {
- // ByteArrayOutputStream out = null;
- // OutputStreamWriter writer = null;
- // try {
- // out = new ByteArrayOutputStream();
- // writer = new OutputStreamWriter(out, "UTF-8");
- // writer.write(password);
- // MessageDigest pwDigest = MessageDigest.getInstance("SHA-256");
- // pwDigest.reset();
- // pwDigest.update(salt);
- // byte[] btPass = pwDigest.digest(out.toByteArray());
- // for (int i = 0; i < iterationCount; i++) {
- // pwDigest.reset();
- // btPass = pwDigest.digest(btPass);
- // }
- // return btPass;
- // } catch (Exception e) {
- // throw new CmsException("Cannot hash", e);
- // } finally {
- // IOUtils.closeQuietly(out);
- // IOUtils.closeQuietly(writer);
- // }
- //
- // }
-
- /**
- * Convenience method using the underlying callback to ask for a password
- * (typically used when the password is not saved in the keyring)
- */
- protected char[] ask() {
- PasswordCallback passwordCb = new PasswordCallback("Password", false);
- Callback[] dialogCbs = new Callback[] { passwordCb };
- try {
- defaultCallbackHandler.handle(dialogCbs);
- char[] password = passwordCb.getPassword();
- return password;
- } catch (Exception e) {
- throw new CmsException("Cannot ask for a password", e);
- }
-
- }
-
- class KeyringCallbackHandler implements CallbackHandler {
- public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
- // checks
- if (callbacks.length != 2)
- throw new IllegalArgumentException(
- "Keyring requires 2 and only 2 callbacks: {PasswordCallback,PBEKeySpecCallback}");
- if (!(callbacks[0] instanceof PasswordCallback))
- throw new UnsupportedCallbackException(callbacks[0]);
- if (!(callbacks[1] instanceof PBEKeySpecCallback))
- throw new UnsupportedCallbackException(callbacks[0]);
-
- PasswordCallback passwordCb = (PasswordCallback) callbacks[0];
- PBEKeySpecCallback pbeCb = (PBEKeySpecCallback) callbacks[1];
-
- if (isSetup()) {
- Callback[] dialogCbs = new Callback[] { passwordCb };
- defaultCallbackHandler.handle(dialogCbs);
- } else {// setup keyring
- TextOutputCallback textCb1 = new TextOutputCallback(TextOutputCallback.INFORMATION,
- "Enter a master password which will protect your private data");
- TextOutputCallback textCb2 = new TextOutputCallback(TextOutputCallback.INFORMATION,
- "(for example your credentials to third-party services)");
- TextOutputCallback textCb3 = new TextOutputCallback(TextOutputCallback.INFORMATION,
- "Don't forget this password since the data cannot be read without it");
- PasswordCallback confirmPasswordCb = new PasswordCallback("Confirm password", false);
- // first try
- Callback[] dialogCbs = new Callback[] { textCb1, textCb2, textCb3, passwordCb, confirmPasswordCb };
- defaultCallbackHandler.handle(dialogCbs);
-
- // if passwords different, retry (except if cancelled)
- while (passwordCb.getPassword() != null
- && !Arrays.equals(passwordCb.getPassword(), confirmPasswordCb.getPassword())) {
- TextOutputCallback textCb = new TextOutputCallback(TextOutputCallback.ERROR,
- "The passwords do not match");
- dialogCbs = new Callback[] { textCb, passwordCb, confirmPasswordCb };
- defaultCallbackHandler.handle(dialogCbs);
- }
-
- if (passwordCb.getPassword() != null) {// not cancelled
- setup(passwordCb.getPassword());
- }
- }
-
- if (passwordCb.getPassword() != null)
- handleKeySpecCallback(pbeCb);
- }
-
- }
-
- class PasswordProvidedCallBackHandler implements CallbackHandler {
- private final char[] password;
-
- public PasswordProvidedCallBackHandler(char[] password) {
- this.password = password;
- }
-
- @Override
- public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
- // checks
- if (callbacks.length != 2)
- throw new IllegalArgumentException(
- "Keyring requires 2 and only 2 callbacks: {PasswordCallback,PBEKeySpecCallback}");
- if (!(callbacks[0] instanceof PasswordCallback))
- throw new UnsupportedCallbackException(callbacks[0]);
- if (!(callbacks[1] instanceof PBEKeySpecCallback))
- throw new UnsupportedCallbackException(callbacks[0]);
-
- PasswordCallback passwordCb = (PasswordCallback) callbacks[0];
- passwordCb.setPassword(password);
- PBEKeySpecCallback pbeCb = (PBEKeySpecCallback) callbacks[1];
- handleKeySpecCallback(pbeCb);
- }
-
- }
-}
+++ /dev/null
-package org.argeo.cms.security;
-
-import java.io.IOException;
-import java.math.BigInteger;
-import java.nio.MappedByteBuffer;
-import java.nio.channels.FileChannel;
-import java.nio.file.FileVisitResult;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.nio.file.SimpleFileVisitor;
-import java.nio.file.attribute.BasicFileAttributes;
-import java.security.MessageDigest;
-import java.util.Base64;
-import java.util.zip.Checksum;
-
-import org.argeo.cms.CmsException;
-
-/** Allows to fine tune how files are read. */
-public class ChecksumFactory {
- private int regionSize = 10 * 1024 * 1024;
-
- public byte[] digest(Path path, final String algo) {
- try {
- final MessageDigest md = MessageDigest.getInstance(algo);
- if (Files.isDirectory(path)) {
- long begin = System.currentTimeMillis();
- Files.walkFileTree(path, new SimpleFileVisitor<Path>() {
-
- @Override
- public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
- if (!Files.isDirectory(file)) {
- byte[] digest = digest(file, algo);
- md.update(digest);
- }
- return FileVisitResult.CONTINUE;
- }
-
- });
- byte[] digest = md.digest();
- long duration = System.currentTimeMillis() - begin;
- System.out.println(printBase64Binary(digest) + " " + path + " (" + duration / 1000 + "s)");
- return digest;
- } else {
- long begin = System.nanoTime();
- long length = -1;
- try (FileChannel channel = (FileChannel) Files.newByteChannel(path);) {
- length = channel.size();
- long cursor = 0;
- while (cursor < length) {
- long effectiveSize = Math.min(regionSize, length - cursor);
- MappedByteBuffer mb = channel.map(FileChannel.MapMode.READ_ONLY, cursor, effectiveSize);
- // md.update(mb);
- byte[] buffer = new byte[1024];
- while (mb.hasRemaining()) {
- mb.get(buffer);
- md.update(buffer);
- }
-
- // sub digest
- // mb.flip();
- // MessageDigest subMd =
- // MessageDigest.getInstance(algo);
- // subMd.update(mb);
- // byte[] subDigest = subMd.digest();
- // System.out.println(" -> " + cursor);
- // System.out.println(IOUtils.encodeHexString(subDigest));
- // System.out.println(new BigInteger(1,
- // subDigest).toString(16));
- // System.out.println(new BigInteger(1, subDigest)
- // .toString(Character.MAX_RADIX));
- // System.out.println(printBase64Binary(subDigest));
-
- cursor = cursor + regionSize;
- }
- byte[] digest = md.digest();
- long duration = System.nanoTime() - begin;
- System.out.println(printBase64Binary(digest) + " " + path.getFileName() + " (" + duration / 1000000
- + "ms, " + (length / 1024) + "kB, " + (length / (duration / 1000000)) * 1000 / (1024 * 1024)
- + " MB/s)");
- return digest;
- }
- }
- } catch (Exception e) {
- throw new CmsException("Cannot digest " + path, e);
- }
- }
-
- /** Whether the file should be mapped. */
- protected boolean mapFile(FileChannel fileChannel) throws IOException {
- long size = fileChannel.size();
- if (size > (regionSize / 10))
- return true;
- return false;
- }
-
- public long checksum(Path path, Checksum crc) {
- final int bufferSize = 2 * 1024 * 1024;
- long begin = System.currentTimeMillis();
- try (FileChannel channel = (FileChannel) Files.newByteChannel(path);) {
- byte[] bytes = new byte[bufferSize];
- long length = channel.size();
- long cursor = 0;
- while (cursor < length) {
- long effectiveSize = Math.min(regionSize, length - cursor);
- MappedByteBuffer mb = channel.map(FileChannel.MapMode.READ_ONLY, cursor, effectiveSize);
- int nGet;
- while (mb.hasRemaining()) {
- nGet = Math.min(mb.remaining(), bufferSize);
- mb.get(bytes, 0, nGet);
- crc.update(bytes, 0, nGet);
- }
- cursor = cursor + regionSize;
- }
- return crc.getValue();
- } catch (Exception e) {
- throw new CmsException("Cannot checksum " + path, e);
- } finally {
- long duration = System.currentTimeMillis() - begin;
- System.out.println(duration / 1000 + "s");
- }
- }
-
- public static void main(String... args) {
- ChecksumFactory cf = new ChecksumFactory();
- // Path path =
- // Paths.get("/home/mbaudier/apache-maven-3.2.3-bin.tar.gz");
- Path path;
- if (args.length > 0) {
- path = Paths.get(args[0]);
- } else {
- path = Paths.get("/home/mbaudier/Downloads/torrents/CentOS-7-x86_64-DVD-1503-01/"
- + "CentOS-7-x86_64-DVD-1503-01.iso");
- }
- // long adler = cf.checksum(path, new Adler32());
- // System.out.format("Adler=%d%n", adler);
- // long crc = cf.checksum(path, new CRC32());
- // System.out.format("CRC=%d%n", crc);
- String algo = "SHA1";
- byte[] digest = cf.digest(path, algo);
- System.out.println(algo + " " + printBase64Binary(digest));
- System.out.println(algo + " " + new BigInteger(1, digest).toString(16));
- // String sha1 = printBase64Binary(cf.digest(path, "SHA1"));
- // System.out.format("SHA1=%s%n", sha1);
- }
-
- private static String printBase64Binary(byte[] arr) {
- return Base64.getEncoder().encodeToString(arr);
- }
-}
+++ /dev/null
-package org.argeo.cms.security;
-
-/**
- * Marker interface for an advanced keyring based on cryptography.
- */
-public interface CryptoKeyring extends Keyring {
- public void changePassword(char[] oldPassword, char[] newPassword);
-
- public void unlock(char[] password);
-}
+++ /dev/null
-package org.argeo.cms.security;
-
-import java.io.InputStream;
-
-/**
- * Access to private (typically encrypted) data. The keyring is responsible for
- * retrieving the necessary credentials. <b>Experimental. This API may
- * change.</b>
- */
-public interface Keyring {
- /**
- * Returns the confidential information as chars. Must ask for it if it is
- * not stored.
- */
- public char[] getAsChars(String path);
-
- /**
- * Returns the confidential information as a stream. Must ask for it if it
- * is not stored.
- */
- public InputStream getAsStream(String path);
-
- public void set(String path, char[] arr);
-
- public void set(String path, InputStream in);
-}
+++ /dev/null
-package org.argeo.cms.security;
-
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-
-import javax.naming.InvalidNameException;
-import javax.naming.ldap.LdapName;
-
-import org.argeo.api.cms.CmsConstants;
-
-public class NodeSecurityUtils {
- public final static LdapName ROLE_ADMIN_NAME, ROLE_DATA_ADMIN_NAME, ROLE_ANONYMOUS_NAME, ROLE_USER_NAME,
- ROLE_USER_ADMIN_NAME;
- public final static List<LdapName> RESERVED_ROLES;
- static {
- try {
- ROLE_ADMIN_NAME = new LdapName(CmsConstants.ROLE_ADMIN);
- ROLE_DATA_ADMIN_NAME = new LdapName(CmsConstants.ROLE_DATA_ADMIN);
- ROLE_USER_NAME = new LdapName(CmsConstants.ROLE_USER);
- ROLE_USER_ADMIN_NAME = new LdapName(CmsConstants.ROLE_USER_ADMIN);
- ROLE_ANONYMOUS_NAME = new LdapName(CmsConstants.ROLE_ANONYMOUS);
- RESERVED_ROLES = Collections.unmodifiableList(Arrays.asList(
- new LdapName[] { ROLE_ADMIN_NAME, ROLE_ANONYMOUS_NAME, ROLE_USER_NAME, ROLE_USER_ADMIN_NAME }));
- } catch (InvalidNameException e) {
- throw new Error("Cannot initialize login module class", e);
- }
- }
-
- public static void checkUserName(LdapName name) throws IllegalArgumentException {
- if (RESERVED_ROLES.contains(name))
- throw new IllegalArgumentException(name + " is a reserved name");
- }
-
- public static void checkImpliedPrincipalName(LdapName roleName) throws IllegalArgumentException {
-// if (ROLE_USER_NAME.equals(roleName) || ROLE_ANONYMOUS_NAME.equals(roleName))
-// throw new IllegalArgumentException(roleName + " cannot be listed as role");
- }
-
-}
+++ /dev/null
-package org.argeo.cms.security;
-
-import javax.crypto.spec.PBEKeySpec;
-import javax.security.auth.callback.Callback;
-import javax.security.auth.callback.PasswordCallback;
-
-/**
- * All information required to set up a {@link PBEKeySpec} bar the password
- * itself (use a {@link PasswordCallback})
- */
-public class PBEKeySpecCallback implements Callback {
- private String secretKeyFactory;
- private byte[] salt;
- private Integer iterationCount;
- /** Can be null for some algorithms */
- private Integer keyLength;
- /** Can be null, will trigger secret key encryption if not */
- private String secretKeyEncryption;
-
- private String encryptedPasswordHashCipher;
- private byte[] encryptedPasswordHash;
-
- public void set(String secretKeyFactory, byte[] salt,
- Integer iterationCount, Integer keyLength,
- String secretKeyEncryption) {
- this.secretKeyFactory = secretKeyFactory;
- this.salt = salt;
- this.iterationCount = iterationCount;
- this.keyLength = keyLength;
- this.secretKeyEncryption = secretKeyEncryption;
-// this.encryptedPasswordHashCipher = encryptedPasswordHashCipher;
-// this.encryptedPasswordHash = encryptedPasswordHash;
- }
-
- public String getSecretKeyFactory() {
- return secretKeyFactory;
- }
-
- public byte[] getSalt() {
- return salt;
- }
-
- public Integer getIterationCount() {
- return iterationCount;
- }
-
- public Integer getKeyLength() {
- return keyLength;
- }
-
- public String getSecretKeyEncryption() {
- return secretKeyEncryption;
- }
-
- public String getEncryptedPasswordHashCipher() {
- return encryptedPasswordHashCipher;
- }
-
- public byte[] getEncryptedPasswordHash() {
- return encryptedPasswordHash;
- }
-
-}
+++ /dev/null
-/** Argeo CMS reusable security components. */
-package org.argeo.cms.security;
\ No newline at end of file
+++ /dev/null
-package org.argeo.cms.tabular;
-
-import java.util.List;
-
-/** Minimal tabular row wrapping an {@link Object} array */
-public class ArrayTabularRow implements TabularRow {
- private final Object[] arr;
-
- public ArrayTabularRow(List<?> objs) {
- this.arr = objs.toArray();
- }
-
- public Object get(Integer col) {
- return arr[col];
- }
-
- public int size() {
- return arr.length;
- }
-
- public Object[] toArray() {
- return arr;
- }
-
-}
+++ /dev/null
-package org.argeo.cms.tabular;
-
-/** The column in a tabular content */
-public class TabularColumn {
- private String name;
- /**
- * JCR types, see
- * http://www.day.com/maven/javax.jcr/javadocs/jcr-2.0/index.html
- * ?javax/jcr/PropertyType.html
- */
- private Integer type;
-
- /** column with default type */
- public TabularColumn(String name) {
- super();
- this.name = name;
- }
-
- public TabularColumn(String name, Integer type) {
- super();
- this.name = name;
- this.type = type;
- }
-
- public String getName() {
- return name;
- }
-
- public void setName(String name) {
- this.name = name;
- }
-
- public Integer getType() {
- return type;
- }
-
- public void setType(Integer type) {
- this.type = type;
- }
-
-}
+++ /dev/null
-package org.argeo.cms.tabular;
-
-import java.util.List;
-
-/**
- * Content organized as a table, possibly with headers. Only JCR types are
- * supported even though there is not direct dependency on JCR.
- */
-public interface TabularContent {
- /** The headers of this table or <code>null</code> is none available. */
- public List<TabularColumn> getColumns();
-
- public TabularRowIterator read();
-}
+++ /dev/null
-package org.argeo.cms.tabular;
-
-/** A row of tabular data */
-public interface TabularRow {
- /** The value at this column index */
- public Object get(Integer col);
-
- /** The raw objects (direct references) */
- public Object[] toArray();
-
- /** Number of columns */
- public int size();
-}
+++ /dev/null
-package org.argeo.cms.tabular;
-
-import java.util.Iterator;
-
-/** Navigation of rows */
-public interface TabularRowIterator extends Iterator<TabularRow> {
- /**
- * Current row number, has to be incremented by each call to next() ; starts at 0, will
- * therefore be 1 for the first row returned.
- */
- public Long getCurrentRowNumber();
-}
+++ /dev/null
-package org.argeo.cms.tabular;
-
-
-/** Write to a tabular content */
-public interface TabularWriter {
- /** Append a new row of data */
- public void appendRow(Object[] row);
-
- /** Finish persisting data and release resources */
- public void close();
-}
+++ /dev/null
-/** Tabular format API. */
-package org.argeo.cms.tabular;
\ No newline at end of file
--- /dev/null
+package org.argeo.cms.util;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+import java.util.StringTokenizer;
+
+/** A name that can be expressed with various conventions. */
+public class CompositeString {
+ public final static Character UNDERSCORE = Character.valueOf('_');
+ public final static Character SPACE = Character.valueOf(' ');
+ public final static Character DASH = Character.valueOf('-');
+
+ private final String[] parts;
+
+ // optimisation
+ private final int hashCode;
+
+ public CompositeString(String str) {
+ Objects.requireNonNull(str, "String cannot be null");
+ if ("".equals(str.trim()))
+ throw new IllegalArgumentException("String cannot be empty");
+ if (!str.equals(str.trim()))
+ throw new IllegalArgumentException("String must be trimmed");
+ this.parts = toParts(str);
+ hashCode = hashCode(this.parts);
+ }
+
+ public String toString(char separator, boolean upperCase) {
+ StringBuilder sb = null;
+ for (String part : parts) {
+ if (sb == null) {
+ sb = new StringBuilder();
+ } else {
+ sb.append(separator);
+ }
+ sb.append(upperCase ? part.toUpperCase() : part);
+ }
+ return sb.toString();
+ }
+
+ public String toStringCaml(boolean firstCharUpperCase) {
+ StringBuilder sb = null;
+ for (String part : parts) {
+ if (sb == null) {// first
+ sb = new StringBuilder();
+ sb.append(firstCharUpperCase ? Character.toUpperCase(part.charAt(0)) : part.charAt(0));
+ } else {
+ sb.append(Character.toUpperCase(part.charAt(0)));
+ }
+
+ if (part.length() > 1)
+ sb.append(part.substring(1));
+ }
+ return sb.toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return hashCode;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null || !(obj instanceof CompositeString))
+ return false;
+
+ CompositeString other = (CompositeString) obj;
+ return Arrays.equals(parts, other.parts);
+ }
+
+ @Override
+ public String toString() {
+ return toString(DASH, false);
+ }
+
+ public static String[] toParts(String str) {
+ Character separator = null;
+ if (str.indexOf(UNDERSCORE) >= 0) {
+ checkNo(str, SPACE);
+ checkNo(str, DASH);
+ separator = UNDERSCORE;
+ } else if (str.indexOf(DASH) >= 0) {
+ checkNo(str, SPACE);
+ checkNo(str, UNDERSCORE);
+ separator = DASH;
+ } else if (str.indexOf(SPACE) >= 0) {
+ checkNo(str, DASH);
+ checkNo(str, UNDERSCORE);
+ separator = SPACE;
+ }
+
+ List<String> res = new ArrayList<>();
+ if (separator != null) {
+ StringTokenizer st = new StringTokenizer(str, separator.toString());
+ while (st.hasMoreTokens()) {
+ res.add(st.nextToken().toLowerCase());
+ }
+ } else {
+ // single
+ String strLowerCase = str.toLowerCase();
+ if (str.toUpperCase().equals(str) || strLowerCase.equals(str))
+ return new String[] { strLowerCase };
+
+ // CAML
+ StringBuilder current = null;
+ for (char c : str.toCharArray()) {
+ if (Character.isUpperCase(c)) {
+ if (current != null)
+ res.add(current.toString());
+ current = new StringBuilder();
+ }
+ if (current == null)// first char is lower case
+ current = new StringBuilder();
+ current.append(Character.toLowerCase(c));
+ }
+ res.add(current.toString());
+ }
+ return res.toArray(new String[res.size()]);
+ }
+
+ private static void checkNo(String str, Character c) {
+ if (str.indexOf(c) >= 0) {
+ throw new IllegalArgumentException("Only one kind of sperator is allowed");
+ }
+ }
+
+ private static int hashCode(String[] parts) {
+ int hashCode = 0;
+ for (String part : parts) {
+ hashCode = hashCode + part.hashCode();
+ }
+ return hashCode;
+ }
+
+ static boolean smokeTests() {
+ CompositeString plainName = new CompositeString("NAME");
+ assert "name".equals(plainName.toString());
+ assert "NAME".equals(plainName.toString(UNDERSCORE, true));
+ assert "name".equals(plainName.toString(UNDERSCORE, false));
+ assert "name".equals(plainName.toStringCaml(false));
+ assert "Name".equals(plainName.toStringCaml(true));
+
+ CompositeString camlName = new CompositeString("myComplexName");
+
+ assert new CompositeString("my-complex-name").equals(camlName);
+ assert new CompositeString("MY_COMPLEX_NAME").equals(camlName);
+ assert new CompositeString("My complex Name").equals(camlName);
+ assert new CompositeString("MyComplexName").equals(camlName);
+
+ assert "my-complex-name".equals(camlName.toString());
+ assert "MY_COMPLEX_NAME".equals(camlName.toString(UNDERSCORE, true));
+ assert "my_complex_name".equals(camlName.toString(UNDERSCORE, false));
+ assert "myComplexName".equals(camlName.toStringCaml(false));
+ assert "MyComplexName".equals(camlName.toStringCaml(true));
+
+ return CompositeString.class.desiredAssertionStatus();
+ }
+
+ public static void main(String[] args) {
+ System.out.println(smokeTests());
+ }
+}
--- /dev/null
+package org.argeo.cms.util;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Parses a CSV file interpreting the first line as a header. The
+ * {@link #parse(InputStream)} method and the setters are synchronized so that
+ * the object cannot be modified when parsing.
+ */
+public abstract class CsvParser {
+ private char separator = ',';
+ private char quote = '\"';
+
+ private Boolean noHeader = false;
+ private Boolean strictLineAsLongAsHeader = true;
+
+ /**
+ * Actually process a parsed line. If
+ * {@link #setStrictLineAsLongAsHeader(Boolean)} is true (default) the header
+ * and the tokens are guaranteed to have the same size.
+ *
+ * @param lineNumber the current line number, starts at 1 (the header, if header
+ * processing is enabled, the first line otherwise)
+ * @param header the read-only header or null if
+ * {@link #setNoHeader(Boolean)} is true (default is false)
+ * @param tokens the parsed tokens
+ */
+ protected abstract void processLine(Integer lineNumber, List<String> header, List<String> tokens);
+
+ /**
+ * Parses the CSV file (stream is closed at the end)
+ *
+ * @param in the stream to parse
+ *
+ * @deprecated Use {@link #parse(InputStream, Charset)} instead.
+ */
+ @Deprecated
+ public synchronized void parse(InputStream in) {
+ parse(in, (Charset) null);
+ }
+
+ /**
+ * Parses the CSV file (stream is closed at the end)
+ *
+ * @param in the stream to parse
+ * @param encoding the encoding to use.
+ *
+ * @deprecated Use {@link #parse(InputStream, Charset)} instead.
+ */
+ @Deprecated
+ public synchronized void parse(InputStream in, String encoding) {
+ Reader reader;
+ if (encoding == null)
+ reader = new InputStreamReader(in);
+ else
+ try {
+ reader = new InputStreamReader(in, encoding);
+ } catch (UnsupportedEncodingException e) {
+ throw new IllegalArgumentException(e);
+ }
+ parse(reader);
+ }
+
+ /**
+ * Parses the CSV file (stream is closed at the end)
+ *
+ * @param in the stream to parse
+ * @param charset the charset to use
+ */
+ public synchronized void parse(InputStream in, Charset charset) {
+ Reader reader;
+ if (charset == null)
+ reader = new InputStreamReader(in);
+ else
+ reader = new InputStreamReader(in, charset);
+ parse(reader);
+ }
+
+ /**
+ * Parses the CSV file (stream is closed at the end)
+ *
+ * @param reader the reader to use (it will be buffered)
+ */
+ public synchronized void parse(Reader reader) {
+ Integer lineCount = 0;
+ try (BufferedReader bufferedReader = new BufferedReader(reader)) {
+ List<String> header = null;
+ if (!noHeader) {
+ String headerStr = bufferedReader.readLine();
+ if (headerStr == null)// empty file
+ return;
+ lineCount++;
+ header = new ArrayList<String>();
+ StringBuffer currStr = new StringBuffer("");
+ Boolean wasInquote = false;
+ while (parseLine(headerStr, header, currStr, wasInquote)) {
+ headerStr = bufferedReader.readLine();
+ if (headerStr == null)
+ break;
+ wasInquote = true;
+ }
+ header = Collections.unmodifiableList(header);
+ }
+
+ String line = null;
+ lines: while ((line = bufferedReader.readLine()) != null) {
+ line = preProcessLine(line);
+ if (line == null) {
+ // skip line
+ continue lines;
+ }
+ lineCount++;
+ List<String> tokens = new ArrayList<String>();
+ StringBuffer currStr = new StringBuffer("");
+ Boolean wasInquote = false;
+ sublines: while (parseLine(line, tokens, currStr, wasInquote)) {
+ line = bufferedReader.readLine();
+ if (line == null)
+ break sublines;
+ wasInquote = true;
+ }
+ if (!noHeader && strictLineAsLongAsHeader) {
+ int headerSize = header.size();
+ int tokenSize = tokens.size();
+ if (tokenSize == 1 && line.trim().equals(""))
+ continue lines;// empty line
+ if (headerSize != tokenSize) {
+ throw new IllegalStateException("Token size " + tokenSize + " is different from header size "
+ + headerSize + " at line " + lineCount + ", line: " + line + ", header: " + header
+ + ", tokens: " + tokens);
+ }
+ }
+ processLine(lineCount, header, tokens);
+ }
+ } catch (IOException e) {
+ throw new RuntimeException("Cannot parse CSV file (line: " + lineCount + ")", e);
+ }
+ }
+
+ /**
+ * Called before each (logical) line is processed, giving a change to modify it
+ * (typically for cleaning dirty files). To be overridden, return the line
+ * unchanged by default. Skip the line if 'null' is returned.
+ */
+ protected String preProcessLine(String line) {
+ return line;
+ }
+
+ /**
+ * Parses a line character by character for performance purpose
+ *
+ * @return whether to continue parsing this line
+ */
+ protected Boolean parseLine(String str, List<String> tokens, StringBuffer currStr, Boolean wasInquote) {
+ if (wasInquote)
+ currStr.append('\n');
+
+ char[] arr = str.toCharArray();
+ boolean inQuote = wasInquote;
+ for (int i = 0; i < arr.length; i++) {
+ char c = arr[i];
+ if (c == separator) {
+ if (!inQuote) {
+ tokens.add(currStr.toString());
+// currStr.delete(0, currStr.length());
+ currStr.setLength(0);
+ currStr.trimToSize();
+ } else {
+ // we don't remove separator that are in a quoted substring
+ // System.out
+ // .println("IN QUOTE, got a separator: [" + c + "]");
+ currStr.append(c);
+ }
+ } else if (c == quote) {
+ if (inQuote && (i + 1) < arr.length && arr[i + 1] == quote) {
+ // case of double quote
+ currStr.append(quote);
+ i++;
+ } else {// standard
+ inQuote = inQuote ? false : true;
+ }
+ } else {
+ currStr.append(c);
+ }
+ }
+
+ if (!inQuote) {
+ tokens.add(currStr.toString());
+ // System.out.println("# TOKEN: " + currStr);
+ }
+ // if (inQuote)
+ // throw new ArgeoException("Missing quote at the end of the line "
+ // + str + " (parsed: " + tokens + ")");
+ if (inQuote)
+ return true;
+ else
+ return false;
+ // return tokens;
+ }
+
+ public char getSeparator() {
+ return separator;
+ }
+
+ public synchronized void setSeparator(char separator) {
+ this.separator = separator;
+ }
+
+ public char getQuote() {
+ return quote;
+ }
+
+ public synchronized void setQuote(char quote) {
+ this.quote = quote;
+ }
+
+ public Boolean getNoHeader() {
+ return noHeader;
+ }
+
+ public synchronized void setNoHeader(Boolean noHeader) {
+ this.noHeader = noHeader;
+ }
+
+ public Boolean getStrictLineAsLongAsHeader() {
+ return strictLineAsLongAsHeader;
+ }
+
+ public synchronized void setStrictLineAsLongAsHeader(Boolean strictLineAsLongAsHeader) {
+ this.strictLineAsLongAsHeader = strictLineAsLongAsHeader;
+ }
+
+}
--- /dev/null
+package org.argeo.cms.util;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * CSV parser allowing to process lines as maps whose keys are the header
+ * fields.
+ */
+public abstract class CsvParserWithLinesAsMap extends CsvParser {
+
+ /**
+ * Actually processes a line.
+ *
+ * @param lineNumber the current line number, starts at 1 (the header, if header
+ * processing is enabled, the first lien otherwise)
+ * @param line the parsed tokens as a map whose keys are the header fields
+ */
+ protected abstract void processLine(Integer lineNumber, Map<String, String> line);
+
+ protected final void processLine(Integer lineNumber, List<String> header, List<String> tokens) {
+ if (header == null)
+ throw new IllegalArgumentException("Only CSV with header is supported");
+ Map<String, String> line = new HashMap<String, String>();
+ for (int i = 0; i < header.size(); i++) {
+ String key = header.get(i);
+ String value = null;
+ if (i < tokens.size())
+ value = tokens.get(i);
+ line.put(key, value);
+ }
+ processLine(lineNumber, line);
+ }
+
+}
--- /dev/null
+package org.argeo.cms.util;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.UnsupportedEncodingException;
+import java.io.Writer;
+import java.nio.charset.Charset;
+import java.util.Iterator;
+import java.util.List;
+
+/** Write in CSV format. */
+public class CsvWriter {
+ private final Writer out;
+
+ private char separator = ',';
+ private char quote = '\"';
+
+ /**
+ * Creates a CSV writer.
+ *
+ * @param out the stream to write to. Caller is responsible for closing it.
+ *
+ * @deprecated Use {@link #CsvWriter(OutputStream, Charset)} instead.
+ *
+ */
+ @Deprecated
+ public CsvWriter(OutputStream out) {
+ this.out = new OutputStreamWriter(out);
+ }
+
+ /**
+ * Creates a CSV writer.
+ *
+ * @param out the stream to write to. Caller is responsible for closing it.
+ * @param encoding the encoding to use.
+ *
+ * @deprecated Use {@link #CsvWriter(OutputStream, Charset)} instead.
+ */
+ @Deprecated
+ public CsvWriter(OutputStream out, String encoding) {
+ try {
+ this.out = new OutputStreamWriter(out, encoding);
+ } catch (UnsupportedEncodingException e) {
+ throw new IllegalArgumentException(e);
+ }
+ }
+
+ /**
+ * Creates a CSV writer.
+ *
+ * @param out the stream to write to. Caller is responsible for closing it.
+ * @param charset the charset to use
+ */
+ public CsvWriter(OutputStream out, Charset charset) {
+ this.out = new OutputStreamWriter(out, charset);
+ }
+
+ /**
+ * Creates a CSV writer.
+ *
+ * @param out the stream to write to. Caller is responsible for closing it.
+ */
+ public CsvWriter(Writer writer) {
+ this.out = writer;
+ }
+
+ /**
+ * Write a CSV line. Also used to write a header if needed (this is transparent
+ * for the CSV writer): simply call it first, before writing the lines.
+ */
+ public void writeLine(List<?> tokens) {
+ try {
+ Iterator<?> it = tokens.iterator();
+ while (it.hasNext()) {
+ Object obj = it.next();
+ writeToken(obj != null ? obj.toString() : null);
+ if (it.hasNext())
+ out.write(separator);
+ }
+ out.write('\n');
+ out.flush();
+ } catch (IOException e) {
+ throw new RuntimeException("Could not write " + tokens, e);
+ }
+ }
+
+ /**
+ * Write a CSV line. Also used to write a header if needed (this is transparent
+ * for the CSV writer): simply call it first, before writing the lines.
+ */
+ public void writeLine(Object... tokens) {
+ try {
+ for (int i = 0; i < tokens.length; i++) {
+ if (tokens[i] == null) {
+ writeToken(null);
+ } else {
+ writeToken(tokens[i].toString());
+ }
+ if (i != (tokens.length - 1))
+ out.write(separator);
+ }
+ out.write('\n');
+ out.flush();
+ } catch (IOException e) {
+ throw new RuntimeException("Could not write " + tokens, e);
+ }
+ }
+
+ protected void writeToken(String token) throws IOException {
+ if (token == null) {
+ // TODO configure how to deal with null
+ out.write("");
+ return;
+ }
+ // +2 for possible quotes, another +2 assuming there would be an already
+ // quoted string where quotes needs to be duplicated
+ // another +2 for safety
+ // we don't want to increase buffer size while writing
+ StringBuffer buf = new StringBuffer(token.length() + 6);
+ char[] arr = token.toCharArray();
+ boolean shouldQuote = false;
+ for (char c : arr) {
+ if (!shouldQuote) {
+ if (c == separator)
+ shouldQuote = true;
+ if (c == '\n')
+ shouldQuote = true;
+ }
+
+ if (c == quote) {
+ shouldQuote = true;
+ // duplicate quote
+ buf.append(quote);
+ }
+
+ // generic case
+ buf.append(c);
+ }
+
+ if (shouldQuote == true)
+ out.write(quote);
+ out.write(buf.toString());
+ if (shouldQuote == true)
+ out.write(quote);
+ }
+
+ public void setSeparator(char separator) {
+ this.separator = separator;
+ }
+
+ public void setQuote(char quote) {
+ this.quote = quote;
+ }
+
+}
--- /dev/null
+package org.argeo.cms.util;
+
+import java.security.AccessController;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CompletionException;
+
+import javax.security.auth.Subject;
+
+/**
+ * Prepare evolution of Java APIs introduced in JDK 18, as these static methods
+ * will be added to {@link Subject}.
+ */
+@SuppressWarnings("removal")
+public class CurrentSubject {
+
+ private final static boolean useThreadLocal = Boolean
+ .parseBoolean(System.getProperty("jdk.security.auth.subject.useTL"));
+
+ private final static InheritableThreadLocal<Subject> current = new InheritableThreadLocal<>();
+
+ public static Subject current() {
+ if (useThreadLocal) {
+ return current.get();
+ } else {// legacy
+ Subject subject = Subject.getSubject(AccessController.getContext());
+ return subject;
+ }
+ }
+
+ public static <T> T callAs(Subject subject, Callable<T> action) {
+ if (useThreadLocal) {
+ Subject previous = current();
+ current.set(subject);
+ try {
+ return action.call();
+ } catch (Exception e) {
+ throw new CompletionException("Failed to execute action for " + subject, e);
+ } finally {
+ current.set(previous);
+ }
+ } else {// legacy
+ try {
+ return Subject.doAs(subject, new PrivilegedExceptionAction<T>() {
+
+ @Override
+ public T run() throws Exception {
+ return action.call();
+ }
+
+ });
+ } catch (PrivilegedActionException e) {
+ throw new CompletionException("Failed to execute action for " + subject, e.getCause());
+ } catch (Exception e) {
+ throw new CompletionException("Failed to execute action for " + subject, e);
+ }
+ }
+ }
+
+ /** Singleton. */
+ private CurrentSubject() {
+ }
+
+}
--- /dev/null
+package org.argeo.cms.util;
+
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.Iterator;
+
+/**
+ * Access the keys of a {@link String}-keyed {@link Dictionary} (common throughout
+ * the OSGi APIs) as an {@link Iterable} so that they are easily usable in
+ * for-each loops.
+ */
+class DictionaryKeys implements Iterable<String> {
+ private final Dictionary<String, ?> dictionary;
+
+ public DictionaryKeys(Dictionary<String, ?> dictionary) {
+ this.dictionary = dictionary;
+ }
+
+ @Override
+ public Iterator<String> iterator() {
+ return new KeyIterator(dictionary.keys());
+ }
+
+ private static class KeyIterator implements Iterator<String> {
+ private final Enumeration<String> keys;
+
+ KeyIterator(Enumeration<String> keys) {
+ this.keys = keys;
+ }
+
+ @Override
+ public boolean hasNext() {
+ return keys.hasMoreElements();
+ }
+
+ @Override
+ public String next() {
+ return keys.nextElement();
+ }
+
+ }
+}
--- /dev/null
+package org.argeo.cms.util;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+import java.nio.channels.FileChannel.MapMode;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+/** Utilities around cryptographic digests */
+public class DigestUtils {
+ 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";
+
+ private static Boolean debug = false;
+ // TODO: make it configurable
+ private final static Integer byteBufferCapacity = 100 * 1024;// 100 KB
+
+ public static byte[] sha1(byte[]... bytes) {
+ try {
+ MessageDigest digest = MessageDigest.getInstance(SHA1);
+ for (byte[] arr : bytes)
+ digest.update(arr);
+ byte[] checksum = digest.digest();
+ return checksum;
+ } catch (NoSuchAlgorithmException e) {
+ throw new UnsupportedOperationException("SHA1 is not avalaible", e);
+ }
+ }
+
+ public static byte[] digestAsBytes(String algorithm, byte[]... bytes) {
+ try {
+ MessageDigest digest = MessageDigest.getInstance(algorithm);
+ for (byte[] arr : bytes)
+ digest.update(arr);
+ byte[] checksum = digest.digest();
+ return checksum;
+ } catch (NoSuchAlgorithmException e) {
+ throw new UnsupportedOperationException("Cannot digest with algorithm " + algorithm, e);
+ }
+ }
+
+ public static String digest(String algorithm, byte[]... bytes) {
+ return toHexString(digestAsBytes(algorithm, bytes));
+ }
+
+ public static String digest(String algorithm, InputStream in) {
+ try {
+ MessageDigest digest = MessageDigest.getInstance(algorithm);
+ // ReadableByteChannel channel = Channels.newChannel(in);
+ // ByteBuffer bb = ByteBuffer.allocateDirect(byteBufferCapacity);
+ // while (channel.read(bb) > 0)
+ // digest.update(bb);
+ 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 = toHexString(checksum);
+ return res;
+ } catch (NoSuchAlgorithmException e) {
+ throw new IllegalArgumentException("Cannot digest with algorithm " + algorithm, e);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ } finally {
+ StreamUtils.closeQuietly(in);
+ }
+ }
+
+ public static String digest(String algorithm, File file) {
+ FileInputStream fis = null;
+ FileChannel fc = null;
+ try {
+ fis = new FileInputStream(file);
+ fc = fis.getChannel();
+
+ // Get the file's size and then map it into memory
+ int sz = (int) fc.size();
+ ByteBuffer bb = fc.map(FileChannel.MapMode.READ_ONLY, 0, sz);
+ return digest(algorithm, bb);
+ } catch (IOException e) {
+ throw new IllegalArgumentException("Cannot digest " + file + " with algorithm " + algorithm, e);
+ } finally {
+ StreamUtils.closeQuietly(fis);
+ if (fc.isOpen())
+ try {
+ fc.close();
+ } catch (IOException e) {
+ // silent
+ }
+ }
+ }
+
+ protected static String digest(String algorithm, ByteBuffer bb) {
+ long begin = System.currentTimeMillis();
+ try {
+ MessageDigest digest = MessageDigest.getInstance(algorithm);
+ digest.update(bb);
+ byte[] checksum = digest.digest();
+ String res = toHexString(checksum);
+ long end = System.currentTimeMillis();
+ if (debug)
+ System.out.println((end - begin) + " ms / " + ((end - begin) / 1000) + " s");
+ return res;
+ } catch (NoSuchAlgorithmException e) {
+ throw new IllegalArgumentException("Cannot digest with algorithm " + algorithm, e);
+ }
+ }
+
+ public static String sha1hex(Path path) {
+ return digest(SHA1, path, byteBufferCapacity);
+ }
+
+ public static String digest(String algorithm, Path path, long bufferSize) {
+ byte[] digest = digestAsBytes(algorithm, path, bufferSize);
+ return toHexString(digest);
+ }
+
+ public static byte[] digestAsBytes(String algorithm, Path file, long bufferSize) {
+ long begin = System.currentTimeMillis();
+ try {
+ MessageDigest md = MessageDigest.getInstance(algorithm);
+ FileChannel fc = FileChannel.open(file);
+ long fileSize = Files.size(file);
+ if (fileSize <= bufferSize) {
+ ByteBuffer bb = fc.map(MapMode.READ_ONLY, 0, fileSize);
+ md.update(bb);
+ } else {
+ long lastCycle = (fileSize / bufferSize) - 1;
+ long position = 0;
+ for (int i = 0; i <= lastCycle; i++) {
+ ByteBuffer bb;
+ if (i != lastCycle) {
+ bb = fc.map(MapMode.READ_ONLY, position, bufferSize);
+ position = position + bufferSize;
+ } else {
+ bb = fc.map(MapMode.READ_ONLY, position, fileSize - position);
+ position = fileSize;
+ }
+ md.update(bb);
+ }
+ }
+ long end = System.currentTimeMillis();
+ if (debug)
+ System.out.println((end - begin) + " ms / " + ((end - begin) / 1000) + " s");
+ return md.digest();
+ } catch (NoSuchAlgorithmException e) {
+ throw new IllegalArgumentException("Cannot digest " + file + " with algorithm " + algorithm, e);
+ } catch (IOException e) {
+ throw new RuntimeException("Cannot digest " + file + " with algorithm " + algorithm, e);
+ }
+ }
+
+ public static void main(String[] args) {
+ File file;
+ if (args.length > 0)
+ file = new File(args[0]);
+ else {
+ System.err.println("Usage: <file> [<algorithm>]" + " (see http://java.sun.com/j2se/1.5.0/"
+ + "docs/guide/security/CryptoSpec.html#AppA)");
+ return;
+ }
+
+ if (args.length > 1) {
+ String algorithm = args[1];
+ System.out.println(digest(algorithm, file));
+ } else {
+ String algorithm = "MD5";
+ System.out.println(algorithm + ": " + digest(algorithm, file));
+ algorithm = "SHA";
+ System.out.println(algorithm + ": " + digest(algorithm, file));
+ System.out.println(algorithm + ": " + sha1hex(file.toPath()));
+ algorithm = "SHA-256";
+ System.out.println(algorithm + ": " + digest(algorithm, file));
+ algorithm = "SHA-512";
+ System.out.println(algorithm + ": " + digest(algorithm, file));
+ }
+ }
+
+ final private static char[] hexArray = "0123456789abcdef".toCharArray();
+
+ /** Converts a byte array to an hex String. */
+ public static String toHexString(byte[] bytes) {
+ 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);
+ }
+
+}
--- /dev/null
+package org.argeo.cms.util;
+
+import java.io.IOException;
+import java.io.PrintStream;
+import java.nio.charset.Charset;
+import java.nio.file.DirectoryStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/** Hashes the hashes of the files in a directory. */
+public class DirH {
+
+ private final static Charset charset = Charset.forName("UTF-16");
+ private final static long bufferSize = 200 * 1024 * 1024;
+ private final static String algorithm = "SHA";
+
+ private final static byte EOL = (byte) '\n';
+ private final static byte SPACE = (byte) ' ';
+
+ private final int hashSize;
+
+ private final byte[][] hashes;
+ private final byte[][] fileNames;
+ private final byte[] digest;
+ private final byte[] dirName;
+
+ /**
+ * @param dirName can be null or empty
+ */
+ private DirH(byte[][] hashes, byte[][] fileNames, byte[] dirName) {
+ if (hashes.length != fileNames.length)
+ throw new IllegalArgumentException(hashes.length + " hashes and " + fileNames.length + " file names");
+ this.hashes = hashes;
+ this.fileNames = fileNames;
+ this.dirName = dirName == null ? new byte[0] : dirName;
+ if (hashes.length == 0) {// empty dir
+ hashSize = 20;
+ // FIXME what is the digest of an empty dir?
+ digest = new byte[hashSize];
+ Arrays.fill(digest, SPACE);
+ return;
+ }
+ hashSize = hashes[0].length;
+ for (int i = 0; i < hashes.length; i++) {
+ if (hashes[i].length != hashSize)
+ throw new IllegalArgumentException(
+ "Hash size for " + new String(fileNames[i], charset) + " is " + hashes[i].length);
+ }
+
+ try {
+ MessageDigest md = MessageDigest.getInstance(algorithm);
+ for (int i = 0; i < hashes.length; i++) {
+ md.update(this.hashes[i]);
+ md.update(SPACE);
+ md.update(this.fileNames[i]);
+ md.update(EOL);
+ }
+ digest = md.digest();
+ } catch (NoSuchAlgorithmException e) {
+ throw new IllegalArgumentException("Cannot digest", e);
+ }
+ }
+
+ public void print(PrintStream out) {
+ out.print(DigestUtils.toHexString(digest));
+ if (dirName.length > 0) {
+ out.print(' ');
+ out.print(new String(dirName, charset));
+ }
+ out.print('\n');
+ for (int i = 0; i < hashes.length; i++) {
+ out.print(DigestUtils.toHexString(hashes[i]));
+ out.print(' ');
+ out.print(new String(fileNames[i], charset));
+ out.print('\n');
+ }
+ }
+
+ public static DirH digest(Path dir) {
+ try (DirectoryStream<Path> files = Files.newDirectoryStream(dir)) {
+ List<byte[]> hs = new ArrayList<byte[]>();
+ List<String> fNames = new ArrayList<>();
+ for (Path file : files) {
+ if (!Files.isDirectory(file)) {
+ byte[] digest = DigestUtils.digestAsBytes(algorithm, file, bufferSize);
+ hs.add(digest);
+ fNames.add(file.getFileName().toString());
+ }
+ }
+
+ byte[][] fileNames = new byte[fNames.size()][];
+ for (int i = 0; i < fNames.size(); i++) {
+ fileNames[i] = fNames.get(i).getBytes(charset);
+ }
+ byte[][] hashes = hs.toArray(new byte[hs.size()][]);
+ return new DirH(hashes, fileNames, dir.toString().getBytes(charset));
+ } catch (IOException e) {
+ throw new RuntimeException("Cannot digest " + dir, e);
+ }
+ }
+
+ public static void main(String[] args) {
+ try {
+ DirH dirH = DirH.digest(Paths.get("/home/mbaudier/tmp/"));
+ dirH.print(System.out);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+}
--- /dev/null
+package org.argeo.cms.util;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Serialisable wrapper of a {@link Throwable}. typically to be written as XML
+ * or JSON in a server error response.
+ */
+public class ExceptionsChain {
+ private List<SystemException> exceptions = new ArrayList<>();
+
+ public ExceptionsChain() {
+ }
+
+ public ExceptionsChain(Throwable exception) {
+ writeException(exception);
+ }
+
+ /** recursive */
+ protected void writeException(Throwable exception) {
+ SystemException systemException = new SystemException(exception);
+ exceptions.add(systemException);
+ Throwable cause = exception.getCause();
+ if (cause != null)
+ writeException(cause);
+ }
+
+ public List<SystemException> getExceptions() {
+ return exceptions;
+ }
+
+ public void setExceptions(List<SystemException> exceptions) {
+ this.exceptions = exceptions;
+ }
+
+ /** An exception in the chain. */
+ public static class SystemException {
+ private String type;
+ private String message;
+ private List<String> stackTrace;
+
+ public SystemException() {
+ }
+
+ public SystemException(Throwable exception) {
+ this.type = exception.getClass().getName();
+ this.message = exception.getMessage();
+ this.stackTrace = new ArrayList<>();
+ StackTraceElement[] elems = exception.getStackTrace();
+ for (int i = 0; i < elems.length; i++)
+ stackTrace.add("at " + elems[i].toString());
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ public void setMessage(String message) {
+ this.message = message;
+ }
+
+ public List<String> getStackTrace() {
+ return stackTrace;
+ }
+
+ public void setStackTrace(List<String> stackTrace) {
+ this.stackTrace = stackTrace;
+ }
+
+ @Override
+ public String toString() {
+ return "System exception: " + type + ", " + message + ", " + stackTrace;
+ }
+
+ }
+
+ @Override
+ public String toString() {
+ return exceptions.toString();
+ }
+}
--- /dev/null
+package org.argeo.cms.util;
+
+import java.io.IOException;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+
+/** Utilities around the standard Java file abstractions. */
+public class FsUtils {
+
+ /** Deletes this path, recursively if needed. */
+ public static void copyDirectory(Path source, Path target) {
+ if (!Files.exists(source) || !Files.isDirectory(source))
+ throw new IllegalArgumentException(source + " is not a directory");
+ if (Files.exists(target) && !Files.isDirectory(target))
+ throw new IllegalArgumentException(target + " is not a directory");
+ try {
+ Files.createDirectories(target);
+ Files.walkFileTree(source, new SimpleFileVisitor<Path>() {
+
+ @Override
+ public FileVisitResult preVisitDirectory(Path directory, BasicFileAttributes attrs) throws IOException {
+ Path relativePath = source.relativize(directory);
+ Path targetDirectory = target.resolve(relativePath);
+ if (!Files.exists(targetDirectory))
+ Files.createDirectory(targetDirectory);
+ return FileVisitResult.CONTINUE;
+ }
+
+ @Override
+ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
+ Path relativePath = source.relativize(file);
+ Path targetFile = target.resolve(relativePath);
+ Files.copy(file, targetFile);
+ return FileVisitResult.CONTINUE;
+ }
+ });
+ } catch (IOException e) {
+ throw new RuntimeException("Cannot copy " + source + " to " + target, e);
+ }
+
+ }
+
+ /**
+ * Deletes this path, recursively if needed. Does nothing if the path does not
+ * exist.
+ */
+ public static void delete(Path path) {
+ try {
+ if (!Files.exists(path))
+ return;
+ Files.walkFileTree(path, new SimpleFileVisitor<Path>() {
+ @Override
+ public FileVisitResult postVisitDirectory(Path directory, IOException e) throws IOException {
+ if (e != null)
+ throw e;
+ Files.delete(directory);
+ return FileVisitResult.CONTINUE;
+ }
+
+ @Override
+ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
+ Files.delete(file);
+ return FileVisitResult.CONTINUE;
+ }
+ });
+ } catch (IOException e) {
+ throw new RuntimeException("Cannot delete " + path, e);
+ }
+ }
+
+ /** Singleton. */
+ private FsUtils() {
+ }
+
+}
--- /dev/null
+package org.argeo.cms.util;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Writer;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.time.ZonedDateTime;
+import java.time.temporal.ChronoUnit;
+import java.time.temporal.Temporal;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+
+import javax.naming.InvalidNameException;
+import javax.naming.ldap.LdapName;
+
+/** Utilities around Java basic features. */
+public class LangUtils {
+ /*
+ * NON-API OSGi
+ */
+ /**
+ * Returns an array with the names of the provided classes. Useful when
+ * registering services with multiple interfaces in OSGi.
+ */
+ public static String[] names(Class<?>... clzz) {
+ String[] res = new String[clzz.length];
+ for (int i = 0; i < clzz.length; i++)
+ res[i] = clzz[i].getName();
+ return res;
+ }
+
+// /*
+// * MAP
+// */
+// /**
+// * Creates a new {@link Map} with one key-value pair. Key should not be null,
+// * but if the value is null, it returns an empty {@link Map}.
+// *
+// * @deprecated Use {@link Collections#singletonMap(Object, Object)} instead.
+// */
+// @Deprecated
+// public static Map<String, Object> map(String key, Object value) {
+// assert key != null;
+// HashMap<String, Object> props = new HashMap<>();
+// if (value != null)
+// props.put(key, value);
+// return props;
+// }
+
+ /*
+ * DICTIONARY
+ */
+
+ /**
+ * Creates a new {@link Dictionary} with one key-value pair. Key should not be
+ * null, but if the value is null, it returns an empty {@link Dictionary}.
+ */
+ public static Dictionary<String, Object> dict(String key, Object value) {
+ assert key != null;
+ Hashtable<String, Object> props = new Hashtable<>();
+ if (value != null)
+ props.put(key, value);
+ return props;
+ }
+
+ /** @deprecated Use {@link #dict(String, Object)} instead. */
+ @Deprecated
+ public static Dictionary<String, Object> dico(String key, Object value) {
+ return dict(key, value);
+ }
+
+ /** Converts a {@link Dictionary} to a {@link Map} of strings. */
+ public static Map<String, String> dictToStringMap(Dictionary<String, ?> properties) {
+ if (properties == null) {
+ return null;
+ }
+ Map<String, String> res = new HashMap<>(properties.size());
+ Enumeration<String> keys = properties.keys();
+ while (keys.hasMoreElements()) {
+ String key = keys.nextElement();
+ res.put(key, properties.get(key).toString());
+ }
+ return res;
+ }
+
+ /** Converts a {@link Dictionary} to a {@link Map}. */
+ public static Map<String, Object> dictToMap(Dictionary<String, ?> properties) {
+ if (properties == null) {
+ return null;
+ }
+ Map<String, Object> res = new HashMap<>(properties.size());
+ Enumeration<String> keys = properties.keys();
+ while (keys.hasMoreElements()) {
+ String key = keys.nextElement();
+ res.put(key, properties.get(key));
+ }
+ return res;
+ }
+
+ /**
+ * Get a string property from this map, expecting to find it, or
+ * <code>null</code> if not found.
+ */
+ public static String get(Map<String, ?> map, String key) {
+ Object res = map.get(key);
+ if (res == null)
+ return null;
+ return res.toString();
+ }
+
+ /**
+ * Get a string property from this map, expecting to find it.
+ *
+ * @throws IllegalArgumentException if the key was not found
+ */
+ public static String getNotNull(Map<String, ?> map, String key) {
+ Object res = map.get(key);
+ if (res == null)
+ throw new IllegalArgumentException("Map " + map + " should contain key " + key);
+ return res.toString();
+ }
+
+ /**
+ * Wraps the keys of the provided {@link Dictionary} as an {@link Iterable}.
+ */
+ public static Iterable<String> keys(Dictionary<String, ?> props) {
+ assert props != null;
+ return new DictionaryKeys(props);
+ }
+
+ static String toJson(Dictionary<String, ?> props) {
+ return toJson(props, false);
+ }
+
+ static String toJson(Dictionary<String, ?> props, boolean pretty) {
+ StringBuilder sb = new StringBuilder();
+ sb.append('{');
+ if (pretty)
+ sb.append('\n');
+ Enumeration<String> keys = props.keys();
+ while (keys.hasMoreElements()) {
+ String key = keys.nextElement();
+ if (pretty)
+ sb.append(' ');
+ sb.append('\"').append(key).append('\"');
+ if (pretty)
+ sb.append(" : ");
+ else
+ sb.append(':');
+ sb.append('\"').append(props.get(key)).append('\"');
+ if (keys.hasMoreElements())
+ sb.append(", ");
+ if (pretty)
+ sb.append('\n');
+ }
+ sb.append('}');
+ return sb.toString();
+ }
+
+ static void storeAsProperties(Dictionary<String, Object> props, Path path) throws IOException {
+ if (props == null)
+ throw new IllegalArgumentException("Props cannot be null");
+ Properties toStore = new Properties();
+ for (Enumeration<String> keys = props.keys(); keys.hasMoreElements();) {
+ String key = keys.nextElement();
+ toStore.setProperty(key, props.get(key).toString());
+ }
+ try (OutputStream out = Files.newOutputStream(path)) {
+ toStore.store(out, null);
+ }
+ }
+
+ static void appendAsLdif(String dnBase, String dnKey, Dictionary<String, Object> props, Path path)
+ throws IOException {
+ if (props == null)
+ throw new IllegalArgumentException("Props cannot be null");
+ Object dnValue = props.get(dnKey);
+ String dnStr = dnKey + '=' + dnValue + ',' + dnBase;
+ LdapName dn;
+ try {
+ dn = new LdapName(dnStr);
+ } catch (InvalidNameException e) {
+ throw new IllegalArgumentException("Cannot interpret DN " + dnStr, e);
+ }
+ if (dnValue == null)
+ throw new IllegalArgumentException("DN key " + dnKey + " must have a value");
+ try (Writer writer = Files.newBufferedWriter(path, StandardOpenOption.APPEND, StandardOpenOption.CREATE)) {
+ writer.append("\ndn: ");
+ writer.append(dn.toString());
+ writer.append('\n');
+ for (Enumeration<String> keys = props.keys(); keys.hasMoreElements();) {
+ String key = keys.nextElement();
+ Object value = props.get(key);
+ writer.append(key);
+ writer.append(": ");
+ // FIXME deal with binary and multiple values
+ writer.append(value.toString());
+ writer.append('\n');
+ }
+ }
+ }
+
+ static Dictionary<String, Object> loadFromProperties(Path path) throws IOException {
+ Properties toLoad = new Properties();
+ try (InputStream in = Files.newInputStream(path)) {
+ toLoad.load(in);
+ }
+ Dictionary<String, Object> res = new Hashtable<String, Object>();
+ for (Object key : toLoad.keySet())
+ res.put(key.toString(), toLoad.get(key));
+ return res;
+ }
+
+ /*
+ * COLLECTIONS
+ */
+ /**
+ * Convert a comma-separated separated {@link String} or a {@link String} array
+ * to a {@link List} of {@link String}, trimming them. Useful to quickly
+ * interpret OSGi services properties.
+ *
+ * @return a {@link List} containing the trimmed {@link String}s, or an empty
+ * {@link List} if the argument was <code>null</code>.
+ */
+ public static List<String> toStringList(Object value) {
+ List<String> values = new ArrayList<>();
+ if (value == null)
+ return values;
+ String[] arr;
+ if (value instanceof String) {
+ arr = ((String) value).split(",");
+ } else if (value instanceof String[]) {
+ arr = (String[]) value;
+ } else {
+ throw new IllegalArgumentException("Unsupported value type " + value.getClass());
+ }
+ for (String str : arr) {
+ values.add(str.trim());
+ }
+ return values;
+ }
+
+ /** Size of an {@link Iterable}, optimised if it is a {@link Collection}. */
+ public static int size(Iterable<?> iterable) {
+ if (iterable instanceof Collection)
+ return ((Collection<?>) iterable).size();
+
+ int size = 0;
+ for (Iterator<?> it = iterable.iterator(); it.hasNext(); size++)
+ it.next();
+ return size;
+ }
+
+ public static <T> T getAt(Iterable<T> iterable, int index) {
+ if (iterable instanceof List) {
+ List<T> lst = ((List<T>) iterable);
+ if (index >= lst.size())
+ throw new IllegalArgumentException("Index " + index + " is not available (size is " + lst.size() + ")");
+ return lst.get(index);
+ }
+ int i = 0;
+ for (Iterator<T> it = iterable.iterator(); it.hasNext(); i++) {
+ if (i == index)
+ return it.next();
+ else
+ it.next();
+ }
+ throw new IllegalArgumentException("Index " + index + " is not available (size is " + i + ")");
+ }
+
+ /*
+ * EXCEPTIONS
+ */
+ /**
+ * Chain the messages of all causes (one per line, <b>starts with a line
+ * return</b>) without all the stack
+ */
+ public static String chainCausesMessages(Throwable t) {
+ StringBuffer buf = new StringBuffer();
+ chainCauseMessage(buf, t);
+ return buf.toString();
+ }
+
+ /** Recursive chaining of messages */
+ private static void chainCauseMessage(StringBuffer buf, Throwable t) {
+ buf.append('\n').append(' ').append(t.getClass().getCanonicalName()).append(": ").append(t.getMessage());
+ if (t.getCause() != null)
+ chainCauseMessage(buf, t.getCause());
+ }
+
+ /*
+ * TIME
+ */
+ /** Formats time elapsed since start. */
+ public static String since(ZonedDateTime start) {
+ ZonedDateTime now = ZonedDateTime.now();
+ return duration(start, now);
+ }
+
+ /** Formats a duration. */
+ public static String duration(Temporal start, Temporal end) {
+ long count = ChronoUnit.DAYS.between(start, end);
+ if (count != 0)
+ return count > 1 ? count + " days" : count + " day";
+ count = ChronoUnit.HOURS.between(start, end);
+ if (count != 0)
+ return count > 1 ? count + " hours" : count + " hours";
+ count = ChronoUnit.MINUTES.between(start, end);
+ if (count != 0)
+ return count > 1 ? count + " minutes" : count + " minute";
+ count = ChronoUnit.SECONDS.between(start, end);
+ return count > 1 ? count + " seconds" : count + " second";
+ }
+
+ /** Singleton constructor. */
+ private LangUtils() {
+
+ }
+
+}
--- /dev/null
+package org.argeo.cms.util;
+
+import java.io.File;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+/** When OS specific informations are needed. */
+public class OS {
+ public final static OS LOCAL = new OS();
+
+ private final String arch, name, version;
+
+ /** The OS of the running JVM */
+ protected OS() {
+ arch = System.getProperty("os.arch");
+ name = System.getProperty("os.name");
+ version = System.getProperty("os.version");
+ }
+
+ public String getArch() {
+ return arch;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getVersion() {
+ return version;
+ }
+
+ public boolean isMSWindows() {
+ // only MS Windows would use such an horrendous separator...
+ return File.separatorChar == '\\';
+ }
+
+ public String[] getDefaultShellCommand() {
+ if (!isMSWindows())
+ return new String[] { "/bin/bash", "-l", "-i" };
+ else
+ return new String[] { "cmd.exe", "/C" };
+ }
+
+ public static long getJvmPid() {
+ return ProcessHandle.current().pid();
+// String pidAndHost = ManagementFactory.getRuntimeMXBean().getName();
+// return Integer.parseInt(pidAndHost.substring(0, pidAndHost.indexOf('@')));
+ }
+
+ /**
+ * Get the runtime directory. It will be the environment variable
+ * XDG_RUNTIME_DIR if it is set, or ~/.cache/argeo if not.
+ */
+ public static Path getRunDir() {
+ Path runDir;
+ String xdgRunDir = System.getenv("XDG_RUNTIME_DIR");
+ if (xdgRunDir != null) {
+ // TODO support multiple names
+ runDir = Paths.get(xdgRunDir);
+ } else {
+ runDir = Paths.get(System.getProperty("user.home"), ".cache/argeo");
+ }
+ return runDir;
+ }
+}
--- /dev/null
+package org.argeo.cms.util;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.security.GeneralSecurityException;
+import java.security.InvalidKeyException;
+import java.security.Key;
+
+import javax.crypto.Cipher;
+import javax.crypto.CipherInputStream;
+import javax.crypto.CipherOutputStream;
+import javax.crypto.SecretKey;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.PBEKeySpec;
+import javax.crypto.spec.SecretKeySpec;
+
+public class PasswordEncryption {
+ public final static Integer DEFAULT_ITERATION_COUNT = 1024;
+ /** Stronger with 256, but causes problem with Oracle JVM */
+ public final static Integer DEFAULT_SECRETE_KEY_LENGTH = 256;
+ public final static Integer DEFAULT_SECRETE_KEY_LENGTH_RESTRICTED = 128;
+ 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";
+// public final static String DEFAULT_CHARSET = "UTF-8";
+ public final static Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
+
+ private Integer iterationCount = DEFAULT_ITERATION_COUNT;
+ private Integer secreteKeyLength = DEFAULT_SECRETE_KEY_LENGTH;
+ private String secreteKeyFactoryName = DEFAULT_SECRETE_KEY_FACTORY;
+ private String secreteKeyEncryption = DEFAULT_SECRETE_KEY_ENCRYPTION;
+ private String cipherName = DEFAULT_CIPHER_NAME;
+
+ private static byte[] DEFAULT_SALT_8 = { (byte) 0xA9, (byte) 0x9B, (byte) 0xC8, (byte) 0x32, (byte) 0x56,
+ (byte) 0x35, (byte) 0xE3, (byte) 0x03 };
+ private static byte[] DEFAULT_IV_16 = { (byte) 0xA9, (byte) 0x9B, (byte) 0xC8, (byte) 0x32, (byte) 0x56,
+ (byte) 0x35, (byte) 0xE3, (byte) 0x03, (byte) 0xA9, (byte) 0x9B, (byte) 0xC8, (byte) 0x32, (byte) 0x56,
+ (byte) 0x35, (byte) 0xE3, (byte) 0x03 };
+
+ private Key key;
+ private Cipher ecipher;
+ private Cipher dcipher;
+
+ private String securityProviderName = null;
+
+ /**
+ * This is up to the caller to clear the passed array. Neither copy of nor
+ * reference to the passed array is kept
+ */
+ public PasswordEncryption(char[] password) {
+ this(password, DEFAULT_SALT_8, DEFAULT_IV_16);
+ }
+
+ /**
+ * This is up to the caller to clear the passed array. Neither copies of nor
+ * references to the passed arrays are kept
+ */
+ public PasswordEncryption(char[] password, byte[] passwordSalt, byte[] initializationVector) {
+ try {
+ initKeyAndCiphers(password, passwordSalt, initializationVector);
+ } catch (InvalidKeyException e) {
+ Integer previousSecreteKeyLength = secreteKeyLength;
+ secreteKeyLength = DEFAULT_SECRETE_KEY_LENGTH_RESTRICTED;
+ System.err.println("'" + e.getMessage() + "', will use " + secreteKeyLength
+ + " secrete key length instead of " + previousSecreteKeyLength);
+ try {
+ initKeyAndCiphers(password, passwordSalt, initializationVector);
+ } catch (GeneralSecurityException e1) {
+ throw new IllegalStateException("Cannot get secret key (with restricted length)", e1);
+ }
+ } catch (GeneralSecurityException e) {
+ throw new IllegalStateException("Cannot get secret key", e);
+ }
+ }
+
+ protected void initKeyAndCiphers(char[] password, byte[] passwordSalt, byte[] initializationVector)
+ throws GeneralSecurityException {
+ byte[] salt = new byte[8];
+ System.arraycopy(passwordSalt, 0, salt, 0, salt.length);
+ // for (int i = 0; i < password.length && i < salt.length; i++)
+ // salt[i] = (byte) password[i];
+ byte[] iv = new byte[16];
+ System.arraycopy(initializationVector, 0, iv, 0, iv.length);
+
+ SecretKeyFactory keyFac = SecretKeyFactory.getInstance(getSecretKeyFactoryName());
+ PBEKeySpec keySpec = new PBEKeySpec(password, salt, getIterationCount(), getKeyLength());
+ String secKeyEncryption = getSecretKeyEncryption();
+ if (secKeyEncryption != null) {
+ SecretKey tmp = keyFac.generateSecret(keySpec);
+ key = new SecretKeySpec(tmp.getEncoded(), getSecretKeyEncryption());
+ } else {
+ key = keyFac.generateSecret(keySpec);
+ }
+ if (securityProviderName != null)
+ ecipher = Cipher.getInstance(getCipherName(), securityProviderName);
+ else
+ ecipher = Cipher.getInstance(getCipherName());
+ ecipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv));
+ dcipher = Cipher.getInstance(getCipherName());
+ dcipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));
+ }
+
+ public void encrypt(InputStream decryptedIn, OutputStream encryptedOut) throws IOException {
+ try {
+ CipherOutputStream out = new CipherOutputStream(encryptedOut, ecipher);
+ StreamUtils.copy(decryptedIn, out);
+ StreamUtils.closeQuietly(out);
+ } catch (IOException e) {
+ throw e;
+ } finally {
+ StreamUtils.closeQuietly(decryptedIn);
+ }
+ }
+
+ public void decrypt(InputStream encryptedIn, OutputStream decryptedOut) throws IOException {
+ try {
+ CipherInputStream decryptedIn = new CipherInputStream(encryptedIn, dcipher);
+ StreamUtils.copy(decryptedIn, decryptedOut);
+ } catch (IOException e) {
+ throw e;
+ } finally {
+ StreamUtils.closeQuietly(encryptedIn);
+ }
+ }
+
+ public byte[] encryptString(String str) {
+ ByteArrayOutputStream out = null;
+ ByteArrayInputStream in = null;
+ try {
+ out = new ByteArrayOutputStream();
+ in = new ByteArrayInputStream(str.getBytes(DEFAULT_CHARSET));
+ encrypt(in, out);
+ return out.toByteArray();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ } finally {
+ StreamUtils.closeQuietly(out);
+ }
+ }
+
+ /** Closes the input stream */
+ public String decryptAsString(InputStream in) {
+ ByteArrayOutputStream out = null;
+ try {
+ out = new ByteArrayOutputStream();
+ decrypt(in, out);
+ return new String(out.toByteArray(), DEFAULT_CHARSET);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ } finally {
+ StreamUtils.closeQuietly(out);
+ }
+ }
+
+ protected Key getKey() {
+ return key;
+ }
+
+ protected Cipher getEcipher() {
+ return ecipher;
+ }
+
+ protected Cipher getDcipher() {
+ return dcipher;
+ }
+
+ protected Integer getIterationCount() {
+ return iterationCount;
+ }
+
+ protected Integer getKeyLength() {
+ return secreteKeyLength;
+ }
+
+ protected String getSecretKeyFactoryName() {
+ return secreteKeyFactoryName;
+ }
+
+ protected String getSecretKeyEncryption() {
+ return secreteKeyEncryption;
+ }
+
+ protected String getCipherName() {
+ return cipherName;
+ }
+
+ public void setIterationCount(Integer iterationCount) {
+ this.iterationCount = iterationCount;
+ }
+
+ public void setSecreteKeyLength(Integer keyLength) {
+ this.secreteKeyLength = keyLength;
+ }
+
+ public void setSecreteKeyFactoryName(String secreteKeyFactoryName) {
+ this.secreteKeyFactoryName = secreteKeyFactoryName;
+ }
+
+ public void setSecreteKeyEncryption(String secreteKeyEncryption) {
+ this.secreteKeyEncryption = secreteKeyEncryption;
+ }
+
+ public void setCipherName(String cipherName) {
+ this.cipherName = cipherName;
+ }
+
+ public void setSecurityProviderName(String securityProviderName) {
+ this.securityProviderName = securityProviderName;
+ }
+}
--- /dev/null
+package org.argeo.cms.util;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.AsynchronousByteChannel;
+import java.nio.channels.CompletionHandler;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.channels.WritableByteChannel;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+
+/** An {@link AsynchronousByteChannel} based on an {@link ExecutorService}. */
+public class ServiceChannel implements AsynchronousByteChannel {
+ private final ReadableByteChannel in;
+ private final WritableByteChannel out;
+
+ private boolean open = true;
+
+ private ExecutorService executor;
+
+ public ServiceChannel(ReadableByteChannel in, WritableByteChannel out, ExecutorService executor) {
+ this.in = in;
+ this.out = out;
+ this.executor = executor;
+ }
+
+ @Override
+ public Future<Integer> read(ByteBuffer dst) {
+ return executor.submit(() -> in.read(dst));
+ }
+
+ @Override
+ public <A> void read(ByteBuffer dst, A attachment, CompletionHandler<Integer, ? super A> handler) {
+ try {
+ Future<Integer> res = read(dst);
+ handler.completed(res.get(), attachment);
+ } catch (Exception e) {
+ handler.failed(e, attachment);
+ }
+ }
+
+ @Override
+ public Future<Integer> write(ByteBuffer src) {
+ return executor.submit(() -> out.write(src));
+ }
+
+ @Override
+ public <A> void write(ByteBuffer src, A attachment, CompletionHandler<Integer, ? super A> handler) {
+ try {
+ Future<Integer> res = write(src);
+ handler.completed(res.get(), attachment);
+ } catch (Exception e) {
+ handler.failed(e, attachment);
+ }
+ }
+
+ @Override
+ public synchronized void close() throws IOException {
+ try {
+ in.close();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ try {
+ out.close();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ open = false;
+ notifyAll();
+ }
+
+ @Override
+ public synchronized boolean isOpen() {
+ return open;
+ }
+
+}
--- /dev/null
+package org.argeo.cms.util;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Reader;
+import java.io.Writer;
+import java.util.StringJoiner;
+
+/** Stream utilities to be used when Apache Commons IO is not available. */
+public class StreamUtils {
+ private static final int DEFAULT_BUFFER_SIZE = 1024 * 4;
+
+ /*
+ * APACHE COMMONS IO (inspired)
+ */
+
+ /** @return the number of bytes */
+ public static Long copy(InputStream in, OutputStream out) throws IOException {
+ Long count = 0l;
+ byte[] buf = new byte[DEFAULT_BUFFER_SIZE];
+ while (true) {
+ int length = in.read(buf);
+ if (length < 0)
+ break;
+ out.write(buf, 0, length);
+ count = count + length;
+ }
+ return count;
+ }
+
+ /** @return the number of chars */
+ public static Long copy(Reader in, Writer out) throws IOException {
+ Long count = 0l;
+ char[] buf = new char[DEFAULT_BUFFER_SIZE];
+ while (true) {
+ int length = in.read(buf);
+ if (length < 0)
+ break;
+ out.write(buf, 0, length);
+ count = count + length;
+ }
+ return count;
+ }
+
+ public static byte[] toByteArray(InputStream in) throws IOException {
+ try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
+ copy(in, out);
+ return out.toByteArray();
+ }
+ }
+
+ public static void closeQuietly(InputStream in) {
+ if (in != null)
+ try {
+ in.close();
+ } catch (Exception e) {
+ //
+ }
+ }
+
+ public static void closeQuietly(OutputStream out) {
+ if (out != null)
+ try {
+ out.close();
+ } catch (Exception e) {
+ //
+ }
+ }
+
+ public static void closeQuietly(Reader in) {
+ if (in != null)
+ try {
+ in.close();
+ } catch (Exception e) {
+ //
+ }
+ }
+
+ public static void closeQuietly(Writer out) {
+ if (out != null)
+ try {
+ out.close();
+ } catch (Exception e) {
+ //
+ }
+ }
+
+ public static String toString(BufferedReader reader) throws IOException {
+ StringJoiner sn = new StringJoiner("\n");
+ String line = null;
+ while ((line = reader.readLine()) != null)
+ sn.add(line);
+ return sn.toString();
+ }
+}
--- /dev/null
+package org.argeo.cms.util;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+/** A generic tester based on Java assertions and functional programming. */
+public class Tester {
+ private Map<String, TesterStatus> results = Collections.synchronizedSortedMap(new TreeMap<>());
+
+ private ClassLoader classLoader;
+
+ /** Use {@link Thread#getContextClassLoader()} by default. */
+ public Tester() {
+ this(Thread.currentThread().getContextClassLoader());
+ }
+
+ public Tester(ClassLoader classLoader) {
+ this.classLoader = classLoader;
+ }
+
+ public void execute(String className) {
+ Class<?> clss;
+ try {
+ clss = classLoader.loadClass(className);
+ boolean assertionsEnabled = clss.desiredAssertionStatus();
+ if (!assertionsEnabled)
+ throw new IllegalStateException("Test runner " + getClass().getName()
+ + " requires Java assertions to be enabled. Call the JVM with the -ea argument.");
+ } catch (Exception e1) {
+ throw new IllegalArgumentException("Cannot initalise test for " + className, e1);
+
+ }
+ List<Method> methods = findMethods(clss);
+ if (methods.size() == 0)
+ throw new IllegalArgumentException("No test method found in " + clss);
+ // TODO make order more predictable?
+ for (Method method : methods) {
+ String uid = method.getDeclaringClass().getName() + "#" + method.getName();
+ TesterStatus testStatus = new TesterStatus(uid);
+ Object obj = null;
+ try {
+ beforeTest(uid, method);
+ obj = clss.getDeclaredConstructor().newInstance();
+ method.invoke(obj);
+ testStatus.setPassed();
+ afterTestPassed(uid, method, obj);
+ } catch (Exception e) {
+ testStatus.setFailed(e);
+ afterTestFailed(uid, method, obj, e);
+ } finally {
+ results.put(uid, testStatus);
+ }
+ }
+ }
+
+ protected void beforeTest(String uid, Method method) {
+ // System.out.println(uid + ": STARTING");
+ }
+
+ protected void afterTestPassed(String uid, Method method, Object obj) {
+ System.out.println(uid + ": PASSED");
+ }
+
+ protected void afterTestFailed(String uid, Method method, Object obj, Throwable e) {
+ System.out.println(uid + ": FAILED");
+ e.printStackTrace();
+ }
+
+ protected List<Method> findMethods(Class<?> clss) {
+ List<Method> methods = new ArrayList<Method>();
+// Method call = getMethod(clss, "call");
+// if (call != null)
+// methods.add(call);
+//
+ for (Method method : clss.getMethods()) {
+ if (method.getName().startsWith("test")) {
+ methods.add(method);
+ }
+ }
+ return methods;
+ }
+
+ protected Method getMethod(Class<?> clss, String name, Class<?>... parameterTypes) {
+ try {
+ return clss.getMethod(name, parameterTypes);
+ } catch (NoSuchMethodException e) {
+ return null;
+ } catch (SecurityException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ public static void main(String[] args) {
+ // deal with arguments
+ String className;
+ if (args.length < 1) {
+ System.err.println(usage());
+ System.exit(1);
+ throw new IllegalArgumentException();
+ } else {
+ className = args[0];
+ }
+
+ Tester test = new Tester();
+ try {
+ test.execute(className);
+ } catch (Throwable e) {
+ e.printStackTrace();
+ }
+
+ Map<String, TesterStatus> r = test.results;
+ for (String uid : r.keySet()) {
+ TesterStatus testStatus = r.get(uid);
+ System.out.println(testStatus);
+ }
+ }
+
+ public static String usage() {
+ return "java " + Tester.class.getName() + " [test class name]";
+
+ }
+}
--- /dev/null
+package org.argeo.cms.util;
+
+import java.io.Serializable;
+
+/** The status of a test. */
+public class TesterStatus implements Serializable {
+ private static final long serialVersionUID = 6272975746885487000L;
+
+ private Boolean passed = null;
+ private final String uid;
+ private Throwable throwable = null;
+
+ public TesterStatus(String uid) {
+ this.uid = uid;
+ }
+
+ /** For cloning. */
+ public TesterStatus(String uid, Boolean passed, Throwable throwable) {
+ this(uid);
+ this.passed = passed;
+ this.throwable = throwable;
+ }
+
+ public synchronized Boolean isRunning() {
+ return passed == null;
+ }
+
+ public synchronized Boolean isPassed() {
+ assert passed != null;
+ return passed;
+ }
+
+ public synchronized Boolean isFailed() {
+ assert passed != null;
+ return !passed;
+ }
+
+ public synchronized void setPassed() {
+ setStatus(true);
+ }
+
+ public synchronized void setFailed() {
+ setStatus(false);
+ }
+
+ public synchronized void setFailed(Throwable throwable) {
+ setStatus(false);
+ setThrowable(throwable);
+ }
+
+ protected void setStatus(Boolean passed) {
+ if (this.passed != null)
+ throw new IllegalStateException("Passed status of test " + uid + " is already set (to " + passed + ")");
+ this.passed = passed;
+ }
+
+ protected void setThrowable(Throwable throwable) {
+ if (this.throwable != null)
+ throw new IllegalStateException("Throwable of test " + uid + " is already set (to " + passed + ")");
+ this.throwable = throwable;
+ }
+
+ public String getUid() {
+ return uid;
+ }
+
+ public Throwable getThrowable() {
+ return throwable;
+ }
+
+ @Override
+ protected Object clone() throws CloneNotSupportedException {
+ // TODO Auto-generated method stub
+ return super.clone();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof TesterStatus) {
+ TesterStatus other = (TesterStatus) o;
+ // we don't check consistency for performance purposes
+ // this equals() is supposed to be used in collections or for transfer
+ return other.uid.equals(uid);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return uid.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return uid + "\t" + (passed ? "passed" : "failed");
+ }
+
+}
--- /dev/null
+package org.argeo.cms.util;
+
+import java.text.NumberFormat;
+import java.text.ParseException;
+import java.util.Locale;
+
+/** A throughput, that is, a value per unit of time. */
+public class Throughput {
+ private final static NumberFormat usNumberFormat = NumberFormat.getInstance(Locale.US);
+
+ public enum Unit {
+ s, m, h, d
+ }
+
+ private final Double value;
+ private final Unit unit;
+
+ public Throughput(Double value, Unit unit) {
+ this.value = value;
+ this.unit = unit;
+ }
+
+ public Throughput(Long periodMs, Long count, Unit unit) {
+ if (unit.equals(Unit.s))
+ value = ((double) count * 1000d) / periodMs;
+ else if (unit.equals(Unit.m))
+ value = ((double) count * 60d * 1000d) / periodMs;
+ else if (unit.equals(Unit.h))
+ value = ((double) count * 60d * 60d * 1000d) / periodMs;
+ else if (unit.equals(Unit.d))
+ value = ((double) count * 24d * 60d * 60d * 1000d) / periodMs;
+ else
+ throw new IllegalArgumentException("Unsupported unit " + unit);
+ this.unit = unit;
+ }
+
+ public Throughput(Double value, String unitStr) {
+ this(value, Unit.valueOf(unitStr));
+ }
+
+ public Throughput(String def) {
+ int index = def.indexOf('/');
+ if (def.length() < 3 || index <= 0 || index != def.length() - 2)
+ throw new IllegalArgumentException(
+ def + " no a proper throughput definition" + " (should be <value>/<unit>, e.g. 3.54/s or 1500/h");
+ String valueStr = def.substring(0, index);
+ String unitStr = def.substring(index + 1);
+ try {
+ this.value = usNumberFormat.parse(valueStr).doubleValue();
+ } catch (ParseException e) {
+ throw new IllegalArgumentException("Cannot parse " + valueStr + " as a number.", e);
+ }
+ this.unit = Unit.valueOf(unitStr);
+ }
+
+ public Long asMsPeriod() {
+ if (unit.equals(Unit.s))
+ return Math.round(1000d / value);
+ else if (unit.equals(Unit.m))
+ return Math.round((60d * 1000d) / value);
+ else if (unit.equals(Unit.h))
+ return Math.round((60d * 60d * 1000d) / value);
+ else if (unit.equals(Unit.d))
+ return Math.round((24d * 60d * 60d * 1000d) / value);
+ else
+ throw new IllegalArgumentException("Unsupported unit " + unit);
+ }
+
+ @Override
+ public String toString() {
+ return usNumberFormat.format(value) + '/' + unit;
+ }
+
+ public Double getValue() {
+ return value;
+ }
+
+ public Unit getUnit() {
+ return unit;
+ }
+
+}
--- /dev/null
+/** Generic Java utilities. */
+package org.argeo.cms.util;
\ No newline at end of file
<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="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-17">
<attributes>
<attribute name="module" value="true"/>
</attributes>
package org.argeo.init;
+import java.io.IOException;
+import java.io.InputStream;
import java.lang.System.Logger;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
+import java.util.Objects;
+import java.util.Properties;
+import org.argeo.init.logging.ThinLoggerFinder;
+import org.argeo.init.osgi.OsgiBoot;
import org.argeo.init.osgi.OsgiRuntimeContext;
/** Configure and launch an Argeo service. */
-public class Service implements Runnable, AutoCloseable {
- private final static Logger log = System.getLogger(Service.class.getName());
+public class Service {
+ private final static Logger logger = System.getLogger(Service.class.getName());
+
+ public final static String PROP_ARGEO_INIT_MAIN = "argeo.init.main";
private static RuntimeContext runtimeContext = null;
protected Service(String[] args) {
}
- @Override
- public void run() {
- }
-
- @Override
- public void close() throws Exception {
- }
-
public static void main(String[] args) {
- long pid = ProcessHandle.current().pid();
- log.log(Logger.Level.DEBUG, "Argeo Init starting with PID " + pid);
+ final long pid = ProcessHandle.current().pid();
+ logger.log(Logger.Level.DEBUG, () -> "Argeo Init starting with PID " + pid);
// shutdown on exit
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
}
} catch (Exception e) {
e.printStackTrace();
- System.exit(1);
+ Runtime.getRuntime().halt(1);
}
}, "Runtime shutdown"));
+ // TODO use args as well
+ String dataArea = System.getProperty(OsgiBoot.PROP_OSGI_INSTANCE_AREA);
+ String stateArea = System.getProperty(OsgiBoot.PROP_OSGI_CONFIGURATION_AREA);
+ String configArea = System.getProperty(OsgiBoot.PROP_OSGI_SHARED_CONFIGURATION_AREA);
+
+ if (configArea != null) {
+ Path configAreaPath = Paths.get(configArea);
+ Path additionalSystemPropertiesPath = configAreaPath.resolve("system.properties");
+ if (Files.exists(additionalSystemPropertiesPath)) {
+ Properties properties = new Properties();
+ try (InputStream in = Files.newInputStream(additionalSystemPropertiesPath)) {
+ properties.load(in);
+ } catch (IOException e) {
+ logger.log(Logger.Level.ERROR,
+ "Cannot load additional system properties " + additionalSystemPropertiesPath, e);
+ }
+
+ for (Object key : properties.keySet()) {
+ String currentValue = System.getProperty(key.toString());
+ String value = properties.getProperty(key.toString());
+ if (currentValue != null) {
+ if (!Objects.equals(value, currentValue))
+ logger.log(Logger.Level.WARNING, "System property " + key + " already set with value "
+ + currentValue + " instead of " + value + ". Ignoring new value.");
+ } else {
+ System.setProperty(key.toString(), value);
+ }
+ }
+ ThinLoggerFinder.reloadConfiguration();
+ }
+ }
+
Map<String, String> config = new HashMap<>();
- config.put("osgi.framework.useSystemProperties", "true");
-// for (Object key : System.getProperties().keySet()) {
-// config.put(key.toString(), System.getProperty(key.toString()));
-// log.log(Logger.Level.DEBUG, key + "=" + System.getProperty(key.toString()));
-// }
+ config.put(PROP_ARGEO_INIT_MAIN, "true");
+
try {
try {
- OsgiRuntimeContext osgiRuntimeContext = new OsgiRuntimeContext((Map<String, String>) config);
+ if (stateArea != null)
+ config.put(OsgiBoot.PROP_OSGI_CONFIGURATION_AREA, stateArea);
+ if (configArea != null)
+ config.put(OsgiBoot.PROP_OSGI_SHARED_CONFIGURATION_AREA, configArea);
+ if (dataArea != null)
+ config.put(OsgiBoot.PROP_OSGI_INSTANCE_AREA, dataArea);
+ // config.put(OsgiBoot.PROP_OSGI_USE_SYSTEM_PROPERTIES, "true");
+
+ OsgiRuntimeContext osgiRuntimeContext = new OsgiRuntimeContext(config);
osgiRuntimeContext.run();
Service.runtimeContext = osgiRuntimeContext;
Service.runtimeContext.waitForStop(0);
e.printStackTrace();
System.exit(1);
}
- log.log(Logger.Level.DEBUG, "Argeo Init stopped with PID " + pid);
+ logger.log(Logger.Level.DEBUG, "Argeo Init stopped with PID " + pid);
}
-
public static RuntimeContext getRuntimeContext() {
return runtimeContext;
}
component.branches.put(id, this);
}
+ public Iterable<A2Module> listModules(Object filter) {
+ return modules.values();
+ }
+
A2Module getOrAddModule(Version version, Object locator) {
if (modules.containsKey(version)) {
A2Module res = modules.get(version);
return new A2Module(this, version, locator);
}
- A2Module last() {
+ public A2Module last() {
return modules.get(modules.lastKey());
}
- A2Module first() {
+ public A2Module first() {
return modules.get(modules.firstKey());
}
- A2Component getComponent() {
+ public A2Component getComponent() {
return component;
}
- String getId() {
+ public String getId() {
return id;
}
contribution.components.put(id, this);
}
+ public Iterable<A2Branch> listBranches(Object filter) {
+ return branches.values();
+ }
+
A2Branch getOrAddBranch(String branchId) {
if (branches.containsKey(branchId))
return branches.get(branchId);
return module;
}
- A2Branch last() {
+ public A2Branch last() {
return branches.get(branches.lastKey());
}
- A2Contribution getContribution() {
+ public A2Contribution getContribution() {
return contribution;
}
- String getId() {
+ public String getId() {
return id;
}
final static String RUNTIME = "runtime";
final static String CLASSPATH = "classpath";
+ final static String DEFAULT = "default";
+
private final ProvisioningSource source;
private final String id;
// context.contributions.put(id, this);
}
+ public Iterable<A2Component> listComponents(Object filter) {
+ return components.values();
+ }
+
A2Component getOrAddComponent(String componentId) {
if (components.containsKey(componentId))
return components.get(componentId);
* <code>Bundle-SymbolicName</code> and <code>Bundle-version</code>. This is the
* equivalent of the full coordinates of a Maven artifact version.
*/
-class A2Module implements Comparable<A2Module> {
+public class A2Module implements Comparable<A2Module> {
private final A2Branch branch;
private final Version version;
private final Object locator;
branch.modules.put(version, this);
}
- A2Branch getBranch() {
+ public A2Branch getBranch() {
return branch;
}
- Version getVersion() {
+ public Version getVersion() {
return version;
}
package org.argeo.init.a2;
+import java.net.URI;
+
/** A provisioning source in A2 format. */
public interface A2Source extends ProvisioningSource {
+ /** Use standard a2 protocol, installing from source URL. */
final static String SCHEME_A2 = "a2";
+ /**
+ * Use equinox-specific reference: installation, which does not copy the bundle
+ * content.
+ */
+ final static String SCHEME_A2_REFERENCE = "a2+reference";
final static String DEFAULT_A2_URI = SCHEME_A2 + ":///";
+ final static String DEFAULT_A2_REFERENCE_URI = SCHEME_A2_REFERENCE + ":///";
+
+ URI getUri();
}
public abstract class AbstractProvisioningSource implements ProvisioningSource {
protected final Map<String, A2Contribution> contributions = Collections.synchronizedSortedMap(new TreeMap<>());
+ private final boolean usingReference;
+
+ public AbstractProvisioningSource(boolean usingReference) {
+ this.usingReference = usingReference;
+ }
+
public Iterable<A2Contribution> listContributions(Object filter) {
return contributions.values();
}
@Override
public Bundle install(BundleContext bc, A2Module module) {
try {
- Path tempJar = null;
- if (module.getLocator() instanceof Path && Files.isDirectory((Path) module.getLocator()))
- tempJar = toTempJar((Path) module.getLocator());
- Bundle bundle;
- try (InputStream in = newInputStream(tempJar != null ? tempJar : module.getLocator())) {
- bundle = bc.installBundle(module.getBranch().getCoordinates(), in);
+ Object locator = module.getLocator();
+ if (usingReference && locator instanceof Path locatorPath) {
+ String referenceUrl = "reference:file:" + locatorPath.toString();
+ Bundle bundle = bc.installBundle(referenceUrl);
+ return bundle;
+ } else {
+
+ Path tempJar = null;
+ if (locator instanceof Path && Files.isDirectory((Path) locator))
+ tempJar = toTempJar((Path) locator);
+ Bundle bundle;
+ try (InputStream in = newInputStream(tempJar != null ? tempJar : locator)) {
+ bundle = bc.installBundle(module.getBranch().getCoordinates(), in);
+ }
+
+ if (tempJar != null)
+ Files.deleteIfExists(tempJar);
+ return bundle;
}
- if (tempJar != null)
- Files.deleteIfExists(tempJar);
- return bundle;
} catch (BundleException | IOException e) {
throw new A2Exception("Cannot install module " + module, e);
}
@Override
public void update(Bundle bundle, A2Module module) {
try {
- Path tempJar = null;
- if (module.getLocator() instanceof Path && Files.isDirectory((Path) module.getLocator()))
- tempJar = toTempJar((Path) module.getLocator());
- try (InputStream in = newInputStream(tempJar != null ? tempJar : module.getLocator())) {
- bundle.update(in);
+ Object locator = module.getLocator();
+ if (usingReference && locator instanceof Path) {
+ try (InputStream in = newInputStream(locator)) {
+ bundle.update(in);
+ }
+ } else {
+ Path tempJar = null;
+ if (locator instanceof Path && Files.isDirectory((Path) locator))
+ tempJar = toTempJar((Path) locator);
+ try (InputStream in = newInputStream(tempJar != null ? tempJar : locator)) {
+ bundle.update(in);
+ }
+ if (tempJar != null)
+ Files.deleteIfExists(tempJar);
}
- if (tempJar != null)
- Files.deleteIfExists(tempJar);
} catch (BundleException | IOException e) {
throw new A2Exception("Cannot update module " + module, e);
}
}
+ protected String[] readNameVersionFromModule(Path modulePath) {
+ Manifest manifest;
+ if (Files.isDirectory(modulePath)) {
+ manifest = findManifest(modulePath);
+ } else {
+ try (JarInputStream in = new JarInputStream(newInputStream(modulePath))) {
+ manifest = in.getManifest();
+ } catch (IOException e) {
+ throw new A2Exception("Cannot read manifest from " + modulePath, e);
+ }
+ }
+ String versionStr = manifest.getMainAttributes().getValue(Constants.BUNDLE_VERSION);
+ String symbolicName = manifest.getMainAttributes().getValue(Constants.BUNDLE_SYMBOLICNAME);
+ int semiColIndex = symbolicName.indexOf(';');
+ if (semiColIndex >= 0)
+ symbolicName = symbolicName.substring(0, semiColIndex);
+ return new String[] { symbolicName, versionStr };
+ }
+
protected String readVersionFromModule(Path modulePath) {
Manifest manifest;
if (Files.isDirectory(modulePath)) {
return symbolicName;
}
+ protected boolean isUsingReference() {
+ return usingReference;
+ }
+
+ private InputStream newInputStream(Object locator) throws IOException {
+ if (locator instanceof Path) {
+ return Files.newInputStream((Path) locator);
+ } else if (locator instanceof URL) {
+ return ((URL) locator).openStream();
+ } else {
+ throw new IllegalArgumentException("Unsupported module locator type " + locator.getClass());
+ }
+ }
+
private static Manifest findManifest(Path currentPath) {
Path metaInfPath = currentPath.resolve("META-INF");
if (Files.exists(metaInfPath) && Files.isDirectory(metaInfPath)) {
}
- private InputStream newInputStream(Object locator) throws IOException {
- if (locator instanceof Path) {
- return Files.newInputStream((Path) locator);
- } else if (locator instanceof URL) {
- return ((URL) locator).openStream();
- } else {
- throw new IllegalArgumentException("Unsupported module locator type " + locator.getClass());
- }
- }
}
import org.osgi.framework.Version;
/**
- * A provisioning source based on the linear classpath with which the JCM has
+ * A provisioning source based on the linear classpath with which the JVM has
* been started.
*/
public class ClasspathSource extends AbstractProvisioningSource {
+
+ public ClasspathSource() {
+ super(true);
+ }
+
void load() throws IOException {
A2Contribution classpathContribution = getOrAddContribution( A2Contribution.CLASSPATH);
List<String> classpath = Arrays.asList(System.getProperty("java.class.path").split(File.pathSeparator));
package org.argeo.init.a2;
import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.SortedSet;
-import java.util.TreeSet;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.SortedMap;
+import java.util.StringJoiner;
+import java.util.TreeMap;
import org.argeo.init.osgi.OsgiBootUtils;
import org.osgi.framework.Version;
/** A file system {@link AbstractProvisioningSource} in A2 format. */
public class FsA2Source extends AbstractProvisioningSource implements A2Source {
private final Path base;
+ private final Map<String, String> variantsXOr;
- public FsA2Source(Path base) {
- super();
+// public FsA2Source(Path base) {
+// this(base, new HashMap<>());
+// }
+
+ public FsA2Source(Path base, Map<String, String> variantsXOr, boolean usingReference) {
+ super(usingReference);
this.base = base;
+ this.variantsXOr = new HashMap<>(variantsXOr);
}
void load() throws IOException {
+ SortedMap<Path, A2Contribution> contributions = new TreeMap<>();
+
DirectoryStream<Path> contributionPaths = Files.newDirectoryStream(base);
- SortedSet<A2Contribution> contributions = new TreeSet<>();
contributions: for (Path contributionPath : contributionPaths) {
if (Files.isDirectory(contributionPath)) {
String contributionId = contributionPath.getFileName().toString();
if (A2Contribution.BOOT.equals(contributionId))// skip boot
continue contributions;
- A2Contribution contribution = getOrAddContribution(contributionId);
- contributions.add(contribution);
+ if (contributionId.contains(".")) {
+ A2Contribution contribution = getOrAddContribution(contributionId);
+ contributions.put(contributionPath, contribution);
+ } else {// variants
+ Path variantPath = null;
+ // is it an explicit variant?
+ String variant = variantsXOr.get(contributionPath.getFileName().toString());
+ if (variant != null) {
+ variantPath = contributionPath.resolve(variant);
+ }
+
+ // is there a default variant?
+ if (variantPath == null) {
+ Path defaultPath = contributionPath.resolve(A2Contribution.DEFAULT);
+ if (Files.exists(defaultPath)) {
+ variantPath = defaultPath;
+ }
+ }
+
+ if (variantPath == null)
+ continue contributions;
+
+ // a variant was found, let's collect its contributions (also common ones in its
+ // parent)
+ for (Path variantContributionPath : Files.newDirectoryStream(variantPath.getParent())) {
+ String variantContributionId = variantContributionPath.getFileName().toString();
+ if (variantContributionId.contains(".")) {
+ A2Contribution contribution = getOrAddContribution(variantContributionId);
+ contributions.put(variantContributionPath, contribution);
+ }
+ }
+ for (Path variantContributionPath : Files.newDirectoryStream(variantPath)) {
+ String variantContributionId = variantContributionPath.getFileName().toString();
+ if (variantContributionId.contains(".")) {
+ A2Contribution contribution = getOrAddContribution(variantContributionId);
+ contributions.put(variantContributionPath, contribution);
+ }
+ }
+ }
}
}
- for (A2Contribution contribution : contributions) {
- DirectoryStream<Path> modulePaths = Files.newDirectoryStream(base.resolve(contribution.getId()));
+ for (Path contributionPath : contributions.keySet()) {
+ String contributionId = contributionPath.getFileName().toString();
+ A2Contribution contribution = getOrAddContribution(contributionId);
+ DirectoryStream<Path> modulePaths = Files.newDirectoryStream(contributionPath);
modules: for (Path modulePath : modulePaths) {
if (!Files.isDirectory(modulePath)) {
// OsgiBootUtils.debug("Registering " + modulePath);
String ext = moduleFileName.substring(lastDot + 1);
if (!"jar".equals(ext))
continue modules;
-// String moduleName = moduleFileName.substring(0, lastDot);
-// if (moduleName.endsWith("-SNAPSHOT"))
-// moduleName = moduleName.substring(0, moduleName.length() - "-SNAPSHOT".length());
-// int lastDash = moduleName.lastIndexOf('-');
-// String versionStr = moduleName.substring(lastDash + 1);
-// String componentName = moduleName.substring(0, lastDash);
- // if(versionStr.endsWith("-SNAPSHOT")) {
- // versionStr = readVersionFromModule(modulePath);
- // }
Version version;
-// try {
-// version = new Version(versionStr);
-// } catch (Exception e) {
- String versionStr = readVersionFromModule(modulePath);
- String componentName = readSymbolicNameFromModule(modulePath);
+ // TODO optimise? check attributes?
+ String[] nameVersion = readNameVersionFromModule(modulePath);
+ String componentName = nameVersion[0];
+ String versionStr = nameVersion[1];
if (versionStr != null) {
version = new Version(versionStr);
} else {
}
- public static void main(String[] args) {
+ @Override
+ public URI getUri() {
+ URI baseUri = base.toUri();
try {
- FsA2Source context = new FsA2Source(Paths.get(
- "/home/mbaudier/dev/git/apache2/argeo-commons/dist/argeo-node/target/argeo-node-2.1.77-SNAPSHOT/share/osgi"));
- context.load();
- context.asTree();
- } catch (Exception e) {
- e.printStackTrace();
+ if (baseUri.getScheme().equals("file")) {
+ String queryPart = "";
+ if (!getVariantsXOr().isEmpty()) {
+ StringJoiner sj = new StringJoiner("&");
+ for (String key : getVariantsXOr().keySet()) {
+ sj.add(key + "=" + getVariantsXOr().get(key));
+ }
+ queryPart = sj.toString();
+ }
+ return new URI(isUsingReference() ? SCHEME_A2_REFERENCE : SCHEME_A2, null, base.toString(), queryPart,
+ null);
+ } else {
+ throw new UnsupportedOperationException("Unsupported scheme " + baseUri.getScheme());
+ }
+ } catch (URISyntaxException e) {
+ throw new IllegalStateException("Cannot build URI from " + baseUri, e);
}
}
+ protected Map<String, String> getVariantsXOr() {
+ return variantsXOr;
+ }
+
+// public static void main(String[] args) {
+// if (args.length == 0)
+// throw new IllegalArgumentException("Usage: <path to A2 base>");
+// try {
+// Map<String, String> xOr = new HashMap<>();
+// xOr.put("osgi", "equinox");
+// xOr.put("swt", "rap");
+// FsA2Source context = new FsA2Source(Paths.get(args[0]), xOr);
+// context.load();
+// context.asTree();
+// } catch (Exception e) {
+// e.printStackTrace();
+// }
+// }
+
}
private final Path base;
public FsM2Source(Path base) {
- super();
+ super(false);
this.base = base;
}
private final BundleContext bc;
public OsgiContext(BundleContext bc) {
- super();
+ super(false);
this.bc = bc;
}
public OsgiContext() {
+ super(false);
Bundle bundle = FrameworkUtil.getBundle(OsgiContext.class);
if (bundle == null)
throw new IllegalArgumentException(
package org.argeo.init.a2;
+import static org.argeo.init.a2.A2Source.SCHEME_A2;
+import static org.argeo.init.a2.A2Source.SCHEME_A2_REFERENCE;
+
import java.io.File;
+import java.io.UnsupportedEncodingException;
import java.net.URI;
+import java.net.URLDecoder;
+import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.Version;
-import org.osgi.framework.launch.Framework;
import org.osgi.framework.wiring.FrameworkWiring;
/** Loads provisioning sources into an OSGi context. */
updatedBundles.add(bundle);
}
}
- FrameworkWiring frameworkWiring = bc.getBundle(0).adapt(FrameworkWiring.class);
- frameworkWiring.refreshBundles(updatedBundles);
+// FrameworkWiring frameworkWiring = bc.getBundle(0).adapt(FrameworkWiring.class);
+// frameworkWiring.refreshBundles(updatedBundles);
}
public void registerSource(String uri) {
try {
URI u = new URI(uri);
- if (A2Source.SCHEME_A2.equals(u.getScheme())) {
+
+ // XOR
+ Map<String, List<String>> properties = queryToMap(u);
+ Map<String, String> xOr = new HashMap<>();
+ for (String key : properties.keySet()) {
+ List<String> lst = properties.get(key);
+ if (lst.size() != 1)
+ throw new IllegalArgumentException("Invalid XOR definitions in " + uri);
+ xOr.put(key, lst.get(0));
+ }
+
+ if (SCHEME_A2.equals(u.getScheme()) || SCHEME_A2_REFERENCE.equals(u.getScheme())) {
if (u.getHost() == null || "".equals(u.getHost())) {
String baseStr = u.getPath();
if (File.separatorChar == '\\') {// MS Windows
}
Path base = Paths.get(baseStr);
if (Files.exists(base)) {
- FsA2Source source = new FsA2Source(base);
+ FsA2Source source = new FsA2Source(base, xOr, SCHEME_A2_REFERENCE.equals(u.getScheme()));
source.load();
addSource(source);
OsgiBootUtils.info("Registered " + uri + " as source");
+ } else {
+ OsgiBootUtils.debug("Source " + base + " does not exist, ignoring.");
}
+ } else {
+ throw new UnsupportedOperationException(
+ "Remote installation is not yet supported, cannot add source " + u);
}
+ } else {
+ throw new IllegalArgumentException("Unkown scheme: for source " + u);
}
} catch (Exception e) {
throw new A2Exception("Cannot add source " + uri, e);
return updatedBundles;
}
- public static void main(String[] args) {
- Map<String, String> configuration = new HashMap<>();
- configuration.put("osgi.console", "2323");
- Framework framework = OsgiBootUtils.launch(configuration);
+ private static Map<String, List<String>> queryToMap(URI uri) {
+ return queryToMap(uri.getQuery());
+ }
+
+ private static Map<String, List<String>> queryToMap(String queryPart) {
try {
- ProvisioningManager pm = new ProvisioningManager(framework.getBundleContext());
- FsA2Source context = new FsA2Source(Paths.get(
- "/home/mbaudier/dev/git/apache2/argeo-commons/dist/argeo-node/target/argeo-node-2.1.74-SNAPSHOT/argeo-node/share/osgi"));
- context.load();
- if (framework.getBundleContext().getBundles().length == 1) {// initial
- pm.install(null);
- } else {
- pm.update();
- }
- } catch (Exception e) {
- e.printStackTrace();
- } finally {
- try {
- // framework.stop();
- } catch (Exception e) {
- e.printStackTrace();
+ final Map<String, List<String>> query_pairs = new LinkedHashMap<String, List<String>>();
+ if (queryPart == null)
+ return query_pairs;
+ final String[] pairs = queryPart.split("&");
+ for (String pair : pairs) {
+ final int idx = pair.indexOf("=");
+ final String key = idx > 0 ? URLDecoder.decode(pair.substring(0, idx), StandardCharsets.UTF_8.name())
+ : pair;
+ if (!query_pairs.containsKey(key)) {
+ query_pairs.put(key, new LinkedList<String>());
+ }
+ final String value = idx > 0 && pair.length() > idx + 1
+ ? URLDecoder.decode(pair.substring(idx + 1), StandardCharsets.UTF_8.name())
+ : null;
+ query_pairs.get(key).add(value);
}
+ return query_pairs;
+ } catch (UnsupportedEncodingException e) {
+ throw new IllegalArgumentException("Cannot convert " + queryPart + " to map", e);
}
}
+// public static void main(String[] args) {
+// if (args.length == 0)
+// throw new IllegalArgumentException("Usage: <path to A2 base>");
+// Map<String, String> configuration = new HashMap<>();
+// configuration.put("osgi.console", "2323");
+// configuration.put("org.osgi.framework.bootdelegation",
+// "com.sun.jndi.ldap,com.sun.jndi.ldap.sasl,com.sun.security.jgss,com.sun.jndi.dns,com.sun.nio.file,com.sun.nio.sctp,sun.nio.cs");
+// Framework framework = OsgiBootUtils.launch(configuration);
+// try {
+// ProvisioningManager pm = new ProvisioningManager(framework.getBundleContext());
+// Map<String, String> xOr = new HashMap<>();
+// xOr.put("osgi", "equinox");
+// xOr.put("swt", "rap");
+// FsA2Source context = new FsA2Source(Paths.get(args[0]), xOr);
+// context.load();
+// pm.addSource(context);
+// if (framework.getBundleContext().getBundles().length == 1) {// initial
+// pm.install(null);
+// } else {
+// pm.update();
+// }
+//
+// Thread.sleep(2000);
+//
+// Bundle[] bundles = framework.getBundleContext().getBundles();
+// Arrays.sort(bundles, (b1, b2) -> b1.getSymbolicName().compareTo(b2.getSymbolicName()));
+// for (Bundle b : bundles)
+// if (b.getState() == Bundle.RESOLVED || b.getState() == Bundle.STARTING || b.getState() == Bundle.ACTIVE)
+// System.out.println(b.getSymbolicName() + " " + b.getVersion());
+// else
+// System.err.println(b.getSymbolicName() + " " + b.getVersion() + " (" + b.getState() + ")");
+// } catch (Exception e) {
+// e.printStackTrace();
+// } finally {
+// try {
+// framework.stop();
+// } catch (Exception e) {
+// e.printStackTrace();
+// }
+// }
+// }
+
}
@Override
public void publish(LogRecord record) {
java.lang.System.Logger systemLogger = ThinLoggerFinder.getLogger(record.getLoggerName());
- systemLogger.log(ThinJavaUtilLogging.fromJulLevel(record.getLevel()), record.getMessage(),
- record.getThrown());
+ if (record.getParameters() != null && record.getParameters().length > 0) {
+ systemLogger.log(ThinJavaUtilLogging.fromJulLevel(record.getLevel()), record.getMessage(),
+ record.getParameters());
+ } else {
+ systemLogger.log(ThinJavaUtilLogging.fromJulLevel(record.getLevel()), record.getMessage(),
+ record.getThrown());
+ }
}
@Override
public ThinLoggerFinder() {
if (logging != null)
throw new IllegalStateException("Only one logging can be initialised.");
- init();
+// init();
}
@Override
public Logger getLogger(String name, Module module) {
+ lazyInit();
return logging.getLogger(name, module);
}
private static void init() {
logging = new ThinLogging();
+ reloadConfiguration();
+ }
+ /** Reload configuration form system properties */
+ public static void reloadConfiguration() {
+ if (logging == null)
+ return;
Map<String, Object> configuration = new HashMap<>();
for (Object key : System.getProperties().keySet()) {
Objects.requireNonNull(key);
import java.util.SortedMap;
import java.util.StringTokenizer;
import java.util.TreeMap;
-import java.util.concurrent.Executor;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
import java.util.concurrent.Flow;
import java.util.concurrent.Flow.Subscription;
+import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.SubmissionPublisher;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
final static String DEFAULT_LEVEL_PROPERTY = "log";
final static String LEVEL_PROPERTY_PREFIX = DEFAULT_LEVEL_PROPERTY + ".";
- final static String JOURNALD_PROPERTY = "argeo.logging.journald";
- final static String CALL_LOCATION_PROPERTY = "argeo.logging.callLocation";
+ /**
+ * Whether logged event are only immediately printed to the standard output.
+ * Required for native images.
+ */
+ final static String PROP_ARGEO_LOGGING_SYNCHRONOUS = "argeo.logging.synchronous";
+ /**
+ * Whether to enable jounrald compatible output, either: auto (default), true,
+ * or false.
+ */
+ final static String PROP_ARGEO_LOGGING_JOURNALD = "argeo.logging.journald";
+ /**
+ * The level from which call location (that is, line number in Java code) will
+ * be searched (default is WARNING)
+ */
+ final static String PROP_ARGEO_LOGGING_CALL_LOCATION_LEVEL = "argeo.logging.callLocationLevel";
+
+ final static String ENV_INVOCATION_ID = "INVOCATION_ID";
+ final static String ENV_GIO_LAUNCHED_DESKTOP_FILE_PID = "GIO_LAUNCHED_DESKTOP_FILE_PID";
private final static AtomicLong nextEntry = new AtomicLong(0l);
private NavigableMap<String, Level> levels = new TreeMap<>();
private volatile boolean updatingConfiguration = false;
- private final ExecutorService executor;
private final LogEntryPublisher publisher;
+ private PrintStreamSubscriber synchronousSubscriber;
private final boolean journald;
private final Level callLocationLevel;
- ThinLogging() {
- executor = Executors.newCachedThreadPool((r) -> {
- Thread t = new Thread(r);
- t.setDaemon(true);
- return t;
- });
- publisher = new LogEntryPublisher(executor, Flow.defaultBufferSize());
-
- PrintStreamSubscriber subscriber = new PrintStreamSubscriber();
- publisher.subscribe(subscriber);
+ private final boolean synchronous;
+ ThinLogging() {
+ publisher = new LogEntryPublisher();
+ synchronous = Boolean.parseBoolean(System.getProperty(PROP_ARGEO_LOGGING_SYNCHRONOUS, "false"));
+ if (synchronous) {
+ synchronousSubscriber = new PrintStreamSubscriber();
+ } else {
+ PrintStreamSubscriber subscriber = new PrintStreamSubscriber();
+ publisher.subscribe(subscriber);
+ }
Runtime.getRuntime().addShutdownHook(new Thread(() -> close(), "Log shutdown"));
// initial default level
- levels.put("", Level.WARNING);
+ levels.put(DEFAULT_LEVEL_NAME, Level.WARNING);
// Logging system config
// journald
-
-// Map<String, String> env = new TreeMap<>(System.getenv());
-// for (String key : env.keySet()) {
-// System.out.println(key + "=" + env.get(key));
-// }
-
- String journaldStr = System.getProperty(JOURNALD_PROPERTY, "auto");
+ String journaldStr = System.getProperty(PROP_ARGEO_LOGGING_JOURNALD, "auto");
switch (journaldStr) {
case "auto":
- String systemdInvocationId = System.getenv("INVOCATION_ID");
+ String systemdInvocationId = System.getenv(ENV_INVOCATION_ID);
if (systemdInvocationId != null) {// in systemd
- // check whether we are indirectly in a desktop app (e.g. eclipse)
- String desktopFilePid = System.getenv("GIO_LAUNCHED_DESKTOP_FILE_PID");
+ // check whether we are indirectly in a desktop app (typically an IDE)
+ String desktopFilePid = System.getenv(ENV_GIO_LAUNCHED_DESKTOP_FILE_PID);
if (desktopFilePid != null) {
Long javaPid = ProcessHandle.current().pid();
if (!javaPid.toString().equals(desktopFilePid)) {
break;
default:
throw new IllegalArgumentException(
- "Unsupported value '" + journaldStr + "' for property " + JOURNALD_PROPERTY);
+ "Unsupported value '" + journaldStr + "' for property " + PROP_ARGEO_LOGGING_JOURNALD);
}
- String callLocationStr = System.getProperty(CALL_LOCATION_PROPERTY, Level.WARNING.getName());
+ String callLocationStr = System.getProperty(PROP_ARGEO_LOGGING_CALL_LOCATION_LEVEL, Level.WARNING.getName());
callLocationLevel = Level.valueOf(callLocationStr);
}
}
}
- publisher.close();
- try {
- // we ait a bit in order to make sure all messages are flushed
- // TODO synchronize more efficiently
- executor.awaitTermination(300, TimeUnit.MILLISECONDS);
- } catch (InterruptedException e) {
- // silent
+ if (!synchronous) {
+ publisher.close();
+ try {
+ // we wait a bit in order to make sure all messages are flushed
+ // TODO synchronize more efficiently
+ // executor.awaitTermination(300, TimeUnit.MILLISECONDS);
+ ForkJoinPool.commonPool().awaitTermination(300, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ // silent
+ }
}
}
private Level computeApplicableLevel(String name) {
Map.Entry<String, Level> entry = levels.floorEntry(name);
assert entry != null;
- return entry.getValue();
+ if (name.startsWith(entry.getKey()))
+ return entry.getValue();
+ else
+ return levels.get(DEFAULT_LEVEL_NAME);// default
}
-// private boolean isLoggable(String name, Level level) {
-// Objects.requireNonNull(name);
-// Objects.requireNonNull(level);
-//
-// if (updatingConfiguration) {
-// synchronized (levels) {
-// try {
-// levels.wait();
-// // TODO make exit more robust
-// } catch (InterruptedException e) {
-// throw new IllegalStateException(e);
-// }
-// }
-// }
-//
-// return level.getSeverity() >= computeApplicableLevel(name).getSeverity();
-// }
-
public Logger getLogger(String name, Module module) {
if (!loggers.containsKey(name)) {
ThinLogger logger = new ThinLogger(name, computeApplicableLevel(name));
updatingConfiguration = false;
levels.notifyAll();
}
-
}
Flow.Publisher<Map<String, Serializable>> getLogEntryPublisher() {
// NOTE: this is the method called when logging a plain message without
// exception, so it should be considered as a format only when args are not null
- if (format.contains("{}"))// workaround for weird Jetty formatting
- params = null;
+// if (format.contains("{}"))// workaround for weird Jetty formatting
+// params = null;
+ // TODO move this to slf4j wrapper?
+ if (format.contains("{}")) {
+ StringBuilder sb = new StringBuilder();
+ String[] segments = format.split("\\{\\}");
+ for (int i = 0; i < segments.length; i++) {
+ sb.append(segments[i]);
+ if (i != (segments.length - 1))
+ sb.append("{" + i + "}");
+ }
+ format = sb.toString();
+ }
String msg = params == null ? format : MessageFormat.format(format, params);
publisher.log(this, level, bundle, msg, now, thread, (Throwable) null, findCallLocation(level, thread));
}
case "org.osgi.service.log.Logger":
case "org.eclipse.osgi.internal.log.LoggerImpl":
case "org.argeo.api.cms.CmsLog":
+ case "org.argeo.init.osgi.OsgiBootUtils":
case "org.slf4j.impl.ArgeoLogger":
case "org.argeo.cms.internal.osgi.CmsOsgiLogger":
case "org.eclipse.jetty.util.log.Slf4jLog":
private class LogEntryPublisher extends SubmissionPublisher<Map<String, Serializable>> {
- private LogEntryPublisher(Executor executor, int maxBufferCapacity) {
- super(executor, maxBufferCapacity);
+ private LogEntryPublisher() {
+ super();
}
private void log(ThinLogger logger, Level level, ResourceBundle bundle, String msg, Instant instant,
logEntry.put(KEY_THREAD, thread.getName());
// should be unmodifiable for security reasons
- if (!isClosed())
- submit(Collections.unmodifiableMap(logEntry));
+ if (synchronous) {
+ assert synchronousSubscriber != null;
+ synchronousSubscriber.onNext(logEntry);
+ } else {
+ if (!isClosed())
+ submit(Collections.unmodifiableMap(logEntry));
+ }
}
}
private PrintStream err;
private int writeToErrLevel = Level.WARNING.getSeverity();
+ private Subscription subscription;
+
protected PrintStreamSubscriber() {
this(System.out, System.err);
}
@Override
public void onSubscribe(Subscription subscription) {
- subscription.request(Long.MAX_VALUE);
+ this.subscription = subscription;
+ this.subscription.request(1);
}
@Override
out.print(toPrint(item));
}
// TODO flush for journald?
+ if (this.subscription != null)
+ this.subscription.request(1);
}
@Override
protected String toPrint(Map<String, Serializable> logEntry) {
StringBuilder sb = new StringBuilder();
StringTokenizer st = new StringTokenizer((String) logEntry.get(KEY_MSG), "\r\n");
- assert st.hasMoreTokens();
// first line
- String firstLine = st.nextToken();
+ String firstLine = st.hasMoreTokens() ? st.nextToken() : "";
sb.append(firstLinePrefix(logEntry));
sb.append(firstLine);
sb.append(firstLineSuffix(logEntry));
logger.log(Logger.Level.INFO, "Log info");
logger.log(Logger.Level.WARNING, "Log warning");
logger.log(Logger.Level.ERROR, "Log exception", new Throwable());
+
+ try {
+ // we ait a bit in order to make sure all messages are flushed
+ // TODO synchronize more efficiently
+ // executor.awaitTermination(300, TimeUnit.MILLISECONDS);
+ ForkJoinPool.commonPool().awaitTermination(300, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ // silent
+ }
+
}
}
import java.lang.System.Logger.Level;
import java.util.Objects;
+import org.argeo.init.Service;
import org.argeo.init.logging.ThinLoggerFinder;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
// must be called first
ThinLoggerFinder.lazyInit();
}
- private Logger logger = System.getLogger(Activator.class.getName());
+ private final static Logger logger = System.getLogger(Activator.class.getName());
private Long checkpoint = null;
+
+ private boolean argeoInit = false;
+ /** Not null if we created it ourselves. */
private OsgiRuntimeContext runtimeContext;
public void start(final BundleContext bundleContext) throws Exception {
- if (runtimeContext == null) {
- runtimeContext = new OsgiRuntimeContext(bundleContext);
- }
- logger.log(Level.DEBUG, () -> "Argeo init via OSGi activator");
-
- // admin thread
+ // The OSGi runtime was created by us, and therefore already initialized
+ argeoInit = Boolean.parseBoolean(bundleContext.getProperty(Service.PROP_ARGEO_INIT_MAIN));
+ if (!argeoInit) {
+ if (runtimeContext == null) {
+ runtimeContext = new OsgiRuntimeContext(bundleContext);
+ logger.log(Level.DEBUG, () -> "Argeo init via OSGi activator");
+ }
+
+ // admin thread
// Thread adminThread = new AdminThread(bundleContext);
// adminThread.start();
- // bootstrap
+ // bootstrap
// OsgiBoot osgiBoot = new OsgiBoot(bundleContext);
- if (checkpoint == null) {
+ if (checkpoint == null) {
// osgiBoot.bootstrap();
- checkpoint = System.currentTimeMillis();
- } else {
- runtimeContext.update();
- checkpoint = System.currentTimeMillis();
+ checkpoint = System.currentTimeMillis();
+ } else {
+ runtimeContext.update();
+ checkpoint = System.currentTimeMillis();
+ }
}
}
public void stop(BundleContext context) throws Exception {
- Objects.requireNonNull(runtimeContext);
- runtimeContext.stop(context);
+ if (!argeoInit) {
+ Objects.nonNull(runtimeContext);
+ runtimeContext.stop(context);
+ runtimeContext = null;
+ }
}
+
}
dirPath = dirPath.substring("file:".length());
dir = new File(dirPath.replace('/', File.separatorChar)).getCanonicalPath();
- if (OsgiBootUtils.debug)
+ if (OsgiBootUtils.isDebug())
OsgiBootUtils.debug("Base dir: " + dir);
} catch (IOException e) {
throw new RuntimeException("Cannot convert to absolute path", e);
*/
public class OsgiBoot implements OsgiBootConstants {
public final static String PROP_ARGEO_OSGI_START = "argeo.osgi.start";
+ public final static String PROP_ARGEO_OSGI_MAX_START_LEVEL = "argeo.osgi.maxStartLevel";
public final static String PROP_ARGEO_OSGI_SOURCES = "argeo.osgi.sources";
- public final static String PROP_ARGEO_OSGI_BUNDLES = "argeo.osgi.bundles";
- public final static String PROP_ARGEO_OSGI_BASE_URL = "argeo.osgi.baseUrl";
- public final static String PROP_ARGEO_OSGI_LOCAL_CACHE = "argeo.osgi.localCache";
- public final static String PROP_ARGEO_OSGI_DISTRIBUTION_URL = "argeo.osgi.distributionUrl";
+ @Deprecated
+ final static String PROP_ARGEO_OSGI_BUNDLES = "argeo.osgi.bundles";
+ final static String PROP_ARGEO_OSGI_BASE_URL = "argeo.osgi.baseUrl";
+ final static String PROP_ARGEO_OSGI_LOCAL_CACHE = "argeo.osgi.localCache";
+ final static String PROP_ARGEO_OSGI_DISTRIBUTION_URL = "argeo.osgi.distributionUrl";
// booleans
- public final static String PROP_ARGEO_OSGI_BOOT_DEBUG = "argeo.osgi.boot.debug";
- // public final static String PROP_ARGEO_OSGI_BOOT_EXCLUDE_SVN =
- // "argeo.osgi.boot.excludeSvn";
+ @Deprecated
+ final static String PROP_ARGEO_OSGI_BOOT_DEBUG = "argeo.osgi.boot.debug";
- public final static String PROP_ARGEO_OSGI_BOOT_SYSTEM_PROPERTIES_FILE = "argeo.osgi.boot.systemPropertiesFile";
- public final static String PROP_ARGEO_OSGI_BOOT_APPCLASS = "argeo.osgi.boot.appclass";
- public final static String PROP_ARGEO_OSGI_BOOT_APPARGS = "argeo.osgi.boot.appargs";
+ final static String PROP_ARGEO_OSGI_BOOT_SYSTEM_PROPERTIES_FILE = "argeo.osgi.boot.systemPropertiesFile";
+ final static String PROP_ARGEO_OSGI_BOOT_APPCLASS = "argeo.osgi.boot.appclass";
+ final static String PROP_ARGEO_OSGI_BOOT_APPARGS = "argeo.osgi.boot.appargs";
+ @Deprecated
public final static String DEFAULT_BASE_URL = "reference:file:";
- // public final static String EXCLUDES_SVN_PATTERN = "**/.svn/**";
+ final static String DEFAULT_MAX_START_LEVEL = "32";
- // OSGi system properties
+ // OSGi standard properties
final static String PROP_OSGI_BUNDLES_DEFAULTSTARTLEVEL = "osgi.bundles.defaultStartLevel";
final static String PROP_OSGI_STARTLEVEL = "osgi.startLevel";
- final static String INSTANCE_AREA_PROP = "osgi.instance.area";
- final static String CONFIGURATION_AREA_PROP = "osgi.configuration.area";
+ public final static String PROP_OSGI_INSTANCE_AREA = "osgi.instance.area";
+ public final static String PROP_OSGI_CONFIGURATION_AREA = "osgi.configuration.area";
+ public final static String PROP_OSGI_SHARED_CONFIGURATION_AREA = "osgi.sharedConfiguration.area";
+ public final static String PROP_OSGI_USE_SYSTEM_PROPERTIES = "osgi.framework.useSystemProperties";
// Symbolic names
- public final static String SYMBOLIC_NAME_OSGI_BOOT = "org.argeo.osgi.boot";
- public final static String SYMBOLIC_NAME_EQUINOX = "org.eclipse.osgi";
-
- /** Exclude svn metadata implicitely(a bit costly) */
- // private boolean excludeSvn =
- // Boolean.valueOf(System.getProperty(PROP_ARGEO_OSGI_BOOT_EXCLUDE_SVN,
- // "false"))
- // .booleanValue();
-
- /** Default is 10s */
- @Deprecated
- private long defaultTimeout = 10000l;
+ final static String SYMBOLIC_NAME_OSGI_BOOT = "org.argeo.init";
+ final static String SYMBOLIC_NAME_EQUINOX = "org.eclipse.osgi";
private final BundleContext bundleContext;
private final String localCache;
-
private final ProvisioningManager provisioningManager;
/*
- * INITIALIZATION
+ * INITIALISATION
*/
/** Constructor */
public OsgiBoot(BundleContext bundleContext) {
if (sources == null) {
provisioningManager.registerDefaultSource();
} else {
+// OsgiBootUtils.debug("Found sources " + sources);
for (String source : sources.split(",")) {
+ int qmIndex = source.lastIndexOf('?');
+ String queryPart = "";
+ if (qmIndex >= 0) {
+ queryPart = source.substring(qmIndex);
+ source = source.substring(0, qmIndex);
+ }
if (source.trim().equals(A2Source.DEFAULT_A2_URI)) {
if (Files.exists(homePath))
provisioningManager.registerSource(
- A2Source.SCHEME_A2 + "://" + homePath.toString() + "/.local/share/a2");
- provisioningManager.registerSource(A2Source.SCHEME_A2 + ":///usr/local/share/a2");
- provisioningManager.registerSource(A2Source.SCHEME_A2 + ":///usr/share/a2");
+ A2Source.SCHEME_A2 + "://" + homePath.toString() + "/.local/share/a2" + queryPart);
+ provisioningManager.registerSource(A2Source.SCHEME_A2 + ":///usr/local/share/a2" + queryPart);
+ provisioningManager.registerSource(A2Source.SCHEME_A2 + ":///usr/share/a2" + queryPart);
+ } else if (source.trim().equals(A2Source.DEFAULT_A2_REFERENCE_URI)) {
+ if (Files.exists(homePath))
+ provisioningManager.registerSource(A2Source.SCHEME_A2_REFERENCE + "://" + homePath.toString()
+ + "/.local/share/a2" + queryPart);
+ provisioningManager
+ .registerSource(A2Source.SCHEME_A2_REFERENCE + ":///usr/local/share/a2" + queryPart);
+ provisioningManager.registerSource(A2Source.SCHEME_A2_REFERENCE + ":///usr/share/a2" + queryPart);
} else {
- provisioningManager.registerSource(source);
+ provisioningManager.registerSource(source + queryPart);
}
}
}
/*
* HIGH-LEVEL METHODS
*/
- /** Bootstraps the OSGi runtime */
- public void bootstrap() {
+ /**
+ * Bootstraps the OSGi runtime using these properties, which MUST be consistent
+ * with {@link BundleContext#getProperty(String)}. If these properties are
+ * <code>null</code>, system properties are used instead.
+ */
+ public void bootstrap(Map<String, String> properties) {
try {
long begin = System.currentTimeMillis();
- System.out.println();
- String osgiInstancePath = bundleContext.getProperty(INSTANCE_AREA_PROP);
- OsgiBootUtils
- .info("OSGi bootstrap starting" + (osgiInstancePath != null ? " (" + osgiInstancePath + ")" : ""));
+
+ // notify start
+ String osgiInstancePath = getProperty(PROP_OSGI_INSTANCE_AREA);
+ String osgiConfigurationPath = getProperty(PROP_OSGI_CONFIGURATION_AREA);
+ String osgiSharedConfigurationPath = getProperty(PROP_OSGI_CONFIGURATION_AREA);
+ OsgiBootUtils.info("OSGi bootstrap starting" //
+ + (osgiInstancePath != null ? " data: " + osgiInstancePath + "" : "") //
+ + (osgiConfigurationPath != null ? " state: " + osgiConfigurationPath + "" : "") //
+ + (osgiSharedConfigurationPath != null ? " config: " + osgiSharedConfigurationPath + "" : "") //
+ );
+
+ // legacy install bundles
installUrls(getBundlesUrls());
installUrls(getDistributionUrls());
+
+ // A2 install bundles
provisioningManager.install(null);
- startBundles();
+
+ // Make sure fragments are properly considered by refreshing
+ refreshFramework();
+
+ // start bundles
+// if (properties != null && !Boolean.parseBoolean(properties.get(PROP_OSGI_USE_SYSTEM_PROPERTIES)))
+ startBundles(properties);
+// else
+// startBundles();
+
+ // complete
long duration = System.currentTimeMillis() - begin;
OsgiBootUtils.info("OSGi bootstrap completed in " + Math.round(((double) duration) / 1000) + "s ("
+ duration + "ms), " + bundleContext.getBundles().length + " bundles");
}
// diagnostics
- if (OsgiBootUtils.debug) {
+ if (OsgiBootUtils.isDebug()) {
OsgiBootDiagnostics diagnostics = new OsgiBootDiagnostics(bundleContext);
diagnostics.checkUnresolved();
Map<String, Set<String>> duplicatePackages = diagnostics.findPackagesExportedTwice();
System.out.println();
}
+ /**
+ * Calls {@link #bootstrap(Map)} with <code>null</code>.
+ *
+ * @see #bootstrap(Map)
+ */
+ @Deprecated
+ public void bootstrap() {
+ bootstrap(null);
+ }
+
public void update() {
provisioningManager.update();
}
String url = (String) urls.get(i);
installUrl(url, installedBundles);
}
- refreshFramework();
+// refreshFramework();
}
/** Actually install the provided URL */
try {
if (installedBundles.containsKey(url)) {
Bundle bundle = (Bundle) installedBundles.get(url);
- if (OsgiBootUtils.debug)
+ if (OsgiBootUtils.isDebug())
debug("Bundle " + bundle.getSymbolicName() + " already installed from " + url);
} else if (url.contains("/" + SYMBOLIC_NAME_EQUINOX + "/")
|| url.contains("/" + SYMBOLIC_NAME_OSGI_BOOT + "/")) {
- if (OsgiBootUtils.debug)
+ if (OsgiBootUtils.isDebug())
warn("Skip " + url);
return;
} else {
if (url.startsWith("http"))
OsgiBootUtils
.info("Installed " + bundle.getSymbolicName() + "-" + bundle.getVersion() + " from " + url);
- else if (OsgiBootUtils.debug)
+ else if (OsgiBootUtils.isDebug())
OsgiBootUtils.debug(
"Installed " + bundle.getSymbolicName() + "-" + bundle.getVersion() + " from " + url);
assert bundle.getSymbolicName() != null;
} else
OsgiBootUtils.warn("Could not install bundle from " + url + ": " + message);
}
- if (OsgiBootUtils.debug && !message.contains(ALREADY_INSTALLED))
+ if (OsgiBootUtils.isDebug() && !message.contains(ALREADY_INSTALLED))
e.printStackTrace();
}
}
/*
* START
*/
- public void startBundles() {
- startBundles(System.getProperties());
+
+ /**
+ * Start bundles based on these properties.
+ *
+ * @see OsgiBoot#doStartBundles(Map)
+ */
+ public void startBundles(Map<String, String> properties) {
+ Map<String, String> map = new TreeMap<>();
+ // first use properties
+ if (properties != null) {
+ for (String key : properties.keySet()) {
+ String property = key;
+ if (property.startsWith(PROP_ARGEO_OSGI_START)) {
+ map.put(property, properties.get(property));
+ }
+ }
+ }
+ // then try all start level until a maximum
+ int maxStartLevel = Integer.parseInt(getProperty(PROP_ARGEO_OSGI_MAX_START_LEVEL, DEFAULT_MAX_START_LEVEL));
+ for (int i = 1; i <= maxStartLevel; i++) {
+ String key = PROP_ARGEO_OSGI_START + "." + i;
+ String value = getProperty(key);
+ if (value != null)
+ map.put(key, value);
+
+ }
+ // finally, override with system properties
+ for (Object key : System.getProperties().keySet()) {
+ if (key.toString().startsWith(PROP_ARGEO_OSGI_START)) {
+ map.put(key.toString(), System.getProperty(key.toString()));
+ }
+ }
+ // start
+ doStartBundles(map);
}
+ @Deprecated
public void startBundles(Properties properties) {
+ Map<String, String> map = new TreeMap<>();
+ // first use properties
+ if (properties != null) {
+ for (Object key : properties.keySet()) {
+ String property = key.toString();
+ if (property.startsWith(PROP_ARGEO_OSGI_START)) {
+ map.put(property, properties.get(property).toString());
+ }
+ }
+ }
+ startBundles(map);
+ }
+
+ /** Start bundle based on keys starting with {@link #PROP_ARGEO_OSGI_START}. */
+ protected void doStartBundles(Map<String, String> properties) {
FrameworkStartLevel frameworkStartLevel = bundleContext.getBundle(0).adapt(FrameworkStartLevel.class);
// default and active start levels from System properties
- Integer defaultStartLevel = Integer.parseInt(getProperty(PROP_OSGI_BUNDLES_DEFAULTSTARTLEVEL, "4"));
- Integer activeStartLevel = Integer.parseInt(getProperty(PROP_OSGI_STARTLEVEL, "6"));
+ int initialStartLevel = frameworkStartLevel.getInitialBundleStartLevel();
+ int defaultStartLevel = Integer.parseInt(getProperty(PROP_OSGI_BUNDLES_DEFAULTSTARTLEVEL, "4"));
+ int activeStartLevel = Integer.parseInt(getProperty(PROP_OSGI_STARTLEVEL, "6"));
+ if (OsgiBootUtils.isDebug()) {
+ OsgiBootUtils.debug("OSGi default start level: "
+ + getProperty(PROP_OSGI_BUNDLES_DEFAULTSTARTLEVEL, "<not set>") + ", using " + defaultStartLevel);
+ OsgiBootUtils.debug("OSGi active start level: " + getProperty(PROP_OSGI_STARTLEVEL, "<not set>")
+ + ", using " + activeStartLevel);
+ OsgiBootUtils.debug("Framework start level: " + frameworkStartLevel.getStartLevel() + " (initial: "
+ + initialStartLevel + ")");
+ }
SortedMap<Integer, List<String>> startLevels = new TreeMap<Integer, List<String>>();
computeStartLevels(startLevels, properties, defaultStartLevel);
} catch (BundleException e) {
OsgiBootUtils.error("Cannot mark " + bsn + " as started", e);
}
- if (getDebug())
+ if (OsgiBootUtils.isDebug())
OsgiBootUtils.debug(bsn + " starts at level " + level);
}
}
}
+
+ if (OsgiBootUtils.isDebug())
+ OsgiBootUtils.debug("About to set framework start level to " + activeStartLevel + " ...");
+
frameworkStartLevel.setStartLevel(activeStartLevel, (FrameworkEvent event) -> {
- if (getDebug())
- OsgiBootUtils.debug("Framework event: " + event);
- int initialStartLevel = frameworkStartLevel.getInitialBundleStartLevel();
- int startLevel = frameworkStartLevel.getStartLevel();
- OsgiBootUtils.debug("Framework start level: " + startLevel + " (initial: " + initialStartLevel + ")");
+ if (event.getType() == FrameworkEvent.ERROR) {
+ OsgiBootUtils.error("Start sequence failed", event.getThrowable());
+ } else {
+ if (OsgiBootUtils.isDebug())
+ OsgiBootUtils.debug("Framework started at level " + frameworkStartLevel.getStartLevel());
+ }
});
+
+// // Start the framework level after level
+// int currentStartLevel = frameworkStartLevel.getStartLevel();
+// stages: for (int stage = currentStartLevel + 1; stage <= activeStartLevel; stage++) {
+// if (OsgiBootUtils.isDebug())
+// OsgiBootUtils.debug("Starting stage " + stage + "...");
+// final int nextStage = stage;
+// final CompletableFuture<FrameworkEvent> stageCompleted = new CompletableFuture<>();
+// frameworkStartLevel.setStartLevel(nextStage, (FrameworkEvent event) -> {
+// stageCompleted.complete(event);
+// });
+// FrameworkEvent event;
+// try {
+// event = stageCompleted.get();
+// } catch (InterruptedException | ExecutionException e) {
+// throw new IllegalStateException("Cannot continue start", e);
+// }
+// if (event.getThrowable() != null) {
+// OsgiBootUtils.error("Stage " + nextStage + " failed, aborting start.", event.getThrowable());
+// break stages;
+// }
+// }
}
- private static void computeStartLevels(SortedMap<Integer, List<String>> startLevels, Properties properties,
+ private static void computeStartLevels(SortedMap<Integer, List<String>> startLevels, Map<String, String> properties,
Integer defaultStartLevel) {
// default (and previously, only behaviour)
- appendToStartLevels(startLevels, defaultStartLevel, properties.getProperty(PROP_ARGEO_OSGI_START, ""));
+ appendToStartLevels(startLevels, defaultStartLevel, properties.getOrDefault(PROP_ARGEO_OSGI_START, ""));
// list argeo.osgi.start.* system properties
- Iterator<Object> keys = properties.keySet().iterator();
+ Iterator<String> keys = properties.keySet().iterator();
final String prefix = PROP_ARGEO_OSGI_START + ".";
while (keys.hasNext()) {
- String key = keys.next().toString();
+ String key = keys.next();
if (key.startsWith(prefix)) {
Integer startLevel;
String suffix = key.substring(prefix.length());
startLevel = defaultStartLevel;
// append bundle names
- String bundleNames = properties.getProperty(key);
+ String bundleNames = properties.get(key);
appendToStartLevels(startLevels, startLevel, bundleNames);
}
}
}
}
- /**
- * Start the provided list of bundles
- *
- * @return whether all bundles are now in active state
- * @deprecated
- */
- @Deprecated
- public boolean startBundles(List<String> bundlesToStart) {
- if (bundlesToStart.size() == 0)
- return true;
-
- // used to monitor ACTIVE states
- List<Bundle> startedBundles = new ArrayList<Bundle>();
- // used to log the bundles not found
- List<String> notFoundBundles = new ArrayList<String>(bundlesToStart);
-
- Bundle[] bundles = bundleContext.getBundles();
- long startBegin = System.currentTimeMillis();
- for (int i = 0; i < bundles.length; i++) {
- Bundle bundle = bundles[i];
- String symbolicName = bundle.getSymbolicName();
- if (bundlesToStart.contains(symbolicName))
- try {
- try {
- bundle.start();
- if (OsgiBootUtils.debug)
- debug("Bundle " + symbolicName + " started");
- } catch (Exception e) {
- OsgiBootUtils.warn("Start of bundle " + symbolicName + " failed because of " + e
- + ", maybe bundle is not yet resolved," + " waiting and trying again.");
- waitForBundleResolvedOrActive(startBegin, bundle);
- bundle.start();
- startedBundles.add(bundle);
- }
- notFoundBundles.remove(symbolicName);
- } catch (Exception e) {
- OsgiBootUtils.warn("Bundle " + symbolicName + " cannot be started: " + e.getMessage());
- if (OsgiBootUtils.debug)
- e.printStackTrace();
- // was found even if start failed
- notFoundBundles.remove(symbolicName);
- }
- }
-
- for (int i = 0; i < notFoundBundles.size(); i++)
- OsgiBootUtils.warn("Bundle '" + notFoundBundles.get(i) + "' not started because it was not found.");
-
- // monitors that all bundles are started
- long beginMonitor = System.currentTimeMillis();
- boolean allStarted = !(startedBundles.size() > 0);
- List<String> notStarted = new ArrayList<String>();
- while (!allStarted && (System.currentTimeMillis() - beginMonitor) < defaultTimeout) {
- notStarted = new ArrayList<String>();
- allStarted = true;
- for (int i = 0; i < startedBundles.size(); i++) {
- Bundle bundle = (Bundle) startedBundles.get(i);
- // TODO check behaviour of lazs bundles
- if (bundle.getState() != Bundle.ACTIVE) {
- allStarted = false;
- notStarted.add(bundle.getSymbolicName());
- }
- }
- try {
- Thread.sleep(100);
- } catch (InterruptedException e) {
- // silent
- }
- }
- long duration = System.currentTimeMillis() - beginMonitor;
-
- if (!allStarted)
- for (int i = 0; i < notStarted.size(); i++)
- OsgiBootUtils.warn("Bundle '" + notStarted.get(i) + "' not ACTIVE after " + (duration / 1000) + "s");
-
- return allStarted;
- }
-
- /** Waits for a bundle to become active or resolved */
- @Deprecated
- private void waitForBundleResolvedOrActive(long startBegin, Bundle bundle) throws Exception {
- int originalState = bundle.getState();
- if ((originalState == Bundle.RESOLVED) || (originalState == Bundle.ACTIVE))
- return;
-
- String originalStateStr = OsgiBootUtils.stateAsString(originalState);
-
- int currentState = bundle.getState();
- while (!(currentState == Bundle.RESOLVED || currentState == Bundle.ACTIVE)) {
- long now = System.currentTimeMillis();
- if ((now - startBegin) > defaultTimeout * 10)
- throw new Exception("Bundle " + bundle.getSymbolicName() + " was not RESOLVED or ACTIVE after "
- + (now - startBegin) + "ms (originalState=" + originalStateStr + ", currentState="
- + OsgiBootUtils.stateAsString(currentState) + ")");
-
- try {
- Thread.sleep(100l);
- } catch (InterruptedException e) {
- // silent
- }
- currentState = bundle.getState();
- }
- }
-
/*
* BUNDLE PATTERNS INSTALLATION
*/
}
/** Implements the path matching logic */
+ @Deprecated
public List<String> getBundlesUrls(String baseUrl, String bundlePatterns) {
List<String> urls = new ArrayList<String>();
if (bundlePatterns == null)
return urls;
// bundlePatterns = SystemPropertyUtils.resolvePlaceholders(bundlePatterns);
- if (OsgiBootUtils.debug)
+ if (OsgiBootUtils.isDebug())
debug(PROP_ARGEO_OSGI_BUNDLES + "=" + bundlePatterns);
StringTokenizer st = new StringTokenizer(bundlePatterns, ",");
distributionBundle = new DistributionBundle(baseUrl, distributionUrl, localCache);
}
- // if (baseUrl != null && !(distributionUrl.startsWith("http") ||
- // distributionUrl.startsWith("file"))) {
- // // relative url
- // distributionBundle = new DistributionBundle(baseUrl, distributionUrl,
- // localCache);
- // } else {
- // distributionBundle = new DistributionBundle(distributionUrl);
- // if (baseUrl != null)
- // distributionBundle.setBaseUrl(baseUrl);
- // }
distributionBundle.processUrl();
return distributionBundle.listUrls();
}
File[] files = baseDir.listFiles();
if (files == null) {
- if (OsgiBootUtils.debug)
+ if (OsgiBootUtils.isDebug())
OsgiBootUtils.warn("Base dir " + baseDir + " has no children, exists=" + baseDir.exists()
+ ", isDirectory=" + baseDir.isDirectory());
return;
// FIXME recurse only if start matches ?
match(matched, base, newCurrentPath, pattern);
// } else {
-// if (OsgiBootUtils.debug)
+// if (OsgiBootUtils.isDebug())
// debug(newCurrentPath + " does not start match with " + pattern);
//
// }
} else {
boolean nonDirectoryOk = matcher.matches(Paths.get(newCurrentPath));
- if (OsgiBootUtils.debug)
+ if (OsgiBootUtils.isDebug())
debug(currentPath + " " + (ok ? "" : " not ") + " matched with " + pattern);
if (nonDirectoryOk)
matched.add(relativeToFullPath(base, newCurrentPath));
}
}
- protected void matchFile() {
-
- }
-
/*
* LOW LEVEL UTILITIES
*/
private void refreshFramework() {
Bundle systemBundle = bundleContext.getBundle(0);
FrameworkWiring frameworkWiring = systemBundle.adapt(FrameworkWiring.class);
+ // TODO deal with refresh breaking native loading (e.g SWT)
frameworkWiring.refreshBundles(null);
}
* BEAN METHODS
*/
- public boolean getDebug() {
- return OsgiBootUtils.debug;
- }
-
- // public void setDebug(boolean debug) {
- // this.debug = debug;
- // }
-
public BundleContext getBundleContext() {
return bundleContext;
}
- public String getLocalCache() {
- return localCache;
- }
-
- // public void setDefaultTimeout(long defaultTimeout) {
- // this.defaultTimeout = defaultTimeout;
- // }
-
- // public boolean isExcludeSvn() {
- // return excludeSvn;
- // }
- //
- // public void setExcludeSvn(boolean excludeSvn) {
- // this.excludeSvn = excludeSvn;
- // }
-
- /*
- * INTERNAL CLASSES
- */
-
}
package org.argeo.init.osgi;
-import java.text.DateFormat;
-import java.text.SimpleDateFormat;
+import java.lang.System.Logger;
+import java.lang.System.Logger.Level;
import java.util.ArrayList;
-import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Optional;
import java.util.ServiceLoader;
import java.util.StringTokenizer;
/** Utilities, mostly related to logging. */
public class OsgiBootUtils {
- /** ISO8601 (as per log4j) and difference to UTC */
- private static DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss,SSS Z");
-
- static boolean debug = System.getProperty(OsgiBoot.PROP_ARGEO_OSGI_BOOT_DEBUG) == null ? false
- : !System.getProperty(OsgiBoot.PROP_ARGEO_OSGI_BOOT_DEBUG).trim().equals("false");
+ private final static Logger logger = System.getLogger(OsgiBootUtils.class.getName());
public static void info(Object obj) {
- System.out.println("# OSGiBOOT # " + dateFormat.format(new Date()) + " # " + obj);
+ logger.log(Level.INFO, () -> Objects.toString(obj));
}
public static void debug(Object obj) {
- if (debug)
- System.out.println("# OSGiBOOT DBG # " + dateFormat.format(new Date()) + " # " + obj);
+ logger.log(Level.TRACE, () -> Objects.toString(obj));
}
public static void warn(Object obj) {
- System.out.println("# OSGiBOOT WARN # " + dateFormat.format(new Date()) + " # " + obj);
+ logger.log(Level.WARNING, () -> Objects.toString(obj));
}
public static void error(Object obj, Throwable e) {
- System.err.println("# OSGiBOOT ERR # " + dateFormat.format(new Date()) + " # " + obj);
- if (e != null)
- e.printStackTrace();
+ logger.log(Level.ERROR, () -> Objects.toString(obj), e);
}
public static boolean isDebug() {
- return debug;
+ return logger.isLoggable(Level.TRACE);
}
public static String stateAsString(int state) {
return framework;
}
+ @Deprecated
public static Map<String, String> equinoxArgsToConfiguration(String[] args) {
// FIXME implement it
return new HashMap<>();
public OsgiBuilder() {
// configuration.put("osgi.clean", "true");
- configuration.put(OsgiBoot.CONFIGURATION_AREA_PROP, System.getProperty(OsgiBoot.CONFIGURATION_AREA_PROP));
- configuration.put(OsgiBoot.INSTANCE_AREA_PROP, System.getProperty(OsgiBoot.INSTANCE_AREA_PROP));
+ configuration.put(OsgiBoot.PROP_OSGI_CONFIGURATION_AREA, System.getProperty(OsgiBoot.PROP_OSGI_CONFIGURATION_AREA));
+ configuration.put(OsgiBoot.PROP_OSGI_INSTANCE_AREA, System.getProperty(OsgiBoot.PROP_OSGI_INSTANCE_AREA));
configuration.put(PROP_OSGI_CLEAN, System.getProperty(PROP_OSGI_CLEAN));
}
framework = OsgiBootUtils.launch(configuration);
BundleContext bc = framework.getBundleContext();
- String osgiData = bc.getProperty(OsgiBoot.INSTANCE_AREA_PROP);
+ String osgiData = bc.getProperty(OsgiBoot.PROP_OSGI_INSTANCE_AREA);
// String osgiConf = bc.getProperty(OsgiBoot.CONFIGURATION_AREA_PROP);
String osgiConf = framework.getDataFile("").getAbsolutePath();
if (OsgiBootUtils.isDebug())
package org.argeo.init.osgi;
+import java.lang.System.LoggerFinder;
import java.util.Collections;
import java.util.Hashtable;
import java.util.Map;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleException;
import org.osgi.framework.Constants;
-import org.osgi.framework.ServiceRegistration;
import org.osgi.framework.launch.Framework;
import org.osgi.framework.launch.FrameworkFactory;
/** An OSGi runtime context. */
-public class OsgiRuntimeContext implements RuntimeContext {
+public class OsgiRuntimeContext implements RuntimeContext, AutoCloseable {
private Map<String, String> config;
private Framework framework;
private OsgiBoot osgiBoot;
- @SuppressWarnings("rawtypes")
- private ServiceRegistration<Consumer> loggingConfigurationSr;
- @SuppressWarnings("rawtypes")
- private ServiceRegistration<Flow.Publisher> logEntryPublisherSr;
-
+ /**
+ * Constructor to use when the runtime context will create the OSGi
+ * {@link Framework}.
+ */
public OsgiRuntimeContext(Map<String, String> config) {
this.config = config;
}
- public OsgiRuntimeContext(BundleContext bundleContext) {
+ /**
+ * Constructor to use when the OSGi {@link Framework} has been created by other
+ * means.
+ */
+ OsgiRuntimeContext(BundleContext bundleContext) {
start(bundleContext);
}
}
public void start(BundleContext bundleContext) {
+ // preferences
+// SystemRootPreferences systemRootPreferences = ThinPreferencesFactory.getInstance().getSystemRootPreferences();
+// bundleContext.registerService(AbstractPreferences.class, systemRootPreferences, new Hashtable<>());
+
+ // Make sure LoggerFinder has been searched for, since it is lazily loaded
+ LoggerFinder.getLoggerFinder();
+
// logging
- loggingConfigurationSr = bundleContext.registerService(Consumer.class,
- ThinLoggerFinder.getConfigurationConsumer(),
+ bundleContext.registerService(Consumer.class, ThinLoggerFinder.getConfigurationConsumer(),
new Hashtable<>(Collections.singletonMap(Constants.SERVICE_PID, "argeo.logging.configuration")));
- logEntryPublisherSr = bundleContext.registerService(Flow.Publisher.class,
- ThinLoggerFinder.getLogEntryPublisher(),
+ bundleContext.registerService(Flow.Publisher.class, ThinLoggerFinder.getLogEntryPublisher(),
new Hashtable<>(Collections.singletonMap(Constants.SERVICE_PID, "argeo.logging.publisher")));
osgiBoot = new OsgiBoot(bundleContext);
- osgiBoot.bootstrap();
+ osgiBoot.bootstrap(config);
}
}
+ public Framework getFramework() {
+ return framework;
+ }
+
}
+++ /dev/null
-log4j.rootLogger=WARN, console
-
-log4j.logger.org.argeo=INFO
-
-## Appenders
-log4j.appender.console=org.apache.log4j.ConsoleAppender
-log4j.appender.console.layout=org.apache.log4j.PatternLayout
-log4j.appender.console.layout.ConversionPattern= %-5p %d{ISO8601} %m - %c - [%t]%n
-
-log4j.appender.development=org.apache.log4j.ConsoleAppender
-log4j.appender.development.layout=org.apache.log4j.PatternLayout
-log4j.appender.development.layout.ConversionPattern=%d{ABSOLUTE} %m (%F:%L) [%t] %p %n
--- /dev/null
+package org.argeo.init.prefs;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.function.Consumer;
+import java.util.prefs.AbstractPreferences;
+import java.util.prefs.BackingStoreException;
+
+public class SystemRootPreferences extends AbstractPreferences implements Consumer<AbstractPreferences> {
+ private CompletableFuture<AbstractPreferences> singleChild;
+
+ protected SystemRootPreferences() {
+ super(null, "");
+ }
+
+ @Override
+ public void accept(AbstractPreferences t) {
+ this.singleChild.complete(t);
+ }
+
+ /*
+ * ABSTRACT PREFERENCES
+ */
+
+ @Override
+ protected void putSpi(String key, String value) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ protected String getSpi(String key) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ protected void removeSpi(String key) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ protected void removeNodeSpi() throws BackingStoreException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ protected String[] keysSpi() throws BackingStoreException {
+ return new String[0];
+ }
+
+ /** Will block. */
+ @Override
+ protected String[] childrenNamesSpi() throws BackingStoreException {
+ String childName;
+ try {
+ childName = singleChild.get().name();
+ } catch (InterruptedException | ExecutionException e) {
+ throw new IllegalStateException("Cannot get child preferences name", e);
+ }
+ return new String[] { childName };
+ }
+
+ @Override
+ protected AbstractPreferences childSpi(String name) {
+ String childName;
+ try {
+ childName = singleChild.get().name();
+ } catch (InterruptedException | ExecutionException e) {
+ throw new IllegalStateException("Cannot get child preferences name", e);
+ }
+ if (!childName.equals(name))
+ throw new IllegalArgumentException("Child name is " + childName + ", not " + name);
+ return null;
+ }
+
+ @Override
+ protected void syncSpi() throws BackingStoreException {
+ }
+
+ @Override
+ protected void flushSpi() throws BackingStoreException {
+ }
+
+}
--- /dev/null
+package org.argeo.init.prefs;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.prefs.Preferences;
+import java.util.prefs.PreferencesFactory;
+
+public class ThinPreferencesFactory implements PreferencesFactory {
+ private static CompletableFuture<ThinPreferencesFactory> INSTANCE = new CompletableFuture<>();
+
+ private SystemRootPreferences systemRootPreferences;
+
+ public ThinPreferencesFactory() {
+ systemRootPreferences = new SystemRootPreferences();
+ if (INSTANCE.isDone())
+ throw new IllegalStateException(
+ "There is already a " + ThinPreferencesFactory.class.getName() + " instance.");
+ INSTANCE.complete(this);
+ }
+
+ @Override
+ public Preferences systemRoot() {
+ return systemRootPreferences;
+ }
+
+ @Override
+ public Preferences userRoot() {
+ throw new UnsupportedOperationException();
+ }
+
+ public SystemRootPreferences getSystemRootPreferences() {
+ return systemRootPreferences;
+ }
+
+ public static ThinPreferencesFactory getInstance() {
+ try {
+ return INSTANCE.get();
+ } catch (InterruptedException | ExecutionException e) {
+ throw new IllegalStateException("Cannot get " + ThinPreferencesFactory.class.getName() + " instance.", 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">
- <attributes>
- <attribute name="module" value="true"/>
- </attributes>
- </classpathentry>
- <classpathentry kind="output" path="bin"/>
-</classpath>
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
- <name>org.argeo.util</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.jdt.core.javanature</nature>
- <nature>org.eclipse.pde.PluginNature</nature>
- </natures>
-</projectDescription>
+++ /dev/null
-/MANIFEST.MF
+++ /dev/null
-Bundle-Activator: org.argeo.osgi.internal.EnterpriseActivator
-Bundle-ActivationPolicy: lazy
-
-Import-Package: org.osgi.*;version=0.0.0,\
-!org.apache.commons.logging,\
-*
+++ /dev/null
-source.. = src/
\ No newline at end of file
+++ /dev/null
-package org.argeo.osgi.internal;
-
-import org.osgi.framework.BundleActivator;
-import org.osgi.framework.BundleContext;
-
-/**
- * Called to gather information about the OSGi runtime. Should not activate
- * anything else that canonical monitoring services (not creating implicit
- * APIs), which is the responsibility of higher levels.
- */
-public class EnterpriseActivator implements BundleActivator {
-
- @Override
- public void start(BundleContext context) throws Exception {
- }
-
- @Override
- public void stop(BundleContext context) throws Exception {
- }
-
-}
+++ /dev/null
-package org.argeo.osgi.metatype;
-
-import org.argeo.util.naming.SpecifiedName;
-import org.osgi.service.metatype.AttributeDefinition;
-
-public interface EnumAD extends SpecifiedName, AttributeDefinition {
- String name();
-
- default Object getDefault() {
- return null;
- }
-
- @Override
- default String getName() {
- return name();
- }
-
- @Override
- default String getID() {
- return getClass().getName() + "." + name();
- }
-
- @Override
- default String getDescription() {
- return null;
- }
-
- @Override
- default int getCardinality() {
- return 0;
- }
-
- @Override
- default int getType() {
- return STRING;
- }
-
- @Override
- default String[] getOptionValues() {
- return null;
- }
-
- @Override
- default String[] getOptionLabels() {
- return null;
- }
-
- @Override
- default String validate(String value) {
- return null;
- }
-
- @Override
- default String[] getDefaultValue() {
- Object value = getDefault();
- if (value == null)
- return null;
- return new String[] { value.toString() };
- }
-}
+++ /dev/null
-package org.argeo.osgi.metatype;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.EnumSet;
-import java.util.List;
-
-import org.osgi.service.metatype.AttributeDefinition;
-import org.osgi.service.metatype.ObjectClassDefinition;
-
-public class EnumOCD<T extends Enum<T>> implements ObjectClassDefinition {
- private final Class<T> enumClass;
- private String locale;
-
- public EnumOCD(Class<T> clazz, String locale) {
- this.enumClass = clazz;
- this.locale = locale;
- }
-
- @Override
- public String getName() {
- return null;
- }
-
- public String getLocale() {
- return locale;
- }
-
- @Override
- public String getID() {
- return enumClass.getName();
- }
-
- @Override
- public String getDescription() {
- return null;
- }
-
- @Override
- public AttributeDefinition[] getAttributeDefinitions(int filter) {
- EnumSet<T> set = EnumSet.allOf(enumClass);
- List<AttributeDefinition> attrs = new ArrayList<>();
- for (T key : set)
- attrs.add((AttributeDefinition) key);
- return attrs.toArray(new AttributeDefinition[attrs.size()]);
- }
-
- @Override
- public InputStream getIcon(int size) throws IOException {
- return null;
- }
-
-}
+++ /dev/null
-/** OSGi metatype support. */
-package org.argeo.osgi.metatype;
\ No newline at end of file
+++ /dev/null
-package org.argeo.osgi.provisioning;
-
-import java.io.IOException;
-import java.util.Collections;
-import java.util.Dictionary;
-import java.util.Enumeration;
-import java.util.Iterator;
-import java.util.Map;
-import java.util.TreeMap;
-import java.util.zip.ZipInputStream;
-
-import org.osgi.service.provisioning.ProvisioningService;
-
-public class SimpleProvisioningService implements ProvisioningService {
- private Map<String, Object> map = Collections.synchronizedSortedMap(new TreeMap<>());
-
- public SimpleProvisioningService() {
- // update count
- map.put(PROVISIONING_UPDATE_COUNT, 0);
- }
-
- @Override
- public Dictionary<String, Object> getInformation() {
- return new Information();
- }
-
- @Override
- public synchronized void setInformation(Dictionary<String, ?> info) {
- map.clear();
- addInformation(info);
- }
-
- @Override
- public synchronized void addInformation(Dictionary<String, ?> info) {
- Enumeration<String> e = info.keys();
- while (e.hasMoreElements()) {
- String key = e.nextElement();
- map.put(key, info.get(key));
- }
- incrementProvisioningUpdateCount();
- }
-
- protected synchronized void incrementProvisioningUpdateCount() {
- Integer current = (Integer) map.get(PROVISIONING_UPDATE_COUNT);
- Integer newValue = current + 1;
- map.put(PROVISIONING_UPDATE_COUNT, newValue);
- }
-
- @Override
- public synchronized void addInformation(ZipInputStream zis) throws IOException {
- throw new UnsupportedOperationException();
- }
-
- class Information extends Dictionary<String, Object> {
-
- @Override
- public int size() {
- return map.size();
- }
-
- @Override
- public boolean isEmpty() {
- return map.isEmpty();
- }
-
- @Override
- public Enumeration<String> keys() {
- Iterator<String> it = map.keySet().iterator();
- return new Enumeration<String>() {
-
- @Override
- public boolean hasMoreElements() {
- return it.hasNext();
- }
-
- @Override
- public String nextElement() {
- return it.next();
- }
-
- };
- }
-
- @Override
- public Enumeration<Object> elements() {
- Iterator<Object> it = map.values().iterator();
- return new Enumeration<Object>() {
-
- @Override
- public boolean hasMoreElements() {
- return it.hasNext();
- }
-
- @Override
- public Object nextElement() {
- return it.next();
- }
-
- };
- }
-
- @Override
- public Object get(Object key) {
- return map.get(key);
- }
-
- @Override
- public Object put(String key, Object value) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public Object remove(Object key) {
- throw new UnsupportedOperationException();
- }
-
- }
-}
+++ /dev/null
-/** OSGi provisioning support. */
-package org.argeo.osgi.provisioning;
\ No newline at end of file
+++ /dev/null
-package org.argeo.osgi.transaction;
-
-/** JTA transaction status. */
-public class JtaStatusAdapter implements TransactionStatusAdapter<Integer> {
- private static final Integer STATUS_ACTIVE = 0;
- private static final Integer STATUS_COMMITTED = 3;
- private static final Integer STATUS_COMMITTING = 8;
- private static final Integer STATUS_MARKED_ROLLBACK = 1;
- private static final Integer STATUS_NO_TRANSACTION = 6;
- private static final Integer STATUS_PREPARED = 2;
- private static final Integer STATUS_PREPARING = 7;
- private static final Integer STATUS_ROLLEDBACK = 4;
- private static final Integer STATUS_ROLLING_BACK = 9;
-// private static final Integer STATUS_UNKNOWN = 5;
-
- @Override
- public Integer getActiveStatus() {
- return STATUS_ACTIVE;
- }
-
- @Override
- public Integer getPreparingStatus() {
- return STATUS_PREPARING;
- }
-
- @Override
- public Integer getMarkedRollbackStatus() {
- return STATUS_MARKED_ROLLBACK;
- }
-
- @Override
- public Integer getPreparedStatus() {
- return STATUS_PREPARED;
- }
-
- @Override
- public Integer getCommittingStatus() {
- return STATUS_COMMITTING;
- }
-
- @Override
- public Integer getCommittedStatus() {
- return STATUS_COMMITTED;
- }
-
- @Override
- public Integer getRollingBackStatus() {
- return STATUS_ROLLING_BACK;
- }
-
- @Override
- public Integer getRolledBackStatus() {
- return STATUS_ROLLEDBACK;
- }
-
- @Override
- public Integer getNoTransactionStatus() {
- return STATUS_NO_TRANSACTION;
- }
-
-}
+++ /dev/null
-package org.argeo.osgi.transaction;
-
-/** Internal unchecked rollback exception. */
-class SimpleRollbackException extends RuntimeException {
- private static final long serialVersionUID = 8055601819719780566L;
-
- public SimpleRollbackException() {
- super();
- }
-
- public SimpleRollbackException(Throwable cause) {
- super(cause);
- }
-
-}
+++ /dev/null
-package org.argeo.osgi.transaction;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.transaction.xa.XAException;
-import javax.transaction.xa.XAResource;
-import javax.transaction.xa.Xid;
-
-/** Simple implementation of an XA transaction. */
-class SimpleTransaction<T>
-//implements Transaction, Status
-{
- private final Xid xid;
- private T status;
- private final List<XAResource> xaResources = new ArrayList<XAResource>();
-
- private final SimpleTransactionManager transactionManager;
- private TransactionStatusAdapter<T> tsa;
-
- public SimpleTransaction(SimpleTransactionManager transactionManager, TransactionStatusAdapter<T> tsa) {
- this.tsa = tsa;
- this.status = tsa.getActiveStatus();
- this.xid = new UuidXid();
- this.transactionManager = transactionManager;
- }
-
- public synchronized void commit()
-// throws RollbackException, HeuristicMixedException, HeuristicRollbackException,
-// SecurityException, IllegalStateException, SystemException
- {
- status = tsa.getPreparingStatus();
- for (XAResource xaRes : xaResources) {
- if (status.equals(tsa.getMarkedRollbackStatus()))
- break;
- try {
- xaRes.prepare(xid);
- } catch (XAException e) {
- status = tsa.getMarkedRollbackStatus();
- error("Cannot prepare " + xaRes + " for " + xid, e);
- }
- }
- if (status.equals(tsa.getMarkedRollbackStatus())) {
- rollback();
- throw new SimpleRollbackException();
- }
- status = tsa.getPreparedStatus();
-
- status = tsa.getCommittingStatus();
- for (XAResource xaRes : xaResources) {
- if (status.equals(tsa.getMarkedRollbackStatus()))
- break;
- try {
- xaRes.commit(xid, false);
- } catch (XAException e) {
- status = tsa.getMarkedRollbackStatus();
- error("Cannot prepare " + xaRes + " for " + xid, e);
- }
- }
- if (status.equals(tsa.getMarkedRollbackStatus())) {
- rollback();
- throw new SimpleRollbackException();
- }
-
- // complete
- status = tsa.getCommittedStatus();
- clearResources(XAResource.TMSUCCESS);
- transactionManager.unregister(xid);
- }
-
- public synchronized void rollback()
-// throws IllegalStateException, SystemException
- {
- status = tsa.getRollingBackStatus();
- for (XAResource xaRes : xaResources) {
- try {
- xaRes.rollback(xid);
- } catch (XAException e) {
- error("Cannot rollback " + xaRes + " for " + xid, e);
- }
- }
-
- // complete
- status = tsa.getRolledBackStatus();
- clearResources(XAResource.TMFAIL);
- transactionManager.unregister(xid);
- }
-
- public synchronized boolean enlistResource(XAResource xaRes)
-// throws RollbackException, IllegalStateException, SystemException
- {
- if (xaResources.add(xaRes)) {
- try {
- xaRes.start(getXid(), XAResource.TMNOFLAGS);
- return true;
- } catch (XAException e) {
- error("Cannot enlist " + xaRes, e);
- return false;
- }
- } else
- return false;
- }
-
- public synchronized boolean delistResource(XAResource xaRes, int flag)
-// throws IllegalStateException, SystemException
- {
- if (xaResources.remove(xaRes)) {
- try {
- xaRes.end(getXid(), flag);
- } catch (XAException e) {
- error("Cannot delist " + xaRes, e);
- return false;
- }
- return true;
- } else
- return false;
- }
-
- protected void clearResources(int flag) {
- for (XAResource xaRes : xaResources)
- try {
- xaRes.end(getXid(), flag);
- } catch (XAException e) {
- error("Cannot end " + xaRes, e);
- }
- xaResources.clear();
- }
-
- protected void error(Object obj, Exception e) {
- System.err.println(obj);
- e.printStackTrace();
- }
-
- public synchronized T getStatus()
-// throws SystemException
- {
- return status;
- }
-
-// public void registerSynchronization(Synchronization sync)
-// throws RollbackException, IllegalStateException, SystemException {
-// throw new UnsupportedOperationException();
-// }
-
- public void setRollbackOnly()
-// throws IllegalStateException, SystemException
- {
- status = tsa.getMarkedRollbackStatus();
- }
-
- @Override
- public int hashCode() {
- return xid.hashCode();
- }
-
- Xid getXid() {
- return xid;
- }
-
-}
+++ /dev/null
-package org.argeo.osgi.transaction;
-
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.Callable;
-
-import javax.transaction.xa.XAResource;
-import javax.transaction.xa.Xid;
-
-/**
- * Simple implementation of an XA transaction manager.
- */
-public class SimpleTransactionManager
-// implements TransactionManager, UserTransaction
- implements WorkControl, WorkTransaction {
- private ThreadLocal<SimpleTransaction<Integer>> current = new ThreadLocal<SimpleTransaction<Integer>>();
-
- private Map<Xid, SimpleTransaction<Integer>> knownTransactions = Collections
- .synchronizedMap(new HashMap<Xid, SimpleTransaction<Integer>>());
- private TransactionStatusAdapter<Integer> tsa = new JtaStatusAdapter();
-// private SyncRegistry syncRegistry = new SyncRegistry();
-
- /*
- * WORK IMPLEMENTATION
- */
- @Override
- public <T> T required(Callable<T> work) {
- T res;
- begin();
- try {
- res = work.call();
- commit();
- } catch (Exception e) {
- rollback();
- throw new SimpleRollbackException(e);
- }
- return res;
- }
-
- @Override
- public WorkContext getWorkContext() {
- return new WorkContext() {
-
- @Override
- public void registerXAResource(XAResource resource, String recoveryId) {
- getTransaction().enlistResource(resource);
- }
- };
- }
-
- /*
- * WORK TRANSACTION IMPLEMENTATION
- */
-
- @Override
- public boolean isNoTransactionStatus() {
- return tsa.getNoTransactionStatus().equals(getStatus());
- }
-
- /*
- * JTA IMPLEMENTATION
- */
-
- public void begin()
-// throws NotSupportedException, SystemException
- {
- if (getCurrent() != null)
- throw new UnsupportedOperationException("Nested transactions are not supported");
- SimpleTransaction<Integer> transaction = new SimpleTransaction<Integer>(this, tsa);
- knownTransactions.put(transaction.getXid(), transaction);
- current.set(transaction);
- }
-
- public void commit()
-// throws RollbackException, HeuristicMixedException, HeuristicRollbackException,
-// SecurityException, IllegalStateException, SystemException
- {
- if (getCurrent() == null)
- throw new IllegalStateException("No transaction registered with the current thread.");
- getCurrent().commit();
- }
-
- public int getStatus()
-// throws SystemException
- {
- if (getCurrent() == null)
- return tsa.getNoTransactionStatus();
- return getTransaction().getStatus();
- }
-
- public SimpleTransaction<Integer> getTransaction()
-// throws SystemException
- {
- return getCurrent();
- }
-
- protected SimpleTransaction<Integer> getCurrent()
-// throws SystemException
- {
- SimpleTransaction<Integer> transaction = current.get();
- if (transaction == null)
- return null;
- Integer status = transaction.getStatus();
- if (status.equals(tsa.getCommittedStatus()) || status.equals(tsa.getRolledBackStatus())) {
- current.remove();
- return null;
- }
- return transaction;
- }
-
- void unregister(Xid xid) {
- knownTransactions.remove(xid);
- }
-
- public void resume(SimpleTransaction<Integer> tobj)
-// throws InvalidTransactionException, IllegalStateException, SystemException
- {
- if (getCurrent() != null)
- throw new IllegalStateException("Transaction " + current.get() + " already registered");
- current.set(tobj);
- }
-
- public void rollback()
-// throws IllegalStateException, SecurityException, SystemException
- {
- if (getCurrent() == null)
- throw new IllegalStateException("No transaction registered with the current thread.");
- getCurrent().rollback();
- }
-
- public void setRollbackOnly()
-// throws IllegalStateException, SystemException
- {
- if (getCurrent() == null)
- throw new IllegalStateException("No transaction registered with the current thread.");
- getCurrent().setRollbackOnly();
- }
-
- public void setTransactionTimeout(int seconds)
-// throws SystemException
- {
- throw new UnsupportedOperationException();
- }
-
- public SimpleTransaction<Integer> suspend()
-// throws SystemException
- {
- SimpleTransaction<Integer> transaction = getCurrent();
- current.remove();
- return transaction;
- }
-
-// public TransactionSynchronizationRegistry getTsr() {
-// return syncRegistry;
-// }
-//
-// private class SyncRegistry implements TransactionSynchronizationRegistry {
-// @Override
-// public Object getTransactionKey() {
-// try {
-// SimpleTransaction transaction = getCurrent();
-// if (transaction == null)
-// return null;
-// return getCurrent().getXid();
-// } catch (SystemException e) {
-// throw new IllegalStateException("Cannot get transaction key", e);
-// }
-// }
-//
-// @Override
-// public void putResource(Object key, Object value) {
-// throw new UnsupportedOperationException();
-// }
-//
-// @Override
-// public Object getResource(Object key) {
-// throw new UnsupportedOperationException();
-// }
-//
-// @Override
-// public void registerInterposedSynchronization(Synchronization sync) {
-// throw new UnsupportedOperationException();
-// }
-//
-// @Override
-// public int getTransactionStatus() {
-// try {
-// return getStatus();
-// } catch (SystemException e) {
-// throw new IllegalStateException("Cannot get status", e);
-// }
-// }
-//
-// @Override
-// public boolean getRollbackOnly() {
-// try {
-// return getStatus() == Status.STATUS_MARKED_ROLLBACK;
-// } catch (SystemException e) {
-// throw new IllegalStateException("Cannot get status", e);
-// }
-// }
-//
-// @Override
-// public void setRollbackOnly() {
-// try {
-// getCurrent().setRollbackOnly();
-// } catch (Exception e) {
-// throw new IllegalStateException("Cannot set rollback only", e);
-// }
-// }
-//
-// }
-}
+++ /dev/null
-package org.argeo.osgi.transaction;
-
-/** Abstract the various approaches to represent transaction status. */
-public interface TransactionStatusAdapter<T> {
- T getActiveStatus();
-
- T getPreparingStatus();
-
- T getMarkedRollbackStatus();
-
- T getPreparedStatus();
-
- T getCommittingStatus();
-
- T getCommittedStatus();
-
- T getRollingBackStatus();
-
- T getRolledBackStatus();
-
- T getNoTransactionStatus();
-}
+++ /dev/null
-package org.argeo.osgi.transaction;
-
-import java.io.Serializable;
-import java.nio.ByteBuffer;
-import java.util.Arrays;
-import java.util.UUID;
-
-import javax.transaction.xa.Xid;
-
-/**
- * Implementation of {@link Xid} based on {@link UUID}, using max significant
- * bits as global transaction id, and least significant bits as branch
- * qualifier.
- */
-public class UuidXid implements Xid, Serializable {
- private static final long serialVersionUID = -5380531989917886819L;
- public final static int FORMAT = (int) serialVersionUID;
-
- private final static int BYTES_PER_LONG = Long.SIZE / Byte.SIZE;
-
- private final int format;
- private final byte[] globalTransactionId;
- private final byte[] branchQualifier;
- private final String uuid;
- private final int hashCode;
-
- public UuidXid() {
- this(UUID.randomUUID());
- }
-
- public UuidXid(UUID uuid) {
- this.format = FORMAT;
- this.globalTransactionId = uuidToBytes(uuid.getMostSignificantBits());
- this.branchQualifier = uuidToBytes(uuid.getLeastSignificantBits());
- this.uuid = uuid.toString();
- this.hashCode = uuid.hashCode();
- }
-
- public UuidXid(Xid xid) {
- this(xid.getFormatId(), xid.getGlobalTransactionId(), xid
- .getBranchQualifier());
- }
-
- private UuidXid(int format, byte[] globalTransactionId,
- byte[] branchQualifier) {
- this.format = format;
- this.globalTransactionId = globalTransactionId;
- this.branchQualifier = branchQualifier;
- this.uuid = bytesToUUID(globalTransactionId, branchQualifier)
- .toString();
- this.hashCode = uuid.hashCode();
- }
-
- @Override
- public int getFormatId() {
- return format;
- }
-
- @Override
- public byte[] getGlobalTransactionId() {
- return Arrays.copyOf(globalTransactionId, globalTransactionId.length);
- }
-
- @Override
- public byte[] getBranchQualifier() {
- return Arrays.copyOf(branchQualifier, branchQualifier.length);
- }
-
- @Override
- public int hashCode() {
- return hashCode;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj)
- return true;
- if (obj instanceof UuidXid) {
- UuidXid that = (UuidXid) obj;
- return Arrays.equals(globalTransactionId, that.globalTransactionId)
- && Arrays.equals(branchQualifier, that.branchQualifier);
- }
- if (obj instanceof Xid) {
- Xid that = (Xid) obj;
- return Arrays.equals(globalTransactionId,
- that.getGlobalTransactionId())
- && Arrays
- .equals(branchQualifier, that.getBranchQualifier());
- }
- return uuid.equals(obj.toString());
- }
-
- @Override
- protected Object clone() throws CloneNotSupportedException {
- return new UuidXid(format, globalTransactionId, branchQualifier);
- }
-
- @Override
- public String toString() {
- return uuid;
- }
-
- public UUID asUuid() {
- return bytesToUUID(globalTransactionId, branchQualifier);
- }
-
- public static byte[] uuidToBytes(long bits) {
- ByteBuffer buffer = ByteBuffer.allocate(BYTES_PER_LONG);
- buffer.putLong(0, bits);
- return buffer.array();
- }
-
- public static UUID bytesToUUID(byte[] most, byte[] least) {
- if (most.length < BYTES_PER_LONG)
- most = Arrays.copyOf(most, BYTES_PER_LONG);
- if (least.length < BYTES_PER_LONG)
- least = Arrays.copyOf(least, BYTES_PER_LONG);
- ByteBuffer buffer = ByteBuffer.allocate(2 * BYTES_PER_LONG);
- buffer.put(most, 0, BYTES_PER_LONG);
- buffer.put(least, 0, BYTES_PER_LONG);
- buffer.flip();
- return new UUID(buffer.getLong(), buffer.getLong());
- }
-
- // public static void main(String[] args) {
- // UUID uuid = UUID.randomUUID();
- // System.out.println(uuid);
- // uuid = bytesToUUID(uuidToBytes(uuid.getMostSignificantBits()),
- // uuidToBytes(uuid.getLeastSignificantBits()));
- // System.out.println(uuid);
- // }
-}
+++ /dev/null
-package org.argeo.osgi.transaction;
-
-import javax.transaction.xa.XAResource;
-
-/**
- * A minimalistic interface similar to OSGi transaction context in order to
- * register XA resources.
- */
-public interface WorkContext {
- void registerXAResource(XAResource resource, String recoveryId);
-}
+++ /dev/null
-package org.argeo.osgi.transaction;
-
-import java.util.concurrent.Callable;
-
-/**
- * A minimalistic interface inspired by OSGi transaction control in order to
- * commit units of work externally.
- */
-public interface WorkControl {
- <T> T required(Callable<T> work);
-
- void setRollbackOnly();
-
- WorkContext getWorkContext();
-}
+++ /dev/null
-package org.argeo.osgi.transaction;
-
-/**
- * A minimalistic interface inspired by JTA user transaction in order to commit
- * units of work externally.
- */
-public interface WorkTransaction {
- void begin();
-
- void commit();
-
- void rollback();
-
- boolean isNoTransactionStatus();
-}
+++ /dev/null
-/** Minimalistic and partial XA transaction manager implementation. */
-package org.argeo.osgi.transaction;
\ No newline at end of file
+++ /dev/null
-package org.argeo.osgi.useradmin;
-
-import static org.argeo.util.naming.LdapAttrs.objectClass;
-import static org.argeo.util.naming.LdapObjs.extensibleObject;
-import static org.argeo.util.naming.LdapObjs.inetOrgPerson;
-import static org.argeo.util.naming.LdapObjs.organizationalPerson;
-import static org.argeo.util.naming.LdapObjs.person;
-import static org.argeo.util.naming.LdapObjs.top;
-
-import java.io.File;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Dictionary;
-import java.util.Enumeration;
-import java.util.Hashtable;
-import java.util.Iterator;
-import java.util.List;
-
-import javax.naming.InvalidNameException;
-import javax.naming.NameNotFoundException;
-import javax.naming.NamingEnumeration;
-import javax.naming.directory.Attribute;
-import javax.naming.directory.Attributes;
-import javax.naming.directory.BasicAttribute;
-import javax.naming.directory.BasicAttributes;
-import javax.naming.ldap.LdapName;
-import javax.naming.ldap.Rdn;
-
-import org.argeo.osgi.transaction.WorkControl;
-import org.argeo.util.naming.LdapAttrs;
-import org.osgi.framework.Filter;
-import org.osgi.framework.FrameworkUtil;
-import org.osgi.framework.InvalidSyntaxException;
-import org.osgi.service.useradmin.Authorization;
-import org.osgi.service.useradmin.Role;
-import org.osgi.service.useradmin.User;
-import org.osgi.service.useradmin.UserAdmin;
-
-/** Base class for a {@link UserDirectory}. */
-public abstract class AbstractUserDirectory implements UserAdmin, UserDirectory {
- static final String SHARED_STATE_USERNAME = "javax.security.auth.login.name";
- static final String SHARED_STATE_PASSWORD = "javax.security.auth.login.password";
-
- private final Hashtable<String, Object> properties;
- private final LdapName baseDn, userBaseDn, groupBaseDn;
- private final String userObjectClass, userBase, groupObjectClass, groupBase;
-
- private final boolean readOnly;
- private final boolean disabled;
- private final String uri;
-
- private UserAdmin externalRoles;
- // private List<String> indexedUserProperties = Arrays
- // .asList(new String[] { LdapAttrs.uid.name(), LdapAttrs.mail.name(),
- // LdapAttrs.cn.name() });
-
- private final boolean scoped;
-
- private String memberAttributeId = "member";
- private List<String> credentialAttributeIds = Arrays
- .asList(new String[] { LdapAttrs.userPassword.name(), LdapAttrs.authPassword.name() });
-
- // Transaction
-// private TransactionManager transactionManager;
- private WorkControl transactionControl;
- private WcXaResource xaResource = new WcXaResource(this);
-
- private String forcedPassword;
-
- AbstractUserDirectory(URI uriArg, Dictionary<String, ?> props, boolean scoped) {
- this.scoped = scoped;
- properties = new Hashtable<String, Object>();
- for (Enumeration<String> keys = props.keys(); keys.hasMoreElements();) {
- String key = keys.nextElement();
- properties.put(key, props.get(key));
- }
-
- if (uriArg != null) {
- uri = uriArg.toString();
- // uri from properties is ignored
- } else {
- String uriStr = UserAdminConf.uri.getValue(properties);
- if (uriStr == null)
- uri = null;
- else
- uri = uriStr;
- }
-
- forcedPassword = UserAdminConf.forcedPassword.getValue(properties);
-
- userObjectClass = UserAdminConf.userObjectClass.getValue(properties);
- userBase = UserAdminConf.userBase.getValue(properties);
- groupObjectClass = UserAdminConf.groupObjectClass.getValue(properties);
- groupBase = UserAdminConf.groupBase.getValue(properties);
- try {
- baseDn = new LdapName(UserAdminConf.baseDn.getValue(properties));
- userBaseDn = new LdapName(userBase + "," + baseDn);
- groupBaseDn = new LdapName(groupBase + "," + baseDn);
- } catch (InvalidNameException e) {
- throw new UserDirectoryException("Badly formated base DN " + UserAdminConf.baseDn.getValue(properties), e);
- }
- String readOnlyStr = UserAdminConf.readOnly.getValue(properties);
- if (readOnlyStr == null) {
- readOnly = readOnlyDefault(uri);
- properties.put(UserAdminConf.readOnly.name(), Boolean.toString(readOnly));
- } else
- readOnly = Boolean.parseBoolean(readOnlyStr);
- String disabledStr = UserAdminConf.disabled.getValue(properties);
- if (disabledStr != null)
- disabled = Boolean.parseBoolean(disabledStr);
- else
- disabled = false;
- }
-
- /** Returns the groups this user is a direct member of. */
- protected abstract List<LdapName> getDirectGroups(LdapName dn);
-
- protected abstract Boolean daoHasRole(LdapName dn);
-
- protected abstract DirectoryUser daoGetRole(LdapName key) throws NameNotFoundException;
-
- protected abstract List<DirectoryUser> doGetRoles(Filter f);
-
- protected abstract AbstractUserDirectory scope(User user);
-
- public void init() {
-
- }
-
- public void destroy() {
-
- }
-
- protected boolean isEditing() {
- return xaResource.wc() != null;
- }
-
- protected UserDirectoryWorkingCopy getWorkingCopy() {
- UserDirectoryWorkingCopy wc = xaResource.wc();
- if (wc == null)
- return null;
- return wc;
- }
-
- protected void checkEdit() {
-// Transaction transaction;
-// try {
-// transaction = transactionManager.getTransaction();
-// } catch (SystemException e) {
-// throw new UserDirectoryException("Cannot get transaction", e);
-// }
-// if (transaction == null)
-// throw new UserDirectoryException("A transaction needs to be active in order to edit");
- if (xaResource.wc() == null) {
- try {
-// transaction.enlistResource(xaResource);
- transactionControl.getWorkContext().registerXAResource(xaResource, null);
- } catch (Exception e) {
- throw new UserDirectoryException("Cannot enlist " + xaResource, e);
- }
- } else {
- }
- }
-
- protected List<Role> getAllRoles(DirectoryUser user) {
- List<Role> allRoles = new ArrayList<Role>();
- if (user != null) {
- collectRoles(user, allRoles);
- allRoles.add(user);
- } else
- collectAnonymousRoles(allRoles);
- return allRoles;
- }
-
- private void collectRoles(DirectoryUser user, List<Role> allRoles) {
- Attributes attrs = user.getAttributes();
- // TODO centralize attribute name
- Attribute memberOf = attrs.get(LdapAttrs.memberOf.name());
- // if user belongs to this directory, we only check meberOf
- if (memberOf != null && user.getDn().startsWith(getBaseDn())) {
- try {
- NamingEnumeration<?> values = memberOf.getAll();
- while (values.hasMore()) {
- Object value = values.next();
- LdapName groupDn = new LdapName(value.toString());
- DirectoryUser group = doGetRole(groupDn);
- if (group != null)
- allRoles.add(group);
- }
- } catch (Exception e) {
- throw new UserDirectoryException("Cannot get memberOf groups for " + user, e);
- }
- } else {
- for (LdapName groupDn : getDirectGroups(user.getDn())) {
- // TODO check for loops
- DirectoryUser group = doGetRole(groupDn);
- if (group != null) {
- allRoles.add(group);
- collectRoles(group, allRoles);
- }
- }
- }
- }
-
- private void collectAnonymousRoles(List<Role> allRoles) {
- // TODO gather anonymous roles
- }
-
- // USER ADMIN
- @Override
- public Role getRole(String name) {
- return doGetRole(toDn(name));
- }
-
- protected DirectoryUser doGetRole(LdapName dn) {
- UserDirectoryWorkingCopy wc = getWorkingCopy();
- DirectoryUser user;
- try {
- user = daoGetRole(dn);
- } catch (NameNotFoundException e) {
- user = null;
- }
- if (wc != null) {
- if (user == null && wc.getNewUsers().containsKey(dn))
- user = wc.getNewUsers().get(dn);
- else if (wc.getDeletedUsers().containsKey(dn))
- user = null;
- }
- return user;
- }
-
- @Override
- public Role[] getRoles(String filter) throws InvalidSyntaxException {
- UserDirectoryWorkingCopy wc = getWorkingCopy();
- Filter f = filter != null ? FrameworkUtil.createFilter(filter) : null;
- List<DirectoryUser> res = doGetRoles(f);
- if (wc != null) {
- for (Iterator<DirectoryUser> it = res.iterator(); it.hasNext();) {
- DirectoryUser user = it.next();
- LdapName dn = user.getDn();
- if (wc.getDeletedUsers().containsKey(dn))
- it.remove();
- }
- for (DirectoryUser user : wc.getNewUsers().values()) {
- if (f == null || f.match(user.getProperties()))
- res.add(user);
- }
- // no need to check modified users,
- // since doGetRoles was already based on the modified attributes
- }
- return res.toArray(new Role[res.size()]);
- }
-
- @Override
- public User getUser(String key, String value) {
- // TODO check value null or empty
- List<DirectoryUser> collectedUsers = new ArrayList<DirectoryUser>();
- if (key != null) {
- doGetUser(key, value, collectedUsers);
- } else {
- throw new UserDirectoryException("Key cannot be null");
- }
-
- if (collectedUsers.size() == 1) {
- return collectedUsers.get(0);
- } else if (collectedUsers.size() > 1) {
- // log.warn(collectedUsers.size() + " users for " + (key != null ? key + "=" :
- // "") + value);
- }
- return null;
- }
-
- protected void doGetUser(String key, String value, List<DirectoryUser> collectedUsers) {
- try {
- Filter f = FrameworkUtil.createFilter("(" + key + "=" + value + ")");
- List<DirectoryUser> users = doGetRoles(f);
- collectedUsers.addAll(users);
- } catch (InvalidSyntaxException e) {
- throw new UserDirectoryException("Cannot get user with " + key + "=" + value, e);
- }
- }
-
- @Override
- public Authorization getAuthorization(User user) {
- if (user == null || user instanceof DirectoryUser) {
- return new LdifAuthorization(user, getAllRoles((DirectoryUser) user));
- } else {
- // bind
- AbstractUserDirectory scopedUserAdmin = scope(user);
- try {
- DirectoryUser directoryUser = (DirectoryUser) scopedUserAdmin.getRole(user.getName());
- if (directoryUser == null)
- throw new UserDirectoryException("No scoped user found for " + user);
- LdifAuthorization authorization = new LdifAuthorization(directoryUser,
- scopedUserAdmin.getAllRoles(directoryUser));
- return authorization;
- } finally {
- scopedUserAdmin.destroy();
- }
- }
- }
-
- @Override
- public Role createRole(String name, int type) {
- checkEdit();
- UserDirectoryWorkingCopy wc = getWorkingCopy();
- LdapName dn = toDn(name);
- if ((daoHasRole(dn) && !wc.getDeletedUsers().containsKey(dn)) || wc.getNewUsers().containsKey(dn))
- throw new UserDirectoryException("Already a role " + name);
- BasicAttributes attrs = new BasicAttributes(true);
- // attrs.put(LdifName.dn.name(), dn.toString());
- Rdn nameRdn = dn.getRdn(dn.size() - 1);
- // TODO deal with multiple attr RDN
- attrs.put(nameRdn.getType(), nameRdn.getValue());
- if (wc.getDeletedUsers().containsKey(dn)) {
- wc.getDeletedUsers().remove(dn);
- wc.getModifiedUsers().put(dn, attrs);
- return getRole(name);
- } else {
- wc.getModifiedUsers().put(dn, attrs);
- DirectoryUser newRole = newRole(dn, type, attrs);
- wc.getNewUsers().put(dn, newRole);
- return newRole;
- }
- }
-
- protected DirectoryUser newRole(LdapName dn, int type, Attributes attrs) {
- LdifUser newRole;
- BasicAttribute objClass = new BasicAttribute(objectClass.name());
- if (type == Role.USER) {
- String userObjClass = newUserObjectClass(dn);
- objClass.add(userObjClass);
- if (inetOrgPerson.name().equals(userObjClass)) {
- objClass.add(organizationalPerson.name());
- objClass.add(person.name());
- } else if (organizationalPerson.name().equals(userObjClass)) {
- objClass.add(person.name());
- }
- objClass.add(top.name());
- objClass.add(extensibleObject.name());
- attrs.put(objClass);
- newRole = new LdifUser(this, dn, attrs);
- } else if (type == Role.GROUP) {
- String groupObjClass = getGroupObjectClass();
- objClass.add(groupObjClass);
- // objClass.add(LdifName.extensibleObject.name());
- objClass.add(top.name());
- attrs.put(objClass);
- newRole = new LdifGroup(this, dn, attrs);
- } else
- throw new UserDirectoryException("Unsupported type " + type);
- return newRole;
- }
-
- @Override
- public boolean removeRole(String name) {
- checkEdit();
- UserDirectoryWorkingCopy wc = getWorkingCopy();
- LdapName dn = toDn(name);
- boolean actuallyDeleted;
- if (daoHasRole(dn) || wc.getNewUsers().containsKey(dn)) {
- DirectoryUser user = (DirectoryUser) getRole(name);
- wc.getDeletedUsers().put(dn, user);
- actuallyDeleted = true;
- } else {// just removing from groups (e.g. system roles)
- actuallyDeleted = false;
- }
- for (LdapName groupDn : getDirectGroups(dn)) {
- DirectoryUser group = doGetRole(groupDn);
- group.getAttributes().get(getMemberAttributeId()).remove(dn.toString());
- }
- return actuallyDeleted;
- }
-
- // TRANSACTION
- protected void prepare(UserDirectoryWorkingCopy wc) {
-
- }
-
- protected void commit(UserDirectoryWorkingCopy wc) {
-
- }
-
- protected void rollback(UserDirectoryWorkingCopy wc) {
-
- }
-
- // UTILITIES
- protected LdapName toDn(String name) {
- try {
- return new LdapName(name);
- } catch (InvalidNameException e) {
- throw new UserDirectoryException("Badly formatted name", e);
- }
- }
-
- // GETTERS
- protected String getMemberAttributeId() {
- return memberAttributeId;
- }
-
- protected List<String> getCredentialAttributeIds() {
- return credentialAttributeIds;
- }
-
- protected String getUri() {
- return uri;
- }
-
- private static boolean readOnlyDefault(String uriStr) {
- if (uriStr == null)
- return true;
- /// TODO make it more generic
- URI uri;
- try {
- uri = new URI(uriStr.split(" ")[0]);
- } catch (URISyntaxException e) {
- throw new IllegalArgumentException(e);
- }
- if (uri.getScheme() == null)
- return false;// assume relative file to be writable
- if (uri.getScheme().equals(UserAdminConf.SCHEME_FILE)) {
- File file = new File(uri);
- if (file.exists())
- return !file.canWrite();
- else
- return !file.getParentFile().canWrite();
- } else if (uri.getScheme().equals(UserAdminConf.SCHEME_LDAP)) {
- if (uri.getAuthority() != null)// assume writable if authenticated
- return false;
- } else if (uri.getScheme().equals(UserAdminConf.SCHEME_OS)) {
- return true;
- }
- return true;// read only by default
- }
-
- public boolean isReadOnly() {
- return readOnly;
- }
-
- public boolean isDisabled() {
- return disabled;
- }
-
- protected UserAdmin getExternalRoles() {
- return externalRoles;
- }
-
- protected int roleType(LdapName dn) {
- if (dn.startsWith(groupBaseDn))
- return Role.GROUP;
- else if (dn.startsWith(userBaseDn))
- return Role.USER;
- else
- return Role.GROUP;
- }
-
- /** dn can be null, in that case a default should be returned. */
- public String getUserObjectClass() {
- return userObjectClass;
- }
-
- public String getUserBase() {
- return userBase;
- }
-
- protected String newUserObjectClass(LdapName dn) {
- return getUserObjectClass();
- }
-
- public String getGroupObjectClass() {
- return groupObjectClass;
- }
-
- public String getGroupBase() {
- return groupBase;
- }
-
- public LdapName getBaseDn() {
- return (LdapName) baseDn.clone();
- }
-
- public Dictionary<String, Object> getProperties() {
- return properties;
- }
-
- public Dictionary<String, Object> cloneProperties() {
- return new Hashtable<>(properties);
- }
-
- public void setExternalRoles(UserAdmin externalRoles) {
- this.externalRoles = externalRoles;
- }
-
-// public void setTransactionManager(TransactionManager transactionManager) {
-// this.transactionManager = transactionManager;
-// }
-
- public String getForcedPassword() {
- return forcedPassword;
- }
-
- public void setTransactionControl(WorkControl transactionControl) {
- this.transactionControl = transactionControl;
- }
-
- public WcXaResource getXaResource() {
- return xaResource;
- }
-
- public boolean isScoped() {
- return scoped;
- }
-
-}
+++ /dev/null
-package org.argeo.osgi.useradmin;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-import javax.security.auth.x500.X500Principal;
-
-import org.osgi.service.useradmin.Authorization;
-
-/** An {@link Authorization} which combines roles form various auth sources. */
-class AggregatingAuthorization implements Authorization {
- private final String name;
- private final String displayName;
- private final Set<String> systemRoles;
- private final Set<String> roles;
-
- public AggregatingAuthorization(String name, String displayName, Set<String> systemRoles, String[] roles) {
- this.name = new X500Principal(name).getName();
- this.displayName = displayName;
- this.systemRoles = Collections.unmodifiableSet(new HashSet<>(systemRoles));
- Set<String> temp = new HashSet<>();
- for (String role : roles) {
- if (!temp.contains(role))
- temp.add(role);
- }
- this.roles = Collections.unmodifiableSet(temp);
- }
-
- @Override
- public String getName() {
- return name;
- }
-
- @Override
- public boolean hasRole(String name) {
- if (systemRoles.contains(name))
- return true;
- if (roles.contains(name))
- return true;
- return false;
- }
-
- @Override
- public String[] getRoles() {
- int size = systemRoles.size() + roles.size();
- List<String> res = new ArrayList<String>(size);
- res.addAll(systemRoles);
- res.addAll(roles);
- return res.toArray(new String[size]);
- }
-
- @Override
- public int hashCode() {
- if (name == null)
- return super.hashCode();
- return name.hashCode();
- }
-
- @Override
- public boolean equals(Object obj) {
- if (!(obj instanceof Authorization))
- return false;
- Authorization that = (Authorization) obj;
- if (name == null)
- return that.getName() == null;
- return name.equals(that.getName());
- }
-
- @Override
- public String toString() {
- return displayName;
- }
-
-}
+++ /dev/null
-package org.argeo.osgi.useradmin;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import javax.naming.InvalidNameException;
-import javax.naming.ldap.LdapName;
-
-import org.osgi.framework.InvalidSyntaxException;
-import org.osgi.service.useradmin.Authorization;
-import org.osgi.service.useradmin.Group;
-import org.osgi.service.useradmin.Role;
-import org.osgi.service.useradmin.User;
-import org.osgi.service.useradmin.UserAdmin;
-
-/**
- * Aggregates multiple {@link UserDirectory} and integrates them with system
- * roles.
- */
-public class AggregatingUserAdmin implements UserAdmin {
- private final LdapName systemRolesBaseDn;
- private final LdapName tokensBaseDn;
-
- // DAOs
- private AbstractUserDirectory systemRoles = null;
- private AbstractUserDirectory tokens = null;
- private Map<LdapName, AbstractUserDirectory> businessRoles = new HashMap<LdapName, AbstractUserDirectory>();
-
- // TODO rather use an empty constructor and an init method
- public AggregatingUserAdmin(String systemRolesBaseDn, String tokensBaseDn) {
- try {
- this.systemRolesBaseDn = new LdapName(systemRolesBaseDn);
- if (tokensBaseDn != null)
- this.tokensBaseDn = new LdapName(tokensBaseDn);
- else
- this.tokensBaseDn = null;
- } catch (InvalidNameException e) {
- throw new UserDirectoryException("Cannot initialize " + AggregatingUserAdmin.class, e);
- }
- }
-
- @Override
- public Role createRole(String name, int type) {
- return findUserAdmin(name).createRole(name, type);
- }
-
- @Override
- public boolean removeRole(String name) {
- boolean actuallyDeleted = findUserAdmin(name).removeRole(name);
- systemRoles.removeRole(name);
- return actuallyDeleted;
- }
-
- @Override
- public Role getRole(String name) {
- return findUserAdmin(name).getRole(name);
- }
-
- @Override
- public Role[] getRoles(String filter) throws InvalidSyntaxException {
- List<Role> res = new ArrayList<Role>();
- for (UserAdmin userAdmin : businessRoles.values()) {
- res.addAll(Arrays.asList(userAdmin.getRoles(filter)));
- }
- res.addAll(Arrays.asList(systemRoles.getRoles(filter)));
- return res.toArray(new Role[res.size()]);
- }
-
- @Override
- public User getUser(String key, String value) {
- List<User> res = new ArrayList<User>();
- for (UserAdmin userAdmin : businessRoles.values()) {
- User u = userAdmin.getUser(key, value);
- if (u != null)
- res.add(u);
- }
- // Note: node roles cannot contain users, so it is not searched
- return res.size() == 1 ? res.get(0) : null;
- }
-
- @Override
- public Authorization getAuthorization(User user) {
- if (user == null) {// anonymous
- return systemRoles.getAuthorization(null);
- }
- AbstractUserDirectory userReferentialOfThisUser = findUserAdmin(user.getName());
- Authorization rawAuthorization = userReferentialOfThisUser.getAuthorization(user);
- String usernameToUse;
- String displayNameToUse;
- if (user instanceof Group) {
- // TODO check whether this is still working
- String ownerDn = TokenUtils.userDn((Group) user);
- if (ownerDn != null) {// tokens
- UserAdmin ownerUserAdmin = findUserAdmin(ownerDn);
- User ownerUser = (User) ownerUserAdmin.getRole(ownerDn);
- usernameToUse = ownerDn;
- displayNameToUse = LdifAuthorization.extractDisplayName(ownerUser);
- } else {
- usernameToUse = rawAuthorization.getName();
- displayNameToUse = rawAuthorization.toString();
- }
- } else {// regular users
- usernameToUse = rawAuthorization.getName();
- displayNameToUse = rawAuthorization.toString();
- }
-
- // gather roles from other referentials
- final AbstractUserDirectory userAdminToUse;// possibly scoped when authenticating
- if (user instanceof DirectoryUser) {
- userAdminToUse = userReferentialOfThisUser;
- } else if (user instanceof AuthenticatingUser) {
- userAdminToUse = userReferentialOfThisUser.scope(user);
- } else {
- throw new IllegalArgumentException("Unsupported user type " + user.getClass());
- }
-
- try {
- Set<String> sysRoles = new HashSet<String>();
- for (String role : rawAuthorization.getRoles()) {
- User userOrGroup = (User) userAdminToUse.getRole(role);
- Authorization auth = systemRoles.getAuthorization(userOrGroup);
- systemRoles: for (String systemRole : auth.getRoles()) {
- if (role.equals(systemRole))
- continue systemRoles;
- sysRoles.add(systemRole);
- }
-// sysRoles.addAll(Arrays.asList(auth.getRoles()));
- }
- addAbstractSystemRoles(rawAuthorization, sysRoles);
- Authorization authorization = new AggregatingAuthorization(usernameToUse, displayNameToUse, sysRoles,
- rawAuthorization.getRoles());
- return authorization;
- } finally {
- if (userAdminToUse != null && userAdminToUse.isScoped()) {
- userAdminToUse.destroy();
- }
- }
- }
-
- /**
- * Enrich with application-specific roles which are strictly programmatic, such
- * as anonymous/user semantics.
- */
- protected void addAbstractSystemRoles(Authorization rawAuthorization, Set<String> sysRoles) {
-
- }
-
- //
- // USER ADMIN AGGREGATOR
- //
- protected void addUserDirectory(AbstractUserDirectory userDirectory) {
- LdapName baseDn = userDirectory.getBaseDn();
- if (isSystemRolesBaseDn(baseDn)) {
- this.systemRoles = userDirectory;
- systemRoles.setExternalRoles(this);
- } else if (isTokensBaseDn(baseDn)) {
- this.tokens = userDirectory;
- tokens.setExternalRoles(this);
- } else {
- if (businessRoles.containsKey(baseDn))
- throw new UserDirectoryException("There is already a user admin for " + baseDn);
- businessRoles.put(baseDn, userDirectory);
- }
- userDirectory.init();
- postAdd(userDirectory);
- }
-
- /** Called after a new user directory has been added */
- protected void postAdd(AbstractUserDirectory userDirectory) {
- }
-
-// private UserAdmin findUserAdmin(User user) {
-// if (user == null)
-// throw new IllegalArgumentException("User should not be null");
-// AbstractUserDirectory userAdmin = findUserAdmin(user.getName());
-// if (user instanceof DirectoryUser) {
-// return userAdmin;
-// } else {
-// return userAdmin.scope(user);
-// }
-// }
-
- private AbstractUserDirectory findUserAdmin(String name) {
- try {
- return findUserAdmin(new LdapName(name));
- } catch (InvalidNameException e) {
- throw new UserDirectoryException("Badly formatted name " + name, e);
- }
- }
-
- private AbstractUserDirectory findUserAdmin(LdapName name) {
- if (name.startsWith(systemRolesBaseDn))
- return systemRoles;
- if (tokensBaseDn != null && name.startsWith(tokensBaseDn))
- return tokens;
- List<AbstractUserDirectory> res = new ArrayList<>(1);
- userDirectories: for (LdapName baseDn : businessRoles.keySet()) {
- AbstractUserDirectory userDirectory = businessRoles.get(baseDn);
- if (name.startsWith(baseDn)) {
- if (userDirectory.isDisabled())
- continue userDirectories;
-// if (res.isEmpty()) {
- res.add(userDirectory);
-// } else {
-// for (AbstractUserDirectory ud : res) {
-// LdapName bd = ud.getBaseDn();
-// if (userDirectory.getBaseDn().startsWith(bd)) {
-// // child user directory
-// }
-// }
-// }
- }
- }
- if (res.size() == 0)
- throw new UserDirectoryException("Cannot find user admin for " + name);
- if (res.size() > 1)
- throw new UserDirectoryException("Multiple user admin found for " + name);
- return res.get(0);
- }
-
- protected boolean isSystemRolesBaseDn(LdapName baseDn) {
- return baseDn.equals(systemRolesBaseDn);
- }
-
- protected boolean isTokensBaseDn(LdapName baseDn) {
- return tokensBaseDn != null && baseDn.equals(tokensBaseDn);
- }
-
-// protected Dictionary<String, Object> currentState() {
-// Dictionary<String, Object> res = new Hashtable<String, Object>();
-// // res.put(NodeConstants.CN, NodeConstants.DEFAULT);
-// for (LdapName name : businessRoles.keySet()) {
-// AbstractUserDirectory userDirectory = businessRoles.get(name);
-// String uri = UserAdminConf.propertiesAsUri(userDirectory.getProperties()).toString();
-// res.put(uri, "");
-// }
-// return res;
-// }
-
- public void destroy() {
- for (LdapName name : businessRoles.keySet()) {
- AbstractUserDirectory userDirectory = businessRoles.get(name);
- destroy(userDirectory);
- }
- businessRoles.clear();
- businessRoles = null;
- destroy(systemRoles);
- systemRoles = null;
- }
-
- private void destroy(AbstractUserDirectory userDirectory) {
- preDestroy(userDirectory);
- userDirectory.destroy();
- }
-
- protected void removeUserDirectory(LdapName baseDn) {
- if (isSystemRolesBaseDn(baseDn))
- throw new UserDirectoryException("System roles cannot be removed ");
- if (!businessRoles.containsKey(baseDn))
- throw new UserDirectoryException("No user directory registered for " + baseDn);
- AbstractUserDirectory userDirectory = businessRoles.remove(baseDn);
- destroy(userDirectory);
- }
-
- /**
- * Called before each user directory is destroyed, so that additional actions
- * can be performed.
- */
- protected void preDestroy(AbstractUserDirectory userDirectory) {
- }
-
-}
+++ /dev/null
-package org.argeo.osgi.useradmin;
-
-import java.util.Dictionary;
-import java.util.Hashtable;
-
-import javax.naming.ldap.LdapName;
-
-import org.osgi.service.useradmin.User;
-
-/**
- * A special user type used during authentication in order to provide the
- * credentials required for scoping the user admin.
- */
-public class AuthenticatingUser implements User {
- /** From com.sun.security.auth.module.*LoginModule */
- public final static String SHARED_STATE_NAME = "javax.security.auth.login.name";
- /** From com.sun.security.auth.module.*LoginModule */
- public final static String SHARED_STATE_PWD = "javax.security.auth.login.password";
-
- private final String name;
- private final Dictionary<String, Object> credentials;
-
- public AuthenticatingUser(LdapName name) {
- if (name == null)
- throw new NullPointerException("Provided name cannot be null.");
- this.name = name.toString();
- this.credentials = new Hashtable<>();
- }
-
- public AuthenticatingUser(String name, Dictionary<String, Object> credentials) {
- this.name = name;
- this.credentials = credentials;
- }
-
- public AuthenticatingUser(String name, char[] password) {
- if (name == null)
- throw new NullPointerException("Provided name cannot be null.");
- this.name = name;
- credentials = new Hashtable<>();
- credentials.put(SHARED_STATE_NAME, name);
- byte[] pwd = DigestUtils.charsToBytes(password);
- credentials.put(SHARED_STATE_PWD, pwd);
- }
-
- @Override
- public String getName() {
- return name;
- }
-
- @Override
- public int getType() {
- return User.USER;
- }
-
- @SuppressWarnings({ "rawtypes", "unchecked" })
- @Override
- public Dictionary getProperties() {
- throw new UnsupportedOperationException();
- }
-
- @SuppressWarnings({ "rawtypes", "unchecked" })
- @Override
- public Dictionary getCredentials() {
- return credentials;
- }
-
- @Override
- public boolean hasCredential(String key, Object value) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public int hashCode() {
- return name.hashCode();
- }
-
- @Override
- public String toString() {
- return "Authenticating user " + name;
- }
-
-}
+++ /dev/null
-package org.argeo.osgi.useradmin;
-
-import java.math.BigInteger;
-import java.nio.ByteBuffer;
-import java.nio.CharBuffer;
-import java.nio.charset.StandardCharsets;
-import java.security.MessageDigest;
-import java.security.spec.KeySpec;
-import java.util.Arrays;
-
-import javax.crypto.SecretKeyFactory;
-import javax.crypto.spec.PBEKeySpec;
-
-/** Utilities around digests, mostly those related to passwords. */
-class DigestUtils {
- final static String PASSWORD_SCHEME_SHA = "SHA";
- final static String PASSWORD_SCHEME_PBKDF2_SHA256 = "PBKDF2_SHA256";
-
- static byte[] sha1(byte[] bytes) {
- try {
- MessageDigest digest = MessageDigest.getInstance("SHA1");
- digest.update(bytes);
- byte[] checksum = digest.digest();
- return checksum;
- } catch (Exception e) {
- throw new UserDirectoryException("Cannot SHA1 digest", e);
- }
- }
-
- static byte[] toPasswordScheme(String passwordScheme, char[] password, byte[] salt, Integer iterations,
- Integer keyLength) {
- try {
- if (PASSWORD_SCHEME_SHA.equals(passwordScheme)) {
- MessageDigest digest = MessageDigest.getInstance("SHA1");
- byte[] bytes = charsToBytes(password);
- digest.update(bytes);
- return digest.digest();
- } else if (PASSWORD_SCHEME_PBKDF2_SHA256.equals(passwordScheme)) {
- KeySpec spec = new PBEKeySpec(password, salt, iterations, keyLength);
-
- SecretKeyFactory f = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
- final int ITERATION_LENGTH = 4;
- byte[] key = f.generateSecret(spec).getEncoded();
- byte[] result = new byte[ITERATION_LENGTH + salt.length + key.length];
- byte iterationsArr[] = new BigInteger(iterations.toString()).toByteArray();
- if (iterationsArr.length < ITERATION_LENGTH) {
- Arrays.fill(result, 0, ITERATION_LENGTH - iterationsArr.length, (byte) 0);
- System.arraycopy(iterationsArr, 0, result, ITERATION_LENGTH - iterationsArr.length,
- iterationsArr.length);
- } else {
- System.arraycopy(iterationsArr, 0, result, 0, ITERATION_LENGTH);
- }
- System.arraycopy(salt, 0, result, ITERATION_LENGTH, salt.length);
- System.arraycopy(key, 0, result, ITERATION_LENGTH + salt.length, key.length);
- return result;
- } else {
- throw new UnsupportedOperationException("Unkown password scheme " + passwordScheme);
- }
- } catch (Exception e) {
- throw new UserDirectoryException("Cannot digest", e);
- }
- }
-
- static char[] bytesToChars(Object obj) {
- if (obj instanceof char[])
- return (char[]) obj;
- if (!(obj instanceof byte[]))
- throw new IllegalArgumentException(obj.getClass() + " is not a byte array");
- ByteBuffer fromBuffer = ByteBuffer.wrap((byte[]) obj);
- CharBuffer toBuffer = StandardCharsets.UTF_8.decode(fromBuffer);
- char[] res = Arrays.copyOfRange(toBuffer.array(), toBuffer.position(), toBuffer.limit());
- // Arrays.fill(fromBuffer.array(), (byte) 0); // clear sensitive data
- // Arrays.fill((byte[]) obj, (byte) 0); // clear sensitive data
- // Arrays.fill(toBuffer.array(), '\u0000'); // clear sensitive data
- return res;
- }
-
- static byte[] charsToBytes(char[] chars) {
- CharBuffer charBuffer = CharBuffer.wrap(chars);
- ByteBuffer byteBuffer = StandardCharsets.UTF_8.encode(charBuffer);
- byte[] bytes = Arrays.copyOfRange(byteBuffer.array(), byteBuffer.position(), byteBuffer.limit());
- // Arrays.fill(charBuffer.array(), '\u0000'); // clear sensitive data
- // Arrays.fill(byteBuffer.array(), (byte) 0); // clear sensitive data
- return bytes;
- }
-
- static String sha1str(String str) {
- byte[] hash = sha1(str.getBytes(StandardCharsets.UTF_8));
- return encodeHexString(hash);
- }
-
- final private static char[] hexArray = "0123456789abcdef".toCharArray();
-
- /**
- * From
- * http://stackoverflow.com/questions/9655181/how-to-convert-a-byte-array-to
- * -a-hex-string-in-java
- */
- public static String encodeHexString(byte[] bytes) {
- 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);
- }
-
- private DigestUtils() {
- }
-}
+++ /dev/null
-package org.argeo.osgi.useradmin;
-
-import java.util.List;
-
-import javax.naming.ldap.LdapName;
-
-import org.osgi.service.useradmin.Group;
-
-/** A group in a user directroy. */
-interface DirectoryGroup extends Group, DirectoryUser {
- List<LdapName> getMemberNames();
-}
+++ /dev/null
-package org.argeo.osgi.useradmin;
-
-import javax.naming.directory.Attributes;
-import javax.naming.ldap.LdapName;
-
-import org.osgi.service.useradmin.User;
-
-/** A user in a user directory. */
-interface DirectoryUser extends User {
- LdapName getDn();
-
- Attributes getAttributes();
-
- void publishAttributes(Attributes modifiedAttributes);
-}
+++ /dev/null
-package org.argeo.osgi.useradmin;
-
-import java.io.IOException;
-import java.net.InetAddress;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.ArrayList;
-import java.util.Dictionary;
-import java.util.Hashtable;
-import java.util.List;
-
-import javax.naming.InvalidNameException;
-import javax.naming.NamingException;
-import javax.naming.ldap.LdapName;
-
-import org.argeo.util.naming.DnsBrowser;
-import org.argeo.util.naming.LdapAttrs;
-
-/** Free IPA specific conventions. */
-public class IpaUtils {
- public final static String IPA_USER_BASE = "cn=users,cn=accounts";
- public final static String IPA_GROUP_BASE = "cn=groups,cn=accounts";
- public final static String IPA_SERVICE_BASE = "cn=services,cn=accounts";
-
- private final static String KRB_PRINCIPAL_NAME = LdapAttrs.krbPrincipalName.name().toLowerCase();
-
- public final static String IPA_USER_DIRECTORY_CONFIG = UserAdminConf.userBase + "=" + IPA_USER_BASE + "&"
- + UserAdminConf.groupBase + "=" + IPA_GROUP_BASE + "&" + UserAdminConf.readOnly + "=true";
-
- @Deprecated
- static String domainToUserDirectoryConfigPath(String realm) {
- return domainToBaseDn(realm) + "?" + IPA_USER_DIRECTORY_CONFIG + "&" + UserAdminConf.realm.name() + "=" + realm;
- }
-
- public static void addIpaConfig(String realm, Dictionary<String, Object> properties) {
- properties.put(UserAdminConf.baseDn.name(), domainToBaseDn(realm));
- properties.put(UserAdminConf.realm.name(), realm);
- properties.put(UserAdminConf.userBase.name(), IPA_USER_BASE);
- properties.put(UserAdminConf.groupBase.name(), IPA_GROUP_BASE);
- properties.put(UserAdminConf.readOnly.name(), Boolean.TRUE.toString());
- }
-
- public static String domainToBaseDn(String domain) {
- String[] dcs = domain.split("\\.");
- StringBuilder sb = new StringBuilder();
- for (int i = 0; i < dcs.length; i++) {
- if (i != 0)
- sb.append(',');
- String dc = dcs[i];
- sb.append(LdapAttrs.dc.name()).append('=').append(dc.toLowerCase());
- }
- return sb.toString();
- }
-
- public static LdapName kerberosToDn(String kerberosName) {
- String[] kname = kerberosName.split("@");
- String username = kname[0];
- String baseDn = domainToBaseDn(kname[1]);
- String dn;
- if (!username.contains("/"))
- dn = LdapAttrs.uid + "=" + username + "," + IPA_USER_BASE + "," + baseDn;
- else
- dn = KRB_PRINCIPAL_NAME + "=" + kerberosName + "," + IPA_SERVICE_BASE + "," + baseDn;
- try {
- return new LdapName(dn);
- } catch (InvalidNameException e) {
- throw new IllegalArgumentException("Badly formatted name for " + kerberosName + ": " + dn);
- }
- }
-
- private IpaUtils() {
-
- }
-
- public static String kerberosDomainFromDns() {
- String kerberosDomain;
- try (DnsBrowser dnsBrowser = new DnsBrowser()) {
- InetAddress localhost = InetAddress.getLocalHost();
- String hostname = localhost.getHostName();
- String dnsZone = hostname.substring(hostname.indexOf('.') + 1);
- kerberosDomain = dnsBrowser.getRecord("_kerberos." + dnsZone, "TXT");
- return kerberosDomain;
- } catch (Exception e) {
- throw new UserDirectoryException("Cannot determine Kerberos domain from DNS", e);
- }
-
- }
-
- public static Dictionary<String, Object> convertIpaUri(URI uri) {
- String path = uri.getPath();
- String kerberosRealm;
- if (path == null || path.length() <= 1) {
- kerberosRealm = kerberosDomainFromDns();
- } else {
- kerberosRealm = path.substring(1);
- }
-
- if (kerberosRealm == null)
- throw new UserDirectoryException("No Kerberos domain available for " + uri);
- // TODO intergrate CA certificate in truststore
- // String schemeToUse = SCHEME_LDAPS;
- String schemeToUse = UserAdminConf.SCHEME_LDAP;
- List<String> ldapHosts;
- String ldapHostsStr = uri.getHost();
- if (ldapHostsStr == null || ldapHostsStr.trim().equals("")) {
- try (DnsBrowser dnsBrowser = new DnsBrowser()) {
- ldapHosts = dnsBrowser.getSrvRecordsAsHosts("_ldap._tcp." + kerberosRealm.toLowerCase(),
- schemeToUse.equals(UserAdminConf.SCHEME_LDAP) ? true : false);
- if (ldapHosts == null || ldapHosts.size() == 0) {
- throw new UserDirectoryException("Cannot configure LDAP for IPA " + uri);
- } else {
- ldapHostsStr = ldapHosts.get(0);
- }
- } catch (NamingException | IOException e) {
- throw new UserDirectoryException("cannot convert IPA uri " + uri, e);
- }
- } else {
- ldapHosts = new ArrayList<>();
- ldapHosts.add(ldapHostsStr);
- }
-
- StringBuilder uriStr = new StringBuilder();
- try {
- for (String host : ldapHosts) {
- URI convertedUri = new URI(schemeToUse + "://" + host + "/");
- uriStr.append(convertedUri).append(' ');
- }
- } catch (URISyntaxException e) {
- throw new UserDirectoryException("cannot convert IPA uri " + uri, e);
- }
-
- Hashtable<String, Object> res = new Hashtable<>();
- res.put(UserAdminConf.uri.name(), uriStr.toString());
- addIpaConfig(kerberosRealm, res);
- return res;
- }
-}
+++ /dev/null
-package org.argeo.osgi.useradmin;
-
-import java.util.Dictionary;
-import java.util.Hashtable;
-
-import javax.naming.CommunicationException;
-import javax.naming.Context;
-import javax.naming.NameNotFoundException;
-import javax.naming.NamingEnumeration;
-import javax.naming.NamingException;
-import javax.naming.directory.Attributes;
-import javax.naming.directory.DirContext;
-import javax.naming.directory.SearchControls;
-import javax.naming.directory.SearchResult;
-import javax.naming.ldap.InitialLdapContext;
-import javax.naming.ldap.LdapName;
-
-import org.argeo.util.naming.LdapAttrs;
-
-/** A synchronized wrapper for a single {@link InitialLdapContext}. */
-// TODO implement multiple contexts and connection pooling.
-class LdapConnection {
- private InitialLdapContext initialLdapContext = null;
-
- LdapConnection(String url, Dictionary<String, ?> properties) {
- try {
- Hashtable<String, Object> connEnv = new Hashtable<String, Object>();
- connEnv.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
- connEnv.put(Context.PROVIDER_URL, url);
- connEnv.put("java.naming.ldap.attributes.binary", LdapAttrs.userPassword.name());
- // use pooling in order to avoid connection timeout
-// connEnv.put("com.sun.jndi.ldap.connect.pool", "true");
-// connEnv.put("com.sun.jndi.ldap.connect.pool.timeout", 300000);
-
- initialLdapContext = new InitialLdapContext(connEnv, null);
- // StartTlsResponse tls = (StartTlsResponse) ctx
- // .extendedOperation(new StartTlsRequest());
- // tls.negotiate();
- Object securityAuthentication = properties.get(Context.SECURITY_AUTHENTICATION);
- if (securityAuthentication != null)
- initialLdapContext.addToEnvironment(Context.SECURITY_AUTHENTICATION, securityAuthentication);
- else
- initialLdapContext.addToEnvironment(Context.SECURITY_AUTHENTICATION, "simple");
- Object principal = properties.get(Context.SECURITY_PRINCIPAL);
- if (principal != null) {
- initialLdapContext.addToEnvironment(Context.SECURITY_PRINCIPAL, principal.toString());
- Object creds = properties.get(Context.SECURITY_CREDENTIALS);
- if (creds != null) {
- initialLdapContext.addToEnvironment(Context.SECURITY_CREDENTIALS, creds.toString());
- }
- }
- } catch (Exception e) {
- throw new UserDirectoryException("Cannot connect to LDAP", e);
- }
-
- }
-
- public void init() {
-
- }
-
- public void destroy() {
- try {
- // tls.close();
- initialLdapContext.close();
- initialLdapContext = null;
- } catch (NamingException e) {
- e.printStackTrace();
- }
- }
-
- protected InitialLdapContext getLdapContext() {
- return initialLdapContext;
- }
-
- protected void reconnect() throws NamingException {
- initialLdapContext.reconnect(initialLdapContext.getConnectControls());
- }
-
- public synchronized NamingEnumeration<SearchResult> search(LdapName searchBase, String searchFilter,
- SearchControls searchControls) throws NamingException {
- NamingEnumeration<SearchResult> results;
- try {
- results = getLdapContext().search(searchBase, searchFilter, searchControls);
- } catch (CommunicationException e) {
- reconnect();
- results = getLdapContext().search(searchBase, searchFilter, searchControls);
- }
- return results;
- }
-
- public synchronized Attributes getAttributes(LdapName name) throws NamingException {
- try {
- return getLdapContext().getAttributes(name);
- } catch (CommunicationException e) {
- reconnect();
- return getLdapContext().getAttributes(name);
- }
- }
-
- synchronized void prepareChanges(UserDirectoryWorkingCopy wc) throws NamingException {
- // make sure connection will work
- reconnect();
-
- // delete
- for (LdapName dn : wc.getDeletedUsers().keySet()) {
- if (!entryExists(dn))
- throw new UserDirectoryException("User to delete no found " + dn);
- }
- // add
- for (LdapName dn : wc.getNewUsers().keySet()) {
- if (entryExists(dn))
- throw new UserDirectoryException("User to create found " + dn);
- }
- // modify
- for (LdapName dn : wc.getModifiedUsers().keySet()) {
- if (!wc.getNewUsers().containsKey(dn) && !entryExists(dn))
- throw new UserDirectoryException("User to modify not found " + dn);
- }
-
- }
-
- protected boolean entryExists(LdapName dn) throws NamingException {
- try {
- return getAttributes(dn).size() != 0;
- } catch (NameNotFoundException e) {
- return false;
- }
- }
-
- synchronized void commitChanges(UserDirectoryWorkingCopy wc) throws NamingException {
- // delete
- for (LdapName dn : wc.getDeletedUsers().keySet()) {
- getLdapContext().destroySubcontext(dn);
- }
- // add
- for (LdapName dn : wc.getNewUsers().keySet()) {
- DirectoryUser user = wc.getNewUsers().get(dn);
- getLdapContext().createSubcontext(dn, user.getAttributes());
- }
- // modify
- for (LdapName dn : wc.getModifiedUsers().keySet()) {
- Attributes modifiedAttrs = wc.getModifiedUsers().get(dn);
- getLdapContext().modifyAttributes(dn, DirContext.REPLACE_ATTRIBUTE, modifiedAttrs);
- }
- }
-}
+++ /dev/null
-package org.argeo.osgi.useradmin;
-
-import static org.argeo.util.naming.LdapAttrs.objectClass;
-
-import java.util.ArrayList;
-import java.util.Dictionary;
-import java.util.List;
-
-import javax.naming.AuthenticationNotSupportedException;
-import javax.naming.Binding;
-import javax.naming.Context;
-import javax.naming.InvalidNameException;
-import javax.naming.NameNotFoundException;
-import javax.naming.NamingEnumeration;
-import javax.naming.NamingException;
-import javax.naming.directory.Attribute;
-import javax.naming.directory.Attributes;
-import javax.naming.directory.SearchControls;
-import javax.naming.directory.SearchResult;
-import javax.naming.ldap.LdapName;
-
-import org.osgi.framework.Filter;
-import org.osgi.service.useradmin.Role;
-import org.osgi.service.useradmin.User;
-
-/** A user admin based on a LDAP server. */
-public class LdapUserAdmin extends AbstractUserDirectory {
- private LdapConnection ldapConnection;
-
- public LdapUserAdmin(Dictionary<String, ?> properties) {
- this(properties, false);
- }
-
- public LdapUserAdmin(Dictionary<String, ?> properties, boolean scoped) {
- super(null, properties, scoped);
- ldapConnection = new LdapConnection(getUri().toString(), properties);
- }
-
- public void destroy() {
- ldapConnection.destroy();
- }
-
- @Override
- protected AbstractUserDirectory scope(User user) {
- Dictionary<String, Object> credentials = user.getCredentials();
- String username = (String) credentials.get(SHARED_STATE_USERNAME);
- if (username == null)
- username = user.getName();
- Dictionary<String, Object> properties = cloneProperties();
- properties.put(Context.SECURITY_PRINCIPAL, username.toString());
- Object pwdCred = credentials.get(SHARED_STATE_PASSWORD);
- byte[] pwd = (byte[]) pwdCred;
- if (pwd != null) {
- char[] password = DigestUtils.bytesToChars(pwd);
- properties.put(Context.SECURITY_CREDENTIALS, new String(password));
- } else {
- properties.put(Context.SECURITY_AUTHENTICATION, "GSSAPI");
- }
- return new LdapUserAdmin(properties, true);
- }
-
-// protected InitialLdapContext getLdapContext() {
-// return initialLdapContext;
-// }
-
- @Override
- protected Boolean daoHasRole(LdapName dn) {
- try {
- return daoGetRole(dn) != null;
- } catch (NameNotFoundException e) {
- return false;
- }
- }
-
- @Override
- protected DirectoryUser daoGetRole(LdapName name) throws NameNotFoundException {
- try {
- Attributes attrs = ldapConnection.getAttributes(name);
- if (attrs.size() == 0)
- return null;
- int roleType = roleType(name);
- LdifUser res;
- if (roleType == Role.GROUP)
- res = new LdifGroup(this, name, attrs);
- else if (roleType == Role.USER)
- res = new LdifUser(this, name, attrs);
- else
- throw new UserDirectoryException("Unsupported LDAP type for " + name);
- return res;
- } catch (NameNotFoundException e) {
- throw e;
- } catch (NamingException e) {
- return null;
- }
- }
-
- @Override
- protected List<DirectoryUser> doGetRoles(Filter f) {
- ArrayList<DirectoryUser> res = new ArrayList<DirectoryUser>();
- try {
- String searchFilter = f != null ? f.toString()
- : "(|(" + objectClass + "=" + getUserObjectClass() + ")(" + objectClass + "="
- + getGroupObjectClass() + "))";
- SearchControls searchControls = new SearchControls();
- searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
-
- LdapName searchBase = getBaseDn();
- NamingEnumeration<SearchResult> results = ldapConnection.search(searchBase, searchFilter, searchControls);
-
- results: while (results.hasMoreElements()) {
- SearchResult searchResult = results.next();
- Attributes attrs = searchResult.getAttributes();
- Attribute objectClassAttr = attrs.get(objectClass.name());
- LdapName dn = toDn(searchBase, searchResult);
- LdifUser role;
- if (objectClassAttr.contains(getGroupObjectClass())
- || objectClassAttr.contains(getGroupObjectClass().toLowerCase()))
- role = new LdifGroup(this, dn, attrs);
- else if (objectClassAttr.contains(getUserObjectClass())
- || objectClassAttr.contains(getUserObjectClass().toLowerCase()))
- role = new LdifUser(this, dn, attrs);
- else {
-// log.warn("Unsupported LDAP type for " + searchResult.getName());
- continue results;
- }
- res.add(role);
- }
- return res;
- } catch (AuthenticationNotSupportedException e) {
- // ignore (typically an unsupported anonymous bind)
- // TODO better logging
- return res;
- } catch (Exception e) {
- e.printStackTrace();
- throw new UserDirectoryException("Cannot get roles for filter " + f, e);
- }
- }
-
- private LdapName toDn(LdapName baseDn, Binding binding) throws InvalidNameException {
- return new LdapName(binding.isRelative() ? binding.getName() + "," + baseDn : binding.getName());
- }
-
- @Override
- protected List<LdapName> getDirectGroups(LdapName dn) {
- List<LdapName> directGroups = new ArrayList<LdapName>();
- try {
- String searchFilter = "(&(" + objectClass + "=" + getGroupObjectClass() + ")(" + getMemberAttributeId()
- + "=" + dn + "))";
-
- SearchControls searchControls = new SearchControls();
- searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
-
- LdapName searchBase = getBaseDn();
- NamingEnumeration<SearchResult> results = ldapConnection.search(searchBase, searchFilter, searchControls);
-
- while (results.hasMoreElements()) {
- SearchResult searchResult = (SearchResult) results.nextElement();
- directGroups.add(toDn(searchBase, searchResult));
- }
- return directGroups;
- } catch (Exception e) {
- throw new UserDirectoryException("Cannot populate direct members of " + dn, e);
- }
- }
-
- @Override
- protected void prepare(UserDirectoryWorkingCopy wc) {
- try {
- ldapConnection.prepareChanges(wc);
- } catch (NamingException e) {
- throw new UserDirectoryException("Cannot prepare LDAP", e);
- }
- }
-
- @Override
- protected void commit(UserDirectoryWorkingCopy wc) {
- try {
- ldapConnection.commitChanges(wc);
- } catch (NamingException e) {
- throw new UserDirectoryException("Cannot commit LDAP", e);
- }
- }
-
- @Override
- protected void rollback(UserDirectoryWorkingCopy wc) {
- // prepare not impacting
- }
-
-}
+++ /dev/null
-package org.argeo.osgi.useradmin;
-
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Dictionary;
-import java.util.List;
-
-import org.argeo.util.naming.LdapAttrs;
-import org.osgi.service.useradmin.Authorization;
-import org.osgi.service.useradmin.Role;
-import org.osgi.service.useradmin.User;
-
-/** Basic authorization. */
-class LdifAuthorization implements Authorization {
- private final String name;
- private final String displayName;
- private final List<String> allRoles;
-
- public LdifAuthorization(User user, List<Role> allRoles) {
- if (user == null) {
- this.name = null;
- this.displayName = "anonymous";
- } else {
- this.name = user.getName();
- this.displayName = extractDisplayName(user);
- }
- // roles
- String[] roles = new String[allRoles.size()];
- for (int i = 0; i < allRoles.size(); i++) {
- roles[i] = allRoles.get(i).getName();
- }
- this.allRoles = Collections.unmodifiableList(Arrays.asList(roles));
- }
-
- @Override
- public String getName() {
- return name;
- }
-
- @Override
- public boolean hasRole(String name) {
- return allRoles.contains(name);
- }
-
- @Override
- public String[] getRoles() {
- return allRoles.toArray(new String[allRoles.size()]);
- }
-
- @Override
- public int hashCode() {
- if (name == null)
- return super.hashCode();
- return name.hashCode();
- }
-
- @Override
- public boolean equals(Object obj) {
- if (!(obj instanceof Authorization))
- return false;
- Authorization that = (Authorization) obj;
- if (name == null)
- return that.getName() == null;
- return name.equals(that.getName());
- }
-
- @Override
- public String toString() {
- return displayName;
- }
-
- final static String extractDisplayName(User user) {
- Dictionary<String, Object> props = user.getProperties();
- Object displayName = props.get(LdapAttrs.displayName);
- if (displayName == null)
- displayName = props.get(LdapAttrs.cn);
- if (displayName == null)
- displayName = props.get(LdapAttrs.uid);
- if (displayName == null)
- displayName = user.getName();
- if (displayName == null)
- throw new UserDirectoryException("Cannot set display name for " + user);
- return displayName.toString();
- }
-}
+++ /dev/null
-package org.argeo.osgi.useradmin;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.naming.InvalidNameException;
-import javax.naming.NamingEnumeration;
-import javax.naming.directory.Attribute;
-import javax.naming.directory.Attributes;
-import javax.naming.ldap.LdapName;
-
-import org.osgi.service.useradmin.Role;
-
-/** Directory group implementation */
-class LdifGroup extends LdifUser implements DirectoryGroup {
- private final String memberAttributeId;
-
- LdifGroup(AbstractUserDirectory userAdmin, LdapName dn, Attributes attributes) {
- super(userAdmin, dn, attributes);
- memberAttributeId = userAdmin.getMemberAttributeId();
- }
-
- @Override
- public boolean addMember(Role role) {
- try {
- Role foundRole = findRole(new LdapName(role.getName()));
- if (foundRole == null)
- throw new UnsupportedOperationException(
- "Adding role " + role.getName() + " is unsupported within this context.");
- } catch (InvalidNameException e) {
- throw new IllegalArgumentException("Role name" + role.getName() + " is badly formatted");
- }
-
- getUserAdmin().checkEdit();
- if (!isEditing())
- startEditing();
-
- Attribute member = getAttributes().get(memberAttributeId);
- if (member != null) {
- if (member.contains(role.getName()))
- return false;
- else
- member.add(role.getName());
- } else
- getAttributes().put(memberAttributeId, role.getName());
- return true;
- }
-
- @Override
- public boolean addRequiredMember(Role role) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public boolean removeMember(Role role) {
- getUserAdmin().checkEdit();
- if (!isEditing())
- startEditing();
-
- Attribute member = getAttributes().get(memberAttributeId);
- if (member != null) {
- if (!member.contains(role.getName()))
- return false;
- member.remove(role.getName());
- return true;
- } else
- return false;
- }
-
- @Override
- public Role[] getMembers() {
- List<Role> directMembers = new ArrayList<Role>();
- for (LdapName ldapName : getMemberNames()) {
- Role role = findRole(ldapName);
- if (role == null) {
- throw new UserDirectoryException("Role " + ldapName + " cannot be added.");
- }
- directMembers.add(role);
- }
- return directMembers.toArray(new Role[directMembers.size()]);
- }
-
- /**
- * Whether a role with this name can be found from this context.
- *
- * @return The related {@link Role} or <code>null</code>.
- */
- protected Role findRole(LdapName ldapName) {
- Role role = getUserAdmin().getRole(ldapName.toString());
- if (role == null) {
- if (getUserAdmin().getExternalRoles() != null)
- role = getUserAdmin().getExternalRoles().getRole(ldapName.toString());
- }
- return role;
- }
-
- @Override
- public List<LdapName> getMemberNames() {
- Attribute memberAttribute = getAttributes().get(memberAttributeId);
- if (memberAttribute == null)
- return new ArrayList<LdapName>();
- try {
- List<LdapName> roles = new ArrayList<LdapName>();
- NamingEnumeration<?> values = memberAttribute.getAll();
- while (values.hasMore()) {
- LdapName dn = new LdapName(values.next().toString());
- roles.add(dn);
- }
- return roles;
- } catch (Exception e) {
- throw new UserDirectoryException("Cannot get members", e);
- }
- }
-
- @Override
- public Role[] getRequiredMembers() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public int getType() {
- return GROUP;
- }
-}
+++ /dev/null
-package org.argeo.osgi.useradmin;
-
-import static java.nio.charset.StandardCharsets.US_ASCII;
-
-import java.math.BigInteger;
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Base64;
-import java.util.Collections;
-import java.util.Dictionary;
-import java.util.Enumeration;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Set;
-
-import javax.naming.NamingEnumeration;
-import javax.naming.NamingException;
-import javax.naming.directory.Attribute;
-import javax.naming.directory.Attributes;
-import javax.naming.directory.BasicAttribute;
-import javax.naming.ldap.LdapName;
-
-import org.argeo.util.naming.AuthPassword;
-import org.argeo.util.naming.LdapAttrs;
-import org.argeo.util.naming.SharedSecret;
-
-/** Directory user implementation */
-class LdifUser implements DirectoryUser {
- private final AbstractUserDirectory userAdmin;
-
- private final LdapName dn;
-
- private final boolean frozen;
- private Attributes publishedAttributes;
-
- private final AttributeDictionary properties;
- private final AttributeDictionary credentials;
-
- LdifUser(AbstractUserDirectory userAdmin, LdapName dn, Attributes attributes) {
- this(userAdmin, dn, attributes, false);
- }
-
- private LdifUser(AbstractUserDirectory userAdmin, LdapName dn, Attributes attributes, boolean frozen) {
- this.userAdmin = userAdmin;
- this.dn = dn;
- this.publishedAttributes = attributes;
- properties = new AttributeDictionary(false);
- credentials = new AttributeDictionary(true);
- this.frozen = frozen;
- }
-
- @Override
- public String getName() {
- return dn.toString();
- }
-
- @Override
- public int getType() {
- return USER;
- }
-
- @Override
- public Dictionary<String, Object> getProperties() {
- return properties;
- }
-
- @Override
- public Dictionary<String, Object> getCredentials() {
- return credentials;
- }
-
- @Override
- public boolean hasCredential(String key, Object value) {
- if (key == null) {
- // TODO check other sources (like PKCS12)
- // String pwd = new String((char[]) value);
- // authPassword (RFC 312 https://tools.ietf.org/html/rfc3112)
- char[] password = DigestUtils.bytesToChars(value);
-
- if (userAdmin.getForcedPassword() != null && userAdmin.getForcedPassword().equals(new String(password)))
- return true;
-
- AuthPassword authPassword = AuthPassword.matchAuthValue(getAttributes(), password);
- if (authPassword != null) {
- if (authPassword.getAuthScheme().equals(SharedSecret.X_SHARED_SECRET)) {
- SharedSecret onceToken = new SharedSecret(authPassword);
- if (onceToken.isExpired()) {
- // AuthPassword.remove(getAttributes(), onceToken);
- return false;
- } else {
- // boolean wasRemoved = AuthPassword.remove(getAttributes(), onceToken);
- return true;
- }
- // TODO delete expired tokens?
- } else {
- // TODO implement SHA
- throw new UnsupportedOperationException(
- "Unsupported authPassword scheme " + authPassword.getAuthScheme());
- }
- }
-
- // Regular password
-// byte[] hashedPassword = hash(password, DigestUtils.PASSWORD_SCHEME_PBKDF2_SHA256);
- if (hasCredential(LdapAttrs.userPassword.name(), DigestUtils.charsToBytes(password)))
- return true;
- return false;
- }
-
- Object storedValue = getCredentials().get(key);
- if (storedValue == null || value == null)
- return false;
- if (!(value instanceof String || value instanceof byte[]))
- return false;
- if (storedValue instanceof String && value instanceof String)
- return storedValue.equals(value);
- if (storedValue instanceof byte[] && value instanceof byte[]) {
- String storedBase64 = new String((byte[]) storedValue, US_ASCII);
- String passwordScheme = null;
- if (storedBase64.charAt(0) == '{') {
- int index = storedBase64.indexOf('}');
- if (index > 0) {
- passwordScheme = storedBase64.substring(1, index);
- String storedValueBase64 = storedBase64.substring(index + 1);
- byte[] storedValueBytes = Base64.getDecoder().decode(storedValueBase64);
- char[] passwordValue = DigestUtils.bytesToChars((byte[]) value);
- byte[] valueBytes;
- if (DigestUtils.PASSWORD_SCHEME_SHA.equals(passwordScheme)) {
- valueBytes = DigestUtils.toPasswordScheme(passwordScheme, passwordValue, null, null, null);
- } else if (DigestUtils.PASSWORD_SCHEME_PBKDF2_SHA256.equals(passwordScheme)) {
- // see https://www.thesubtlety.com/post/a-389-ds-pbkdf2-password-checker/
- byte[] iterationsArr = Arrays.copyOfRange(storedValueBytes, 0, 4);
- BigInteger iterations = new BigInteger(iterationsArr);
- byte[] salt = Arrays.copyOfRange(storedValueBytes, iterationsArr.length,
- iterationsArr.length + 64);
- byte[] keyArr = Arrays.copyOfRange(storedValueBytes, iterationsArr.length + salt.length,
- storedValueBytes.length);
- int keyLengthBits = keyArr.length * 8;
- valueBytes = DigestUtils.toPasswordScheme(passwordScheme, passwordValue, salt,
- iterations.intValue(), keyLengthBits);
- } else {
- throw new UnsupportedOperationException("Unknown password scheme " + passwordScheme);
- }
- return Arrays.equals(storedValueBytes, valueBytes);
- }
- }
- }
-// if (storedValue instanceof byte[] && value instanceof byte[]) {
-// return Arrays.equals((byte[]) storedValue, (byte[]) value);
-// }
- return false;
- }
-
- /** Hash the password */
- byte[] sha1hash(char[] password) {
- byte[] hashedPassword = ("{SHA}"
- + Base64.getEncoder().encodeToString(DigestUtils.sha1(DigestUtils.charsToBytes(password))))
- .getBytes(StandardCharsets.UTF_8);
- return hashedPassword;
- }
-
-// byte[] hash(char[] password, String passwordScheme) {
-// if (passwordScheme == null)
-// passwordScheme = DigestUtils.PASSWORD_SCHEME_SHA;
-// byte[] hashedPassword = ("{" + passwordScheme + "}"
-// + Base64.getEncoder().encodeToString(DigestUtils.toPasswordScheme(passwordScheme, password)))
-// .getBytes(US_ASCII);
-// return hashedPassword;
-// }
-
- @Override
- public LdapName getDn() {
- return dn;
- }
-
- @Override
- public synchronized Attributes getAttributes() {
- return isEditing() ? getModifiedAttributes() : publishedAttributes;
- }
-
- /** Should only be called from working copy thread. */
- private synchronized Attributes getModifiedAttributes() {
- assert getWc() != null;
- return getWc().getAttributes(getDn());
- }
-
- protected synchronized boolean isEditing() {
- return getWc() != null && getModifiedAttributes() != null;
- }
-
- private synchronized UserDirectoryWorkingCopy getWc() {
- return userAdmin.getWorkingCopy();
- }
-
- protected synchronized void startEditing() {
- if (frozen)
- throw new UserDirectoryException("Cannot edit frozen view");
- if (getUserAdmin().isReadOnly())
- throw new UserDirectoryException("User directory is read-only");
- assert getModifiedAttributes() == null;
- getWc().startEditing(this);
- // modifiedAttributes = (Attributes) publishedAttributes.clone();
- }
-
- public synchronized void publishAttributes(Attributes modifiedAttributes) {
- publishedAttributes = modifiedAttributes;
- }
-
- public DirectoryUser getPublished() {
- return new LdifUser(userAdmin, dn, publishedAttributes, true);
- }
-
- @Override
- public int hashCode() {
- return dn.hashCode();
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj)
- return true;
- if (obj instanceof LdifUser) {
- LdifUser that = (LdifUser) obj;
- return this.dn.equals(that.dn);
- }
- return false;
- }
-
- @Override
- public String toString() {
- return dn.toString();
- }
-
- protected AbstractUserDirectory getUserAdmin() {
- return userAdmin;
- }
-
- private class AttributeDictionary extends Dictionary<String, Object> {
- private final List<String> effectiveKeys = new ArrayList<String>();
- private final List<String> attrFilter;
- private final Boolean includeFilter;
-
- public AttributeDictionary(Boolean includeFilter) {
- this.attrFilter = userAdmin.getCredentialAttributeIds();
- this.includeFilter = includeFilter;
- try {
- NamingEnumeration<String> ids = getAttributes().getIDs();
- while (ids.hasMore()) {
- String id = ids.next();
- if (includeFilter && attrFilter.contains(id))
- effectiveKeys.add(id);
- else if (!includeFilter && !attrFilter.contains(id))
- effectiveKeys.add(id);
- }
- } catch (NamingException e) {
- throw new UserDirectoryException("Cannot initialise attribute dictionary", e);
- }
- }
-
- @Override
- public int size() {
- return effectiveKeys.size();
- }
-
- @Override
- public boolean isEmpty() {
- return effectiveKeys.size() == 0;
- }
-
- @Override
- public Enumeration<String> keys() {
- return Collections.enumeration(effectiveKeys);
- }
-
- @Override
- public Enumeration<Object> elements() {
- final Iterator<String> it = effectiveKeys.iterator();
- return new Enumeration<Object>() {
-
- @Override
- public boolean hasMoreElements() {
- return it.hasNext();
- }
-
- @Override
- public Object nextElement() {
- String key = it.next();
- return get(key);
- }
-
- };
- }
-
- @Override
- public Object get(Object key) {
- try {
- Attribute attr = getAttributes().get(key.toString());
- if (attr == null)
- return null;
- Object value = attr.get();
- if (value instanceof byte[]) {
- if (key.equals(LdapAttrs.userPassword.name()))
- // TODO other cases (certificates, images)
- return value;
- value = new String((byte[]) value, StandardCharsets.UTF_8);
- }
- if (attr.size() == 1)
- return value;
- if (!attr.getID().equals(LdapAttrs.objectClass.name()))
- return value;
- // special case for object class
- NamingEnumeration<?> en = attr.getAll();
- Set<String> objectClasses = new HashSet<String>();
- while (en.hasMore()) {
- String objectClass = en.next().toString();
- objectClasses.add(objectClass);
- }
-
- if (objectClasses.contains(userAdmin.getUserObjectClass()))
- return userAdmin.getUserObjectClass();
- else if (objectClasses.contains(userAdmin.getGroupObjectClass()))
- return userAdmin.getGroupObjectClass();
- else
- return value;
- } catch (NamingException e) {
- throw new UserDirectoryException("Cannot get value for attribute " + key, e);
- }
- }
-
- @Override
- public Object put(String key, Object value) {
- if (key == null) {
- // TODO persist to other sources (like PKCS12)
- char[] password = DigestUtils.bytesToChars(value);
- byte[] hashedPassword = sha1hash(password);
- return put(LdapAttrs.userPassword.name(), hashedPassword);
- }
- if (key.startsWith("X-")) {
- return put(LdapAttrs.authPassword.name(), value);
- }
-
- userAdmin.checkEdit();
- if (!isEditing())
- startEditing();
-
- if (!(value instanceof String || value instanceof byte[]))
- throw new IllegalArgumentException("Value must be String or byte[]");
-
- if (includeFilter && !attrFilter.contains(key))
- throw new IllegalArgumentException("Key " + key + " not included");
- else if (!includeFilter && attrFilter.contains(key))
- throw new IllegalArgumentException("Key " + key + " excluded");
-
- try {
- Attribute attribute = getModifiedAttributes().get(key.toString());
- // if (attribute == null) // block unit tests
- attribute = new BasicAttribute(key.toString());
- if (value instanceof String && !isAsciiPrintable(((String) value)))
- attribute.add(((String) value).getBytes(StandardCharsets.UTF_8));
- else
- attribute.add(value);
- Attribute previousAttribute = getModifiedAttributes().put(attribute);
- if (previousAttribute != null)
- return previousAttribute.get();
- else
- return null;
- } catch (NamingException e) {
- throw new UserDirectoryException("Cannot get value for attribute " + key, e);
- }
- }
-
- @Override
- public Object remove(Object key) {
- userAdmin.checkEdit();
- if (!isEditing())
- startEditing();
-
- if (includeFilter && !attrFilter.contains(key))
- throw new IllegalArgumentException("Key " + key + " not included");
- else if (!includeFilter && attrFilter.contains(key))
- throw new IllegalArgumentException("Key " + key + " excluded");
-
- try {
- Attribute attr = getModifiedAttributes().remove(key.toString());
- if (attr != null)
- return attr.get();
- else
- return null;
- } catch (NamingException e) {
- throw new UserDirectoryException("Cannot remove attribute " + key, e);
- }
- }
- }
-
- private static boolean isAsciiPrintable(String str) {
- if (str == null) {
- return false;
- }
- int sz = str.length();
- for (int i = 0; i < sz; i++) {
- if (isAsciiPrintable(str.charAt(i)) == false) {
- return false;
- }
- }
- return true;
- }
-
- private static boolean isAsciiPrintable(char ch) {
- return ch >= 32 && ch < 127;
- }
-
-}
+++ /dev/null
-package org.argeo.osgi.useradmin;
-
-import static org.argeo.util.naming.LdapAttrs.objectClass;
-import static org.argeo.util.naming.LdapObjs.inetOrgPerson;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Dictionary;
-import java.util.HashSet;
-import java.util.Hashtable;
-import java.util.List;
-import java.util.Set;
-import java.util.SortedMap;
-import java.util.TreeMap;
-
-import javax.naming.NameNotFoundException;
-import javax.naming.NamingEnumeration;
-import javax.naming.directory.Attributes;
-import javax.naming.ldap.LdapName;
-
-import org.argeo.util.naming.LdifParser;
-import org.argeo.util.naming.LdifWriter;
-import org.osgi.framework.Filter;
-import org.osgi.service.useradmin.Role;
-import org.osgi.service.useradmin.User;
-
-/** A user admin based on a LDIF files. */
-public class LdifUserAdmin extends AbstractUserDirectory {
- private SortedMap<LdapName, DirectoryUser> users = new TreeMap<LdapName, DirectoryUser>();
- private SortedMap<LdapName, DirectoryGroup> groups = new TreeMap<LdapName, DirectoryGroup>();
-
- public LdifUserAdmin(String uri, String baseDn) {
- this(fromUri(uri, baseDn), false);
- }
-
- public LdifUserAdmin(Dictionary<String, ?> properties) {
- this(properties, false);
- }
-
- protected LdifUserAdmin(Dictionary<String, ?> properties, boolean scoped) {
- super(null, properties, scoped);
- }
-
- public LdifUserAdmin(URI uri, Dictionary<String, ?> properties) {
- super(uri, properties, false);
- }
-
- @Override
- protected AbstractUserDirectory scope(User user) {
- Dictionary<String, Object> credentials = user.getCredentials();
- String username = (String) credentials.get(SHARED_STATE_USERNAME);
- if (username == null)
- username = user.getName();
- Object pwdCred = credentials.get(SHARED_STATE_PASSWORD);
- byte[] pwd = (byte[]) pwdCred;
- if (pwd != null) {
- char[] password = DigestUtils.bytesToChars(pwd);
- User directoryUser = (User) getRole(username);
- if (!directoryUser.hasCredential(null, password))
- throw new UserDirectoryException("Invalid credentials");
- } else {
- throw new UserDirectoryException("Password is required");
- }
- Dictionary<String, Object> properties = cloneProperties();
- properties.put(UserAdminConf.readOnly.name(), "true");
- LdifUserAdmin scopedUserAdmin = new LdifUserAdmin(properties, true);
- scopedUserAdmin.groups = Collections.unmodifiableSortedMap(groups);
- scopedUserAdmin.users = Collections.unmodifiableSortedMap(users);
- return scopedUserAdmin;
- }
-
- private static Dictionary<String, Object> fromUri(String uri, String baseDn) {
- Hashtable<String, Object> res = new Hashtable<String, Object>();
- res.put(UserAdminConf.uri.name(), uri);
- res.put(UserAdminConf.baseDn.name(), baseDn);
- return res;
- }
-
- public void init() {
-
- try {
- URI u = new URI(getUri());
- if (u.getScheme().equals("file")) {
- File file = new File(u);
- if (!file.exists())
- return;
- }
- load(u.toURL().openStream());
- } catch (Exception e) {
- throw new UserDirectoryException("Cannot open URL " + getUri(), e);
- }
- }
-
- public void save() {
- if (getUri() == null)
- throw new UserDirectoryException("Cannot save LDIF user admin: no URI is set");
- if (isReadOnly())
- throw new UserDirectoryException("Cannot save LDIF user admin: " + getUri() + " is read-only");
- try (FileOutputStream out = new FileOutputStream(new File(new URI(getUri())))) {
- save(out);
- } catch (IOException | URISyntaxException e) {
- throw new UserDirectoryException("Cannot save user admin to " + getUri(), e);
- }
- }
-
- public void save(OutputStream out) throws IOException {
- try {
- LdifWriter ldifWriter = new LdifWriter(out);
- for (LdapName name : groups.keySet())
- ldifWriter.writeEntry(name, groups.get(name).getAttributes());
- for (LdapName name : users.keySet())
- ldifWriter.writeEntry(name, users.get(name).getAttributes());
- } finally {
- out.close();
- }
- }
-
- protected void load(InputStream in) {
- try {
- users.clear();
- groups.clear();
-
- LdifParser ldifParser = new LdifParser();
- SortedMap<LdapName, Attributes> allEntries = ldifParser.read(in);
- for (LdapName key : allEntries.keySet()) {
- Attributes attributes = allEntries.get(key);
- // check for inconsistency
- Set<String> lowerCase = new HashSet<String>();
- NamingEnumeration<String> ids = attributes.getIDs();
- while (ids.hasMoreElements()) {
- String id = ids.nextElement().toLowerCase();
- if (lowerCase.contains(id))
- throw new UserDirectoryException(key + " has duplicate id " + id);
- lowerCase.add(id);
- }
-
- // analyse object classes
- NamingEnumeration<?> objectClasses = attributes.get(objectClass.name()).getAll();
- // System.out.println(key);
- objectClasses: while (objectClasses.hasMore()) {
- String objectClass = objectClasses.next().toString();
- // System.out.println(" " + objectClass);
- if (objectClass.toLowerCase().equals(inetOrgPerson.name().toLowerCase())) {
- users.put(key, new LdifUser(this, key, attributes));
- break objectClasses;
- } else if (objectClass.toLowerCase().equals(getGroupObjectClass().toLowerCase())) {
- groups.put(key, new LdifGroup(this, key, attributes));
- break objectClasses;
- }
- }
- }
- } catch (Exception e) {
- throw new UserDirectoryException("Cannot load user admin service from LDIF", e);
- }
- }
-
- public void destroy() {
- if (users == null || groups == null)
- throw new UserDirectoryException("User directory " + getBaseDn() + " is already destroyed");
- users = null;
- groups = null;
- }
-
- @Override
- protected DirectoryUser daoGetRole(LdapName key) throws NameNotFoundException {
- if (groups.containsKey(key))
- return groups.get(key);
- if (users.containsKey(key))
- return users.get(key);
- throw new NameNotFoundException(key + " not persisted");
- }
-
- @Override
- protected Boolean daoHasRole(LdapName dn) {
- return users.containsKey(dn) || groups.containsKey(dn);
- }
-
- protected List<DirectoryUser> doGetRoles(Filter f) {
- ArrayList<DirectoryUser> res = new ArrayList<DirectoryUser>();
- if (f == null) {
- res.addAll(users.values());
- res.addAll(groups.values());
- } else {
- for (DirectoryUser user : users.values()) {
- if (f.match(user.getProperties()))
- res.add(user);
- }
- for (DirectoryUser group : groups.values())
- if (f.match(group.getProperties()))
- res.add(group);
- }
- return res;
- }
-
- @Override
- protected List<LdapName> getDirectGroups(LdapName dn) {
- List<LdapName> directGroups = new ArrayList<LdapName>();
- for (LdapName name : groups.keySet()) {
- DirectoryGroup group = groups.get(name);
- if (group.getMemberNames().contains(dn))
- directGroups.add(group.getDn());
- }
- return directGroups;
- }
-
- @Override
- protected void prepare(UserDirectoryWorkingCopy wc) {
- // delete
- for (LdapName dn : wc.getDeletedUsers().keySet()) {
- if (users.containsKey(dn))
- users.remove(dn);
- else if (groups.containsKey(dn))
- groups.remove(dn);
- else
- throw new UserDirectoryException("User to delete not found " + dn);
- }
- // add
- for (LdapName dn : wc.getNewUsers().keySet()) {
- DirectoryUser user = wc.getNewUsers().get(dn);
- if (users.containsKey(dn) || groups.containsKey(dn))
- throw new UserDirectoryException("User to create found " + dn);
- else if (Role.USER == user.getType())
- users.put(dn, user);
- else if (Role.GROUP == user.getType())
- groups.put(dn, (DirectoryGroup) user);
- else
- throw new UserDirectoryException("Unsupported role type " + user.getType() + " for new user " + dn);
- }
- // modify
- for (LdapName dn : wc.getModifiedUsers().keySet()) {
- Attributes modifiedAttrs = wc.getModifiedUsers().get(dn);
- DirectoryUser user;
- if (users.containsKey(dn))
- user = users.get(dn);
- else if (groups.containsKey(dn))
- user = groups.get(dn);
- else
- throw new UserDirectoryException("User to modify no found " + dn);
- user.publishAttributes(modifiedAttrs);
- }
- }
-
- @Override
- protected void commit(UserDirectoryWorkingCopy wc) {
- save();
- }
-
- @Override
- protected void rollback(UserDirectoryWorkingCopy wc) {
- init();
- }
-
-}
+++ /dev/null
-package org.argeo.osgi.useradmin;
-
-import java.net.URI;
-import java.util.ArrayList;
-import java.util.Dictionary;
-import java.util.List;
-
-import javax.naming.NameNotFoundException;
-import javax.naming.NamingException;
-import javax.naming.directory.Attributes;
-import javax.naming.directory.BasicAttributes;
-import javax.naming.ldap.LdapName;
-
-import org.argeo.util.naming.LdapAttrs;
-import org.osgi.framework.Filter;
-import org.osgi.service.useradmin.User;
-
-public class OsUserDirectory extends AbstractUserDirectory {
- private final String osUsername = System.getProperty("user.name");
- private final LdapName osUserDn;
- private final LdifUser osUser;
-
- public OsUserDirectory(URI uriArg, Dictionary<String, ?> props) {
- super(uriArg, props, false);
- try {
- osUserDn = new LdapName(LdapAttrs.uid.name() + "=" + osUsername + "," + getUserBase() + "," + getBaseDn());
- Attributes attributes = new BasicAttributes();
- attributes.put(LdapAttrs.uid.name(), osUsername);
- osUser = new LdifUser(this, osUserDn, attributes);
- } catch (NamingException e) {
- throw new UserDirectoryException("Cannot create system user", e);
- }
- }
-
- @Override
- protected List<LdapName> getDirectGroups(LdapName dn) {
- return new ArrayList<>();
- }
-
- @Override
- protected Boolean daoHasRole(LdapName dn) {
- return osUserDn.equals(dn);
- }
-
- @Override
- protected DirectoryUser daoGetRole(LdapName key) throws NameNotFoundException {
- if (osUserDn.equals(key))
- return osUser;
- else
- throw new NameNotFoundException("Not an OS role");
- }
-
- @Override
- protected List<DirectoryUser> doGetRoles(Filter f) {
- List<DirectoryUser> res = new ArrayList<>();
- if (f == null || f.match(osUser.getProperties()))
- res.add(osUser);
- return res;
- }
-
- @Override
- protected AbstractUserDirectory scope(User user) {
- throw new UnsupportedOperationException();
- }
-
-}
+++ /dev/null
-package org.argeo.osgi.useradmin;
-
-import java.net.URISyntaxException;
-import java.net.URL;
-import java.security.NoSuchAlgorithmException;
-import java.security.URIParameter;
-
-import javax.security.auth.Subject;
-import javax.security.auth.login.Configuration;
-import javax.security.auth.login.LoginContext;
-import javax.security.auth.login.LoginException;
-
-public class OsUserUtils {
- private static String LOGIN_CONTEXT_USER_NIX = "USER_NIX";
- private static String LOGIN_CONTEXT_USER_NT = "USER_NT";
-
- public static String getOsUsername() {
- return System.getProperty("user.name");
- }
-
- public static LoginContext loginAsSystemUser(Subject subject) {
- try {
- URL jaasConfigurationUrl = OsUserUtils.class.getClassLoader()
- .getResource("org/argeo/osgi/useradmin/jaas-os.cfg");
- URIParameter uriParameter = new URIParameter(jaasConfigurationUrl.toURI());
- Configuration jaasConfiguration = Configuration.getInstance("JavaLoginConfig", uriParameter);
- LoginContext lc = new LoginContext(isWindows() ? LOGIN_CONTEXT_USER_NT : LOGIN_CONTEXT_USER_NIX, subject,
- null, jaasConfiguration);
- lc.login();
- return lc;
- } catch (URISyntaxException | NoSuchAlgorithmException | LoginException e) {
- throw new RuntimeException("Cannot login as system user", e);
- }
- }
-
- public static void main(String args[]) {
- Subject subject = new Subject();
- LoginContext loginContext = loginAsSystemUser(subject);
- System.out.println(subject);
- try {
- loginContext.logout();
- } catch (LoginException e) {
- // silent
- }
- }
-
- private static boolean isWindows() {
- return System.getProperty("os.name").startsWith("Windows");
- }
-
- private OsUserUtils() {
- }
-}
+++ /dev/null
-package org.argeo.osgi.useradmin;
-
-import static org.argeo.util.naming.LdapAttrs.description;
-import static org.argeo.util.naming.LdapAttrs.owner;
-
-import java.security.Principal;
-import java.time.Instant;
-import java.util.HashSet;
-import java.util.Set;
-
-import javax.naming.InvalidNameException;
-import javax.naming.ldap.LdapName;
-import javax.security.auth.Subject;
-
-import org.argeo.util.naming.NamingUtils;
-import org.osgi.service.useradmin.Group;
-
-/**
- * Canonically implements the Argeo token conventions.
- */
-public class TokenUtils {
- public static Set<String> tokensUsed(Subject subject, String tokensBaseDn) {
- Set<String> res = new HashSet<>();
- for (Principal principal : subject.getPrincipals()) {
- String name = principal.getName();
- if (name.endsWith(tokensBaseDn)) {
- try {
- LdapName ldapName = new LdapName(name);
- String token = ldapName.getRdn(ldapName.size()).getValue().toString();
- res.add(token);
- } catch (InvalidNameException e) {
- throw new UserDirectoryException("Invalid principal " + principal, e);
- }
- }
- }
- return res;
- }
-
- /** The user related to this token group */
- public static String userDn(Group tokenGroup) {
- return (String) tokenGroup.getProperties().get(owner.name());
- }
-
- public static boolean isExpired(Group tokenGroup) {
- return isExpired(tokenGroup, Instant.now());
-
- }
-
- public static boolean isExpired(Group tokenGroup, Instant instant) {
- String expiryDateStr = (String) tokenGroup.getProperties().get(description.name());
- if (expiryDateStr != null) {
- Instant expiryDate = NamingUtils.ldapDateToInstant(expiryDateStr);
- if (expiryDate.isBefore(instant)) {
- return true;
- }
- }
- return false;
- }
-
-// private final String token;
-//
-// public TokenUtils(String token) {
-// this.token = token;
-// }
-//
-// public String getToken() {
-// return token;
-// }
-//
-// @Override
-// public int hashCode() {
-// return token.hashCode();
-// }
-//
-// @Override
-// public boolean equals(Object obj) {
-// if ((obj instanceof TokenUtils) && ((TokenUtils) obj).token.equals(token))
-// return true;
-// return false;
-// }
-//
-// @Override
-// public String toString() {
-// return "Token #" + hashCode();
-// }
-
-}
+++ /dev/null
-package org.argeo.osgi.useradmin;
-
-import java.net.InetAddress;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.net.UnknownHostException;
-import java.util.Dictionary;
-import java.util.Hashtable;
-import java.util.List;
-import java.util.Map;
-
-import javax.naming.Context;
-import javax.naming.ldap.LdapName;
-
-import org.argeo.util.naming.NamingUtils;
-
-/** Properties used to configure user admins. */
-public enum UserAdminConf {
- /** Base DN (cannot be configured externally) */
- baseDn("dc=example,dc=com"),
-
- /** URI of the underlying resource (cannot be configured externally) */
- uri("ldap://localhost:10389"),
-
- /** User objectClass */
- userObjectClass("inetOrgPerson"),
-
- /** Relative base DN for users */
- userBase("ou=People"),
-
- /** Groups objectClass */
- groupObjectClass("groupOfNames"),
-
- /** Relative base DN for users */
- groupBase("ou=Groups"),
-
- /** Read-only source */
- readOnly(null),
-
- /** Disabled source */
- disabled(null),
-
- /** Authentication realm */
- realm(null),
-
- /** Override all passwords with this value (typically for testing purposes) */
- forcedPassword(null);
-
- public final static String FACTORY_PID = "org.argeo.osgi.useradmin.config";
-
- public final static String SCHEME_LDAP = "ldap";
- public final static String SCHEME_LDAPS = "ldaps";
- public final static String SCHEME_FILE = "file";
- public final static String SCHEME_OS = "os";
- public final static String SCHEME_IPA = "ipa";
-
- /** The default value. */
- private Object def;
-
- UserAdminConf(Object def) {
- this.def = def;
- }
-
- public Object getDefault() {
- return def;
- }
-
- /**
- * For use as Java property.
- *
- * @deprecated use {@link #name()} instead
- */
- @Deprecated
- public String property() {
- return name();
- }
-
- public String getValue(Dictionary<String, ?> properties) {
- Object res = getRawValue(properties);
- if (res == null)
- return null;
- return res.toString();
- }
-
- @SuppressWarnings("unchecked")
- public <T> T getRawValue(Dictionary<String, ?> properties) {
- Object res = properties.get(name());
- if (res == null)
- res = getDefault();
- return (T) res;
- }
-
- /** @deprecated use {@link #valueOf(String)} instead */
- @Deprecated
- public static UserAdminConf local(String property) {
- return UserAdminConf.valueOf(property);
- }
-
- /** Hides host and credentials. */
- public static URI propertiesAsUri(Dictionary<String, ?> properties) {
- StringBuilder query = new StringBuilder();
-
- boolean first = true;
-// for (Enumeration<String> keys = properties.keys(); keys.hasMoreElements();) {
-// String key = keys.nextElement();
-// // TODO clarify which keys are relevant (list only the enum?)
-// if (!key.equals("service.factoryPid") && !key.equals("cn") && !key.equals("dn")
-// && !key.equals(Constants.SERVICE_PID) && !key.startsWith("java") && !key.equals(baseDn.name())
-// && !key.equals(uri.name()) && !key.equals(Constants.OBJECTCLASS)
-// && !key.equals(Constants.SERVICE_ID) && !key.equals("bundle.id")) {
-// if (first)
-// first = false;
-// else
-// query.append('&');
-// query.append(valueOf(key).name());
-// query.append('=').append(properties.get(key).toString());
-// }
-// }
-
- keys: for (UserAdminConf key : UserAdminConf.values()) {
- if (key.equals(baseDn) || key.equals(uri))
- continue keys;
- Object value = properties.get(key.name());
- if (value == null)
- continue keys;
- if (first)
- first = false;
- else
- query.append('&');
- query.append(key.name());
- query.append('=').append(value.toString());
-
- }
-
- Object bDnObj = properties.get(baseDn.name());
- String bDn = bDnObj != null ? bDnObj.toString() : null;
- try {
- return new URI(null, null, bDn != null ? '/' + bDn : null, query.length() != 0 ? query.toString() : null,
- null);
- } catch (URISyntaxException e) {
- throw new UserDirectoryException("Cannot create URI from properties", e);
- }
- }
-
- public static Dictionary<String, Object> uriAsProperties(String uriStr) {
- try {
- Hashtable<String, Object> res = new Hashtable<String, Object>();
- URI u = new URI(uriStr);
- String scheme = u.getScheme();
- if (scheme != null && scheme.equals(SCHEME_IPA)) {
- return IpaUtils.convertIpaUri(u);
-// scheme = u.getScheme();
- }
- String path = u.getPath();
- // base DN
- String bDn = path.substring(path.lastIndexOf('/') + 1, path.length());
- if (bDn.equals("") && SCHEME_OS.equals(scheme)) {
- bDn = getBaseDnFromHostname();
- }
-
- if (bDn.endsWith(".ldif"))
- bDn = bDn.substring(0, bDn.length() - ".ldif".length());
-
- // Normalize base DN as LDAP name
- bDn = new LdapName(bDn).toString();
-
- String principal = null;
- String credentials = null;
- if (scheme != null)
- if (scheme.equals(SCHEME_LDAP) || scheme.equals(SCHEME_LDAPS)) {
- // TODO additional checks
- if (u.getUserInfo() != null) {
- String[] userInfo = u.getUserInfo().split(":");
- principal = userInfo.length > 0 ? userInfo[0] : null;
- credentials = userInfo.length > 1 ? userInfo[1] : null;
- }
- } else if (scheme.equals(SCHEME_FILE)) {
- } else if (scheme.equals(SCHEME_IPA)) {
- } else if (scheme.equals(SCHEME_OS)) {
- } else
- throw new UserDirectoryException("Unsupported scheme " + scheme);
- Map<String, List<String>> query = NamingUtils.queryToMap(u);
- for (String key : query.keySet()) {
- UserAdminConf ldapProp = UserAdminConf.valueOf(key);
- List<String> values = query.get(key);
- if (values.size() == 1) {
- res.put(ldapProp.name(), values.get(0));
- } else {
- throw new UserDirectoryException("Only single values are supported");
- }
- }
- res.put(baseDn.name(), bDn);
- if (SCHEME_OS.equals(scheme))
- res.put(readOnly.name(), "true");
- if (principal != null)
- res.put(Context.SECURITY_PRINCIPAL, principal);
- if (credentials != null)
- res.put(Context.SECURITY_CREDENTIALS, credentials);
- if (scheme != null) {// relative URIs are dealt with externally
- if (SCHEME_OS.equals(scheme)) {
- res.put(uri.name(), SCHEME_OS + ":///");
- } else {
- URI bareUri = new URI(scheme, null, u.getHost(), u.getPort(),
- scheme.equals(SCHEME_FILE) ? u.getPath() : null, null, null);
- res.put(uri.name(), bareUri.toString());
- }
- }
- return res;
- } catch (Exception e) {
- throw new UserDirectoryException("Cannot convert " + uri + " to properties", e);
- }
- }
-
- private static String getBaseDnFromHostname() {
- String hostname;
- try {
- hostname = InetAddress.getLocalHost().getHostName();
- } catch (UnknownHostException e) {
- hostname = "localhost.localdomain";
- }
- int dotIdx = hostname.indexOf('.');
- if (dotIdx >= 0) {
- String domain = hostname.substring(dotIdx + 1, hostname.length());
- String bDn = ("." + domain).replaceAll("\\.", ",dc=");
- bDn = bDn.substring(1, bDn.length());
- return bDn;
- } else {
- return "dc=" + hostname;
- }
- }
-
- /**
- * Hash the base DN in order to have a deterministic string to be used as a cn
- * for the underlying user directory.
- */
- public static String baseDnHash(Dictionary<String, Object> properties) {
- String bDn = (String) properties.get(baseDn.name());
- if (bDn == null)
- throw new UserDirectoryException("No baseDn in " + properties);
- return DigestUtils.sha1str(bDn);
- }
-}
+++ /dev/null
-package org.argeo.osgi.useradmin;
-
-import javax.naming.ldap.LdapName;
-import javax.transaction.xa.XAResource;
-
-/** Information about a user directory. */
-public interface UserDirectory {
- /** The base DN of all entries in this user directory */
- LdapName getBaseDn();
-
- /** The related {@link XAResource} */
- XAResource getXaResource();
-
- boolean isReadOnly();
-
- boolean isDisabled();
-
- String getUserObjectClass();
-
- String getUserBase();
-
- String getGroupObjectClass();
-
- String getGroupBase();
-}
+++ /dev/null
-package org.argeo.osgi.useradmin;
-
-import org.osgi.service.useradmin.UserAdmin;
-
-/**
- * Exceptions related to Argeo's implementation of OSGi {@link UserAdmin}
- * service.
- */
-public class UserDirectoryException extends RuntimeException {
- private static final long serialVersionUID = 1419352360062048603L;
-
- public UserDirectoryException(String message) {
- super(message);
- }
-
- public UserDirectoryException(String message, Throwable e) {
- super(message, e);
- }
-}
+++ /dev/null
-package org.argeo.osgi.useradmin;
-
-import java.util.HashMap;
-import java.util.Map;
-
-import javax.naming.directory.Attributes;
-import javax.naming.ldap.LdapName;
-import javax.transaction.xa.XAResource;
-
-/** {@link XAResource} for a user directory being edited. */
-class UserDirectoryWorkingCopy {
- // private final static Log log = LogFactory
- // .getLog(UserDirectoryWorkingCopy.class);
-
- private Map<LdapName, DirectoryUser> newUsers = new HashMap<LdapName, DirectoryUser>();
- private Map<LdapName, Attributes> modifiedUsers = new HashMap<LdapName, Attributes>();
- private Map<LdapName, DirectoryUser> deletedUsers = new HashMap<LdapName, DirectoryUser>();
-
- void cleanUp() {
- // clean collections
- newUsers.clear();
- newUsers = null;
- modifiedUsers.clear();
- modifiedUsers = null;
- deletedUsers.clear();
- deletedUsers = null;
- }
-
- public boolean noModifications() {
- return newUsers.size() == 0 && modifiedUsers.size() == 0
- && deletedUsers.size() == 0;
- }
-
- public Attributes getAttributes(LdapName dn) {
- if (modifiedUsers.containsKey(dn))
- return modifiedUsers.get(dn);
- return null;
- }
-
- public void startEditing(DirectoryUser user) {
- LdapName dn = user.getDn();
- if (modifiedUsers.containsKey(dn))
- throw new UserDirectoryException("Already editing " + dn);
- modifiedUsers.put(dn, (Attributes) user.getAttributes().clone());
- }
-
- public Map<LdapName, DirectoryUser> getNewUsers() {
- return newUsers;
- }
-
- public Map<LdapName, DirectoryUser> getDeletedUsers() {
- return deletedUsers;
- }
-
- public Map<LdapName, Attributes> getModifiedUsers() {
- return modifiedUsers;
- }
-}
+++ /dev/null
-package org.argeo.osgi.useradmin;
-
-import java.util.HashMap;
-import java.util.Map;
-
-import javax.transaction.xa.XAException;
-import javax.transaction.xa.XAResource;
-import javax.transaction.xa.Xid;
-
-/** {@link XAResource} for a user directory being edited. */
-class WcXaResource implements XAResource {
- private final AbstractUserDirectory userDirectory;
-
- private Map<Xid, UserDirectoryWorkingCopy> workingCopies = new HashMap<Xid, UserDirectoryWorkingCopy>();
- private Xid editingXid = null;
- private int transactionTimeout = 0;
-
- public WcXaResource(AbstractUserDirectory userDirectory) {
- this.userDirectory = userDirectory;
- }
-
- @Override
- public synchronized void start(Xid xid, int flags) throws XAException {
- if (editingXid != null)
- throw new UserDirectoryException("Already editing " + editingXid);
- UserDirectoryWorkingCopy wc = workingCopies.put(xid, new UserDirectoryWorkingCopy());
- if (wc != null)
- throw new UserDirectoryException("There is already a working copy for " + xid);
- this.editingXid = xid;
- }
-
- @Override
- public void end(Xid xid, int flags) throws XAException {
- checkXid(xid);
- }
-
- private UserDirectoryWorkingCopy wc(Xid xid) {
- return workingCopies.get(xid);
- }
-
- synchronized UserDirectoryWorkingCopy wc() {
- if (editingXid == null)
- return null;
- UserDirectoryWorkingCopy wc = workingCopies.get(editingXid);
- if (wc == null)
- throw new UserDirectoryException("No working copy found for " + editingXid);
- return wc;
- }
-
- private synchronized void cleanUp(Xid xid) {
- wc(xid).cleanUp();
- workingCopies.remove(xid);
- editingXid = null;
- }
-
- @Override
- public int prepare(Xid xid) throws XAException {
- checkXid(xid);
- UserDirectoryWorkingCopy wc = wc(xid);
- if (wc.noModifications())
- return XA_RDONLY;
- try {
- userDirectory.prepare(wc);
- } catch (Exception e) {
- e.printStackTrace();
- throw new XAException(XAException.XAER_RMERR);
- }
- return XA_OK;
- }
-
- @Override
- public void commit(Xid xid, boolean onePhase) throws XAException {
- try {
- checkXid(xid);
- UserDirectoryWorkingCopy wc = wc(xid);
- if (wc.noModifications())
- return;
- if (onePhase)
- userDirectory.prepare(wc);
- userDirectory.commit(wc);
- } catch (Exception e) {
- e.printStackTrace();
- throw new XAException(XAException.XAER_RMERR);
- } finally {
- cleanUp(xid);
- }
- }
-
- @Override
- public void rollback(Xid xid) throws XAException {
- try {
- checkXid(xid);
- userDirectory.rollback(wc(xid));
- } catch (Exception e) {
- e.printStackTrace();
- throw new XAException(XAException.XAER_RMERR);
- } finally {
- cleanUp(xid);
- }
- }
-
- @Override
- public void forget(Xid xid) throws XAException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public boolean isSameRM(XAResource xares) throws XAException {
- return xares == this;
- }
-
- @Override
- public Xid[] recover(int flag) throws XAException {
- return new Xid[0];
- }
-
- @Override
- public int getTransactionTimeout() throws XAException {
- return transactionTimeout;
- }
-
- @Override
- public boolean setTransactionTimeout(int seconds) throws XAException {
- transactionTimeout = seconds;
- return true;
- }
-
- private void checkXid(Xid xid) throws XAException {
- if (xid == null)
- throw new XAException(XAException.XAER_OUTSIDE);
- if (!xid.equals(xid))
- throw new XAException(XAException.XAER_NOTA);
- }
-
-}
+++ /dev/null
-USER_NIX {
- com.sun.security.auth.module.UnixLoginModule requisite;
-};
-
-USER_NT {
- com.sun.security.auth.module.NTLoginModule requisite;
-};
-
+++ /dev/null
-/** LDAP and LDIF based OSGi useradmin implementation. */
-package org.argeo.osgi.useradmin;
\ No newline at end of file
+++ /dev/null
-package org.argeo.osgi.util;
-
-import java.util.HashMap;
-import java.util.Map;
-
-import org.osgi.resource.Namespace;
-import org.osgi.resource.Requirement;
-import org.osgi.resource.Resource;
-
-/** Simplify filtering resources. */
-public class FilterRequirement implements Requirement {
- private String namespace;
- private String filter;
-
- public FilterRequirement(String namespace, String filter) {
- this.namespace = namespace;
- this.filter = filter;
- }
-
- @Override
- public Resource getResource() {
- return null;
- }
-
- @Override
- public String getNamespace() {
- return namespace;
- }
-
- @Override
- public Map<String, String> getDirectives() {
- Map<String, String> directives = new HashMap<>();
- directives.put(Namespace.REQUIREMENT_FILTER_DIRECTIVE, filter);
- return directives;
- }
-
- @Override
- public Map<String, Object> getAttributes() {
- return new HashMap<>();
- }
-
-}
+++ /dev/null
-package org.argeo.osgi.util;
-
-import java.util.concurrent.CancellationException;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Future;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-import java.util.function.Function;
-
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.FrameworkUtil;
-import org.osgi.framework.ServiceReference;
-import org.osgi.util.tracker.ServiceTracker;
-
-public class OnServiceRegistration<R> implements Future<R> {
- private BundleContext ownBundleContext = FrameworkUtil.getBundle(OnServiceRegistration.class).getBundleContext();
-
- private ServiceTracker<?, ?> st;
-
- private R result;
- private boolean cancelled = false;
- private Throwable exception;
-
- public <T> OnServiceRegistration(Class<T> clss, Function<T, R> function) {
- this(null, clss, function);
- }
-
- public <T> OnServiceRegistration(BundleContext bundleContext, Class<T> clss, Function<T, R> function) {
- st = new ServiceTracker<T, T>(bundleContext != null ? bundleContext : ownBundleContext, clss, null) {
-
- @Override
- public T addingService(ServiceReference<T> reference) {
- T service = super.addingService(reference);
- try {
- if (result != null)// we only want the first one
- return service;
- result = function.apply(service);
- return service;
- } catch (Exception e) {
- exception = e;
- return service;
- } finally {
- close();
- }
- }
- };
- st.open(bundleContext == null);
- }
-
- @Override
- public boolean cancel(boolean mayInterruptIfRunning) {
- if (result != null || exception != null || cancelled)
- return false;
- st.close();
- cancelled = true;
- return true;
- }
-
- @Override
- public boolean isCancelled() {
- return cancelled;
- }
-
- @Override
- public boolean isDone() {
- return result != null || cancelled;
- }
-
- @Override
- public R get() throws InterruptedException, ExecutionException {
- st.waitForService(0);
- return tryGetResult();
- }
-
- @Override
- public R get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
- st.waitForService(TimeUnit.MILLISECONDS.convert(timeout, unit));
- if (result == null)
- throw new TimeoutException("No result after " + timeout + " " + unit);
- return tryGetResult();
- }
-
- protected R tryGetResult() throws ExecutionException, CancellationException {
- if (cancelled)
- throw new CancellationException();
- if (exception != null)
- throw new ExecutionException(exception);
- if (result == null)// this should not happen
- try {
- throw new IllegalStateException("No result available");
- } catch (Exception e) {
- exception = e;
- throw new ExecutionException(e);
- }
- return result;
- }
-
-}
+++ /dev/null
-package org.argeo.osgi.util;
-
-import java.util.ArrayList;
-import java.util.Hashtable;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Executor;
-import java.util.concurrent.ForkJoinPool;
-
-import org.argeo.util.register.Register;
-import org.argeo.util.register.Singleton;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.ServiceRegistration;
-
-public class OsgiRegister implements Register {
- private final BundleContext bundleContext;
- private Executor executor;
-
- private CompletableFuture<Void> shutdownStarting = new CompletableFuture<Void>();
-
- public OsgiRegister(BundleContext bundleContext) {
- this.bundleContext = bundleContext;
- // TODO experiment with dedicated executors
- this.executor = ForkJoinPool.commonPool();
- }
-
- @Override
- public <T> Singleton<T> set(T obj, Class<T> clss, Map<String, Object> attributes, Class<?>... classes) {
- CompletableFuture<ServiceRegistration<?>> srf = new CompletableFuture<ServiceRegistration<?>>();
- CompletableFuture<T> postRegistration = CompletableFuture.supplyAsync(() -> {
- List<String> lst = new ArrayList<>();
- lst.add(clss.getName());
- for (Class<?> c : classes) {
- lst.add(c.getName());
- }
- ServiceRegistration<?> sr = bundleContext.registerService(lst.toArray(new String[lst.size()]), obj,
- new Hashtable<String, Object>(attributes));
- srf.complete(sr);
- return obj;
- }, executor);
- Singleton<T> singleton = new Singleton<T>(clss, postRegistration);
-
- shutdownStarting. //
- thenCompose(singleton::prepareUnregistration). //
- thenRunAsync(() -> {
- try {
- srf.get().unregister();
- } catch (InterruptedException | ExecutionException e) {
- e.printStackTrace();
- }
- }, executor);
- return singleton;
- }
-
- public void shutdown() {
- shutdownStarting.complete(null);
- }
-}
+++ /dev/null
-package org.argeo.util;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.Reader;
-import java.io.UnsupportedEncodingException;
-import java.nio.charset.Charset;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * Parses a CSV file interpreting the first line as a header. The
- * {@link #parse(InputStream)} method and the setters are synchronized so that
- * the object cannot be modified when parsing.
- */
-public abstract class CsvParser {
- private char separator = ',';
- private char quote = '\"';
-
- private Boolean noHeader = false;
- private Boolean strictLineAsLongAsHeader = true;
-
- /**
- * Actually process a parsed line. If
- * {@link #setStrictLineAsLongAsHeader(Boolean)} is true (default) the header
- * and the tokens are guaranteed to have the same size.
- *
- * @param lineNumber the current line number, starts at 1 (the header, if header
- * processing is enabled, the first line otherwise)
- * @param header the read-only header or null if
- * {@link #setNoHeader(Boolean)} is true (default is false)
- * @param tokens the parsed tokens
- */
- protected abstract void processLine(Integer lineNumber, List<String> header, List<String> tokens);
-
- /**
- * Parses the CSV file (stream is closed at the end)
- *
- * @param in the stream to parse
- *
- * @deprecated Use {@link #parse(InputStream, Charset)} instead.
- */
- @Deprecated
- public synchronized void parse(InputStream in) {
- parse(in, (Charset) null);
- }
-
- /**
- * Parses the CSV file (stream is closed at the end)
- *
- * @param in the stream to parse
- * @param encoding the encoding to use.
- *
- * @deprecated Use {@link #parse(InputStream, Charset)} instead.
- */
- @Deprecated
- public synchronized void parse(InputStream in, String encoding) {
- Reader reader;
- if (encoding == null)
- reader = new InputStreamReader(in);
- else
- try {
- reader = new InputStreamReader(in, encoding);
- } catch (UnsupportedEncodingException e) {
- throw new IllegalArgumentException(e);
- }
- parse(reader);
- }
-
- /**
- * Parses the CSV file (stream is closed at the end)
- *
- * @param in the stream to parse
- * @param charset the charset to use
- */
- public synchronized void parse(InputStream in, Charset charset) {
- Reader reader;
- if (charset == null)
- reader = new InputStreamReader(in);
- else
- reader = new InputStreamReader(in, charset);
- parse(reader);
- }
-
- /**
- * Parses the CSV file (stream is closed at the end)
- *
- * @param reader the reader to use (it will be buffered)
- */
- public synchronized void parse(Reader reader) {
- Integer lineCount = 0;
- try (BufferedReader bufferedReader = new BufferedReader(reader)) {
- List<String> header = null;
- if (!noHeader) {
- String headerStr = bufferedReader.readLine();
- if (headerStr == null)// empty file
- return;
- lineCount++;
- header = new ArrayList<String>();
- StringBuffer currStr = new StringBuffer("");
- Boolean wasInquote = false;
- while (parseLine(headerStr, header, currStr, wasInquote)) {
- headerStr = bufferedReader.readLine();
- if (headerStr == null)
- break;
- wasInquote = true;
- }
- header = Collections.unmodifiableList(header);
- }
-
- String line = null;
- lines: while ((line = bufferedReader.readLine()) != null) {
- line = preProcessLine(line);
- if (line == null) {
- // skip line
- continue lines;
- }
- lineCount++;
- List<String> tokens = new ArrayList<String>();
- StringBuffer currStr = new StringBuffer("");
- Boolean wasInquote = false;
- sublines: while (parseLine(line, tokens, currStr, wasInquote)) {
- line = bufferedReader.readLine();
- if (line == null)
- break sublines;
- wasInquote = true;
- }
- if (!noHeader && strictLineAsLongAsHeader) {
- int headerSize = header.size();
- int tokenSize = tokens.size();
- if (tokenSize == 1 && line.trim().equals(""))
- continue lines;// empty line
- if (headerSize != tokenSize) {
- throw new IllegalStateException("Token size " + tokenSize + " is different from header size "
- + headerSize + " at line " + lineCount + ", line: " + line + ", header: " + header
- + ", tokens: " + tokens);
- }
- }
- processLine(lineCount, header, tokens);
- }
- } catch (IOException e) {
- throw new RuntimeException("Cannot parse CSV file (line: " + lineCount + ")", e);
- }
- }
-
- /**
- * Called before each (logical) line is processed, giving a change to modify it
- * (typically for cleaning dirty files). To be overridden, return the line
- * unchanged by default. Skip the line if 'null' is returned.
- */
- protected String preProcessLine(String line) {
- return line;
- }
-
- /**
- * Parses a line character by character for performance purpose
- *
- * @return whether to continue parsing this line
- */
- protected Boolean parseLine(String str, List<String> tokens, StringBuffer currStr, Boolean wasInquote) {
- if (wasInquote)
- currStr.append('\n');
-
- char[] arr = str.toCharArray();
- boolean inQuote = wasInquote;
- for (int i = 0; i < arr.length; i++) {
- char c = arr[i];
- if (c == separator) {
- if (!inQuote) {
- tokens.add(currStr.toString());
-// currStr.delete(0, currStr.length());
- currStr.setLength(0);
- currStr.trimToSize();
- } else {
- // we don't remove separator that are in a quoted substring
- // System.out
- // .println("IN QUOTE, got a separator: [" + c + "]");
- currStr.append(c);
- }
- } else if (c == quote) {
- if (inQuote && (i + 1) < arr.length && arr[i + 1] == quote) {
- // case of double quote
- currStr.append(quote);
- i++;
- } else {// standard
- inQuote = inQuote ? false : true;
- }
- } else {
- currStr.append(c);
- }
- }
-
- if (!inQuote) {
- tokens.add(currStr.toString());
- // System.out.println("# TOKEN: " + currStr);
- }
- // if (inQuote)
- // throw new ArgeoException("Missing quote at the end of the line "
- // + str + " (parsed: " + tokens + ")");
- if (inQuote)
- return true;
- else
- return false;
- // return tokens;
- }
-
- public char getSeparator() {
- return separator;
- }
-
- public synchronized void setSeparator(char separator) {
- this.separator = separator;
- }
-
- public char getQuote() {
- return quote;
- }
-
- public synchronized void setQuote(char quote) {
- this.quote = quote;
- }
-
- public Boolean getNoHeader() {
- return noHeader;
- }
-
- public synchronized void setNoHeader(Boolean noHeader) {
- this.noHeader = noHeader;
- }
-
- public Boolean getStrictLineAsLongAsHeader() {
- return strictLineAsLongAsHeader;
- }
-
- public synchronized void setStrictLineAsLongAsHeader(Boolean strictLineAsLongAsHeader) {
- this.strictLineAsLongAsHeader = strictLineAsLongAsHeader;
- }
-
-}
+++ /dev/null
-package org.argeo.util;
-
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * CSV parser allowing to process lines as maps whose keys are the header
- * fields.
- */
-public abstract class CsvParserWithLinesAsMap extends CsvParser {
-
- /**
- * Actually processes a line.
- *
- * @param lineNumber the current line number, starts at 1 (the header, if header
- * processing is enabled, the first lien otherwise)
- * @param line the parsed tokens as a map whose keys are the header fields
- */
- protected abstract void processLine(Integer lineNumber, Map<String, String> line);
-
- protected final void processLine(Integer lineNumber, List<String> header, List<String> tokens) {
- if (header == null)
- throw new IllegalArgumentException("Only CSV with header is supported");
- Map<String, String> line = new HashMap<String, String>();
- for (int i = 0; i < header.size(); i++) {
- String key = header.get(i);
- String value = null;
- if (i < tokens.size())
- value = tokens.get(i);
- line.put(key, value);
- }
- processLine(lineNumber, line);
- }
-
-}
+++ /dev/null
-package org.argeo.util;
-
-import java.io.IOException;
-import java.io.OutputStream;
-import java.io.OutputStreamWriter;
-import java.io.UnsupportedEncodingException;
-import java.io.Writer;
-import java.nio.charset.Charset;
-import java.util.Iterator;
-import java.util.List;
-
-/** Write in CSV format. */
-public class CsvWriter {
- private final Writer out;
-
- private char separator = ',';
- private char quote = '\"';
-
- /**
- * Creates a CSV writer.
- *
- * @param out the stream to write to. Caller is responsible for closing it.
- *
- * @deprecated Use {@link #CsvWriter(OutputStream, Charset)} instead.
- *
- */
- @Deprecated
- public CsvWriter(OutputStream out) {
- this.out = new OutputStreamWriter(out);
- }
-
- /**
- * Creates a CSV writer.
- *
- * @param out the stream to write to. Caller is responsible for closing it.
- * @param encoding the encoding to use.
- *
- * @deprecated Use {@link #CsvWriter(OutputStream, Charset)} instead.
- */
- @Deprecated
- public CsvWriter(OutputStream out, String encoding) {
- try {
- this.out = new OutputStreamWriter(out, encoding);
- } catch (UnsupportedEncodingException e) {
- throw new IllegalArgumentException(e);
- }
- }
-
- /**
- * Creates a CSV writer.
- *
- * @param out the stream to write to. Caller is responsible for closing it.
- * @param charset the charset to use
- */
- public CsvWriter(OutputStream out, Charset charset) {
- this.out = new OutputStreamWriter(out, charset);
- }
-
- /**
- * Creates a CSV writer.
- *
- * @param out the stream to write to. Caller is responsible for closing it.
- */
- public CsvWriter(Writer writer) {
- this.out = writer;
- }
-
- /**
- * Write a CSV line. Also used to write a header if needed (this is transparent
- * for the CSV writer): simply call it first, before writing the lines.
- */
- public void writeLine(List<?> tokens) {
- try {
- Iterator<?> it = tokens.iterator();
- while (it.hasNext()) {
- Object obj = it.next();
- writeToken(obj != null ? obj.toString() : null);
- if (it.hasNext())
- out.write(separator);
- }
- out.write('\n');
- out.flush();
- } catch (IOException e) {
- throw new RuntimeException("Could not write " + tokens, e);
- }
- }
-
- /**
- * Write a CSV line. Also used to write a header if needed (this is transparent
- * for the CSV writer): simply call it first, before writing the lines.
- */
- public void writeLine(Object[] tokens) {
- try {
- for (int i = 0; i < tokens.length; i++) {
- if (tokens[i] == null) {
- writeToken(null);
- } else {
- writeToken(tokens[i].toString());
- }
- if (i != (tokens.length - 1))
- out.write(separator);
- }
- out.write('\n');
- out.flush();
- } catch (IOException e) {
- throw new RuntimeException("Could not write " + tokens, e);
- }
- }
-
- protected void writeToken(String token) throws IOException {
- if (token == null) {
- // TODO configure how to deal with null
- out.write("");
- return;
- }
- // +2 for possible quotes, another +2 assuming there would be an already
- // quoted string where quotes needs to be duplicated
- // another +2 for safety
- // we don't want to increase buffer size while writing
- StringBuffer buf = new StringBuffer(token.length() + 6);
- char[] arr = token.toCharArray();
- boolean shouldQuote = false;
- for (char c : arr) {
- if (!shouldQuote) {
- if (c == separator)
- shouldQuote = true;
- if (c == '\n')
- shouldQuote = true;
- }
-
- if (c == quote) {
- shouldQuote = true;
- // duplicate quote
- buf.append(quote);
- }
-
- // generic case
- buf.append(c);
- }
-
- if (shouldQuote == true)
- out.write(quote);
- out.write(buf.toString());
- if (shouldQuote == true)
- out.write(quote);
- }
-
- public void setSeparator(char separator) {
- this.separator = separator;
- }
-
- public void setQuote(char quote) {
- this.quote = quote;
- }
-
-}
+++ /dev/null
-package org.argeo.util;
-
-import java.util.Dictionary;
-import java.util.Enumeration;
-import java.util.Iterator;
-
-/**
- * Access the keys of a {@link String}-keyed {@link Dictionary} (common throughout
- * the OSGi APIs) as an {@link Iterable} so that they are easily usable in
- * for-each loops.
- */
-class DictionaryKeys implements Iterable<String> {
- private final Dictionary<String, ?> dictionary;
-
- public DictionaryKeys(Dictionary<String, ?> dictionary) {
- this.dictionary = dictionary;
- }
-
- @Override
- public Iterator<String> iterator() {
- return new KeyIterator(dictionary.keys());
- }
-
- private static class KeyIterator implements Iterator<String> {
- private final Enumeration<String> keys;
-
- KeyIterator(Enumeration<String> keys) {
- this.keys = keys;
- }
-
- @Override
- public boolean hasNext() {
- return keys.hasMoreElements();
- }
-
- @Override
- public String next() {
- return keys.nextElement();
- }
-
- }
-}
+++ /dev/null
-package org.argeo.util;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.ByteBuffer;
-import java.nio.channels.FileChannel;
-import java.nio.channels.FileChannel.MapMode;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-
-/** Utilities around cryptographic digests */
-public class DigestUtils {
- 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";
-
- private static Boolean debug = false;
- // TODO: make it configurable
- private final static Integer byteBufferCapacity = 100 * 1024;// 100 KB
-
- public static byte[] sha1(byte[]... bytes) {
- try {
- MessageDigest digest = MessageDigest.getInstance(SHA1);
- for (byte[] arr : bytes)
- digest.update(arr);
- byte[] checksum = digest.digest();
- return checksum;
- } catch (NoSuchAlgorithmException e) {
- throw new UnsupportedOperationException("SHA1 is not avalaible", e);
- }
- }
-
- public static byte[] digestAsBytes(String algorithm, byte[]... bytes) {
- try {
- MessageDigest digest = MessageDigest.getInstance(algorithm);
- for (byte[] arr : bytes)
- digest.update(arr);
- byte[] checksum = digest.digest();
- return checksum;
- } catch (NoSuchAlgorithmException e) {
- throw new UnsupportedOperationException("Cannot digest with algorithm " + algorithm, e);
- }
- }
-
- public static String digest(String algorithm, byte[]... bytes) {
- return toHexString(digestAsBytes(algorithm, bytes));
- }
-
- public static String digest(String algorithm, InputStream in) {
- try {
- MessageDigest digest = MessageDigest.getInstance(algorithm);
- // ReadableByteChannel channel = Channels.newChannel(in);
- // ByteBuffer bb = ByteBuffer.allocateDirect(byteBufferCapacity);
- // while (channel.read(bb) > 0)
- // digest.update(bb);
- 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 = toHexString(checksum);
- return res;
- } catch (NoSuchAlgorithmException e) {
- throw new IllegalArgumentException("Cannot digest with algorithm " + algorithm, e);
- } catch (IOException e) {
- throw new RuntimeException(e);
- } finally {
- StreamUtils.closeQuietly(in);
- }
- }
-
- public static String digest(String algorithm, File file) {
- FileInputStream fis = null;
- FileChannel fc = null;
- try {
- fis = new FileInputStream(file);
- fc = fis.getChannel();
-
- // Get the file's size and then map it into memory
- int sz = (int) fc.size();
- ByteBuffer bb = fc.map(FileChannel.MapMode.READ_ONLY, 0, sz);
- return digest(algorithm, bb);
- } catch (IOException e) {
- throw new IllegalArgumentException("Cannot digest " + file + " with algorithm " + algorithm, e);
- } finally {
- StreamUtils.closeQuietly(fis);
- if (fc.isOpen())
- try {
- fc.close();
- } catch (IOException e) {
- // silent
- }
- }
- }
-
- protected static String digest(String algorithm, ByteBuffer bb) {
- long begin = System.currentTimeMillis();
- try {
- MessageDigest digest = MessageDigest.getInstance(algorithm);
- digest.update(bb);
- byte[] checksum = digest.digest();
- String res = toHexString(checksum);
- long end = System.currentTimeMillis();
- if (debug)
- System.out.println((end - begin) + " ms / " + ((end - begin) / 1000) + " s");
- return res;
- } catch (NoSuchAlgorithmException e) {
- throw new IllegalArgumentException("Cannot digest with algorithm " + algorithm, e);
- }
- }
-
- public static String sha1hex(Path path) {
- return digest(SHA1, path, byteBufferCapacity);
- }
-
- public static String digest(String algorithm, Path path, long bufferSize) {
- byte[] digest = digestAsBytes(algorithm, path, bufferSize);
- return toHexString(digest);
- }
-
- public static byte[] digestAsBytes(String algorithm, Path file, long bufferSize) {
- long begin = System.currentTimeMillis();
- try {
- MessageDigest md = MessageDigest.getInstance(algorithm);
- FileChannel fc = FileChannel.open(file);
- long fileSize = Files.size(file);
- if (fileSize <= bufferSize) {
- ByteBuffer bb = fc.map(MapMode.READ_ONLY, 0, fileSize);
- md.update(bb);
- } else {
- long lastCycle = (fileSize / bufferSize) - 1;
- long position = 0;
- for (int i = 0; i <= lastCycle; i++) {
- ByteBuffer bb;
- if (i != lastCycle) {
- bb = fc.map(MapMode.READ_ONLY, position, bufferSize);
- position = position + bufferSize;
- } else {
- bb = fc.map(MapMode.READ_ONLY, position, fileSize - position);
- position = fileSize;
- }
- md.update(bb);
- }
- }
- long end = System.currentTimeMillis();
- if (debug)
- System.out.println((end - begin) + " ms / " + ((end - begin) / 1000) + " s");
- return md.digest();
- } catch (NoSuchAlgorithmException e) {
- throw new IllegalArgumentException("Cannot digest " + file + " with algorithm " + algorithm, e);
- } catch (IOException e) {
- throw new RuntimeException("Cannot digest " + file + " with algorithm " + algorithm, e);
- }
- }
-
- public static void main(String[] args) {
- File file;
- if (args.length > 0)
- file = new File(args[0]);
- else {
- System.err.println("Usage: <file> [<algorithm>]" + " (see http://java.sun.com/j2se/1.5.0/"
- + "docs/guide/security/CryptoSpec.html#AppA)");
- return;
- }
-
- if (args.length > 1) {
- String algorithm = args[1];
- System.out.println(digest(algorithm, file));
- } else {
- String algorithm = "MD5";
- System.out.println(algorithm + ": " + digest(algorithm, file));
- algorithm = "SHA";
- System.out.println(algorithm + ": " + digest(algorithm, file));
- System.out.println(algorithm + ": " + sha1hex(file.toPath()));
- algorithm = "SHA-256";
- System.out.println(algorithm + ": " + digest(algorithm, file));
- algorithm = "SHA-512";
- System.out.println(algorithm + ": " + digest(algorithm, file));
- }
- }
-
- final private static char[] hexArray = "0123456789abcdef".toCharArray();
-
- /** Converts a byte array to an hex String. */
- public static String toHexString(byte[] bytes) {
- 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);
- }
-
-}
+++ /dev/null
-package org.argeo.util;
-
-import java.io.IOException;
-import java.io.PrintStream;
-import java.nio.charset.Charset;
-import java.nio.file.DirectoryStream;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-/** Hashes the hashes of the files in a directory. */
-public class DirH {
-
- private final static Charset charset = Charset.forName("UTF-16");
- private final static long bufferSize = 200 * 1024 * 1024;
- private final static String algorithm = "SHA";
-
- private final static byte EOL = (byte) '\n';
- private final static byte SPACE = (byte) ' ';
-
- private final int hashSize;
-
- private final byte[][] hashes;
- private final byte[][] fileNames;
- private final byte[] digest;
- private final byte[] dirName;
-
- /**
- * @param dirName can be null or empty
- */
- private DirH(byte[][] hashes, byte[][] fileNames, byte[] dirName) {
- if (hashes.length != fileNames.length)
- throw new IllegalArgumentException(hashes.length + " hashes and " + fileNames.length + " file names");
- this.hashes = hashes;
- this.fileNames = fileNames;
- this.dirName = dirName == null ? new byte[0] : dirName;
- if (hashes.length == 0) {// empty dir
- hashSize = 20;
- // FIXME what is the digest of an empty dir?
- digest = new byte[hashSize];
- Arrays.fill(digest, SPACE);
- return;
- }
- hashSize = hashes[0].length;
- for (int i = 0; i < hashes.length; i++) {
- if (hashes[i].length != hashSize)
- throw new IllegalArgumentException(
- "Hash size for " + new String(fileNames[i], charset) + " is " + hashes[i].length);
- }
-
- try {
- MessageDigest md = MessageDigest.getInstance(algorithm);
- for (int i = 0; i < hashes.length; i++) {
- md.update(this.hashes[i]);
- md.update(SPACE);
- md.update(this.fileNames[i]);
- md.update(EOL);
- }
- digest = md.digest();
- } catch (NoSuchAlgorithmException e) {
- throw new IllegalArgumentException("Cannot digest", e);
- }
- }
-
- public void print(PrintStream out) {
- out.print(DigestUtils.toHexString(digest));
- if (dirName.length > 0) {
- out.print(' ');
- out.print(new String(dirName, charset));
- }
- out.print('\n');
- for (int i = 0; i < hashes.length; i++) {
- out.print(DigestUtils.toHexString(hashes[i]));
- out.print(' ');
- out.print(new String(fileNames[i], charset));
- out.print('\n');
- }
- }
-
- public static DirH digest(Path dir) {
- try (DirectoryStream<Path> files = Files.newDirectoryStream(dir)) {
- List<byte[]> hs = new ArrayList<byte[]>();
- List<String> fNames = new ArrayList<>();
- for (Path file : files) {
- if (!Files.isDirectory(file)) {
- byte[] digest = DigestUtils.digestAsBytes(algorithm, file, bufferSize);
- hs.add(digest);
- fNames.add(file.getFileName().toString());
- }
- }
-
- byte[][] fileNames = new byte[fNames.size()][];
- for (int i = 0; i < fNames.size(); i++) {
- fileNames[i] = fNames.get(i).getBytes(charset);
- }
- byte[][] hashes = hs.toArray(new byte[hs.size()][]);
- return new DirH(hashes, fileNames, dir.toString().getBytes(charset));
- } catch (IOException e) {
- throw new RuntimeException("Cannot digest " + dir, e);
- }
- }
-
- public static void main(String[] args) {
- try {
- DirH dirH = DirH.digest(Paths.get("/home/mbaudier/tmp/"));
- dirH.print(System.out);
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
-}
+++ /dev/null
-package org.argeo.util;
-
-import java.io.IOException;
-import java.nio.file.FileVisitResult;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.SimpleFileVisitor;
-import java.nio.file.attribute.BasicFileAttributes;
-
-/** Utilities around the standard Java file abstractions. */
-public class FsUtils {
- /**
- * Deletes this path, recursively if needed. Does nothing if the path does not
- * exist.
- */
- public static void delete(Path path) {
- try {
- if (!Files.exists(path))
- return;
- Files.walkFileTree(path, new SimpleFileVisitor<Path>() {
- @Override
- public FileVisitResult postVisitDirectory(Path directory, IOException e) throws IOException {
- if (e != null)
- throw e;
- Files.delete(directory);
- return FileVisitResult.CONTINUE;
- }
-
- @Override
- public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
- Files.delete(file);
- return FileVisitResult.CONTINUE;
- }
- });
- } catch (IOException e) {
- throw new RuntimeException("Cannot delete " + path, e);
- }
- }
-
- /** Singleton. */
- private FsUtils() {
- }
-
-}
+++ /dev/null
-package org.argeo.util;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.Writer;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.StandardOpenOption;
-import java.time.ZonedDateTime;
-import java.time.temporal.ChronoUnit;
-import java.time.temporal.Temporal;
-import java.util.ArrayList;
-import java.util.Dictionary;
-import java.util.Enumeration;
-import java.util.HashMap;
-import java.util.Hashtable;
-import java.util.List;
-import java.util.Map;
-import java.util.Properties;
-
-import javax.naming.InvalidNameException;
-import javax.naming.ldap.LdapName;
-
-/** Utilities around Java basic features. */
-public class LangUtils {
- /*
- * NON-API OSGi
- */
- /**
- * Returns an array with the names of the provided classes. Useful when
- * registering services with multiple interfaces in OSGi.
- */
- public static String[] names(Class<?>... clzz) {
- String[] res = new String[clzz.length];
- for (int i = 0; i < clzz.length; i++)
- res[i] = clzz[i].getName();
- return res;
- }
-
- /*
- * MAP
- */
- /**
- * Creates a new {@link Dictionary} with one key-value pair. Key should not be
- * null, but if the value is null, it returns an empty {@link Dictionary}.
- */
- public static Map<String, Object> map(String key, Object value) {
- assert key != null;
- HashMap<String, Object> props = new HashMap<>();
- if (value != null)
- props.put(key, value);
- return props;
- }
-
- /*
- * DICTIONARY
- */
-
- /**
- * Creates a new {@link Dictionary} with one key-value pair. Key should not be
- * null, but if the value is null, it returns an empty {@link Dictionary}.
- */
- public static Dictionary<String, Object> dict(String key, Object value) {
- assert key != null;
- Hashtable<String, Object> props = new Hashtable<>();
- if (value != null)
- props.put(key, value);
- return props;
- }
-
- /** @deprecated Use {@link #dict(String, Object)} instead. */
- @Deprecated
- public static Dictionary<String, Object> dico(String key, Object value) {
- return dict(key, value);
- }
-
- /** Converts a {@link Dictionary} to a {@link Map} of strings. */
- public static Map<String, String> dictToStringMap(Dictionary<String, ?> properties) {
- if (properties == null) {
- return null;
- }
- Map<String, String> res = new HashMap<>(properties.size());
- Enumeration<String> keys = properties.keys();
- while (keys.hasMoreElements()) {
- String key = keys.nextElement();
- res.put(key, properties.get(key).toString());
- }
- return res;
- }
-
- /**
- * Get a string property from this map, expecting to find it, or
- * <code>null</code> if not found.
- */
- public static String get(Map<String, ?> map, String key) {
- Object res = map.get(key);
- if (res == null)
- return null;
- return res.toString();
- }
-
- /**
- * Get a string property from this map, expecting to find it.
- *
- * @throws IllegalArgumentException if the key was not found
- */
- public static String getNotNull(Map<String, ?> map, String key) {
- Object res = map.get(key);
- if (res == null)
- throw new IllegalArgumentException("Map " + map + " should contain key " + key);
- return res.toString();
- }
-
- /**
- * Wraps the keys of the provided {@link Dictionary} as an {@link Iterable}.
- */
- public static Iterable<String> keys(Dictionary<String, ?> props) {
- assert props != null;
- return new DictionaryKeys(props);
- }
-
- static String toJson(Dictionary<String, ?> props) {
- return toJson(props, false);
- }
-
- static String toJson(Dictionary<String, ?> props, boolean pretty) {
- StringBuilder sb = new StringBuilder();
- sb.append('{');
- if (pretty)
- sb.append('\n');
- Enumeration<String> keys = props.keys();
- while (keys.hasMoreElements()) {
- String key = keys.nextElement();
- if (pretty)
- sb.append(' ');
- sb.append('\"').append(key).append('\"');
- if (pretty)
- sb.append(" : ");
- else
- sb.append(':');
- sb.append('\"').append(props.get(key)).append('\"');
- if (keys.hasMoreElements())
- sb.append(", ");
- if (pretty)
- sb.append('\n');
- }
- sb.append('}');
- return sb.toString();
- }
-
- static void storeAsProperties(Dictionary<String, Object> props, Path path) throws IOException {
- if (props == null)
- throw new IllegalArgumentException("Props cannot be null");
- Properties toStore = new Properties();
- for (Enumeration<String> keys = props.keys(); keys.hasMoreElements();) {
- String key = keys.nextElement();
- toStore.setProperty(key, props.get(key).toString());
- }
- try (OutputStream out = Files.newOutputStream(path)) {
- toStore.store(out, null);
- }
- }
-
- static void appendAsLdif(String dnBase, String dnKey, Dictionary<String, Object> props, Path path)
- throws IOException {
- if (props == null)
- throw new IllegalArgumentException("Props cannot be null");
- Object dnValue = props.get(dnKey);
- String dnStr = dnKey + '=' + dnValue + ',' + dnBase;
- LdapName dn;
- try {
- dn = new LdapName(dnStr);
- } catch (InvalidNameException e) {
- throw new IllegalArgumentException("Cannot interpret DN " + dnStr, e);
- }
- if (dnValue == null)
- throw new IllegalArgumentException("DN key " + dnKey + " must have a value");
- try (Writer writer = Files.newBufferedWriter(path, StandardOpenOption.APPEND, StandardOpenOption.CREATE)) {
- writer.append("\ndn: ");
- writer.append(dn.toString());
- writer.append('\n');
- for (Enumeration<String> keys = props.keys(); keys.hasMoreElements();) {
- String key = keys.nextElement();
- Object value = props.get(key);
- writer.append(key);
- writer.append(": ");
- // FIXME deal with binary and multiple values
- writer.append(value.toString());
- writer.append('\n');
- }
- }
- }
-
- static Dictionary<String, Object> loadFromProperties(Path path) throws IOException {
- Properties toLoad = new Properties();
- try (InputStream in = Files.newInputStream(path)) {
- toLoad.load(in);
- }
- Dictionary<String, Object> res = new Hashtable<String, Object>();
- for (Object key : toLoad.keySet())
- res.put(key.toString(), toLoad.get(key));
- return res;
- }
-
- /*
- * COLLECTIONS
- */
- /**
- * Convert a comma-separated separated {@link String} or a {@link String} array
- * to a {@link List} of {@link String}, trimming them. Useful to quickly
- * interpret OSGi services properties.
- *
- * @return a {@link List} containing the trimmed {@link String}s, or an empty
- * {@link List} if the argument was <code>null</code>.
- */
- public static List<String> toStringList(Object value) {
- List<String> values = new ArrayList<>();
- if (value == null)
- return values;
- String[] arr;
- if (value instanceof String) {
- arr = ((String) value).split(",");
- } else if (value instanceof String[]) {
- arr = (String[]) value;
- } else {
- throw new IllegalArgumentException("Unsupported value type " + value.getClass());
- }
- for (String str : arr) {
- values.add(str.trim());
- }
- return values;
- }
-
- /*
- * EXCEPTIONS
- */
- /**
- * Chain the messages of all causes (one per line, <b>starts with a line
- * return</b>) without all the stack
- */
- public static String chainCausesMessages(Throwable t) {
- StringBuffer buf = new StringBuffer();
- chainCauseMessage(buf, t);
- return buf.toString();
- }
-
- /** Recursive chaining of messages */
- private static void chainCauseMessage(StringBuffer buf, Throwable t) {
- buf.append('\n').append(' ').append(t.getClass().getCanonicalName()).append(": ").append(t.getMessage());
- if (t.getCause() != null)
- chainCauseMessage(buf, t.getCause());
- }
-
- /*
- * TIME
- */
- /** Formats time elapsed since start. */
- public static String since(ZonedDateTime start) {
- ZonedDateTime now = ZonedDateTime.now();
- return duration(start, now);
- }
-
- /** Formats a duration. */
- public static String duration(Temporal start, Temporal end) {
- long count = ChronoUnit.DAYS.between(start, end);
- if (count != 0)
- return count > 1 ? count + " days" : count + " day";
- count = ChronoUnit.HOURS.between(start, end);
- if (count != 0)
- return count > 1 ? count + " hours" : count + " hours";
- count = ChronoUnit.MINUTES.between(start, end);
- if (count != 0)
- return count > 1 ? count + " minutes" : count + " minute";
- count = ChronoUnit.SECONDS.between(start, end);
- return count > 1 ? count + " seconds" : count + " second";
- }
-
- /** Singleton constructor. */
- private LangUtils() {
-
- }
-
-}
+++ /dev/null
-package org.argeo.util;
-
-import java.io.File;
-import java.lang.management.ManagementFactory;
-
-/** When OS specific informations are needed. */
-public class OS {
- public final static OS LOCAL = new OS();
-
- private final String arch, name, version;
-
- /** The OS of the running JVM */
- protected OS() {
- arch = System.getProperty("os.arch");
- name = System.getProperty("os.name");
- version = System.getProperty("os.version");
- }
-
- public String getArch() {
- return arch;
- }
-
- public String getName() {
- return name;
- }
-
- public String getVersion() {
- return version;
- }
-
- public boolean isMSWindows() {
- // only MS Windows would use such an horrendous separator...
- return File.separatorChar == '\\';
- }
-
- public String[] getDefaultShellCommand() {
- if (!isMSWindows())
- return new String[] { "/bin/sh", "-l", "-i" };
- else
- return new String[] { "cmd.exe", "/C" };
- }
-
- public static Integer getJvmPid() {
- /*
- * This method works on most platforms (including Linux). Although when Java 9
- * comes along, there is a better way: long pid =
- * ProcessHandle.current().getPid();
- *
- * See:
- * http://stackoverflow.com/questions/35842/how-can-a-java-program-get-its-own-
- * process-id
- */
- String pidAndHost = ManagementFactory.getRuntimeMXBean().getName();
- return Integer.parseInt(pidAndHost.substring(0, pidAndHost.indexOf('@')));
- }
-}
+++ /dev/null
-package org.argeo.util;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.nio.charset.Charset;
-import java.nio.charset.StandardCharsets;
-import java.security.GeneralSecurityException;
-import java.security.InvalidKeyException;
-import java.security.Key;
-
-import javax.crypto.Cipher;
-import javax.crypto.CipherInputStream;
-import javax.crypto.CipherOutputStream;
-import javax.crypto.SecretKey;
-import javax.crypto.SecretKeyFactory;
-import javax.crypto.spec.IvParameterSpec;
-import javax.crypto.spec.PBEKeySpec;
-import javax.crypto.spec.SecretKeySpec;
-
-public class PasswordEncryption {
- public final static Integer DEFAULT_ITERATION_COUNT = 1024;
- /** Stronger with 256, but causes problem with Oracle JVM */
- public final static Integer DEFAULT_SECRETE_KEY_LENGTH = 256;
- public final static Integer DEFAULT_SECRETE_KEY_LENGTH_RESTRICTED = 128;
- 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";
-// public final static String DEFAULT_CHARSET = "UTF-8";
- public final static Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
-
- private Integer iterationCount = DEFAULT_ITERATION_COUNT;
- private Integer secreteKeyLength = DEFAULT_SECRETE_KEY_LENGTH;
- private String secreteKeyFactoryName = DEFAULT_SECRETE_KEY_FACTORY;
- private String secreteKeyEncryption = DEFAULT_SECRETE_KEY_ENCRYPTION;
- private String cipherName = DEFAULT_CIPHER_NAME;
-
- private static byte[] DEFAULT_SALT_8 = { (byte) 0xA9, (byte) 0x9B, (byte) 0xC8, (byte) 0x32, (byte) 0x56,
- (byte) 0x35, (byte) 0xE3, (byte) 0x03 };
- private static byte[] DEFAULT_IV_16 = { (byte) 0xA9, (byte) 0x9B, (byte) 0xC8, (byte) 0x32, (byte) 0x56,
- (byte) 0x35, (byte) 0xE3, (byte) 0x03, (byte) 0xA9, (byte) 0x9B, (byte) 0xC8, (byte) 0x32, (byte) 0x56,
- (byte) 0x35, (byte) 0xE3, (byte) 0x03 };
-
- private Key key;
- private Cipher ecipher;
- private Cipher dcipher;
-
- private String securityProviderName = null;
-
- /**
- * This is up to the caller to clear the passed array. Neither copy of nor
- * reference to the passed array is kept
- */
- public PasswordEncryption(char[] password) {
- this(password, DEFAULT_SALT_8, DEFAULT_IV_16);
- }
-
- /**
- * This is up to the caller to clear the passed array. Neither copies of nor
- * references to the passed arrays are kept
- */
- public PasswordEncryption(char[] password, byte[] passwordSalt, byte[] initializationVector) {
- try {
- initKeyAndCiphers(password, passwordSalt, initializationVector);
- } catch (InvalidKeyException e) {
- Integer previousSecreteKeyLength = secreteKeyLength;
- secreteKeyLength = DEFAULT_SECRETE_KEY_LENGTH_RESTRICTED;
- System.err.println("'" + e.getMessage() + "', will use " + secreteKeyLength
- + " secrete key length instead of " + previousSecreteKeyLength);
- try {
- initKeyAndCiphers(password, passwordSalt, initializationVector);
- } catch (GeneralSecurityException e1) {
- throw new IllegalStateException("Cannot get secret key (with restricted length)", e1);
- }
- } catch (GeneralSecurityException e) {
- throw new IllegalStateException("Cannot get secret key", e);
- }
- }
-
- protected void initKeyAndCiphers(char[] password, byte[] passwordSalt, byte[] initializationVector)
- throws GeneralSecurityException {
- byte[] salt = new byte[8];
- System.arraycopy(passwordSalt, 0, salt, 0, salt.length);
- // for (int i = 0; i < password.length && i < salt.length; i++)
- // salt[i] = (byte) password[i];
- byte[] iv = new byte[16];
- System.arraycopy(initializationVector, 0, iv, 0, iv.length);
-
- SecretKeyFactory keyFac = SecretKeyFactory.getInstance(getSecretKeyFactoryName());
- PBEKeySpec keySpec = new PBEKeySpec(password, salt, getIterationCount(), getKeyLength());
- String secKeyEncryption = getSecretKeyEncryption();
- if (secKeyEncryption != null) {
- SecretKey tmp = keyFac.generateSecret(keySpec);
- key = new SecretKeySpec(tmp.getEncoded(), getSecretKeyEncryption());
- } else {
- key = keyFac.generateSecret(keySpec);
- }
- if (securityProviderName != null)
- ecipher = Cipher.getInstance(getCipherName(), securityProviderName);
- else
- ecipher = Cipher.getInstance(getCipherName());
- ecipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv));
- dcipher = Cipher.getInstance(getCipherName());
- dcipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));
- }
-
- public void encrypt(InputStream decryptedIn, OutputStream encryptedOut) throws IOException {
- try {
- CipherOutputStream out = new CipherOutputStream(encryptedOut, ecipher);
- StreamUtils.copy(decryptedIn, out);
- StreamUtils.closeQuietly(out);
- } catch (IOException e) {
- throw e;
- } finally {
- StreamUtils.closeQuietly(decryptedIn);
- }
- }
-
- public void decrypt(InputStream encryptedIn, OutputStream decryptedOut) throws IOException {
- try {
- CipherInputStream decryptedIn = new CipherInputStream(encryptedIn, dcipher);
- StreamUtils.copy(decryptedIn, decryptedOut);
- } catch (IOException e) {
- throw e;
- } finally {
- StreamUtils.closeQuietly(encryptedIn);
- }
- }
-
- public byte[] encryptString(String str) {
- ByteArrayOutputStream out = null;
- ByteArrayInputStream in = null;
- try {
- out = new ByteArrayOutputStream();
- in = new ByteArrayInputStream(str.getBytes(DEFAULT_CHARSET));
- encrypt(in, out);
- return out.toByteArray();
- } catch (IOException e) {
- throw new RuntimeException(e);
- } finally {
- StreamUtils.closeQuietly(out);
- }
- }
-
- /** Closes the input stream */
- public String decryptAsString(InputStream in) {
- ByteArrayOutputStream out = null;
- try {
- out = new ByteArrayOutputStream();
- decrypt(in, out);
- return new String(out.toByteArray(), DEFAULT_CHARSET);
- } catch (IOException e) {
- throw new RuntimeException(e);
- } finally {
- StreamUtils.closeQuietly(out);
- }
- }
-
- protected Key getKey() {
- return key;
- }
-
- protected Cipher getEcipher() {
- return ecipher;
- }
-
- protected Cipher getDcipher() {
- return dcipher;
- }
-
- protected Integer getIterationCount() {
- return iterationCount;
- }
-
- protected Integer getKeyLength() {
- return secreteKeyLength;
- }
-
- protected String getSecretKeyFactoryName() {
- return secreteKeyFactoryName;
- }
-
- protected String getSecretKeyEncryption() {
- return secreteKeyEncryption;
- }
-
- protected String getCipherName() {
- return cipherName;
- }
-
- public void setIterationCount(Integer iterationCount) {
- this.iterationCount = iterationCount;
- }
-
- public void setSecreteKeyLength(Integer keyLength) {
- this.secreteKeyLength = keyLength;
- }
-
- public void setSecreteKeyFactoryName(String secreteKeyFactoryName) {
- this.secreteKeyFactoryName = secreteKeyFactoryName;
- }
-
- public void setSecreteKeyEncryption(String secreteKeyEncryption) {
- this.secreteKeyEncryption = secreteKeyEncryption;
- }
-
- public void setCipherName(String cipherName) {
- this.cipherName = cipherName;
- }
-
- public void setSecurityProviderName(String securityProviderName) {
- this.securityProviderName = securityProviderName;
- }
-}
+++ /dev/null
-package org.argeo.util;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.nio.channels.AsynchronousByteChannel;
-import java.nio.channels.CompletionHandler;
-import java.nio.channels.ReadableByteChannel;
-import java.nio.channels.WritableByteChannel;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Future;
-
-/** An {@link AsynchronousByteChannel} based on an {@link ExecutorService}. */
-public class ServiceChannel implements AsynchronousByteChannel {
- private final ReadableByteChannel in;
- private final WritableByteChannel out;
-
- private boolean open = true;
-
- private ExecutorService executor;
-
- public ServiceChannel(ReadableByteChannel in, WritableByteChannel out, ExecutorService executor) {
- this.in = in;
- this.out = out;
- this.executor = executor;
- }
-
- @Override
- public Future<Integer> read(ByteBuffer dst) {
- return executor.submit(() -> in.read(dst));
- }
-
- @Override
- public <A> void read(ByteBuffer dst, A attachment, CompletionHandler<Integer, ? super A> handler) {
- try {
- Future<Integer> res = read(dst);
- handler.completed(res.get(), attachment);
- } catch (Exception e) {
- handler.failed(e, attachment);
- }
- }
-
- @Override
- public Future<Integer> write(ByteBuffer src) {
- return executor.submit(() -> out.write(src));
- }
-
- @Override
- public <A> void write(ByteBuffer src, A attachment, CompletionHandler<Integer, ? super A> handler) {
- try {
- Future<Integer> res = write(src);
- handler.completed(res.get(), attachment);
- } catch (Exception e) {
- handler.failed(e, attachment);
- }
- }
-
- @Override
- public synchronized void close() throws IOException {
- try {
- in.close();
- } catch (Exception e) {
- e.printStackTrace();
- }
- try {
- out.close();
- } catch (Exception e) {
- e.printStackTrace();
- }
- open = false;
- notifyAll();
- }
-
- @Override
- public synchronized boolean isOpen() {
- return open;
- }
-
-}
+++ /dev/null
-package org.argeo.util;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.Reader;
-import java.io.Writer;
-
-/** Utilities to be used when Apache Commons IO is not available. */
-class StreamUtils {
- private static final int DEFAULT_BUFFER_SIZE = 1024 * 4;
-
- /*
- * APACHE COMMONS IO (inspired)
- */
-
- /** @return the number of bytes */
- public static Long copy(InputStream in, OutputStream out)
- throws IOException {
- Long count = 0l;
- byte[] buf = new byte[DEFAULT_BUFFER_SIZE];
- while (true) {
- int length = in.read(buf);
- if (length < 0)
- break;
- out.write(buf, 0, length);
- count = count + length;
- }
- return count;
- }
-
- /** @return the number of chars */
- public static Long copy(Reader in, Writer out) throws IOException {
- Long count = 0l;
- char[] buf = new char[DEFAULT_BUFFER_SIZE];
- while (true) {
- int length = in.read(buf);
- if (length < 0)
- break;
- out.write(buf, 0, length);
- count = count + length;
- }
- return count;
- }
-
- public static void closeQuietly(InputStream in) {
- if (in != null)
- try {
- in.close();
- } catch (Exception e) {
- //
- }
- }
-
- public static void closeQuietly(OutputStream out) {
- if (out != null)
- try {
- out.close();
- } catch (Exception e) {
- //
- }
- }
-
- public static void closeQuietly(Reader in) {
- if (in != null)
- try {
- in.close();
- } catch (Exception e) {
- //
- }
- }
-
- public static void closeQuietly(Writer out) {
- if (out != null)
- try {
- out.close();
- } catch (Exception e) {
- //
- }
- }
-}
+++ /dev/null
-package org.argeo.util;
-
-import java.lang.reflect.Method;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.TreeMap;
-
-/** A generic tester based on Java assertions and functional programming. */
-public class Tester {
- private Map<String, TesterStatus> results = Collections.synchronizedSortedMap(new TreeMap<>());
-
- private ClassLoader classLoader;
-
- /** Use {@link Thread#getContextClassLoader()} by default. */
- public Tester() {
- this(Thread.currentThread().getContextClassLoader());
- }
-
- public Tester(ClassLoader classLoader) {
- this.classLoader = classLoader;
- }
-
- public void execute(String className) {
- Class<?> clss;
- try {
- clss = classLoader.loadClass(className);
- boolean assertionsEnabled = clss.desiredAssertionStatus();
- if (!assertionsEnabled)
- throw new IllegalStateException("Test runner " + getClass().getName()
- + " requires Java assertions to be enabled. Call the JVM with the -ea argument.");
- } catch (Exception e1) {
- throw new IllegalArgumentException("Cannot initalise test for " + className, e1);
-
- }
- List<Method> methods = findMethods(clss);
- if (methods.size() == 0)
- throw new IllegalArgumentException("No test method found in " + clss);
- // TODO make order more predictable?
- for (Method method : methods) {
- String uid = method.getDeclaringClass().getName() + "#" + method.getName();
- TesterStatus testStatus = new TesterStatus(uid);
- Object obj = null;
- try {
- beforeTest(uid, method);
- obj = clss.getDeclaredConstructor().newInstance();
- method.invoke(obj);
- testStatus.setPassed();
- afterTestPassed(uid, method, obj);
- } catch (Exception e) {
- testStatus.setFailed(e);
- afterTestFailed(uid, method, obj, e);
- } finally {
- results.put(uid, testStatus);
- }
- }
- }
-
- protected void beforeTest(String uid, Method method) {
- // System.out.println(uid + ": STARTING");
- }
-
- protected void afterTestPassed(String uid, Method method, Object obj) {
- System.out.println(uid + ": PASSED");
- }
-
- protected void afterTestFailed(String uid, Method method, Object obj, Throwable e) {
- System.out.println(uid + ": FAILED");
- e.printStackTrace();
- }
-
- protected List<Method> findMethods(Class<?> clss) {
- List<Method> methods = new ArrayList<Method>();
-// Method call = getMethod(clss, "call");
-// if (call != null)
-// methods.add(call);
-//
- for (Method method : clss.getMethods()) {
- if (method.getName().startsWith("test")) {
- methods.add(method);
- }
- }
- return methods;
- }
-
- protected Method getMethod(Class<?> clss, String name, Class<?>... parameterTypes) {
- try {
- return clss.getMethod(name, parameterTypes);
- } catch (NoSuchMethodException e) {
- return null;
- } catch (SecurityException e) {
- throw new IllegalStateException(e);
- }
- }
-
- public static void main(String[] args) {
- // deal with arguments
- String className;
- if (args.length < 1) {
- System.err.println(usage());
- System.exit(1);
- throw new IllegalArgumentException();
- } else {
- className = args[0];
- }
-
- Tester test = new Tester();
- try {
- test.execute(className);
- } catch (Throwable e) {
- e.printStackTrace();
- }
-
- Map<String, TesterStatus> r = test.results;
- for (String uid : r.keySet()) {
- TesterStatus testStatus = r.get(uid);
- System.out.println(testStatus);
- }
- }
-
- public static String usage() {
- return "java " + Tester.class.getName() + " [test class name]";
-
- }
-}
+++ /dev/null
-package org.argeo.util;
-
-import java.io.Serializable;
-
-/** The status of a test. */
-public class TesterStatus implements Serializable {
- private static final long serialVersionUID = 6272975746885487000L;
-
- private Boolean passed = null;
- private final String uid;
- private Throwable throwable = null;
-
- public TesterStatus(String uid) {
- this.uid = uid;
- }
-
- /** For cloning. */
- public TesterStatus(String uid, Boolean passed, Throwable throwable) {
- this(uid);
- this.passed = passed;
- this.throwable = throwable;
- }
-
- public synchronized Boolean isRunning() {
- return passed == null;
- }
-
- public synchronized Boolean isPassed() {
- assert passed != null;
- return passed;
- }
-
- public synchronized Boolean isFailed() {
- assert passed != null;
- return !passed;
- }
-
- public synchronized void setPassed() {
- setStatus(true);
- }
-
- public synchronized void setFailed() {
- setStatus(false);
- }
-
- public synchronized void setFailed(Throwable throwable) {
- setStatus(false);
- setThrowable(throwable);
- }
-
- protected void setStatus(Boolean passed) {
- if (this.passed != null)
- throw new IllegalStateException("Passed status of test " + uid + " is already set (to " + passed + ")");
- this.passed = passed;
- }
-
- protected void setThrowable(Throwable throwable) {
- if (this.throwable != null)
- throw new IllegalStateException("Throwable of test " + uid + " is already set (to " + passed + ")");
- this.throwable = throwable;
- }
-
- public String getUid() {
- return uid;
- }
-
- public Throwable getThrowable() {
- return throwable;
- }
-
- @Override
- protected Object clone() throws CloneNotSupportedException {
- // TODO Auto-generated method stub
- return super.clone();
- }
-
- @Override
- public boolean equals(Object o) {
- if (o instanceof TesterStatus) {
- TesterStatus other = (TesterStatus) o;
- // we don't check consistency for performance purposes
- // this equals() is supposed to be used in collections or for transfer
- return other.uid.equals(uid);
- }
- return false;
- }
-
- @Override
- public int hashCode() {
- return uid.hashCode();
- }
-
- @Override
- public String toString() {
- return uid + "\t" + (passed ? "passed" : "failed");
- }
-
-}
+++ /dev/null
-package org.argeo.util;
-
-import java.text.NumberFormat;
-import java.text.ParseException;
-import java.util.Locale;
-
-/** A throughput, that is, a value per unit of time. */
-public class Throughput {
- private final static NumberFormat usNumberFormat = NumberFormat.getInstance(Locale.US);
-
- public enum Unit {
- s, m, h, d
- }
-
- private final Double value;
- private final Unit unit;
-
- public Throughput(Double value, Unit unit) {
- this.value = value;
- this.unit = unit;
- }
-
- public Throughput(Long periodMs, Long count, Unit unit) {
- if (unit.equals(Unit.s))
- value = ((double) count * 1000d) / periodMs;
- else if (unit.equals(Unit.m))
- value = ((double) count * 60d * 1000d) / periodMs;
- else if (unit.equals(Unit.h))
- value = ((double) count * 60d * 60d * 1000d) / periodMs;
- else if (unit.equals(Unit.d))
- value = ((double) count * 24d * 60d * 60d * 1000d) / periodMs;
- else
- throw new IllegalArgumentException("Unsupported unit " + unit);
- this.unit = unit;
- }
-
- public Throughput(Double value, String unitStr) {
- this(value, Unit.valueOf(unitStr));
- }
-
- public Throughput(String def) {
- int index = def.indexOf('/');
- if (def.length() < 3 || index <= 0 || index != def.length() - 2)
- throw new IllegalArgumentException(
- def + " no a proper throughput definition" + " (should be <value>/<unit>, e.g. 3.54/s or 1500/h");
- String valueStr = def.substring(0, index);
- String unitStr = def.substring(index + 1);
- try {
- this.value = usNumberFormat.parse(valueStr).doubleValue();
- } catch (ParseException e) {
- throw new IllegalArgumentException("Cannot parse " + valueStr + " as a number.", e);
- }
- this.unit = Unit.valueOf(unitStr);
- }
-
- public Long asMsPeriod() {
- if (unit.equals(Unit.s))
- return Math.round(1000d / value);
- else if (unit.equals(Unit.m))
- return Math.round((60d * 1000d) / value);
- else if (unit.equals(Unit.h))
- return Math.round((60d * 60d * 1000d) / value);
- else if (unit.equals(Unit.d))
- return Math.round((24d * 60d * 60d * 1000d) / value);
- else
- throw new IllegalArgumentException("Unsupported unit " + unit);
- }
-
- @Override
- public String toString() {
- return usNumberFormat.format(value) + '/' + unit;
- }
-
- public Double getValue() {
- return value;
- }
-
- public Unit getUnit() {
- return unit;
- }
-
-}
+++ /dev/null
-package org.argeo.util.naming;
-
-import java.util.Dictionary;
-import java.util.Enumeration;
-
-import javax.naming.NamingEnumeration;
-import javax.naming.NamingException;
-import javax.naming.directory.Attribute;
-import javax.naming.directory.Attributes;
-import javax.naming.directory.BasicAttribute;
-
-public class AttributesDictionary extends Dictionary<String, Object> {
- private final Attributes attributes;
-
- /** The provided attributes is wrapped, not copied. */
- public AttributesDictionary(Attributes attributes) {
- if (attributes == null)
- throw new IllegalArgumentException("Attributes cannot be null");
- this.attributes = attributes;
- }
-
- @Override
- public int size() {
- return attributes.size();
- }
-
- @Override
- public boolean isEmpty() {
- return attributes.size() == 0;
- }
-
- @Override
- public Enumeration<String> keys() {
- NamingEnumeration<String> namingEnumeration = attributes.getIDs();
- return new Enumeration<String>() {
-
- @Override
- public boolean hasMoreElements() {
- return namingEnumeration.hasMoreElements();
- }
-
- @Override
- public String nextElement() {
- return namingEnumeration.nextElement();
- }
-
- };
- }
-
- @Override
- public Enumeration<Object> elements() {
- NamingEnumeration<String> namingEnumeration = attributes.getIDs();
- return new Enumeration<Object>() {
-
- @Override
- public boolean hasMoreElements() {
- return namingEnumeration.hasMoreElements();
- }
-
- @Override
- public Object nextElement() {
- String key = namingEnumeration.nextElement();
- return get(key);
- }
-
- };
- }
-
- @Override
- /** @returns a <code>String</code> or <code>String[]</code> */
- public Object get(Object key) {
- try {
- if (key == null)
- throw new IllegalArgumentException("Key cannot be null");
- Attribute attr = attributes.get(key.toString());
- if (attr == null)
- return null;
- if (attr.size() == 0)
- throw new IllegalStateException("There must be at least one value");
- else if (attr.size() == 1) {
- return attr.get().toString();
- } else {// multiple
- String[] res = new String[attr.size()];
- for (int i = 0; i < attr.size(); i++) {
- Object value = attr.get();
- if (value == null)
- throw new RuntimeException("Values cannot be null");
- res[i] = attr.get(i).toString();
- }
- return res;
- }
- } catch (NamingException e) {
- throw new RuntimeException("Cannot get value for " + key, e);
- }
- }
-
- @Override
- public Object put(String key, Object value) {
- if (key == null)
- throw new IllegalArgumentException("Key cannot be null");
- if (value == null)
- throw new IllegalArgumentException("Value cannot be null");
-
- Object oldValue = get(key);
- Attribute attr = attributes.get(key);
- if (attr == null) {
- attr = new BasicAttribute(key);
- attributes.put(attr);
- }
-
- if (value instanceof String[]) {
- String[] values = (String[]) value;
- // clean additional values
- for (int i = values.length; i < attr.size(); i++)
- attr.remove(i);
- // set values
- for (int i = 0; i < values.length; i++) {
- attr.set(i, values[i]);
- }
- } else {
- if (attr.size() > 1)
- throw new IllegalArgumentException("Attribute " + key + " is multi-valued");
- if (attr.size() == 1) {
- try {
- if (!attr.get(0).equals(value))
- attr.set(0, value.toString());
- } catch (NamingException e) {
- throw new RuntimeException("Cannot check existing value", e);
- }
- } else {
- attr.add(value.toString());
- }
- }
- return oldValue;
- }
-
- @Override
- public Object remove(Object key) {
- if (key == null)
- throw new IllegalArgumentException("Key cannot be null");
- Object oldValue = get(key);
- if (oldValue == null)
- return null;
- return attributes.remove(key.toString());
- }
-
- /**
- * Copy the <b>content</b> of an {@link Attributes} to the provided
- * {@link Dictionary}.
- */
- public static void copy(Attributes attributes, Dictionary<String, Object> dictionary) {
- AttributesDictionary ad = new AttributesDictionary(attributes);
- Enumeration<String> keys = ad.keys();
- while (keys.hasMoreElements()) {
- String key = keys.nextElement();
- dictionary.put(key, ad.get(key));
- }
- }
-
- /**
- * Copy a {@link Dictionary} into an {@link Attributes}.
- */
- public static void copy(Dictionary<String, Object> dictionary, Attributes attributes) {
- AttributesDictionary ad = new AttributesDictionary(attributes);
- Enumeration<String> keys = dictionary.keys();
- while (keys.hasMoreElements()) {
- String key = keys.nextElement();
- ad.put(key, dictionary.get(key));
- }
- }
-}
+++ /dev/null
-package org.argeo.util.naming;
-
-import java.io.IOException;
-import java.util.Arrays;
-import java.util.StringTokenizer;
-
-import javax.naming.NamingEnumeration;
-import javax.naming.NamingException;
-import javax.naming.directory.Attribute;
-import javax.naming.directory.Attributes;
-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.UnsupportedCallbackException;
-
-import org.argeo.osgi.useradmin.UserDirectoryException;
-
-/** LDAP authPassword field according to RFC 3112 */
-public class AuthPassword implements CallbackHandler {
- private final String authScheme;
- private final String authInfo;
- private final String authValue;
-
- public AuthPassword(String value) {
- StringTokenizer st = new StringTokenizer(value, "$");
- // TODO make it more robust, deal with bad formatting
- this.authScheme = st.nextToken().trim();
- this.authInfo = st.nextToken().trim();
- this.authValue = st.nextToken().trim();
-
- String expectedAuthScheme = getExpectedAuthScheme();
- if (expectedAuthScheme != null && !authScheme.equals(expectedAuthScheme))
- throw new IllegalArgumentException(
- "Auth scheme " + authScheme + " is not compatible with " + expectedAuthScheme);
- }
-
- protected AuthPassword(String authInfo, String authValue) {
- this.authScheme = getExpectedAuthScheme();
- if (authScheme == null)
- throw new IllegalArgumentException("Expected auth scheme cannot be null");
- this.authInfo = authInfo;
- this.authValue = authValue;
- }
-
- protected AuthPassword(AuthPassword authPassword) {
- this.authScheme = authPassword.getAuthScheme();
- this.authInfo = authPassword.getAuthInfo();
- this.authValue = authPassword.getAuthValue();
- }
-
- protected String getExpectedAuthScheme() {
- return null;
- }
-
- protected boolean matchAuthValue(Object object) {
- return authValue.equals(object.toString());
- }
-
- @Override
- public boolean equals(Object obj) {
- if (!(obj instanceof AuthPassword))
- return false;
- AuthPassword authPassword = (AuthPassword) obj;
- return authScheme.equals(authPassword.authScheme) && authInfo.equals(authPassword.authInfo)
- && authValue.equals(authValue);
- }
-
- public boolean keyEquals(AuthPassword authPassword) {
- return authScheme.equals(authPassword.authScheme) && authInfo.equals(authPassword.authInfo);
- }
-
- @Override
- public int hashCode() {
- return authValue.hashCode();
- }
-
- @Override
- public String toString() {
- return toAuthPassword();
- }
-
- public final String toAuthPassword() {
- return getAuthScheme() + '$' + authInfo + '$' + authValue;
- }
-
- public String getAuthScheme() {
- return authScheme;
- }
-
- public String getAuthInfo() {
- return authInfo;
- }
-
- public String getAuthValue() {
- return authValue;
- }
-
- public static AuthPassword matchAuthValue(Attributes attributes, char[] value) {
- try {
- Attribute authPassword = attributes.get(LdapAttrs.authPassword.name());
- if (authPassword != null) {
- NamingEnumeration<?> values = authPassword.getAll();
- while (values.hasMore()) {
- Object val = values.next();
- AuthPassword token = new AuthPassword(val.toString());
- String auth;
- if (Arrays.binarySearch(value, '$') >= 0) {
- auth = token.authInfo + '$' + token.authValue;
- } else {
- auth = token.authValue;
- }
- if (Arrays.equals(auth.toCharArray(), value))
- return token;
- // if (token.matchAuthValue(value))
- // return token;
- }
- }
- return null;
- } catch (NamingException e) {
- throw new UserDirectoryException("Cannot check attribute", e);
- }
- }
-
- public static boolean remove(Attributes attributes, AuthPassword value) {
- Attribute authPassword = attributes.get(LdapAttrs.authPassword.name());
- return authPassword.remove(value.toAuthPassword());
- }
-
- @Override
- public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
- for (Callback callback : callbacks) {
- if (callback instanceof NameCallback)
- ((NameCallback) callback).setName(toAuthPassword());
- else if (callback instanceof PasswordCallback)
- ((PasswordCallback) callback).setPassword(getAuthValue().toCharArray());
- }
- }
-
-}
+++ /dev/null
-package org.argeo.util.naming;
-
-import java.util.EnumSet;
-import java.util.Set;
-import java.util.TreeSet;
-
-import javax.naming.InvalidNameException;
-import javax.naming.ldap.LdapName;
-
-/**
- * An object that can be identified with an X.500 distinguished name.
- *
- * @see https://tools.ietf.org/html/rfc1779
- */
-public interface Distinguished {
- /** The related distinguished name. */
- String dn();
-
- /** The related distinguished name as an {@link LdapName}. */
- default LdapName distinguishedName() {
- try {
- return new LdapName(dn());
- } catch (InvalidNameException e) {
- throw new IllegalArgumentException("Distinguished name " + dn() + " is not properly formatted.", e);
- }
- }
-
- /** List all DNs of an enumeration as strings. */
- static Set<String> enumToDns(EnumSet<? extends Distinguished> enumSet) {
- Set<String> res = new TreeSet<>();
- for (Enum<? extends Distinguished> enm : enumSet) {
- res.add(((Distinguished) enm).dn());
- }
- return res;
- }
-}
+++ /dev/null
-package org.argeo.util.naming;
-
-import java.io.Closeable;
-import java.io.IOException;
-import java.io.PrintStream;
-import java.util.ArrayList;
-import java.util.Base64;
-import java.util.Collections;
-import java.util.Hashtable;
-import java.util.List;
-import java.util.Map;
-import java.util.SortedSet;
-import java.util.TreeMap;
-import java.util.TreeSet;
-
-import javax.naming.Binding;
-import javax.naming.NamingEnumeration;
-import javax.naming.NamingException;
-import javax.naming.directory.Attribute;
-import javax.naming.directory.Attributes;
-import javax.naming.directory.DirContext;
-import javax.naming.directory.InitialDirContext;
-
-public class DnsBrowser implements Closeable {
- private final DirContext initialCtx;
-
- public DnsBrowser() throws NamingException {
- this(null);
- }
-
- public DnsBrowser(String dnsServerUrls) throws NamingException {
- Hashtable<String, Object> env = new Hashtable<>();
- env.put("java.naming.factory.initial", "com.sun.jndi.dns.DnsContextFactory");
- if (dnsServerUrls != null)
- env.put("java.naming.provider.url", dnsServerUrls);
- initialCtx = new InitialDirContext(env);
- }
-
- public Map<String, List<String>> getAllRecords(String name) throws NamingException {
- Map<String, List<String>> res = new TreeMap<>();
- Attributes attrs = initialCtx.getAttributes(name);
- NamingEnumeration<String> ids = attrs.getIDs();
- while (ids.hasMore()) {
- String recordType = ids.next();
- List<String> lst = new ArrayList<String>();
- res.put(recordType, lst);
- Attribute attr = attrs.get(recordType);
- addValues(attr, lst);
- }
- return Collections.unmodifiableMap(res);
- }
-
- /**
- * Return a single record (typically A, AAAA, etc. or null if not available.
- * Will fail if multiple records.
- */
- public String getRecord(String name, String recordType) throws NamingException {
- Attributes attrs = initialCtx.getAttributes(name, new String[] { recordType });
- if (attrs.size() == 0)
- return null;
- Attribute attr = attrs.get(recordType);
- if (attr.size() > 1)
- throw new IllegalArgumentException("Multiple record type " + recordType);
- assert attr.size() != 0;
- Object value = attr.get();
- assert value != null;
- return value.toString();
- }
-
- /**
- * Return records of a given type.
- */
- public List<String> getRecords(String name, String recordType) throws NamingException {
- List<String> res = new ArrayList<String>();
- Attributes attrs = initialCtx.getAttributes(name, new String[] { recordType });
- Attribute attr = attrs.get(recordType);
- addValues(attr, res);
- return res;
- }
-
- /** Ordered, with preferred first. */
- public List<String> getSrvRecordsAsHosts(String name, boolean withPort) throws NamingException {
- List<String> raw = getRecords(name, "SRV");
- if (raw.size() == 0)
- return null;
- SortedSet<SrvRecord> res = new TreeSet<>();
- for (int i = 0; i < raw.size(); i++) {
- String record = raw.get(i);
- String[] arr = record.split(" ");
- Integer priority = Integer.parseInt(arr[0]);
- Integer weight = Integer.parseInt(arr[1]);
- Integer port = Integer.parseInt(arr[2]);
- String hostname = arr[3];
- SrvRecord order = new SrvRecord(priority, weight, port, hostname);
- res.add(order);
- }
- List<String> lst = new ArrayList<>();
- for (SrvRecord order : res) {
- lst.add(order.toHost(withPort));
- }
- return Collections.unmodifiableList(lst);
- }
-
- private void addValues(Attribute attr, List<String> lst) throws NamingException {
- NamingEnumeration<?> values = attr.getAll();
- while (values.hasMore()) {
- Object value = values.next();
- if (value != null) {
- if (value instanceof byte[]) {
- String str = Base64.getEncoder().encodeToString((byte[]) value);
- lst.add(str);
- } else
- lst.add(value.toString());
- }
- }
-
- }
-
- public List<String> listEntries(String name) throws NamingException {
- List<String> res = new ArrayList<String>();
- NamingEnumeration<Binding> ne = initialCtx.listBindings(name);
- while (ne.hasMore()) {
- Binding b = ne.next();
- res.add(b.getName());
- }
- return Collections.unmodifiableList(res);
- }
-
- @Override
- public void close() throws IOException {
- destroy();
- }
-
- public void destroy() {
- try {
- initialCtx.close();
- } catch (NamingException e) {
- // silent
- }
- }
-
- public static void main(String[] args) {
- if (args.length == 0) {
- printUsage(System.err);
- System.exit(1);
- }
- try (DnsBrowser dnsBrowser = new DnsBrowser()) {
- String hostname = args[0];
- String recordType = args.length > 1 ? args[1] : "A";
- if (recordType.equals("*")) {
- Map<String, List<String>> records = dnsBrowser.getAllRecords(hostname);
- for (String type : records.keySet()) {
- for (String record : records.get(type)) {
- String typeLabel;
- if ("44".equals(type))
- typeLabel = "SSHFP";
- else if ("46".equals(type))
- typeLabel = "RRSIG";
- else if ("48".equals(type))
- typeLabel = "DNSKEY";
- else
- typeLabel = type;
- System.out.println(typeLabel + "\t" + record);
- }
- }
- } else {
- System.out.println(dnsBrowser.getRecord(hostname, recordType));
- }
-
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
-
- public static void printUsage(PrintStream out) {
- out.println("java org.argeo.naming.DnsBrowser <hostname> [<record type> | *]");
- }
-
-}
\ No newline at end of file
+++ /dev/null
-uid,,,0.9.2342.19200300.100.1.1,,RFC 4519
-mail,,,0.9.2342.19200300.100.1.3,,RFC 4524
-info,,,0.9.2342.19200300.100.1.4,,RFC 4524
-drink,,,0.9.2342.19200300.100.1.5,,RFC 4524
-roomNumber,,,0.9.2342.19200300.100.1.6,,RFC 4524
-photo,,,0.9.2342.19200300.100.1.7,,RFC 2798
-userClass,,,0.9.2342.19200300.100.1.8,,RFC 4524
-host,,,0.9.2342.19200300.100.1.9,,RFC 4524
-manager,,,0.9.2342.19200300.100.1.10,,RFC 4524
-documentIdentifier,,,0.9.2342.19200300.100.1.11,,RFC 4524
-documentTitle,,,0.9.2342.19200300.100.1.12,,RFC 4524
-documentVersion,,,0.9.2342.19200300.100.1.13,,RFC 4524
-documentAuthor,,,0.9.2342.19200300.100.1.14,,RFC 4524
-documentLocation,,,0.9.2342.19200300.100.1.15,,RFC 4524
-homePhone,,,0.9.2342.19200300.100.1.20,,RFC 4524
-secretary,,,0.9.2342.19200300.100.1.21,,RFC 4524
-dc,,,0.9.2342.19200300.100.1.25,,RFC 4519
-associatedDomain,,,0.9.2342.19200300.100.1.37,,RFC 4524
-associatedName,,,0.9.2342.19200300.100.1.38,,RFC 4524
-homePostalAddress,,,0.9.2342.19200300.100.1.39,,RFC 4524
-personalTitle,,,0.9.2342.19200300.100.1.40,,RFC 4524
-mobile,,,0.9.2342.19200300.100.1.41,,RFC 4524
-pager,,,0.9.2342.19200300.100.1.42,,RFC 4524
-co,,,0.9.2342.19200300.100.1.43,,RFC 4524
-uniqueIdentifier,,,0.9.2342.19200300.100.1.44,,RFC 4524
-organizationalStatus,,,0.9.2342.19200300.100.1.45,,RFC 4524
-buildingName,,,0.9.2342.19200300.100.1.48,,RFC 4524
-audio,,,0.9.2342.19200300.100.1.55,,RFC 2798
-documentPublisher,,,0.9.2342.19200300.100.1.56,,RFC 4524
-jpegPhoto,,,0.9.2342.19200300.100.1.60,,RFC 2798
-vendorName,,,1.3.6.1.1.4,,RFC 3045
-vendorVersion,,,1.3.6.1.1.5,,RFC 3045
-entryUUID,,,1.3.6.1.1.16.4,,RFC 4530
-entryDN,,,1.3.6.1.1.20,,RFC 5020
-labeledURI,,,1.3.6.1.4.1.250.1.57,,RFC 2798
-numSubordinates,,,1.3.6.1.4.1.453.16.2.103,,draft-ietf-boreham-numsubordinates
-namingContexts,,,1.3.6.1.4.1.1466.101.120.5,,RFC 4512
-altServer,,,1.3.6.1.4.1.1466.101.120.6,,RFC 4512
-supportedExtension,,,1.3.6.1.4.1.1466.101.120.7,,RFC 4512
-supportedControl,,,1.3.6.1.4.1.1466.101.120.13,,RFC 4512
-supportedSASLMechanisms,,,1.3.6.1.4.1.1466.101.120.14,,RFC 4512
-supportedLDAPVersion,,,1.3.6.1.4.1.1466.101.120.15,,RFC 4512
-ldapSyntaxes,,,1.3.6.1.4.1.1466.101.120.16,,RFC 4512
-supportedAuthPasswordSchemes,,,1.3.6.1.4.1.4203.1.3.3,,RFC 3112
-authPassword,,,1.3.6.1.4.1.4203.1.3.4,,RFC 3112
-supportedFeatures,,,1.3.6.1.4.1.4203.1.3.5,,RFC 4512
-inheritable,,,1.3.6.1.4.1.7628.5.4.1,,draft-ietf-ldup-subentry
-blockInheritance,,,1.3.6.1.4.1.7628.5.4.2,,draft-ietf-ldup-subentry
-objectClass,,,2.5.4.0,,RFC 4512
-aliasedObjectName,,,2.5.4.1,,RFC 4512
-cn,,,2.5.4.3,,RFC 4519
-sn,,,2.5.4.4,,RFC 4519
-serialNumber,,,2.5.4.5,,RFC 4519
-c,,,2.5.4.6,,RFC 4519
-l,,,2.5.4.7,,RFC 4519
-st,,,2.5.4.8,,RFC 4519
-street,,,2.5.4.9,,RFC 4519
-o,,,2.5.4.10,,RFC 4519
-ou,,,2.5.4.11,,RFC 4519
-title,,,2.5.4.12,,RFC 4519
-description,,,2.5.4.13,,RFC 4519
-searchGuide,,,2.5.4.14,,RFC 4519
-businessCategory,,,2.5.4.15,,RFC 4519
-postalAddress,,,2.5.4.16,,RFC 4519
-postalCode,,,2.5.4.17,,RFC 4519
-postOfficeBox,,,2.5.4.18,,RFC 4519
-physicalDeliveryOfficeName,,,2.5.4.19,,RFC 4519
-telephoneNumber,,,2.5.4.20,,RFC 4519
-telexNumber,,,2.5.4.21,,RFC 4519
-teletexTerminalIdentifier,,,2.5.4.22,,RFC 4519
-facsimileTelephoneNumber,,,2.5.4.23,,RFC 4519
-x121Address,,,2.5.4.24,,RFC 4519
-internationalISDNNumber,,,2.5.4.25,,RFC 4519
-registeredAddress,,,2.5.4.26,,RFC 4519
-destinationIndicator,,,2.5.4.27,,RFC 4519
-preferredDeliveryMethod,,,2.5.4.28,,RFC 4519
-member,,,2.5.4.31,,RFC 4519
-owner,,,2.5.4.32,,RFC 4519
-roleOccupant,,,2.5.4.33,,RFC 4519
-seeAlso,,,2.5.4.34,,RFC 4519
-userPassword,,,2.5.4.35,,RFC 4519
-userCertificate,,,2.5.4.36,,RFC 4523
-cACertificate,,,2.5.4.37,,RFC 4523
-authorityRevocationList,,,2.5.4.38,,RFC 4523
-certificateRevocationList,,,2.5.4.39,,RFC 4523
-crossCertificatePair,,,2.5.4.40,,RFC 4523
-name,,,2.5.4.41,,RFC 4519
-givenName,,,2.5.4.42,,RFC 4519
-initials,,,2.5.4.43,,RFC 4519
-generationQualifier,,,2.5.4.44,,RFC 4519
-x500UniqueIdentifier,,,2.5.4.45,,RFC 4519
-dnQualifier,,,2.5.4.46,,RFC 4519
-enhancedSearchGuide,,,2.5.4.47,,RFC 4519
-distinguishedName,,,2.5.4.49,,RFC 4519
-uniqueMember,,,2.5.4.50,,RFC 4519
-houseIdentifier,,,2.5.4.51,,RFC 4519
-supportedAlgorithms,,,2.5.4.52,,RFC 4523
-deltaRevocationList,,,2.5.4.53,,RFC 4523
-createTimestamp,,,2.5.18.1,,RFC 4512
-modifyTimestamp,,,2.5.18.2,,RFC 4512
-creatorsName,,,2.5.18.3,,RFC 4512
-modifiersName,,,2.5.18.4,,RFC 4512
-subschemaSubentry,,,2.5.18.10,,RFC 4512
-dITStructureRules,,,2.5.21.1,,RFC 4512
-dITContentRules,,,2.5.21.2,,RFC 4512
-matchingRules,,,2.5.21.4,,RFC 4512
-attributeTypes,,,2.5.21.5,,RFC 4512
-objectClasses,,,2.5.21.6,,RFC 4512
-nameForms,,,2.5.21.7,,RFC 4512
-matchingRuleUse,,,2.5.21.8,,RFC 4512
-structuralObjectClass,,,2.5.21.9,,RFC 4512
-governingStructureRule,,,2.5.21.10,,RFC 4512
-carLicense,,,2.16.840.1.113730.3.1.1,,RFC 2798
-departmentNumber,,,2.16.840.1.113730.3.1.2,,RFC 2798
-employeeNumber,,,2.16.840.1.113730.3.1.3,,RFC 2798
-employeeType,,,2.16.840.1.113730.3.1.4,,RFC 2798
-changeNumber,,,2.16.840.1.113730.3.1.5,,draft-good-ldap-changelog
-targetDN,,,2.16.840.1.113730.3.1.6,,draft-good-ldap-changelog
-changeType,,,2.16.840.1.113730.3.1.7,,draft-good-ldap-changelog
-changes,,,2.16.840.1.113730.3.1.8,,draft-good-ldap-changelog
-newRDN,,,2.16.840.1.113730.3.1.9,,draft-good-ldap-changelog
-deleteOldRDN,,,2.16.840.1.113730.3.1.10,,draft-good-ldap-changelog
-newSuperior,,,2.16.840.1.113730.3.1.11,,draft-good-ldap-changelog
-ref,,,2.16.840.1.113730.3.1.34,,RFC 3296
-changelog,,,2.16.840.1.113730.3.1.35,,draft-good-ldap-changelog
-preferredLanguage,,,2.16.840.1.113730.3.1.39,,RFC 2798
-userSMIMECertificate,,,2.16.840.1.113730.3.1.40,,RFC 2798
-userPKCS12,,,2.16.840.1.113730.3.1.216,,RFC 2798
-displayName,,,2.16.840.1.113730.3.1.241,,RFC 2798
+++ /dev/null
-package org.argeo.util.naming;
-
-import java.util.function.Supplier;
-
-/**
- * Standard LDAP attributes as per:<br>
- * - <a href= "https://www.ldap.com/ldap-oid-reference">Standard LDAP</a><br>
- * - <a href=
- * "https://github.com/krb5/krb5/blob/master/src/plugins/kdb/ldap/libkdb_ldap/kerberos.schema">Kerberos
- * LDAP (partial)</a>
- */
-public enum LdapAttrs implements SpecifiedName, Supplier<String> {
- /** */
- uid("0.9.2342.19200300.100.1.1", "RFC 4519"),
- /** */
- mail("0.9.2342.19200300.100.1.3", "RFC 4524"),
- /** */
- info("0.9.2342.19200300.100.1.4", "RFC 4524"),
- /** */
- drink("0.9.2342.19200300.100.1.5", "RFC 4524"),
- /** */
- roomNumber("0.9.2342.19200300.100.1.6", "RFC 4524"),
- /** */
- photo("0.9.2342.19200300.100.1.7", "RFC 2798"),
- /** */
- userClass("0.9.2342.19200300.100.1.8", "RFC 4524"),
- /** */
- host("0.9.2342.19200300.100.1.9", "RFC 4524"),
- /** */
- manager("0.9.2342.19200300.100.1.10", "RFC 4524"),
- /** */
- documentIdentifier("0.9.2342.19200300.100.1.11", "RFC 4524"),
- /** */
- documentTitle("0.9.2342.19200300.100.1.12", "RFC 4524"),
- /** */
- documentVersion("0.9.2342.19200300.100.1.13", "RFC 4524"),
- /** */
- documentAuthor("0.9.2342.19200300.100.1.14", "RFC 4524"),
- /** */
- documentLocation("0.9.2342.19200300.100.1.15", "RFC 4524"),
- /** */
- homePhone("0.9.2342.19200300.100.1.20", "RFC 4524"),
- /** */
- secretary("0.9.2342.19200300.100.1.21", "RFC 4524"),
- /** */
- dc("0.9.2342.19200300.100.1.25", "RFC 4519"),
- /** */
- associatedDomain("0.9.2342.19200300.100.1.37", "RFC 4524"),
- /** */
- associatedName("0.9.2342.19200300.100.1.38", "RFC 4524"),
- /** */
- homePostalAddress("0.9.2342.19200300.100.1.39", "RFC 4524"),
- /** */
- personalTitle("0.9.2342.19200300.100.1.40", "RFC 4524"),
- /** */
- mobile("0.9.2342.19200300.100.1.41", "RFC 4524"),
- /** */
- pager("0.9.2342.19200300.100.1.42", "RFC 4524"),
- /** */
- co("0.9.2342.19200300.100.1.43", "RFC 4524"),
- /** */
- uniqueIdentifier("0.9.2342.19200300.100.1.44", "RFC 4524"),
- /** */
- organizationalStatus("0.9.2342.19200300.100.1.45", "RFC 4524"),
- /** */
- buildingName("0.9.2342.19200300.100.1.48", "RFC 4524"),
- /** */
- audio("0.9.2342.19200300.100.1.55", "RFC 2798"),
- /** */
- documentPublisher("0.9.2342.19200300.100.1.56", "RFC 4524"),
- /** */
- jpegPhoto("0.9.2342.19200300.100.1.60", "RFC 2798"),
- /** */
- vendorName("1.3.6.1.1.4", "RFC 3045"),
- /** */
- vendorVersion("1.3.6.1.1.5", "RFC 3045"),
- /** */
- entryUUID("1.3.6.1.1.16.4", "RFC 4530"),
- /** */
- entryDN("1.3.6.1.1.20", "RFC 5020"),
- /** */
- labeledURI("1.3.6.1.4.1.250.1.57", "RFC 2798"),
- /** */
- numSubordinates("1.3.6.1.4.1.453.16.2.103", "draft-ietf-boreham-numsubordinates"),
- /** */
- namingContexts("1.3.6.1.4.1.1466.101.120.5", "RFC 4512"),
- /** */
- altServer("1.3.6.1.4.1.1466.101.120.6", "RFC 4512"),
- /** */
- supportedExtension("1.3.6.1.4.1.1466.101.120.7", "RFC 4512"),
- /** */
- supportedControl("1.3.6.1.4.1.1466.101.120.13", "RFC 4512"),
- /** */
- supportedSASLMechanisms("1.3.6.1.4.1.1466.101.120.14", "RFC 4512"),
- /** */
- supportedLDAPVersion("1.3.6.1.4.1.1466.101.120.15", "RFC 4512"),
- /** */
- ldapSyntaxes("1.3.6.1.4.1.1466.101.120.16", "RFC 4512"),
- /** */
- supportedAuthPasswordSchemes("1.3.6.1.4.1.4203.1.3.3", "RFC 3112"),
- /** */
- authPassword("1.3.6.1.4.1.4203.1.3.4", "RFC 3112"),
- /** */
- supportedFeatures("1.3.6.1.4.1.4203.1.3.5", "RFC 4512"),
- /** */
- inheritable("1.3.6.1.4.1.7628.5.4.1", "draft-ietf-ldup-subentry"),
- /** */
- blockInheritance("1.3.6.1.4.1.7628.5.4.2", "draft-ietf-ldup-subentry"),
- /** */
- objectClass("2.5.4.0", "RFC 4512"),
- /** */
- aliasedObjectName("2.5.4.1", "RFC 4512"),
- /** */
- cn("2.5.4.3", "RFC 4519"),
- /** */
- sn("2.5.4.4", "RFC 4519"),
- /** */
- serialNumber("2.5.4.5", "RFC 4519"),
- /** */
- c("2.5.4.6", "RFC 4519"),
- /** */
- l("2.5.4.7", "RFC 4519"),
- /** */
- st("2.5.4.8", "RFC 4519"),
- /** */
- street("2.5.4.9", "RFC 4519"),
- /** */
- o("2.5.4.10", "RFC 4519"),
- /** */
- ou("2.5.4.11", "RFC 4519"),
- /** */
- title("2.5.4.12", "RFC 4519"),
- /** */
- description("2.5.4.13", "RFC 4519"),
- /** */
- searchGuide("2.5.4.14", "RFC 4519"),
- /** */
- businessCategory("2.5.4.15", "RFC 4519"),
- /** */
- postalAddress("2.5.4.16", "RFC 4519"),
- /** */
- postalCode("2.5.4.17", "RFC 4519"),
- /** */
- postOfficeBox("2.5.4.18", "RFC 4519"),
- /** */
- physicalDeliveryOfficeName("2.5.4.19", "RFC 4519"),
- /** */
- telephoneNumber("2.5.4.20", "RFC 4519"),
- /** */
- telexNumber("2.5.4.21", "RFC 4519"),
- /** */
- teletexTerminalIdentifier("2.5.4.22", "RFC 4519"),
- /** */
- facsimileTelephoneNumber("2.5.4.23", "RFC 4519"),
- /** */
- x121Address("2.5.4.24", "RFC 4519"),
- /** */
- internationalISDNNumber("2.5.4.25", "RFC 4519"),
- /** */
- registeredAddress("2.5.4.26", "RFC 4519"),
- /** */
- destinationIndicator("2.5.4.27", "RFC 4519"),
- /** */
- preferredDeliveryMethod("2.5.4.28", "RFC 4519"),
- /** */
- member("2.5.4.31", "RFC 4519"),
- /** */
- owner("2.5.4.32", "RFC 4519"),
- /** */
- roleOccupant("2.5.4.33", "RFC 4519"),
- /** */
- seeAlso("2.5.4.34", "RFC 4519"),
- /** */
- userPassword("2.5.4.35", "RFC 4519"),
- /** */
- userCertificate("2.5.4.36", "RFC 4523"),
- /** */
- cACertificate("2.5.4.37", "RFC 4523"),
- /** */
- authorityRevocationList("2.5.4.38", "RFC 4523"),
- /** */
- certificateRevocationList("2.5.4.39", "RFC 4523"),
- /** */
- crossCertificatePair("2.5.4.40", "RFC 4523"),
- /** */
- name("2.5.4.41", "RFC 4519"),
- /** */
- givenName("2.5.4.42", "RFC 4519"),
- /** */
- initials("2.5.4.43", "RFC 4519"),
- /** */
- generationQualifier("2.5.4.44", "RFC 4519"),
- /** */
- x500UniqueIdentifier("2.5.4.45", "RFC 4519"),
- /** */
- dnQualifier("2.5.4.46", "RFC 4519"),
- /** */
- enhancedSearchGuide("2.5.4.47", "RFC 4519"),
- /** */
- distinguishedName("2.5.4.49", "RFC 4519"),
- /** */
- uniqueMember("2.5.4.50", "RFC 4519"),
- /** */
- houseIdentifier("2.5.4.51", "RFC 4519"),
- /** */
- supportedAlgorithms("2.5.4.52", "RFC 4523"),
- /** */
- deltaRevocationList("2.5.4.53", "RFC 4523"),
- /** */
- createTimestamp("2.5.18.1", "RFC 4512"),
- /** */
- modifyTimestamp("2.5.18.2", "RFC 4512"),
- /** */
- creatorsName("2.5.18.3", "RFC 4512"),
- /** */
- modifiersName("2.5.18.4", "RFC 4512"),
- /** */
- subschemaSubentry("2.5.18.10", "RFC 4512"),
- /** */
- dITStructureRules("2.5.21.1", "RFC 4512"),
- /** */
- dITContentRules("2.5.21.2", "RFC 4512"),
- /** */
- matchingRules("2.5.21.4", "RFC 4512"),
- /** */
- attributeTypes("2.5.21.5", "RFC 4512"),
- /** */
- objectClasses("2.5.21.6", "RFC 4512"),
- /** */
- nameForms("2.5.21.7", "RFC 4512"),
- /** */
- matchingRuleUse("2.5.21.8", "RFC 4512"),
- /** */
- structuralObjectClass("2.5.21.9", "RFC 4512"),
- /** */
- governingStructureRule("2.5.21.10", "RFC 4512"),
- /** */
- carLicense("2.16.840.1.113730.3.1.1", "RFC 2798"),
- /** */
- departmentNumber("2.16.840.1.113730.3.1.2", "RFC 2798"),
- /** */
- employeeNumber("2.16.840.1.113730.3.1.3", "RFC 2798"),
- /** */
- employeeType("2.16.840.1.113730.3.1.4", "RFC 2798"),
- /** */
- changeNumber("2.16.840.1.113730.3.1.5", "draft-good-ldap-changelog"),
- /** */
- targetDN("2.16.840.1.113730.3.1.6", "draft-good-ldap-changelog"),
- /** */
- changeType("2.16.840.1.113730.3.1.7", "draft-good-ldap-changelog"),
- /** */
- changes("2.16.840.1.113730.3.1.8", "draft-good-ldap-changelog"),
- /** */
- newRDN("2.16.840.1.113730.3.1.9", "draft-good-ldap-changelog"),
- /** */
- deleteOldRDN("2.16.840.1.113730.3.1.10", "draft-good-ldap-changelog"),
- /** */
- newSuperior("2.16.840.1.113730.3.1.11", "draft-good-ldap-changelog"),
- /** */
- ref("2.16.840.1.113730.3.1.34", "RFC 3296"),
- /** */
- changelog("2.16.840.1.113730.3.1.35", "draft-good-ldap-changelog"),
- /** */
- preferredLanguage("2.16.840.1.113730.3.1.39", "RFC 2798"),
- /** */
- userSMIMECertificate("2.16.840.1.113730.3.1.40", "RFC 2798"),
- /** */
- userPKCS12("2.16.840.1.113730.3.1.216", "RFC 2798"),
- /** */
- displayName("2.16.840.1.113730.3.1.241", "RFC 2798"),
-
- // Sun memberOf
- memberOf("1.2.840.113556.1.2.102", "389 DS memberOf"),
-
- // KERBEROS (partial)
- krbPrincipalName("2.16.840.1.113719.1.301.6.8.1", "Novell Kerberos Schema Definitions"),
-
- // RFC 2985 and RFC 3039 (partial)
- dateOfBirth("1.3.6.1.5.5.7.9.1", "RFC 2985"),
- /** */
- placeOfBirth("1.3.6.1.5.5.7.9.2", "RFC 2985"),
- /** */
- gender("1.3.6.1.5.5.7.9.3", "RFC 2985"),
- /** */
- countryOfCitizenship("1.3.6.1.5.5.7.9.4", "RFC 2985"),
- /** */
- countryOfResidence("1.3.6.1.5.5.7.9.5", "RFC 2985"),
- //
- ;
-
- public final static String DN = "dn";
-
-// private final static String LDAP_ = "ldap:";
-
- private final String oid, spec;
-
- LdapAttrs(String oid, String spec) {
- this.oid = oid;
- this.spec = spec;
- }
-
- @Override
- public String getID() {
- return oid;
- }
-
- @Override
- public String getSpec() {
- return spec;
- }
-
- public String getPrefix() {
- return prefix();
- }
-
- public static String prefix() {
- return "ldap";
- }
-
- @Deprecated
- public String property() {
- return get();
- }
-
- @Deprecated
- public String qualified() {
- return get();
- }
-
- public String get() {
- String prefix = getPrefix();
- return prefix != null ? prefix + ":" + name() : name();
- }
-
- public String getNamespace() {
- return namespace();
- }
-
- public static String namespace() {
- return "http://www.argeo.org/ns/ldap";
- }
-
- @Override
- public final String toString() {
- // must return the name
- return name();
- }
-
-}
+++ /dev/null
-account,,,0.9.2342.19200300.100.4.5,,RFC 4524
-document,,,0.9.2342.19200300.100.4.6,,RFC 4524
-room,,,0.9.2342.19200300.100.4.7,,RFC 4524
-documentSeries,,,0.9.2342.19200300.100.4.9,,RFC 4524
-domain,,,0.9.2342.19200300.100.4.13,,RFC 4524
-rFC822localPart,,,0.9.2342.19200300.100.4.14,,RFC 4524
-domainRelatedObject,,,0.9.2342.19200300.100.4.17,,RFC 4524
-friendlyCountry,,,0.9.2342.19200300.100.4.18,,RFC 4524
-simpleSecurityObject,,,0.9.2342.19200300.100.4.19,,RFC 4524
-uidObject,,,1.3.6.1.1.3.1,,RFC 4519
-extensibleObject,,,1.3.6.1.4.1.1466.101.120.111,,RFC 4512
-dcObject,,,1.3.6.1.4.1.1466.344,,RFC 4519
-authPasswordObject,,,1.3.6.1.4.1.4203.1.4.7,,RFC 3112
-namedObject,,,1.3.6.1.4.1.5322.13.1.1,,draft-howard-namedobject
-inheritableLDAPSubEntry,,,1.3.6.1.4.1.7628.5.6.1.1,,draft-ietf-ldup-subentry
-top,,,2.5.6.0,,RFC 4512
-alias,,,2.5.6.1,,RFC 4512
-country,,,2.5.6.2,,RFC 4519
-locality,,,2.5.6.3,,RFC 4519
-organization,,,2.5.6.4,,RFC 4519
-organizationalUnit,,,2.5.6.5,,RFC 4519
-person,,,2.5.6.6,,RFC 4519
-organizationalPerson,,,2.5.6.7,,RFC 4519
-organizationalRole,,,2.5.6.8,,RFC 4519
-groupOfNames,,,2.5.6.9,,RFC 4519
-residentialPerson,,,2.5.6.10,,RFC 4519
-applicationProcess,,,2.5.6.11,,RFC 4519
-device,,,2.5.6.14,,RFC 4519
-strongAuthenticationUser,,,2.5.6.15,,RFC 4523
-certificationAuthority,,,2.5.6.16,,RFC 4523
-certificationAuthority-V2,,,2.5.6.16.2,,RFC 4523
-groupOfUniqueNames,,,2.5.6.17,,RFC 4519
-userSecurityInformation,,,2.5.6.18,,RFC 4523
-cRLDistributionPoint,,,2.5.6.19,,RFC 4523
-pkiUser,,,2.5.6.21,,RFC 4523
-pkiCA,,,2.5.6.22,,RFC 4523
-deltaCRL,,,2.5.6.23,,RFC 4523
-subschema,,,2.5.20.1,,RFC 4512
-ldapSubEntry,,,2.16.840.1.113719.2.142.6.1.1,,draft-ietf-ldup-subentry
-changeLogEntry,,,2.16.840.1.113730.3.2.1,,draft-good-ldap-changelog
-inetOrgPerson,,,2.16.840.1.113730.3.2.2,,RFC 2798
-referral,,,2.16.840.1.113730.3.2.6,,RFC 3296
+++ /dev/null
-package org.argeo.util.naming;
-
-/**
- * Standard LDAP object classes as per
- * <a href="https://www.ldap.com/ldap-oid-reference">https://www.ldap.com/ldap-
- * oid-reference</a>
- */
-public enum LdapObjs implements SpecifiedName {
- account("0.9.2342.19200300.100.4.5", "RFC 4524"),
- /** */
- document("0.9.2342.19200300.100.4.6", "RFC 4524"),
- /** */
- room("0.9.2342.19200300.100.4.7", "RFC 4524"),
- /** */
- documentSeries("0.9.2342.19200300.100.4.9", "RFC 4524"),
- /** */
- domain("0.9.2342.19200300.100.4.13", "RFC 4524"),
- /** */
- rFC822localPart("0.9.2342.19200300.100.4.14", "RFC 4524"),
- /** */
- domainRelatedObject("0.9.2342.19200300.100.4.17", "RFC 4524"),
- /** */
- friendlyCountry("0.9.2342.19200300.100.4.18", "RFC 4524"),
- /** */
- simpleSecurityObject("0.9.2342.19200300.100.4.19", "RFC 4524"),
- /** */
- uidObject("1.3.6.1.1.3.1", "RFC 4519"),
- /** */
- extensibleObject("1.3.6.1.4.1.1466.101.120.111", "RFC 4512"),
- /** */
- dcObject("1.3.6.1.4.1.1466.344", "RFC 4519"),
- /** */
- authPasswordObject("1.3.6.1.4.1.4203.1.4.7", "RFC 3112"),
- /** */
- namedObject("1.3.6.1.4.1.5322.13.1.1", "draft-howard-namedobject"),
- /** */
- inheritableLDAPSubEntry("1.3.6.1.4.1.7628.5.6.1.1", "draft-ietf-ldup-subentry"),
- /** */
- top("2.5.6.0", "RFC 4512"),
- /** */
- alias("2.5.6.1", "RFC 4512"),
- /** */
- country("2.5.6.2", "RFC 4519"),
- /** */
- locality("2.5.6.3", "RFC 4519"),
- /** */
- organization("2.5.6.4", "RFC 4519"),
- /** */
- organizationalUnit("2.5.6.5", "RFC 4519"),
- /** */
- person("2.5.6.6", "RFC 4519"),
- /** */
- organizationalPerson("2.5.6.7", "RFC 4519"),
- /** */
- organizationalRole("2.5.6.8", "RFC 4519"),
- /** */
- groupOfNames("2.5.6.9", "RFC 4519"),
- /** */
- residentialPerson("2.5.6.10", "RFC 4519"),
- /** */
- applicationProcess("2.5.6.11", "RFC 4519"),
- /** */
- device("2.5.6.14", "RFC 4519"),
- /** */
- strongAuthenticationUser("2.5.6.15", "RFC 4523"),
- /** */
- certificationAuthority("2.5.6.16", "RFC 4523"),
- // /** Should be certificationAuthority-V2 */
- // certificationAuthority_V2("2.5.6.16.2", "RFC 4523") {
- // },
- /** */
- groupOfUniqueNames("2.5.6.17", "RFC 4519"),
- /** */
- userSecurityInformation("2.5.6.18", "RFC 4523"),
- /** */
- cRLDistributionPoint("2.5.6.19", "RFC 4523"),
- /** */
- pkiUser("2.5.6.21", "RFC 4523"),
- /** */
- pkiCA("2.5.6.22", "RFC 4523"),
- /** */
- deltaCRL("2.5.6.23", "RFC 4523"),
- /** */
- subschema("2.5.20.1", "RFC 4512"),
- /** */
- ldapSubEntry("2.16.840.1.113719.2.142.6.1.1", "draft-ietf-ldup-subentry"),
- /** */
- changeLogEntry("2.16.840.1.113730.3.2.1", "draft-good-ldap-changelog"),
- /** */
- inetOrgPerson("2.16.840.1.113730.3.2.2", "RFC 2798"),
- /** */
- referral("2.16.840.1.113730.3.2.6", "RFC 3296");
-
- private final static String LDAP_ = "ldap:";
- private final String oid, spec;
-
- private LdapObjs(String oid, String spec) {
- this.oid = oid;
- this.spec = spec;
- }
-
- public String getOid() {
- return oid;
- }
-
- public String getSpec() {
- return spec;
- }
-
- public String property() {
- return new StringBuilder(LDAP_).append(name()).toString();
- }
-
-}
+++ /dev/null
-package org.argeo.util.naming;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.Reader;
-import java.nio.charset.Charset;
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.Base64;
-import java.util.List;
-import java.util.SortedMap;
-import java.util.TreeMap;
-
-import javax.naming.InvalidNameException;
-import javax.naming.NamingException;
-import javax.naming.directory.Attribute;
-import javax.naming.directory.Attributes;
-import javax.naming.directory.BasicAttribute;
-import javax.naming.directory.BasicAttributes;
-import javax.naming.ldap.LdapName;
-import javax.naming.ldap.Rdn;
-
-import org.argeo.osgi.useradmin.UserDirectoryException;
-
-/** Basic LDIF parser. */
-public class LdifParser {
- private final static Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
-
- protected Attributes addAttributes(SortedMap<LdapName, Attributes> res, int lineNumber, LdapName currentDn,
- Attributes currentAttributes) {
- try {
- Rdn nameRdn = currentDn.getRdn(currentDn.size() - 1);
- Attribute nameAttr = currentAttributes.get(nameRdn.getType());
- if (nameAttr == null)
- currentAttributes.put(nameRdn.getType(), nameRdn.getValue());
- else if (!nameAttr.get().equals(nameRdn.getValue()))
- throw new UserDirectoryException(
- "Attribute " + nameAttr.getID() + "=" + nameAttr.get() + " not consistent with DN " + currentDn
- + " (shortly before line " + lineNumber + " in LDIF file)");
- Attributes previous = res.put(currentDn, currentAttributes);
- return previous;
- } catch (NamingException e) {
- throw new UserDirectoryException("Cannot add " + currentDn, e);
- }
- }
-
- /** With UTF-8 charset */
- public SortedMap<LdapName, Attributes> read(InputStream in) throws IOException {
- try (Reader reader = new InputStreamReader(in, DEFAULT_CHARSET)) {
- return read(reader);
- } finally {
- try {
- in.close();
- } catch (IOException e) {
- // silent
- }
- }
- }
-
- /** Will close the reader. */
- public SortedMap<LdapName, Attributes> read(Reader reader) throws IOException {
- SortedMap<LdapName, Attributes> res = new TreeMap<LdapName, Attributes>();
- try {
- List<String> lines = new ArrayList<>();
- try (BufferedReader br = new BufferedReader(reader)) {
- String line;
- while ((line = br.readLine()) != null) {
- lines.add(line);
- }
- }
- if (lines.size() == 0)
- return res;
- // add an empty new line since the last line is not checked
- if (!lines.get(lines.size() - 1).equals(""))
- lines.add("");
-
- LdapName currentDn = null;
- Attributes currentAttributes = null;
- StringBuilder currentEntry = new StringBuilder();
-
- readLines: for (int lineNumber = 0; lineNumber < lines.size(); lineNumber++) {
- String line = lines.get(lineNumber);
- boolean isLastLine = false;
- if (lineNumber == lines.size() - 1)
- isLastLine = true;
- if (line.startsWith(" ")) {
- currentEntry.append(line.substring(1));
- if (!isLastLine)
- continue readLines;
- }
-
- if (currentEntry.length() != 0 || isLastLine) {
- // read previous attribute
- StringBuilder attrId = new StringBuilder(8);
- boolean isBase64 = false;
- readAttrId: for (int i = 0; i < currentEntry.length(); i++) {
- char c = currentEntry.charAt(i);
- if (c == ':') {
- if (i + 1 < currentEntry.length() && currentEntry.charAt(i + 1) == ':')
- isBase64 = true;
- currentEntry.delete(0, i + (isBase64 ? 2 : 1));
- break readAttrId;
- } else {
- attrId.append(c);
- }
- }
-
- String attributeId = attrId.toString();
- // TODO should we really trim the end of the string as well?
- String cleanValueStr = currentEntry.toString().trim();
- Object attributeValue = isBase64 ? Base64.getDecoder().decode(cleanValueStr) : cleanValueStr;
-
- // manage DN attributes
- if (attributeId.equals(LdapAttrs.DN) || isLastLine) {
- if (currentDn != null) {
- //
- // ADD
- //
- Attributes previous = addAttributes(res, lineNumber, currentDn, currentAttributes);
- if (previous != null) {
-// log.warn("There was already an entry with DN " + currentDn
-// + ", which has been discarded by a subsequent one.");
- }
- }
-
- if (attributeId.equals(LdapAttrs.DN))
- try {
- currentDn = new LdapName(attributeValue.toString());
- currentAttributes = new BasicAttributes(true);
- } catch (InvalidNameException e) {
-// log.error(attributeValue + " not a valid DN, skipping the entry.");
- currentDn = null;
- currentAttributes = null;
- }
- }
-
- // store attribute
- if (currentAttributes != null) {
- Attribute attribute = currentAttributes.get(attributeId);
- if (attribute == null) {
- attribute = new BasicAttribute(attributeId);
- currentAttributes.put(attribute);
- }
- attribute.add(attributeValue);
- }
- currentEntry = new StringBuilder();
- }
- currentEntry.append(line);
- }
- } finally {
- try {
- reader.close();
- } catch (IOException e) {
- // silent
- }
- }
- return res;
- }
-}
\ No newline at end of file
+++ /dev/null
-package org.argeo.util.naming;
-
-import static org.argeo.util.naming.LdapAttrs.DN;
-import static org.argeo.util.naming.LdapAttrs.member;
-import static org.argeo.util.naming.LdapAttrs.objectClass;
-import static org.argeo.util.naming.LdapAttrs.uniqueMember;
-
-import java.io.IOException;
-import java.io.OutputStream;
-import java.io.OutputStreamWriter;
-import java.io.Writer;
-import java.nio.charset.Charset;
-import java.nio.charset.StandardCharsets;
-import java.util.Base64;
-import java.util.Map;
-import java.util.SortedSet;
-import java.util.TreeSet;
-
-import javax.naming.NamingEnumeration;
-import javax.naming.NamingException;
-import javax.naming.directory.Attribute;
-import javax.naming.directory.Attributes;
-import javax.naming.ldap.LdapName;
-import javax.naming.ldap.Rdn;
-
-import org.argeo.osgi.useradmin.UserDirectoryException;
-
-/** Basic LDIF writer */
-public class LdifWriter {
- private final static Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
- private final Writer writer;
-
- /** Writer must be closed by caller */
- public LdifWriter(Writer writer) {
- this.writer = writer;
- }
-
- /** Stream must be closed by caller */
- public LdifWriter(OutputStream out) {
- this(new OutputStreamWriter(out, DEFAULT_CHARSET));
- }
-
- public void writeEntry(LdapName name, Attributes attributes) throws IOException {
- try {
- // check consistency
- Rdn nameRdn = name.getRdn(name.size() - 1);
- Attribute nameAttr = attributes.get(nameRdn.getType());
- if (!nameAttr.get().equals(nameRdn.getValue()))
- throw new UserDirectoryException(
- "Attribute " + nameAttr.getID() + "=" + nameAttr.get() + " not consistent with DN " + name);
-
- writer.append(DN + ": ").append(name.toString()).append('\n');
- Attribute objectClassAttr = attributes.get(objectClass.name());
- if (objectClassAttr != null)
- writeAttribute(objectClassAttr);
- attributes: for (NamingEnumeration<? extends Attribute> attrs = attributes.getAll(); attrs.hasMore();) {
- Attribute attribute = attrs.next();
- if (attribute.getID().equals(DN) || attribute.getID().equals(objectClass.name()))
- continue attributes;// skip DN attribute
- if (attribute.getID().equals(member.name()) || attribute.getID().equals(uniqueMember.name()))
- continue attributes;// skip member and uniqueMember attributes, so that they are always written last
- writeAttribute(attribute);
- }
- // write member and uniqueMember attributes last
- for (NamingEnumeration<? extends Attribute> attrs = attributes.getAll(); attrs.hasMore();) {
- Attribute attribute = attrs.next();
- if (attribute.getID().equals(member.name()) || attribute.getID().equals(uniqueMember.name()))
- writeMemberAttribute(attribute);
- }
- writer.append('\n');
- writer.flush();
- } catch (NamingException e) {
- throw new UserDirectoryException("Cannot write LDIF", e);
- }
- }
-
- public void write(Map<LdapName, Attributes> entries) throws IOException {
- for (LdapName dn : entries.keySet())
- writeEntry(dn, entries.get(dn));
- }
-
- protected void writeAttribute(Attribute attribute) throws NamingException, IOException {
- for (NamingEnumeration<?> attrValues = attribute.getAll(); attrValues.hasMore();) {
- Object value = attrValues.next();
- if (value instanceof byte[]) {
- String encoded = Base64.getEncoder().encodeToString((byte[]) value);
- writer.append(attribute.getID()).append(":: ").append(encoded).append('\n');
- } else {
- writer.append(attribute.getID()).append(": ").append(value.toString()).append('\n');
- }
- }
- }
-
- protected void writeMemberAttribute(Attribute attribute) throws NamingException, IOException {
- // Note: duplicate entries will be swallowed
- SortedSet<String> values = new TreeSet<>();
- for (NamingEnumeration<?> attrValues = attribute.getAll(); attrValues.hasMore();) {
- String value = attrValues.next().toString();
- values.add(value);
- }
-
- for (String value : values) {
- writer.append(attribute.getID()).append(": ").append(value).append('\n');
- }
- }
-}
+++ /dev/null
-package org.argeo.util.naming;
-
-import java.io.UnsupportedEncodingException;
-import java.net.URI;
-import java.net.URLDecoder;
-import java.nio.charset.StandardCharsets;
-import java.time.Instant;
-import java.time.OffsetDateTime;
-import java.time.ZoneOffset;
-import java.time.ZonedDateTime;
-import java.time.format.DateTimeFormatter;
-import java.time.format.DateTimeParseException;
-import java.time.temporal.ChronoField;
-import java.util.Calendar;
-import java.util.GregorianCalendar;
-import java.util.LinkedHashMap;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-
-public class NamingUtils {
- /** As per https://tools.ietf.org/html/rfc4517#section-3.3.13 */
- private final static DateTimeFormatter utcLdapDate = DateTimeFormatter.ofPattern("uuuuMMddHHmmssX")
- .withZone(ZoneOffset.UTC);
-
- /** @return null if not parseable */
- public static Instant ldapDateToInstant(String ldapDate) {
- try {
- return OffsetDateTime.parse(ldapDate, utcLdapDate).toInstant();
- } catch (DateTimeParseException e) {
- return null;
- }
- }
-
- /** @return null if not parseable */
- public static ZonedDateTime ldapDateToZonedDateTime(String ldapDate) {
- try {
- return OffsetDateTime.parse(ldapDate, utcLdapDate).toZonedDateTime();
- } catch (DateTimeParseException e) {
- return null;
- }
- }
-
- public static Calendar ldapDateToCalendar(String ldapDate) {
- OffsetDateTime instant = OffsetDateTime.parse(ldapDate, utcLdapDate);
- GregorianCalendar calendar = new GregorianCalendar();
- calendar.set(Calendar.DAY_OF_MONTH, instant.get(ChronoField.DAY_OF_MONTH));
- calendar.set(Calendar.MONTH, instant.get(ChronoField.MONTH_OF_YEAR));
- calendar.set(Calendar.YEAR, instant.get(ChronoField.YEAR));
- return calendar;
- }
-
- public static String instantToLdapDate(ZonedDateTime instant) {
- return utcLdapDate.format(instant.withZoneSameInstant(ZoneOffset.UTC));
- }
-
- public static String getQueryValue(Map<String, List<String>> query, String key) {
- if (!query.containsKey(key))
- return null;
- List<String> val = query.get(key);
- if (val.size() == 1)
- return val.get(0);
- else
- throw new IllegalArgumentException("There are " + val.size() + " value(s) for " + key);
- }
-
- public static Map<String, List<String>> queryToMap(URI uri) {
- return queryToMap(uri.getQuery());
- }
-
- private static Map<String, List<String>> queryToMap(String queryPart) {
- try {
- final Map<String, List<String>> query_pairs = new LinkedHashMap<String, List<String>>();
- if (queryPart == null)
- return query_pairs;
- final String[] pairs = queryPart.split("&");
- for (String pair : pairs) {
- final int idx = pair.indexOf("=");
- final String key = idx > 0 ? URLDecoder.decode(pair.substring(0, idx), StandardCharsets.UTF_8.name())
- : pair;
- if (!query_pairs.containsKey(key)) {
- query_pairs.put(key, new LinkedList<String>());
- }
- final String value = idx > 0 && pair.length() > idx + 1
- ? URLDecoder.decode(pair.substring(idx + 1), StandardCharsets.UTF_8.name())
- : null;
- query_pairs.get(key).add(value);
- }
- return query_pairs;
- } catch (UnsupportedEncodingException e) {
- throw new IllegalArgumentException("Cannot convert " + queryPart + " to map", e);
- }
- }
-
- private NamingUtils() {
-
- }
-
- public static void main(String args[]) {
- ZonedDateTime now = ZonedDateTime.now().withZoneSameInstant(ZoneOffset.UTC);
- String str = utcLdapDate.format(now);
- System.out.println(str);
- utcLdapDate.parse(str);
- utcLdapDate.parse("19520512000000Z");
- }
-}
+++ /dev/null
-package org.argeo.util.naming;
-
-interface NodeOID {
- String BASE = "1.3.6.1.4.1" + ".48308" + ".1";
-
- // uuidgen --md5 --namespace @oid --name 1.3.6.1.4.1.48308
- String BASE_UUID_V3 = "6869e86b-96b7-3d49-b6ab-ffffc5ad95fb";
-
- // uuidgen --sha1 --namespace @oid --name 1.3.6.1.4.1.48308
- String BASE_UUID_V5 = "58873947-460c-59a6-a7b4-28a97def5f27";
-
- // ATTRIBUTE TYPES
- String ATTRIBUTE_TYPES = BASE + ".4";
-
- // OBJECT CLASSES
- String OBJECT_CLASSES = BASE + ".6";
-}
+++ /dev/null
-package org.argeo.util.naming;
-
-import java.time.Instant;
-import java.time.ZoneOffset;
-import java.time.ZonedDateTime;
-
-public class SharedSecret extends AuthPassword {
- public final static String X_SHARED_SECRET = "X-SharedSecret";
- private final Instant expiry;
-
- public SharedSecret(String authInfo, String authValue) {
- super(authInfo, authValue);
- expiry = null;
- }
-
- public SharedSecret(AuthPassword authPassword) {
- super(authPassword);
- String authInfo = getAuthInfo();
- if (authInfo.length() == 16) {
- expiry = NamingUtils.ldapDateToInstant(authInfo);
- } else {
- expiry = null;
- }
- }
-
- public SharedSecret(ZonedDateTime expiryTimestamp, String value) {
- super(NamingUtils.instantToLdapDate(expiryTimestamp), value);
- expiry = expiryTimestamp.withZoneSameInstant(ZoneOffset.UTC).toInstant();
- }
-
- public SharedSecret(int hours, String value) {
- this(ZonedDateTime.now().plusHours(hours), value);
- }
-
- @Override
- protected String getExpectedAuthScheme() {
- return X_SHARED_SECRET;
- }
-
- public boolean isExpired() {
- if (expiry == null)
- return false;
- return expiry.isBefore(Instant.now());
- }
-
-}
+++ /dev/null
-package org.argeo.util.naming;
-
-/**
- * A name which has been specified and for which an id has been defined
- * (typically an OID).
- */
-public interface SpecifiedName {
- /** The name */
- String name();
-
- /** An RFC or the URLof some specification */
- default String getSpec() {
- return null;
- }
-
- /** Typically an OID */
- default String getID() {
- return getClass().getName() + "." + name();
- }
-}
+++ /dev/null
-package org.argeo.util.naming;
-
-class SrvRecord implements Comparable<SrvRecord> {
- private final Integer priority;
- private final Integer weight;
- private final Integer port;
- private final String hostname;
-
- public SrvRecord(Integer priority, Integer weight, Integer port, String hostname) {
- this.priority = priority;
- this.weight = weight;
- this.port = port;
- this.hostname = hostname;
- }
-
- @Override
- public int compareTo(SrvRecord other) {
- // https: // en.wikipedia.org/wiki/SRV_record
- if (priority != other.priority)
- return priority - other.priority;
- if (weight != other.weight)
- return other.weight - other.weight;
- String host = toHost(false);
- String otherHost = other.toHost(false);
- if (host.length() == otherHost.length())
- return host.compareTo(otherHost);
- else
- return host.length() - otherHost.length();
- }
-
- @Override
- public boolean equals(Object obj) {
- if (obj instanceof SrvRecord) {
- SrvRecord other = (SrvRecord) obj;
- return priority == other.priority && weight == other.weight && port == other.port
- && hostname.equals(other.hostname);
- }
- return false;
- }
-
- @Override
- public String toString() {
- return priority + " " + weight;
- }
-
- public String toHost(boolean withPort) {
- String hostStr = hostname;
- if (hostname.charAt(hostname.length() - 1) == '.')
- hostStr = hostname.substring(0, hostname.length() - 1);
- return hostStr + (withPort ? ":" + port : "");
- }
-}
+++ /dev/null
-/** Generic naming and LDAP support. */
-package org.argeo.util.naming;
\ No newline at end of file
+++ /dev/null
-/** Generic Java utilities. */
-package org.argeo.util;
\ No newline at end of file
+++ /dev/null
-package org.argeo.util.register;
-
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.CompletableFuture;
-import java.util.function.Consumer;
-
-/**
- * A wrapper for an object, whose dependencies and life cycle can be managed.
- */
-public class Component<I> {
-
- private final I instance;
-
- private final Runnable init;
- private final Runnable close;
-
- private final Map<Class<? super I>, PublishedType<? super I>> types;
- private final Set<Dependency<?>> dependencies;
-
- private CompletableFuture<Void> activationStarted = null;
- private CompletableFuture<Void> activated = null;
-
- private CompletableFuture<Void> deactivationStarted = null;
- private CompletableFuture<Void> deactivated = null;
-
- private Set<Dependency<?>> dependants = new HashSet<>();
-
- Component(Consumer<Component<?>> register, I instance, Runnable init, Runnable close,
- Set<Dependency<?>> dependencies, Set<Class<? super I>> classes) {
- assert instance != null;
- assert init != null;
- assert close != null;
- assert dependencies != null;
- assert classes != null;
-
- this.instance = instance;
- this.init = init;
- this.close = close;
-
- // types
- Map<Class<? super I>, PublishedType<? super I>> types = new HashMap<>(classes.size());
- for (Class<? super I> clss : classes) {
-// if (!clss.isAssignableFrom(instance.getClass()))
-// throw new IllegalArgumentException(
-// "Type " + clss.getName() + " is not compatible with " + instance.getClass().getName());
- types.put(clss, new PublishedType<>(this, clss));
- }
- this.types = Collections.unmodifiableMap(types);
-
- // dependencies
- this.dependencies = Collections.unmodifiableSet(new HashSet<>(dependencies));
- for (Dependency<?> dependency : this.dependencies) {
- dependency.setDependantComponent(this);
- }
-
- // deactivated by default
- deactivated = CompletableFuture.completedFuture(null);
- deactivationStarted = CompletableFuture.completedFuture(null);
-
- // TODO check whether context is active, so that we start right away
- prepareNextActivation();
-
- register.accept(this);
- }
-
- private void prepareNextActivation() {
- activationStarted = new CompletableFuture<Void>();
- activated = activationStarted //
- .thenComposeAsync(this::dependenciesActivated) //
- .thenRun(this.init) //
- .thenRun(() -> prepareNextDeactivation());
- }
-
- private void prepareNextDeactivation() {
- deactivationStarted = new CompletableFuture<Void>();
- deactivated = deactivationStarted //
- .thenComposeAsync(this::dependantsDeactivated) //
- .thenRun(this.close) //
- .thenRun(() -> prepareNextActivation());
- }
-
- public CompletableFuture<Void> getActivated() {
- return activated;
- }
-
- public CompletableFuture<Void> getDeactivated() {
- return deactivated;
- }
-
- void startActivating() {
- if (activated.isDone() || activationStarted.isDone())
- return;
- activationStarted.complete(null);
- }
-
- void startDeactivating() {
- if (deactivated.isDone() || deactivationStarted.isDone())
- return;
- deactivationStarted.complete(null);
- }
-
- CompletableFuture<Void> dependenciesActivated(Void v) {
- Set<CompletableFuture<?>> constraints = new HashSet<>(this.dependencies.size());
- for (Dependency<?> dependency : this.dependencies) {
- CompletableFuture<Void> dependencyActivated = dependency.publisherActivated() //
- .thenCompose(dependency::set);
- constraints.add(dependencyActivated);
- }
- return CompletableFuture.allOf(constraints.toArray(new CompletableFuture[constraints.size()]));
- }
-
- CompletableFuture<Void> dependantsDeactivated(Void v) {
- Set<CompletableFuture<?>> constraints = new HashSet<>(this.dependants.size());
- for (Dependency<?> dependant : this.dependants) {
- CompletableFuture<Void> dependantDeactivated = dependant.dependantDeactivated() //
- .thenCompose(dependant::unset);
- constraints.add(dependantDeactivated);
- }
- CompletableFuture<Void> dependantsDeactivated = CompletableFuture
- .allOf(constraints.toArray(new CompletableFuture[constraints.size()]));
- return dependantsDeactivated;
-
- }
-
- void addDependant(Dependency<?> dependant) {
- dependants.add(dependant);
- }
-
- I getInstance() {
- return instance;
- }
-
- @SuppressWarnings("unchecked")
- <T> PublishedType<T> getType(Class<T> clss) {
- if (!types.containsKey(clss))
- throw new IllegalArgumentException(clss.getName() + " is not a type published by this component");
- return (PublishedType<T>) types.get(clss);
- }
-
- <T> boolean isPublishedType(Class<T> clss) {
- return types.containsKey(clss);
- }
-
- public static class PublishedType<T> {
- private Component<? extends T> component;
- private Class<T> clss;
-
-// private CompletableFuture<Component<? extends T>> publisherAvailable;
- private CompletableFuture<T> value;
-
- public PublishedType(Component<? extends T> component, Class<T> clss) {
- this.clss = clss;
- this.component = component;
- value = CompletableFuture.completedFuture((T) component.instance);
-// value = publisherAvailable.thenApply((c) -> c.getInstance());
- }
-
- Component<?> getPublisher() {
- return component;
- }
-
-// CompletableFuture<Component<? extends T>> publisherAvailable() {
-// return publisherAvailable;
-// }
-
- Class<T> getType() {
- return clss;
- }
- }
-
- public static class Builder<I> {
- private final I instance;
-
- private Runnable init;
- private Runnable close;
-
- private Set<Dependency<?>> dependencies = new HashSet<>();
- private Set<Class<? super I>> types = new HashSet<>();
-
- public Builder(I instance) {
- this.instance = instance;
- }
-
- public Component<I> build(Consumer<Component<?>> register) {
- // default values
- if (types.isEmpty()) {
- types.add(getInstanceClass());
- }
-
- if (init == null)
- init = () -> {
- };
- if (close == null)
- close = () -> {
- };
-
- // instantiation
- Component<I> component = new Component<I>(register, instance, init, close, dependencies, types);
- for (Dependency<?> dependency : dependencies) {
- dependency.type.getPublisher().addDependant(dependency);
- }
- return component;
- }
-
- public Builder<I> addType(Class<? super I> clss) {
- types.add(clss);
- return this;
- }
-
- public Builder<I> addInit(Runnable init) {
- if (this.init != null)
- throw new IllegalArgumentException("init method is already set");
- this.init = init;
- return this;
- }
-
- public Builder<I> addClose(Runnable close) {
- if (this.close != null)
- throw new IllegalArgumentException("close method is already set");
- this.close = close;
- return this;
- }
-
- public <D> Builder<I> addDependency(PublishedType<D> type, Consumer<D> set, Consumer<D> unset) {
- dependencies.add(new Dependency<D>(type, set, unset));
- return this;
- }
-
- public I get() {
- return instance;
- }
-
- @SuppressWarnings("unchecked")
- private Class<I> getInstanceClass() {
- return (Class<I>) instance.getClass();
- }
-
- }
-
- static class Dependency<D> {
- private PublishedType<D> type;
- private Consumer<D> set;
- private Consumer<D> unset;
-
- // live
- Component<?> dependantComponent;
- CompletableFuture<Void> setStage;
- CompletableFuture<Void> unsetStage;
-
- public Dependency(PublishedType<D> types, Consumer<D> set, Consumer<D> unset) {
- super();
- this.type = types;
- this.set = set;
- this.unset = unset != null ? unset : (v) -> set.accept(null);
- }
-
- // live
- void setDependantComponent(Component<?> component) {
- this.dependantComponent = component;
- }
-
- CompletableFuture<Void> publisherActivated() {
- return type.getPublisher().activated.copy();
- }
-
- CompletableFuture<Void> dependantDeactivated() {
- return dependantComponent.deactivated.copy();
- }
-
- CompletableFuture<Void> set(Void v) {
- return type.value.thenAccept(set);
- }
-
- CompletableFuture<Void> unset(Void v) {
- return type.value.thenAccept(unset);
- }
-
- }
-}
+++ /dev/null
-package org.argeo.util.register;
-
-import java.util.Map;
-
-/** A dynamic register of objects. */
-public interface Register {
- <T> Singleton<T> set(T obj, Class<T> clss, Map<String, Object> attributes, Class<?>... classes);
-
- void shutdown();
-}
+++ /dev/null
-package org.argeo.util.register;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.CompletionStage;
-import java.util.concurrent.Future;
-import java.util.function.Consumer;
-
-public class Singleton<T> {
- private final Class<T> clss;
- private final CompletableFuture<T> registrationStage;
- private final List<Consumer<T>> unregistrationHooks = new ArrayList<>();
-
- public Singleton(Class<T> clss, CompletableFuture<T> registrationStage) {
- this.clss = clss;
- this.registrationStage = registrationStage;
- }
-
- CompletionStage<T> getRegistrationStage() {
- return registrationStage.minimalCompletionStage();
- }
-
- public void addUnregistrationHook(Consumer<T> todo) {
- unregistrationHooks.add(todo);
- }
-
- public Future<T> getValue() {
- return registrationStage.copy();
- }
-
- public CompletableFuture<Void> prepareUnregistration(Void v) {
- List<CompletableFuture<Void>> lst = new ArrayList<>();
- for (Consumer<T> hook : unregistrationHooks) {
- lst.add(registrationStage.thenAcceptAsync(hook));
- }
- CompletableFuture<Void> prepareUnregistrationStage = CompletableFuture
- .allOf(lst.toArray(new CompletableFuture<?>[lst.size()]));
- return prepareUnregistrationStage;
- }
-}
+++ /dev/null
-package org.argeo.util.register;
-
-import java.util.HashSet;
-import java.util.IdentityHashMap;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.function.Consumer;
-import java.util.function.Predicate;
-
-/** A minimal component register. */
-public class StaticRegister {
- private final static AtomicBoolean started = new AtomicBoolean(false);
- private final static IdentityHashMap<Object, Component<?>> components = new IdentityHashMap<>();
-
- public static Consumer<Component<?>> asConsumer() {
- return (c) -> registerComponent(c);
- }
-
-// public static BiFunction<Class<?>, Predicate<Map<String, Object>>, Component<?>> asProvider() {
-//
-// }
-
- static synchronized <T> Component<? extends T> find(Class<T> clss, Predicate<Map<String, Object>> filter) {
- Set<Component<? extends T>> result = new HashSet<>();
- instances: for (Object instance : components.keySet()) {
- if (!clss.isAssignableFrom(instance.getClass()))
- continue instances;
- Component<? extends T> component = (Component<? extends T>) components.get(instance);
-
- // TODO filter
- if (component.isPublishedType(clss))
- result.add(component);
- }
- if (result.isEmpty())
- return null;
- return result.iterator().next();
-
- }
-
- static synchronized void registerComponent(Component<?> component) {
- if (started.get()) // TODO make it really dynamic
- throw new IllegalStateException("Already activated");
- if (components.containsKey(component.getInstance()))
- throw new IllegalArgumentException("Already registered as component");
- components.put(component.getInstance(), component);
- }
-
- static synchronized Component<?> get(Object instance) {
- if (!components.containsKey(instance))
- throw new IllegalArgumentException("Not registered as component");
- return components.get(instance);
- }
-
- synchronized static void activate() {
- if (started.get())
- throw new IllegalStateException("Already activated");
- Set<CompletableFuture<?>> constraints = new HashSet<>();
- for (Component<?> component : components.values()) {
- component.startActivating();
- constraints.add(component.getActivated());
- }
-
- // wait
- try {
- CompletableFuture.allOf(constraints.toArray(new CompletableFuture[0])).thenRun(() -> started.set(true))
- .get();
- } catch (ExecutionException | InterruptedException e) {
- throw new RuntimeException(e);
- }
- }
-
- synchronized static void deactivate() {
- if (!started.get())
- throw new IllegalStateException("Not activated");
- Set<CompletableFuture<?>> constraints = new HashSet<>();
- for (Component<?> component : components.values()) {
- component.startDeactivating();
- constraints.add(component.getDeactivated());
- }
-
- // wait
- try {
- CompletableFuture.allOf(constraints.toArray(new CompletableFuture[0])).thenRun(() -> started.set(false))
- .get();
- } catch (ExecutionException | InterruptedException e) {
- throw new RuntimeException(e);
- }
- }
-
- synchronized static void clear() {
- components.clear();
- }
-
-}
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-17"/>
+ <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+ <classpathentry kind="src" path="src"/>
+ <classpathentry kind="output" path="bin"/>
+</classpath>
--- /dev/null
+/bin/
+/target/
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>org.argeo.cms.lib.equinox</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" immediate="true" activate="start" deactivate="stop" name="Jetty Service Factory">
+ <implementation class="org.argeo.cms.equinox.http.jetty.EquinoxJettyServer"/>
+ <property name="service.pid" type="String" value="org.argeo.equinox.jetty.config"/>
+ <reference bind="setCmsState" cardinality="1..1" interface="org.argeo.api.cms.CmsState" name="CmsState" policy="static"/>
+ <service>
+ <provide interface="com.sun.net.httpserver.HttpServer"/>
+ </service>
+</scr:component>
--- /dev/null
+Service-Component: \
+OSGI-INF/jettyServiceFactory.xml,\
--- /dev/null
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+ .
--- /dev/null
+package org.argeo.cms.equinox.http.jetty;
+
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+import javax.servlet.Servlet;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpSessionEvent;
+import javax.servlet.http.HttpSessionIdListener;
+import javax.servlet.http.HttpSessionListener;
+
+import org.argeo.cms.jetty.CmsJettyServer;
+import org.eclipse.equinox.http.servlet.HttpServiceServlet;
+import org.eclipse.jetty.server.session.SessionHandler;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.osgi.framework.Constants;
+
+/** A {@link CmsJettyServer} integrating with Equinox HTTP framework. */
+public class EquinoxJettyServer extends CmsJettyServer {
+ private static final String INTERNAL_CONTEXT_CLASSLOADER = "org.eclipse.equinox.http.jetty.internal.ContextClassLoader";
+
+ @Override
+ protected void addServlets(ServletContextHandler rootContextHandler) throws ServletException {
+ ServletHolder holder = new ServletHolder(new InternalHttpServiceServlet());
+ holder.setInitOrder(0);
+ holder.setInitParameter(Constants.SERVICE_VENDOR, "Eclipse.org"); //$NON-NLS-1$
+ holder.setInitParameter(Constants.SERVICE_DESCRIPTION, "Equinox Jetty-based Http Service"); //$NON-NLS-1$
+
+ rootContextHandler.addServlet(holder, "/*");
+
+ // post-start
+ SessionHandler sessionManager = rootContextHandler.getSessionHandler();
+ sessionManager.addEventListener((HttpSessionIdListener) holder.getServlet());
+ }
+
+ public static class InternalHttpServiceServlet implements HttpSessionListener, HttpSessionIdListener, Servlet {
+ private final Servlet httpServiceServlet = new HttpServiceServlet();
+ private ClassLoader contextLoader;
+ private final Method sessionDestroyed;
+ private final Method sessionIdChanged;
+
+ public InternalHttpServiceServlet() {
+ Class<?> clazz = httpServiceServlet.getClass();
+
+ try {
+ sessionDestroyed = clazz.getMethod("sessionDestroyed", new Class<?>[] { String.class }); //$NON-NLS-1$
+ } catch (Exception e) {
+ throw new IllegalStateException(e);
+ }
+ try {
+ sessionIdChanged = clazz.getMethod("sessionIdChanged", new Class<?>[] { String.class }); //$NON-NLS-1$
+ } catch (Exception e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ @Override
+ public void init(ServletConfig config) throws ServletException {
+ ServletContext context = config.getServletContext();
+ contextLoader = (ClassLoader) context.getAttribute(INTERNAL_CONTEXT_CLASSLOADER);
+
+ Thread thread = Thread.currentThread();
+ ClassLoader current = thread.getContextClassLoader();
+ thread.setContextClassLoader(contextLoader);
+ try {
+ httpServiceServlet.init(config);
+ } finally {
+ thread.setContextClassLoader(current);
+ }
+ }
+
+ @Override
+ public void destroy() {
+ Thread thread = Thread.currentThread();
+ ClassLoader current = thread.getContextClassLoader();
+ thread.setContextClassLoader(contextLoader);
+ try {
+ httpServiceServlet.destroy();
+ } finally {
+ thread.setContextClassLoader(current);
+ }
+ contextLoader = null;
+ }
+
+ @Override
+ public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
+ Thread thread = Thread.currentThread();
+ ClassLoader current = thread.getContextClassLoader();
+ thread.setContextClassLoader(contextLoader);
+ try {
+ httpServiceServlet.service(req, res);
+ } finally {
+ thread.setContextClassLoader(current);
+ }
+ }
+
+ @Override
+ public ServletConfig getServletConfig() {
+ return httpServiceServlet.getServletConfig();
+ }
+
+ @Override
+ public String getServletInfo() {
+ return httpServiceServlet.getServletInfo();
+ }
+
+ @Override
+ public void sessionCreated(HttpSessionEvent event) {
+ // Nothing to do.
+ }
+
+ @Override
+ public void sessionDestroyed(HttpSessionEvent event) {
+ Thread thread = Thread.currentThread();
+ ClassLoader current = thread.getContextClassLoader();
+ thread.setContextClassLoader(contextLoader);
+ try {
+ sessionDestroyed.invoke(httpServiceServlet, event.getSession().getId());
+ } catch (IllegalAccessException | IllegalArgumentException e) {
+ // not likely
+ } catch (InvocationTargetException e) {
+ throw new RuntimeException(e.getCause());
+ } finally {
+ thread.setContextClassLoader(current);
+ }
+ }
+
+ @Override
+ public void sessionIdChanged(HttpSessionEvent event, String oldSessionId) {
+ Thread thread = Thread.currentThread();
+ ClassLoader current = thread.getContextClassLoader();
+ thread.setContextClassLoader(contextLoader);
+ try {
+ sessionIdChanged.invoke(httpServiceServlet, oldSessionId);
+ } catch (IllegalAccessException | IllegalArgumentException e) {
+ // not likely
+ } catch (InvocationTargetException e) {
+ throw new RuntimeException(e.getCause());
+ } finally {
+ thread.setContextClassLoader(current);
+ }
+ }
+ }
+
+}
--- /dev/null
+package org.argeo.cms.servlet.internal.jetty;
+
+import java.io.File;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.Map;
+import java.util.concurrent.ForkJoinPool;
+
+import javax.websocket.DeploymentException;
+import javax.websocket.server.ServerContainer;
+import javax.websocket.server.ServerEndpointConfig;
+
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.api.cms.CmsState;
+import org.argeo.cms.CmsDeployProperty;
+import org.argeo.cms.util.LangUtils;
+import org.argeo.cms.websocket.server.CmsWebSocketConfigurator;
+import org.argeo.cms.websocket.server.TestEndpoint;
+import org.eclipse.equinox.http.jetty.JettyConfigurator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.ServiceReference;
+import org.osgi.util.tracker.ServiceTracker;
+
+public class JettyConfig {
+ private final static CmsLog log = CmsLog.getLog(JettyConfig.class);
+
+ final static String CMS_JETTY_CUSTOMIZER_CLASS = "org.argeo.equinox.jetty.CmsJettyCustomizer";
+
+ private CmsState cmsState;
+
+ private final BundleContext bc = FrameworkUtil.getBundle(JettyConfig.class).getBundleContext();
+
+ // private static final String JETTY_PROPERTY_PREFIX =
+ // "org.eclipse.equinox.http.jetty.";
+
+ public void start() {
+ // We need to start asynchronously so that Jetty bundle get started by lazy init
+ // due to the non-configurable behaviour of its activator
+ ForkJoinPool.commonPool().execute(() -> {
+ Dictionary<String, Object> properties = getHttpServerConfig();
+ startServer(properties);
+ });
+
+ ServiceTracker<ServerContainer, ServerContainer> serverSt = new ServiceTracker<ServerContainer, ServerContainer>(
+ bc, ServerContainer.class, null) {
+
+ @Override
+ public ServerContainer addingService(ServiceReference<ServerContainer> reference) {
+ ServerContainer serverContainer = super.addingService(reference);
+
+ BundleContext bc = reference.getBundle().getBundleContext();
+ ServiceReference<ServerEndpointConfig.Configurator> srConfigurator = bc
+ .getServiceReference(ServerEndpointConfig.Configurator.class);
+ ServerEndpointConfig.Configurator endpointConfigurator = bc.getService(srConfigurator);
+ ServerEndpointConfig config = ServerEndpointConfig.Builder
+ .create(TestEndpoint.class, "/ws/test/events/").configurator(endpointConfigurator).build();
+ try {
+ serverContainer.addEndpoint(config);
+ } catch (DeploymentException e) {
+ throw new IllegalStateException("Cannot initalise the WebSocket server runtime.", e);
+ }
+ return serverContainer;
+ }
+
+ };
+ serverSt.open();
+
+ // check initialisation
+// ServiceTracker<?, ?> httpSt = new ServiceTracker<HttpService, HttpService>(bc, HttpService.class, null) {
+//
+// @Override
+// public HttpService addingService(ServiceReference<HttpService> sr) {
+// Object httpPort = sr.getProperty("http.port");
+// Object httpsPort = sr.getProperty("https.port");
+// log.info(httpPortsMsg(httpPort, httpsPort));
+// close();
+// return super.addingService(sr);
+// }
+// };
+// httpSt.open();
+ }
+
+ public void stop() {
+ try {
+ JettyConfigurator.stopServer(CmsConstants.DEFAULT);
+ } catch (Exception e) {
+ log.error("Cannot stop default Jetty server.", e);
+ }
+
+ }
+
+ public void startServer(Dictionary<String, Object> properties) {
+ // Explicitly configures Jetty so that the default server is not started by the
+ // activator of the Equinox Jetty bundle.
+ Map<String, String> config = LangUtils.dictToStringMap(properties);
+ if (!config.isEmpty()) {
+ config.put("customizer.class", CMS_JETTY_CUSTOMIZER_CLASS);
+
+ // TODO centralise with Jetty extender
+ Object webSocketEnabled = config.get(CmsDeployProperty.WEBSOCKET_ENABLED.getProperty());
+ if (webSocketEnabled != null && webSocketEnabled.toString().equals("true")) {
+ bc.registerService(ServerEndpointConfig.Configurator.class, new CmsWebSocketConfigurator(), null);
+ // config.put(WEBSOCKET_ENABLED, "true");
+ }
+ }
+
+ properties.put(Constants.SERVICE_PID, "default");
+ File jettyWorkDir = new File(bc.getDataFile(""), "jettywork"); //$NON-NLS-1$
+ jettyWorkDir.mkdir();
+
+// HttpServerManager serverManager = new HttpServerManager(jettyWorkDir);
+// try {
+// serverManager.updated("default", properties);
+// } catch (ConfigurationException e) {
+// // TODO Auto-generated catch block
+// e.printStackTrace();
+// }
+ Object httpPort = config.get(JettyHttpConstants.HTTP_PORT);
+ Object httpsPort = config.get(JettyHttpConstants.HTTPS_PORT);
+ log.info(httpPortsMsg(httpPort, httpsPort));
+
+// long begin = System.currentTimeMillis();
+// int tryCount = 60;
+// try {
+// while (tryCount > 0) {
+// try {
+// // FIXME deal with multiple ids
+// JettyConfigurator.startServer(CmsConstants.DEFAULT, new Hashtable<>(config));
+//
+// Object httpPort = config.get(JettyHttpConstants.HTTP_PORT);
+// Object httpsPort = config.get(JettyHttpConstants.HTTPS_PORT);
+// log.info(httpPortsMsg(httpPort, httpsPort));
+//
+// // Explicitly starts Jetty OSGi HTTP bundle, so that it gets triggered if OSGi
+// // configuration is not cleaned
+// FrameworkUtil.getBundle(JettyConfigurator.class).start();
+// return;
+// } catch (IllegalStateException e) {
+// // e.printStackTrace();
+// // Jetty may not be ready
+// try {
+// Thread.sleep(1000);
+// } catch (Exception e1) {
+// // silent
+// }
+// tryCount--;
+// }
+// }
+// long duration = System.currentTimeMillis() - begin;
+// log.error("Gave up with starting Jetty server after " + (duration / 1000) + " s");
+// } catch (Exception e) {
+// log.error("Cannot start default Jetty server with config " + properties, e);
+// }
+
+ }
+
+ private String httpPortsMsg(Object httpPort, Object httpsPort) {
+ return (httpPort != null ? "HTTP " + httpPort + " " : " ") + (httpsPort != null ? "HTTPS " + httpsPort : "");
+ }
+
+ /** Override the provided config with the framework properties */
+ public Dictionary<String, Object> getHttpServerConfig() {
+ String httpPort = getFrameworkProp(CmsDeployProperty.HTTP_PORT);
+ String httpsPort = getFrameworkProp(CmsDeployProperty.HTTPS_PORT);
+ /// TODO make it more generic
+ String httpHost = getFrameworkProp(CmsDeployProperty.HOST);
+// String httpsHost = getFrameworkProp(
+// JettyConfig.JETTY_PROPERTY_PREFIX + CmsHttpConstants.HTTPS_HOST);
+ String webSocketEnabled = getFrameworkProp(CmsDeployProperty.WEBSOCKET_ENABLED);
+
+ final Hashtable<String, Object> props = new Hashtable<String, Object>();
+ // try {
+ if (httpPort != null || httpsPort != null) {
+ boolean httpEnabled = httpPort != null;
+ props.put(JettyHttpConstants.HTTP_ENABLED, httpEnabled);
+ boolean httpsEnabled = httpsPort != null;
+ props.put(JettyHttpConstants.HTTPS_ENABLED, httpsEnabled);
+
+ if (httpEnabled) {
+ props.put(JettyHttpConstants.HTTP_PORT, httpPort);
+ if (httpHost != null)
+ props.put(JettyHttpConstants.HTTP_HOST, httpHost);
+ }
+
+ if (httpsEnabled) {
+ props.put(JettyHttpConstants.HTTPS_PORT, httpsPort);
+ if (httpHost != null)
+ props.put(JettyHttpConstants.HTTPS_HOST, httpHost);
+
+ // keystore
+ props.put(JettyHttpConstants.SSL_KEYSTORETYPE, getFrameworkProp(CmsDeployProperty.SSL_KEYSTORETYPE));
+ props.put(JettyHttpConstants.SSL_KEYSTORE, getFrameworkProp(CmsDeployProperty.SSL_KEYSTORE));
+ props.put(JettyHttpConstants.SSL_PASSWORD, getFrameworkProp(CmsDeployProperty.SSL_PASSWORD));
+
+ // truststore
+ props.put(JettyHttpConstants.SSL_TRUSTSTORETYPE,
+ getFrameworkProp(CmsDeployProperty.SSL_TRUSTSTORETYPE));
+ props.put(JettyHttpConstants.SSL_TRUSTSTORE, getFrameworkProp(CmsDeployProperty.SSL_TRUSTSTORE));
+ props.put(JettyHttpConstants.SSL_TRUSTSTOREPASSWORD,
+ getFrameworkProp(CmsDeployProperty.SSL_TRUSTSTOREPASSWORD));
+
+ // client certificate authentication
+ String wantClientAuth = getFrameworkProp(CmsDeployProperty.SSL_WANTCLIENTAUTH);
+ if (wantClientAuth != null)
+ props.put(JettyHttpConstants.SSL_WANTCLIENTAUTH, Boolean.parseBoolean(wantClientAuth));
+ String needClientAuth = getFrameworkProp(CmsDeployProperty.SSL_NEEDCLIENTAUTH);
+ if (needClientAuth != null)
+ props.put(JettyHttpConstants.SSL_NEEDCLIENTAUTH, Boolean.parseBoolean(needClientAuth));
+ }
+
+ // web socket
+ if (webSocketEnabled != null && webSocketEnabled.equals("true"))
+ props.put(CmsDeployProperty.WEBSOCKET_ENABLED.getProperty(), true);
+
+ props.put(CmsConstants.CN, CmsConstants.DEFAULT);
+ }
+ return props;
+ }
+
+ private String getFrameworkProp(CmsDeployProperty deployProperty) {
+ return cmsState.getDeployProperty(deployProperty.getProperty());
+ }
+
+ public void setCmsState(CmsState cmsState) {
+ this.cmsState = cmsState;
+ }
+
+}
--- /dev/null
+package org.argeo.cms.servlet.internal.jetty;
+
+/** Compatible with Jetty. */
+interface JettyHttpConstants {
+ static final String HTTP_ENABLED = "http.enabled";
+ static final String HTTP_PORT = "http.port";
+ static final String HTTP_HOST = "http.host";
+ static final String HTTPS_ENABLED = "https.enabled";
+ static final String HTTPS_HOST = "https.host";
+ static final String HTTPS_PORT = "https.port";
+ static final String SSL_KEYSTORE = "ssl.keystore";
+ static final String SSL_PASSWORD = "ssl.password";
+ static final String SSL_KEYPASSWORD = "ssl.keypassword";
+ static final String SSL_NEEDCLIENTAUTH = "ssl.needclientauth";
+ static final String SSL_WANTCLIENTAUTH = "ssl.wantclientauth";
+ static final String SSL_PROTOCOL = "ssl.protocol";
+ static final String SSL_ALGORITHM = "ssl.algorithm";
+ static final String SSL_KEYSTORETYPE = "ssl.keystoretype";
+
+ // Argeo
+ static final String SSL_TRUSTSTORE = "ssl.truststore";
+ static final String SSL_TRUSTSTOREPASSWORD = "ssl.truststorepassword";
+ static final String SSL_TRUSTSTORETYPE = "ssl.truststoretype";
+
+}
--- /dev/null
+package org.argeo.equinox.jetty;
+
+import java.util.Dictionary;
+
+import javax.servlet.ServletContext;
+import javax.websocket.DeploymentException;
+import javax.websocket.server.ServerContainer;
+
+import org.eclipse.equinox.http.jetty.JettyCustomizer;
+import org.eclipse.jetty.server.ConnectionFactory;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.SslConnectionFactory;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.eclipse.jetty.websocket.javax.server.config.JavaxWebSocketServletContainerInitializer;
+import org.eclipse.jetty.websocket.javax.server.config.JavaxWebSocketServletContainerInitializer.Configurator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
+
+/** Customises the Jetty HTTP server. */
+public class CmsJettyCustomizer extends JettyCustomizer {
+ static final String SSL_TRUSTSTORE = "ssl.truststore";
+ static final String SSL_TRUSTSTOREPASSWORD = "ssl.truststorepassword";
+ static final String SSL_TRUSTSTORETYPE = "ssl.truststoretype";
+
+ private BundleContext bc = FrameworkUtil.getBundle(CmsJettyCustomizer.class).getBundleContext();
+
+ public final static String WEBSOCKET_ENABLED = "argeo.websocket.enabled";
+
+ @Override
+ public Object customizeContext(Object context, Dictionary<String, ?> settings) {
+ // WebSocket
+ Object webSocketEnabled = settings.get(WEBSOCKET_ENABLED);
+ if (webSocketEnabled != null && webSocketEnabled.toString().equals("true")) {
+ ServletContextHandler servletContextHandler = (ServletContextHandler) context;
+ JavaxWebSocketServletContainerInitializer.configure(servletContextHandler, new Configurator() {
+
+ @Override
+ public void accept(ServletContext servletContext, ServerContainer serverContainer)
+ throws DeploymentException {
+ bc.registerService(javax.websocket.server.ServerContainer.class, serverContainer, null);
+ }
+ });
+ }
+ return super.customizeContext(context, settings);
+
+ }
+
+ @Override
+ public Object customizeHttpsConnector(Object connector, Dictionary<String, ?> settings) {
+ ServerConnector httpsConnector = (ServerConnector) connector;
+ if (httpsConnector != null)
+ for (ConnectionFactory connectionFactory : httpsConnector.getConnectionFactories()) {
+ if (connectionFactory instanceof SslConnectionFactory) {
+ SslContextFactory.Server sslContextFactory = ((SslConnectionFactory) connectionFactory)
+ .getSslContextFactory();
+ sslContextFactory.setTrustStorePath((String) settings.get(SSL_TRUSTSTORE));
+ sslContextFactory.setTrustStoreType((String) settings.get(SSL_TRUSTSTORETYPE));
+ sslContextFactory.setTrustStorePassword((String) settings.get(SSL_TRUSTSTOREPASSWORD));
+ }
+ }
+ return super.customizeHttpsConnector(connector, settings);
+ }
+
+}
--- /dev/null
+/** Equinox Jetty extensions. */
+package org.argeo.equinox.jetty;
\ 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.rap</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" configuration-policy="optional" name="CMS Admin RAP">
- <implementation class="org.argeo.cms.e4.rap.CmsE4AdminApp"/>
- <service>
- <provide interface="org.eclipse.rap.rwt.application.ApplicationConfiguration"/>
- <property name="contextName" type="String" value="cms"/>
- </service>
-</scr:component>
+++ /dev/null
-Bundle-ActivationPolicy: lazy
-Service-Component: OSGI-INF/cms-admin-rap.xml
-
-Import-Package: org.eclipse.swt,\
-org.eclipse.swt.graphics,\
-org.eclipse.e4.ui.workbench,\
-org.eclipse.rap.rwt.client,\
-org.eclipse.nebula.widgets.richtext;resolution:=optional,\
-*
+++ /dev/null
-source.. = src/
-output.. = bin/
-bin.includes = META-INF/,\
- .,\
- OSGI-INF/
+++ /dev/null
-package org.argeo.cms.e4.rap;
-
-import java.util.HashMap;
-import java.util.Map;
-
-import org.argeo.cms.swt.dialogs.CmsFeedback;
-import org.eclipse.rap.e4.E4ApplicationConfig;
-import org.eclipse.rap.rwt.application.Application;
-import org.eclipse.rap.rwt.application.Application.OperationMode;
-import org.eclipse.rap.rwt.application.ApplicationConfiguration;
-import org.eclipse.rap.rwt.application.ExceptionHandler;
-import org.eclipse.rap.rwt.client.WebClient;
-import org.osgi.framework.BundleContext;
-
-/** Base class for CMS RAP applications. */
-public abstract class AbstractRapE4App implements ApplicationConfiguration {
- private String e4Xmi;
- private String path;
- private String lifeCycleUri = "bundleclass://org.argeo.cms.e4.rap/org.argeo.cms.e4.rap.CmsLoginLifecycle";
-
- private Map<String, String> baseProperties = new HashMap<String, String>();
-
- private BundleContext bundleContext;
- public final static String CONTEXT_NAME_PROPERTY = "contextName";
- private String contextName;
-
- /**
- * To be overridden in order to add multiple entry points, directly or using
- * {@link #addE4EntryPoint(Application, String, String, Map)}.
- */
- protected void addEntryPoints(Application application) {
- }
-
- public void configure(Application application) {
- application.setExceptionHandler(new ExceptionHandler() {
-
- @Override
- public void handleException(Throwable throwable) {
- CmsFeedback.show("Unexpected RWT exception", throwable);
- }
- });
-
- if (e4Xmi != null) {// backward compatibility
- addE4EntryPoint(application, path, e4Xmi, getBaseProperties());
- } else {
- addEntryPoints(application);
- }
- }
-
- protected Map<String, String> getBaseProperties() {
- return baseProperties;
- }
-
-// protected void addEntryPoint(Application application, E4ApplicationConfig config, Map<String, String> properties) {
-// CmsE4EntryPointFactory entryPointFactory = new CmsE4EntryPointFactory(config);
-// application.addEntryPoint(path, entryPointFactory, properties);
-// application.setOperationMode(OperationMode.SWT_COMPATIBILITY);
-// }
-
- protected void addE4EntryPoint(Application application, String path, String e4Xmi, Map<String, String> properties) {
- E4ApplicationConfig config = createE4ApplicationConfig(e4Xmi);
- CmsE4EntryPointFactory entryPointFactory = new CmsE4EntryPointFactory(config);
- application.addEntryPoint(path, entryPointFactory, properties);
- application.setOperationMode(OperationMode.SWT_COMPATIBILITY);
- }
-
- /**
- * To be overridden for further configuration.
- *
- * @see E4ApplicationConfig
- */
- protected E4ApplicationConfig createE4ApplicationConfig(String e4Xmi) {
- return new E4ApplicationConfig(e4Xmi, lifeCycleUri, null, null, false, true, true);
- }
-
- @Deprecated
- public void setPageTitle(String pageTitle) {
- if (pageTitle != null)
- baseProperties.put(WebClient.PAGE_TITLE, pageTitle);
- }
-
- /** Returns a new map used to customise and entry point. */
- public Map<String, String> customise(String pageTitle) {
- Map<String, String> custom = new HashMap<>(getBaseProperties());
- if (pageTitle != null)
- custom.put(WebClient.PAGE_TITLE, pageTitle);
- return custom;
- }
-
- @Deprecated
- public void setE4Xmi(String e4Xmi) {
- this.e4Xmi = e4Xmi;
- }
-
- @Deprecated
- public void setPath(String path) {
- this.path = path;
- }
-
- public void setLifeCycleUri(String lifeCycleUri) {
- this.lifeCycleUri = lifeCycleUri;
- }
-
- protected BundleContext getBundleContext() {
- return bundleContext;
- }
-
- protected void setBundleContext(BundleContext bundleContext) {
- this.bundleContext = bundleContext;
- }
-
- public String getContextName() {
- return contextName;
- }
-
- public void setContextName(String contextName) {
- this.contextName = contextName;
- }
-
- public void init(BundleContext bundleContext, Map<String, Object> properties) {
- this.bundleContext = bundleContext;
- for (String key : properties.keySet()) {
- Object value = properties.get(key);
- if (value != null)
- baseProperties.put(key, value.toString());
- }
-
- if (properties.containsKey(CONTEXT_NAME_PROPERTY)) {
- assert properties.get(CONTEXT_NAME_PROPERTY) != null;
- contextName = properties.get(CONTEXT_NAME_PROPERTY).toString();
- } else {
- contextName = "<unknown context>";
- }
- }
-
- public void destroy(Map<String, Object> properties) {
-
- }
-}
+++ /dev/null
-package org.argeo.cms.e4.rap;
-
-import org.eclipse.rap.rwt.application.Application;
-
-/**
- * Access to canonical views of the core CMS concepts, useful for devleopers and
- * operators.
- */
-public class CmsE4AdminApp extends AbstractRapE4App {
- @Override
- protected void addEntryPoints(Application application) {
- addE4EntryPoint(application, "/devops", "org.argeo.cms.e4/e4xmi/cms-devops.e4xmi",
- customise("Argeo CMS DevOps"));
- addE4EntryPoint(application, "/ego", "org.argeo.cms.e4/e4xmi/cms-ego.e4xmi", customise("Argeo CMS Ego"));
- }
-
-}
+++ /dev/null
-package org.argeo.cms.e4.rap;
-
-import java.security.PrivilegedAction;
-
-import javax.security.auth.Subject;
-
-import org.eclipse.rap.e4.E4ApplicationConfig;
-import org.eclipse.rap.e4.E4EntryPointFactory;
-import org.eclipse.rap.rwt.RWT;
-import org.eclipse.rap.rwt.application.EntryPoint;
-import org.eclipse.rap.rwt.client.service.JavaScriptExecutor;
-
-public class CmsE4EntryPointFactory extends E4EntryPointFactory {
- public final static String DEFAULT_LIFECYCLE_URI = "bundleclass://org.argeo.cms.e4.rap/org.argeo.cms.e4.rap.CmsLoginLifecycle";
-
- public CmsE4EntryPointFactory(E4ApplicationConfig config) {
- super(config);
- }
-
- public CmsE4EntryPointFactory(String e4Xmi, String lifeCycleUri) {
- super(defaultConfig(e4Xmi, lifeCycleUri));
- }
-
- public CmsE4EntryPointFactory(String e4Xmi) {
- this(e4Xmi, DEFAULT_LIFECYCLE_URI);
- }
-
- public static E4ApplicationConfig defaultConfig(String e4Xmi, String lifeCycleUri) {
- E4ApplicationConfig config = new E4ApplicationConfig(e4Xmi, lifeCycleUri, null, null, false, true, true);
- return config;
- }
-
- @Override
- public EntryPoint create() {
- EntryPoint ep = createEntryPoint();
- EntryPoint authEp = new EntryPoint() {
-
- @Override
- public int createUI() {
- Subject subject = new Subject();
- return Subject.doAs(subject, new PrivilegedAction<Integer>() {
-
- @Override
- public Integer run() {
- // SPNEGO
- // HttpServletRequest request = RWT.getRequest();
- // String authorization = request.getHeader(HEADER_AUTHORIZATION);
- // if (authorization == null || !authorization.startsWith("Negotiate")) {
- // HttpServletResponse response = RWT.getResponse();
- // response.setStatus(401);
- // response.setHeader(HEADER_WWW_AUTHENTICATE, "Negotiate");
- // 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");
- // }
-
- JavaScriptExecutor jsExecutor = RWT.getClient().getService(JavaScriptExecutor.class);
- Integer exitCode = ep.createUI();
- jsExecutor.execute("location.reload()");
- return exitCode;
- }
-
- });
- }
- };
- return authEp;
- }
-
- protected EntryPoint createEntryPoint() {
- return super.create();
- }
-}
+++ /dev/null
-package org.argeo.cms.e4.rap;
-
-import java.security.AccessController;
-import java.util.UUID;
-
-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.CmsImageManager;
-import org.argeo.api.cms.CmsView;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.api.cms.UxContext;
-import org.argeo.cms.auth.CurrentUser;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.cms.swt.SimpleSwtUxContext;
-import org.argeo.cms.swt.auth.CmsLoginShell;
-import org.argeo.cms.swt.dialogs.CmsFeedback;
-import org.argeo.cms.ui.util.SimpleImageManager;
-import org.eclipse.e4.core.services.events.IEventBroker;
-import org.eclipse.e4.ui.workbench.UIEvents;
-import org.eclipse.e4.ui.workbench.lifecycle.PostContextCreate;
-import org.eclipse.e4.ui.workbench.lifecycle.PreSave;
-import org.eclipse.rap.rwt.RWT;
-import org.eclipse.rap.rwt.client.service.BrowserNavigation;
-import org.eclipse.rap.rwt.client.service.BrowserNavigationEvent;
-import org.eclipse.rap.rwt.client.service.BrowserNavigationListener;
-import org.eclipse.swt.widgets.Display;
-import org.osgi.service.event.Event;
-import org.osgi.service.event.EventHandler;
-
-@SuppressWarnings("restriction")
-public class CmsLoginLifecycle implements CmsView {
- private final static CmsLog log = CmsLog.getLog(CmsLoginLifecycle.class);
-
- private UxContext uxContext;
- private CmsImageManager imageManager;
-
- private LoginContext loginContext;
- private BrowserNavigation browserNavigation;
-
- private String state = null;
- private String uid;
-
- @PostContextCreate
- boolean login(final IEventBroker eventBroker) {
- uid = UUID.randomUUID().toString();
- browserNavigation = RWT.getClient().getService(BrowserNavigation.class);
- if (browserNavigation != null)
- browserNavigation.addBrowserNavigationListener(new BrowserNavigationListener() {
- private static final long serialVersionUID = -3668136623771902865L;
-
- @Override
- public void navigated(BrowserNavigationEvent event) {
- state = event.getState();
- if (uxContext != null)// is logged in
- stateChanged();
- }
- });
-
- Subject subject = Subject.getSubject(AccessController.getContext());
- Display display = Display.getCurrent();
-// UiContext.setData(CmsView.KEY, this);
- // FIXME get CMS context
- CmsLoginShell loginShell = new CmsLoginShell(this, null);
- CmsSwtUtils.registerCmsView(loginShell.getShell(), this);
- loginShell.setSubject(subject);
- try {
- // try pre-auth
- loginContext = new LoginContext(CmsAuth.LOGIN_CONTEXT_USER, subject, loginShell);
- loginContext.login();
- } catch (LoginException e) {
- loginShell.createUi();
- loginShell.open();
-
- while (!loginShell.getShell().isDisposed()) {
- if (!display.readAndDispatch())
- display.sleep();
- }
- }
- if (CurrentUser.getUsername(getSubject()) == null)
- return false;
- uxContext = new SimpleSwtUxContext();
- imageManager = new SimpleImageManager();
-
- eventBroker.subscribe(UIEvents.UILifeCycle.APP_STARTUP_COMPLETE, new EventHandler() {
- @Override
- public void handleEvent(Event event) {
- startupComplete();
- eventBroker.unsubscribe(this);
- }
- });
-
- // lcs.changeApplicationLocale(Locale.FRENCH);
- return true;
- }
-
- @PreSave
- void destroy() {
- // logout();
- }
-
- @Override
- public UxContext getUxContext() {
- return uxContext;
- }
-
- @Override
- public void navigateTo(String state) {
- browserNavigation.pushState(state, state);
- }
-
- @Override
- public void authChange(LoginContext loginContext) {
- if (loginContext == null)
- throw new IllegalArgumentException("Login context cannot be null");
- // logout previous login context
- // if (this.loginContext != null)
- // try {
- // this.loginContext.logout();
- // } catch (LoginException e1) {
- // System.err.println("Could not log out: " + e1);
- // }
- this.loginContext = loginContext;
- }
-
- @Override
- public void logout() {
- if (loginContext == null)
- throw new IllegalStateException("Login context should not be null");
- try {
- CurrentUser.logoutCmsSession(loginContext.getSubject());
- loginContext.logout();
- } catch (LoginException e) {
- throw new IllegalStateException("Cannot log out", e);
- }
- }
-
- @Override
- public void exception(Throwable e) {
- String msg = "Unexpected exception in Eclipse 4 RAP";
- log.error(msg, e);
- CmsFeedback.show(msg, e);
- }
-
- @Override
- public CmsImageManager getImageManager() {
- return imageManager;
- }
-
- protected Subject getSubject() {
- return loginContext.getSubject();
- }
-
- @Override
- public boolean isAnonymous() {
- return CurrentUser.isAnonymous(getSubject());
- }
-
- @Override
- public String getUid() {
- return uid;
- }
-
- // CALLBACKS
- protected void startupComplete() {
- }
-
- protected void stateChanged() {
-
- }
-
- // GETTERS
- protected BrowserNavigation getBrowserNavigation() {
- return browserNavigation;
- }
-
- protected String getState() {
- return state;
- }
-
-}
+++ /dev/null
-package org.argeo.cms.e4.rap;
-
-import java.util.Enumeration;
-
-import org.apache.commons.io.FilenameUtils;
-import org.argeo.api.cms.CmsLog;
-import org.eclipse.rap.rwt.application.Application;
-import org.osgi.framework.Bundle;
-
-/** Simple RAP app which loads all e4xmi files. */
-public class SimpleRapE4App extends AbstractRapE4App {
- private final static CmsLog log = CmsLog.getLog(SimpleRapE4App.class);
-
- private String baseE4xmi = "/e4xmi";
-
- @Override
- protected void addEntryPoints(Application application) {
- Bundle bundle = getBundleContext().getBundle();
- Enumeration<String> paths = bundle.getEntryPaths(baseE4xmi);
- while (paths.hasMoreElements()) {
- String p = paths.nextElement();
- if (p.endsWith(".e4xmi")) {
- String e4xmiPath = bundle.getSymbolicName() + '/' + p;
- String name = '/' + FilenameUtils.removeExtension(FilenameUtils.getName(p));
- addE4EntryPoint(application, name, e4xmiPath, getBaseProperties());
- if (log.isDebugEnabled())
- log.debug("Registered " + e4xmiPath + " as " + getContextName() + name);
- }
- }
- }
-
-}
+++ /dev/null
-/** Eclipse 4 RAP specific extensions. */
-package org.argeo.cms.e4.rap;
\ 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.rap</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.argeo.eclipse.ui,\
-javax.jcr.nodetype,\
-javax.jcr.security,\
-org.eclipse.swt.graphics,\
-org.eclipse.jetty.util.component;version="[9.4,12)";resolution:=optional,\
-org.eclipse.jetty.http;version="[9.4,12)";resolution:=optional,\
-org.eclipse.jetty.io;version="[9.4,12)";resolution:=optional,\
-org.eclipse.jetty.security;version="[9.4,12)";resolution:=optional,\
-org.eclipse.jetty.server.handler;version="[9.4,12)";resolution:=optional,\
-org.eclipse.jetty.*;version="[9.4,12)";resolution:=optional,\
-javax.servlet.*;version="[3,5)",\
-*
-
+++ /dev/null
-source.. = src/
-output.. = bin/
-bin.includes = META-INF/,\
- .
+++ /dev/null
-package org.argeo.cms.ui.script;
-
-import java.util.HashMap;
-import java.util.Map;
-
-import javax.jcr.Node;
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.script.Invocable;
-import javax.script.ScriptException;
-
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.cms.swt.Selected;
-import org.argeo.cms.ui.CmsUiProvider;
-import org.argeo.cms.ui.util.CmsPane;
-import org.argeo.cms.web.SimpleErgonomics;
-import org.eclipse.rap.rwt.RWT;
-import org.eclipse.rap.rwt.application.Application;
-import org.eclipse.rap.rwt.application.EntryPoint;
-import org.eclipse.rap.rwt.application.EntryPointFactory;
-import org.eclipse.rap.rwt.client.WebClient;
-import org.eclipse.rap.rwt.client.service.JavaScriptExecutor;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.events.SelectionEvent;
-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.osgi.framework.BundleContext;
-
-public class AppUi implements CmsUiProvider, Branding {
- private final CmsScriptApp app;
-
- private CmsUiProvider ui;
- private String createUi;
- private Object impl;
- private String script;
- // private Branding branding = new Branding();
-
- private EntryPointFactory factory;
-
- // Branding
- private String themeId;
- private String additionalHeaders;
- private String bodyHtml;
- private String pageTitle;
- private String pageOverflow;
- private String favicon;
-
- public AppUi(CmsScriptApp app) {
- this.app = app;
- }
-
- public AppUi(CmsScriptApp app, String scriptPath) {
- this.app = app;
- this.ui = new ScriptUi((BundleContext) app.getScriptEngine().get(CmsScriptRwtApplication.BC),
- app.getScriptEngine(), scriptPath);
- }
-
- public AppUi(CmsScriptApp app, CmsUiProvider uiProvider) {
- this.app = app;
- this.ui = uiProvider;
- }
-
- public AppUi(CmsScriptApp app, EntryPointFactory factory) {
- this.app = app;
- this.factory = factory;
- }
-
- public void apply(Repository repository, Application application, Branding appBranding, String path) {
- Map<String, String> factoryProperties = new HashMap<>();
- if (appBranding != null)
- appBranding.applyBranding(factoryProperties);
- applyBranding(factoryProperties);
- if (factory != null) {
- application.addEntryPoint("/" + path, factory, factoryProperties);
- } else {
- EntryPointFactory entryPointFactory = new EntryPointFactory() {
- @Override
- public EntryPoint create() {
- SimpleErgonomics ergonomics = new SimpleErgonomics(repository, CmsConstants.SYS_WORKSPACE,
- "/home/root/argeo:keyring", AppUi.this, factoryProperties);
-// CmsUiProvider header = app.getHeader();
-// if (header != null)
-// ergonomics.setHeader(header);
- app.applySides(ergonomics);
- Integer headerHeight = app.getHeaderHeight();
- if (headerHeight != null)
- ergonomics.setHeaderHeight(headerHeight);
- return ergonomics;
- }
- };
- application.addEntryPoint("/" + path, entryPointFactory, factoryProperties);
- }
- }
-
- public void setUi(CmsUiProvider uiProvider) {
- this.ui = uiProvider;
- }
-
- public void applyBranding(Map<String, String> properties) {
- if (themeId != null)
- properties.put(WebClient.THEME_ID, themeId);
- if (additionalHeaders != null)
- properties.put(WebClient.HEAD_HTML, additionalHeaders);
- if (bodyHtml != null)
- properties.put(WebClient.BODY_HTML, bodyHtml);
- if (pageTitle != null)
- properties.put(WebClient.PAGE_TITLE, pageTitle);
- if (pageOverflow != null)
- properties.put(WebClient.PAGE_OVERFLOW, pageOverflow);
- if (favicon != null)
- properties.put(WebClient.FAVICON, favicon);
- }
-
- // public Branding getBranding() {
- // return branding;
- // }
-
- @Override
- public Control createUi(Composite parent, Node context) throws RepositoryException {
- CmsPane cmsPane = new CmsPane(parent, SWT.NONE);
-
- if (false) {
- // QA
- CmsSwtUtils.style(cmsPane.getQaArea(), "qa");
- Button reload = new Button(cmsPane.getQaArea(), SWT.FLAT);
- CmsSwtUtils.style(reload, "qa");
- reload.setText("Reload");
- reload.addSelectionListener(new Selected() {
- private static final long serialVersionUID = 1L;
-
- @Override
- public void widgetSelected(SelectionEvent e) {
- new Thread() {
- @Override
- public void run() {
- app.reload();
- }
- }.start();
- RWT.getClient().getService(JavaScriptExecutor.class)
- .execute("setTimeout('location.reload()',1000)");
- }
- });
-
- // Support
- CmsSwtUtils.style(cmsPane.getSupportArea(), "support");
- Label msg = new Label(cmsPane.getSupportArea(), SWT.NONE);
- CmsSwtUtils.style(msg, "support");
- msg.setText("UNSUPPORTED DEVELOPMENT VERSION");
- }
-
- if (ui != null) {
- ui.createUi(cmsPane.getMainArea(), context);
- }
- if (createUi != null) {
- Invocable invocable = (Invocable) app.getScriptEngine();
- try {
- invocable.invokeFunction(createUi, cmsPane.getMainArea(), context);
-
- } catch (NoSuchMethodException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- } catch (ScriptException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
- if (impl != null) {
- Invocable invocable = (Invocable) app.getScriptEngine();
- try {
- invocable.invokeMethod(impl, "createUi", cmsPane.getMainArea(), context);
-
- } catch (NoSuchMethodException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- } catch (ScriptException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
-
- // Invocable invocable = (Invocable) app.getScriptEngine();
- // try {
- // invocable.invokeMethod(AppUi.this, "initUi", parent, context);
- //
- // } catch (NoSuchMethodException e) {
- // // TODO Auto-generated catch block
- // e.printStackTrace();
- // } catch (ScriptException e) {
- // // TODO Auto-generated catch block
- // e.printStackTrace();
- // }
-
- return null;
- }
-
- public void setCreateUi(String createUi) {
- this.createUi = createUi;
- }
-
- public void setImpl(Object impl) {
- this.impl = impl;
- }
-
- public Object getImpl() {
- return impl;
- }
-
- public String getScript() {
- return script;
- }
-
- public void setScript(String script) {
- this.script = script;
- }
-
- // Branding
- public String getThemeId() {
- return themeId;
- }
-
- public void setThemeId(String themeId) {
- this.themeId = themeId;
- }
-
- public String getAdditionalHeaders() {
- return additionalHeaders;
- }
-
- public void setAdditionalHeaders(String additionalHeaders) {
- this.additionalHeaders = additionalHeaders;
- }
-
- public String getBodyHtml() {
- return bodyHtml;
- }
-
- public void setBodyHtml(String bodyHtml) {
- this.bodyHtml = bodyHtml;
- }
-
- public String getPageTitle() {
- return pageTitle;
- }
-
- public void setPageTitle(String pageTitle) {
- this.pageTitle = pageTitle;
- }
-
- public String getPageOverflow() {
- return pageOverflow;
- }
-
- public void setPageOverflow(String pageOverflow) {
- this.pageOverflow = pageOverflow;
- }
-
- public String getFavicon() {
- return favicon;
- }
-
- public void setFavicon(String favicon) {
- this.favicon = favicon;
- }
-
-}
+++ /dev/null
-package org.argeo.cms.ui.script;
-
-import java.util.Map;
-
-public interface Branding {
- public void applyBranding(Map<String, String> properties);
-
- public String getThemeId();
-
- public String getAdditionalHeaders();
-
- public String getBodyHtml();
-
- public String getPageTitle();
-
- public String getPageOverflow();
-
- public String getFavicon();
-
-}
+++ /dev/null
-package org.argeo.cms.ui.script;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.URL;
-import java.util.ArrayList;
-import java.util.Enumeration;
-import java.util.HashMap;
-import java.util.Hashtable;
-import java.util.List;
-import java.util.Map;
-
-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.script.ScriptEngine;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.argeo.api.cms.CmsTheme;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.cms.CmsException;
-import org.argeo.cms.ui.CmsUiConstants;
-import org.argeo.cms.ui.CmsUiProvider;
-import org.argeo.cms.ui.util.CmsUiUtils;
-import org.argeo.cms.web.BundleResourceLoader;
-import org.argeo.cms.web.SimpleErgonomics;
-import org.argeo.cms.web.WebThemeUtils;
-import org.eclipse.rap.rwt.application.Application;
-import org.eclipse.rap.rwt.application.Application.OperationMode;
-import org.eclipse.rap.rwt.application.ApplicationConfiguration;
-import org.eclipse.rap.rwt.application.ExceptionHandler;
-import org.eclipse.rap.rwt.client.WebClient;
-import org.eclipse.rap.rwt.service.ResourceLoader;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.ServiceRegistration;
-import org.osgi.service.http.HttpContext;
-import org.osgi.service.http.HttpService;
-import org.osgi.service.http.NamespaceException;
-
-public class CmsScriptApp implements Branding {
- public final static String CONTEXT_NAME = "contextName";
-
- ServiceRegistration<ApplicationConfiguration> appConfigReg;
-
- private ScriptEngine scriptEngine;
-
- private final static CmsLog log = CmsLog.getLog(CmsScriptApp.class);
-
- private String webPath;
- private String repo = "(cn=node)";
-
- // private Branding branding = new Branding();
- private CmsTheme theme;
-
- private List<String> resources = new ArrayList<>();
-
- private Map<String, AppUi> ui = new HashMap<>();
-
- private CmsUiProvider header;
- private Integer headerHeight = null;
- private CmsUiProvider lead;
- private CmsUiProvider end;
- private CmsUiProvider footer;
-
- // Branding
- private String themeId;
- private String additionalHeaders;
- private String bodyHtml;
- private String pageTitle;
- private String pageOverflow;
- private String favicon;
-
- public CmsScriptApp(ScriptEngine scriptEngine) {
- super();
- this.scriptEngine = scriptEngine;
- }
-
- public void apply(BundleContext bundleContext, Repository repository, Application application) {
- BundleResourceLoader bundleRL = new BundleResourceLoader(bundleContext.getBundle());
-
- application.setOperationMode(OperationMode.SWT_COMPATIBILITY);
- // application.setOperationMode(OperationMode.JEE_COMPATIBILITY);
-
- application.setExceptionHandler(new CmsExceptionHandler());
-
- // loading animated gif
- application.addResource(CmsUiConstants.LOADING_IMAGE, createResourceLoader(CmsUiConstants.LOADING_IMAGE));
- // empty image
- application.addResource(CmsUiConstants.NO_IMAGE, createResourceLoader(CmsUiConstants.NO_IMAGE));
-
- for (String resource : resources) {
- application.addResource(resource, bundleRL);
- if (log.isTraceEnabled())
- log.trace("Resource " + resource);
- }
-
- if (theme != null) {
- WebThemeUtils.apply(application, theme);
- String themeHeaders = theme.getHtmlHeaders();
- if (themeHeaders != null) {
- if (additionalHeaders == null)
- additionalHeaders = themeHeaders;
- else
- additionalHeaders = themeHeaders + "\n" + additionalHeaders;
- }
- themeId = theme.getThemeId();
- }
-
- // client JavaScript
- Bundle appBundle = bundleRL.getBundle();
- BundleContext bc = appBundle.getBundleContext();
- HttpService httpService = bc.getService(bc.getServiceReference(HttpService.class));
- HttpContext httpContext = new BundleHttpContext(bc);
- Enumeration<URL> themeResources = appBundle.findEntries("/js/", "*", true);
- if (themeResources != null)
- bundleResources: while (themeResources.hasMoreElements()) {
- try {
- String name = themeResources.nextElement().getPath();
- if (name.endsWith("/"))
- continue bundleResources;
- String alias = "/" + getWebPath() + name;
-
- httpService.registerResources(alias, name, httpContext);
- if (log.isDebugEnabled())
- log.debug("Mapped " + name + " to alias " + alias);
-
- } catch (NamespaceException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
-
- // App UIs
- for (String appUiName : ui.keySet()) {
- AppUi appUi = ui.get(appUiName);
- appUi.apply(repository, application, this, appUiName);
-
- }
-
- }
-
- public void applySides(SimpleErgonomics simpleErgonomics) {
- simpleErgonomics.setHeader(header);
- simpleErgonomics.setLead(lead);
- simpleErgonomics.setEnd(end);
- simpleErgonomics.setFooter(footer);
- }
-
- public void register(BundleContext bundleContext, ApplicationConfiguration appConfig) {
- Hashtable<String, String> props = new Hashtable<>();
- props.put(CONTEXT_NAME, webPath);
- appConfigReg = bundleContext.registerService(ApplicationConfiguration.class, appConfig, props);
- }
-
- public void reload() {
- BundleContext bundleContext = appConfigReg.getReference().getBundle().getBundleContext();
- ApplicationConfiguration appConfig = bundleContext.getService(appConfigReg.getReference());
- appConfigReg.unregister();
- register(bundleContext, appConfig);
-
- // BundleContext bundleContext = (BundleContext)
- // getScriptEngine().get("bundleContext");
- // try {
- // Bundle bundle = bundleContext.getBundle();
- // bundle.stop();
- // bundle.start();
- // } catch (BundleException e) {
- // // TODO Auto-generated catch block
- // e.printStackTrace();
- // }
- }
-
- private static ResourceLoader createResourceLoader(final String resourceName) {
- return new ResourceLoader() {
- public InputStream getResourceAsStream(String resourceName) throws IOException {
- return getClass().getClassLoader().getResourceAsStream(resourceName);
- }
- };
- }
-
- public List<String> getResources() {
- return resources;
- }
-
- public AppUi newUi(String name) {
- if (ui.containsKey(name))
- throw new IllegalArgumentException("There is already an UI named " + name);
- AppUi appUi = new AppUi(this);
- // appUi.setApp(this);
- ui.put(name, appUi);
- return appUi;
- }
-
- public void addUi(String name, AppUi appUi) {
- if (ui.containsKey(name))
- throw new IllegalArgumentException("There is already an UI named " + name);
- // appUi.setApp(this);
- ui.put(name, appUi);
- }
-
- public void applyBranding(Map<String, String> properties) {
- if (themeId != null)
- properties.put(WebClient.THEME_ID, themeId);
- if (additionalHeaders != null)
- properties.put(WebClient.HEAD_HTML, additionalHeaders);
- if (bodyHtml != null)
- properties.put(WebClient.BODY_HTML, bodyHtml);
- if (pageTitle != null)
- properties.put(WebClient.PAGE_TITLE, pageTitle);
- if (pageOverflow != null)
- properties.put(WebClient.PAGE_OVERFLOW, pageOverflow);
- if (favicon != null)
- properties.put(WebClient.FAVICON, favicon);
- }
-
- class CmsExceptionHandler implements ExceptionHandler {
-
- @Override
- public void handleException(Throwable throwable) {
- // TODO be smarter
- CmsUiUtils.getCmsView().exception(throwable);
- }
-
- }
-
- // public Branding getBranding() {
- // return branding;
- // }
-
- ScriptEngine getScriptEngine() {
- return scriptEngine;
- }
-
- public static String toJson(Node node) {
- try {
- StringBuilder sb = new StringBuilder();
- sb.append('{');
- PropertyIterator pit = node.getProperties();
- int count = 0;
- while (pit.hasNext()) {
- Property p = pit.nextProperty();
- int type = p.getType();
- if (type == PropertyType.REFERENCE || type == PropertyType.WEAKREFERENCE || type == PropertyType.PATH) {
- Node ref = p.getNode();
- if (count != 0)
- sb.append(',');
- // TODO limit depth?
- sb.append(toJson(ref));
- count++;
- } else if (!p.isMultiple()) {
- if (count != 0)
- sb.append(',');
- sb.append('\"').append(p.getName()).append("\":\"").append(p.getString()).append('\"');
- count++;
- }
- }
- sb.append('}');
- return sb.toString();
- } catch (RepositoryException e) {
- throw new CmsException("Cannot convert " + node + " to JSON", e);
- }
- }
-
- public void fromJson(Node node, String json) {
- // TODO
- }
-
- public CmsTheme getTheme() {
- return theme;
- }
-
- public void setTheme(CmsTheme theme) {
- this.theme = theme;
- }
-
- public String getWebPath() {
- return webPath;
- }
-
- public void setWebPath(String context) {
- this.webPath = context;
- }
-
- public String getRepo() {
- return repo;
- }
-
- public void setRepo(String repo) {
- this.repo = repo;
- }
-
- public Map<String, AppUi> getUi() {
- return ui;
- }
-
- public void setUi(Map<String, AppUi> ui) {
- this.ui = ui;
- }
-
- // Branding
- public String getThemeId() {
- return themeId;
- }
-
- public void setThemeId(String themeId) {
- this.themeId = themeId;
- }
-
- public String getAdditionalHeaders() {
- return additionalHeaders;
- }
-
- public void setAdditionalHeaders(String additionalHeaders) {
- this.additionalHeaders = additionalHeaders;
- }
-
- public String getBodyHtml() {
- return bodyHtml;
- }
-
- public void setBodyHtml(String bodyHtml) {
- this.bodyHtml = bodyHtml;
- }
-
- public String getPageTitle() {
- return pageTitle;
- }
-
- public void setPageTitle(String pageTitle) {
- this.pageTitle = pageTitle;
- }
-
- public String getPageOverflow() {
- return pageOverflow;
- }
-
- public void setPageOverflow(String pageOverflow) {
- this.pageOverflow = pageOverflow;
- }
-
- public String getFavicon() {
- return favicon;
- }
-
- public void setFavicon(String favicon) {
- this.favicon = favicon;
- }
-
- public CmsUiProvider getHeader() {
- return header;
- }
-
- public void setHeader(CmsUiProvider header) {
- this.header = header;
- }
-
- public Integer getHeaderHeight() {
- return headerHeight;
- }
-
- public void setHeaderHeight(Integer headerHeight) {
- this.headerHeight = headerHeight;
- }
-
- public CmsUiProvider getLead() {
- return lead;
- }
-
- public void setLead(CmsUiProvider lead) {
- this.lead = lead;
- }
-
- public CmsUiProvider getEnd() {
- return end;
- }
-
- public void setEnd(CmsUiProvider end) {
- this.end = end;
- }
-
- public CmsUiProvider getFooter() {
- return footer;
- }
-
- public void setFooter(CmsUiProvider footer) {
- this.footer = footer;
- }
-
- static class BundleHttpContext implements HttpContext {
- private BundleContext bundleContext;
-
- public BundleHttpContext(BundleContext bundleContext) {
- super();
- this.bundleContext = bundleContext;
- }
-
- @Override
- public boolean handleSecurity(HttpServletRequest request, HttpServletResponse response) throws IOException {
- // TODO Auto-generated method stub
- return true;
- }
-
- @Override
- public URL getResource(String name) {
-
- return bundleContext.getBundle().getEntry(name);
- }
-
- @Override
- public String getMimeType(String name) {
- return null;
- }
-
- }
-
-}
+++ /dev/null
-package org.argeo.cms.ui.script;
-
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.Reader;
-import java.net.URL;
-
-import javax.jcr.Repository;
-import javax.script.ScriptEngine;
-import javax.script.ScriptEngineManager;
-import javax.script.ScriptException;
-
-import org.argeo.api.cms.CmsLog;
-import org.argeo.cms.CmsException;
-import org.eclipse.rap.rwt.application.Application;
-import org.eclipse.rap.rwt.application.ApplicationConfiguration;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.BundleException;
-import org.osgi.framework.wiring.BundleWiring;
-
-public class CmsScriptRwtApplication implements ApplicationConfiguration {
- public final static String APP = "APP";
- public final static String BC = "BC";
-
- private final CmsLog log = CmsLog.getLog(CmsScriptRwtApplication.class);
-
- BundleContext bundleContext;
- Repository repository;
-
- ScriptEngine engine;
-
- public void init(BundleContext bundleContext) {
- this.bundleContext = bundleContext;
- ClassLoader bundleCl = bundleContext.getBundle().adapt(BundleWiring.class).getClassLoader();
- ClassLoader originalCcl = Thread.currentThread().getContextClassLoader();
- try {
-// Thread.currentThread().setContextClassLoader(bundleCl);// GraalVM needs it to be before creating manager
-// ScriptEngineManager scriptEngineManager = new ScriptEngineManager(bundleCl);
-// engine = scriptEngineManager.getEngineByName("JavaScript");
-// if (engine == null) {// Nashorn
-// Thread.currentThread().setContextClassLoader(originalCcl);
-// scriptEngineManager = new ScriptEngineManager();
-// Thread.currentThread().setContextClassLoader(bundleCl);
-// engine = scriptEngineManager.getEngineByName("JavaScript");
-// }
- engine = loadScriptEngine(originalCcl, bundleCl);
-
- // Load script
- URL appUrl = bundleContext.getBundle().getEntry("cms/app.js");
- // System.out.println("Loading " + appUrl);
- // System.out.println("Loading " + appUrl.getHost());
- // System.out.println("Loading " + appUrl.getPath());
-
- CmsScriptApp app = new CmsScriptApp(engine);
- engine.put(APP, app);
- engine.put(BC, bundleContext);
- try (Reader reader = new InputStreamReader(appUrl.openStream())) {
- engine.eval(reader);
- } catch (IOException | ScriptException e) {
- throw new CmsException("Cannot execute " + appUrl, e);
- }
-
- if (log.isDebugEnabled())
- log.debug("CMS script app initialized from " + appUrl);
-
- } catch (Exception e) {
- e.printStackTrace();
- } finally {
- Thread.currentThread().setContextClassLoader(originalCcl);
- }
- }
-
- public void destroy(BundleContext bundleContext) {
- engine = null;
- }
-
- @Override
- public void configure(Application application) {
- load(application);
- }
-
- void load(Application application) {
- CmsScriptApp app = getApp();
- app.apply(bundleContext, repository, application);
- if (log.isDebugEnabled())
- log.debug("CMS script app loaded to " + app.getWebPath());
- }
-
- CmsScriptApp getApp() {
- if (engine == null)
- throw new IllegalStateException("CMS script app is not initialized");
- return (CmsScriptApp) engine.get(APP);
- }
-
- void update() {
-
- try {
- bundleContext.getBundle().update();
- } catch (BundleException e) {
- e.printStackTrace();
- }
- }
-
- public void setRepository(Repository repository) {
- this.repository = repository;
- }
-
- private static ScriptEngine loadScriptEngine(ClassLoader originalCcl, ClassLoader bundleCl) {
- Thread.currentThread().setContextClassLoader(bundleCl);// GraalVM needs it to be before creating manager
- ScriptEngineManager scriptEngineManager = new ScriptEngineManager(bundleCl);
- ScriptEngine engine = scriptEngineManager.getEngineByName("JavaScript");
- if (engine == null) {// Nashorn
- Thread.currentThread().setContextClassLoader(originalCcl);
- scriptEngineManager = new ScriptEngineManager();
- Thread.currentThread().setContextClassLoader(bundleCl);
- engine = scriptEngineManager.getEngineByName("JavaScript");
- }
- return engine;
- }
-}
+++ /dev/null
-package org.argeo.cms.ui.script;
-
-import javax.jcr.Repository;
-
-import org.argeo.api.cms.CmsLog;
-import org.osgi.framework.BundleActivator;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.FrameworkUtil;
-import org.osgi.framework.ServiceReference;
-import org.osgi.util.tracker.ServiceTracker;
-
-public class ScriptAppActivator implements BundleActivator {
- private final static CmsLog log = CmsLog.getLog(ScriptAppActivator.class);
-
- @Override
- public void start(BundleContext context) throws Exception {
- try {
- CmsScriptRwtApplication appConfig = new CmsScriptRwtApplication();
- appConfig.init(context);
- CmsScriptApp app = appConfig.getApp();
- ServiceTracker<Repository, Repository> repoSt = new ServiceTracker<Repository, Repository>(context,
- FrameworkUtil.createFilter("(&" + app.getRepo() + "(objectClass=javax.jcr.Repository))"), null) {
-
- @Override
- public Repository addingService(ServiceReference<Repository> reference) {
- Repository repository = super.addingService(reference);
- appConfig.setRepository(repository);
- CmsScriptApp app = appConfig.getApp();
- app.register(context, appConfig);
- return repository;
- }
-
- };
- repoSt.open();
- } catch (Exception e) {
- log.error("Cannot initialise script bundle " + context.getBundle().getSymbolicName(), e);
- throw e;
- }
- }
-
- @Override
- public void stop(BundleContext context) throws Exception {
- }
-
-}
+++ /dev/null
-package org.argeo.cms.ui.script;
-
-import java.net.URL;
-
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-import javax.script.Invocable;
-import javax.script.ScriptEngine;
-import javax.script.ScriptException;
-
-import org.argeo.api.cms.CmsLog;
-import org.argeo.cms.ui.CmsUiProvider;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.osgi.framework.BundleContext;
-
-class ScriptUi implements CmsUiProvider {
- private final static CmsLog log = CmsLog.getLog(ScriptUi.class);
-
- private boolean development = true;
- private ScriptEngine scriptEngine;
-
- private URL appUrl;
- // private BundleContext bundleContext;
- // private String path;
-
- // private Bindings bindings;
- // private String script;
-
- public ScriptUi(BundleContext bundleContext,ScriptEngine scriptEngine, String path) {
- this.scriptEngine = scriptEngine;
-//// ScriptEngineManager scriptEngineManager = new ScriptEngineManager();
-// ClassLoader bundleCl = bundleContext.getBundle().adapt(BundleWiring.class).getClassLoader();
-// ClassLoader originalCcl = Thread.currentThread().getContextClassLoader();
-// try {
-//// Thread.currentThread().setContextClassLoader(bundleCl);
-//// scriptEngine = scriptEngineManager.getEngineByName("JavaScript");
-//// scriptEngine.put(CmsScriptRwtApplication.BC, bundleContext);
-// scriptEngine = CmsScriptRwtApplication.loadScriptEngine(originalCcl, bundleCl);
-//
-// } catch (Exception e) {
-// e.printStackTrace();
-// } finally {
-// Thread.currentThread().setContextClassLoader(originalCcl);
-// }
- this.appUrl = bundleContext.getBundle().getEntry(path);
- load();
- }
-
- private void load() {
-// try (Reader reader = new InputStreamReader(appUrl.openStream())) {
-// scriptEngine.eval(reader);
-// } catch (IOException | ScriptException e) {
-// log.warn("Cannot execute " + appUrl, e);
-// }
-
- try {
- scriptEngine.eval("load('" + appUrl + "')");
- } catch (ScriptException e) {
- log.warn("Cannot execute " + appUrl, e);
- }
-
- }
-
- // public ScriptUiProvider(ScriptEngine scriptEngine, String script) throws
- // ScriptException {
- // super();
- // this.scriptEngine = scriptEngine;
- // this.script = script;
- // bindings = scriptEngine.createBindings();
- // scriptEngine.eval(script, bindings);
- // }
-
- @Override
- public Control createUi(Composite parent, Node context) throws RepositoryException {
- long begin = System.currentTimeMillis();
- // if (bindings == null) {
- // bindings = scriptEngine.createBindings();
- // try {
- // scriptEngine.eval(script, bindings);
- // } catch (ScriptException e) {
- // log.warn("Cannot evaluate script", e);
- // }
- // }
- // Bindings bindings = scriptEngine.createBindings();
- // bindings.put("parent", parent);
- // bindings.put("context", context);
- // URL appUrl = bundleContext.getBundle().getEntry(path);
- // try (Reader reader = new InputStreamReader(appUrl.openStream())) {
- // scriptEngine.eval(reader,bindings);
- // } catch (IOException | ScriptException e) {
- // log.warn("Cannot execute " + appUrl, e);
- // }
-
- if (development)
- load();
-
- Invocable invocable = (Invocable) scriptEngine;
- try {
- invocable.invokeFunction("createUi", parent, context);
- } catch (NoSuchMethodException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- } catch (ScriptException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
-
- long duration = System.currentTimeMillis() - begin;
- if (log.isTraceEnabled())
- log.trace(appUrl + " UI in " + duration + " ms");
- return null;
- }
-
-}
+++ /dev/null
-// CMS
-var ScrolledPage = Java.type('org.argeo.cms.ui.widgets.ScrolledPage');
-
-var CmsScriptApp = Java.type('org.argeo.cms.ui.script.CmsScriptApp');
-var AppUi = Java.type('org.argeo.cms.ui.script.AppUi');
-var Theme = Java.type('org.argeo.cms.ui.script.Theme');
-var ScriptUi = Java.type('org.argeo.cms.ui.script.ScriptUi');
-var CmsUtils = Java.type('org.argeo.cms.ui.util.CmsUiUtils');
-var SimpleCmsHeader = Java.type('org.argeo.cms.ui.util.SimpleCmsHeader');
-var CmsLink = Java.type('org.argeo.cms.ui.util.CmsLink');
-var MenuLink = Java.type('org.argeo.cms.ui.util.MenuLink');
-var UserMenuLink = Java.type('org.argeo.cms.ui.util.UserMenuLink');
-
-// SWT
-var SWT = Java.type('org.eclipse.swt.SWT');
-var Composite = Java.type('org.eclipse.swt.widgets.Composite');
-var Label = Java.type('org.eclipse.swt.widgets.Label');
-var Button = Java.type('org.eclipse.swt.widgets.Button');
-var Text = Java.type('org.eclipse.swt.widgets.Text');
-var Browser = Java.type('org.eclipse.swt.browser.Browser');
-
-var FillLayout = Java.type('org.eclipse.swt.layout.FillLayout');
-var GridLayout = Java.type('org.eclipse.swt.layout.GridLayout');
-var RowLayout = Java.type('org.eclipse.swt.layout.RowLayout');
-var FormLayout = Java.type('org.eclipse.swt.layout.FormLayout');
-var GridData = Java.type('org.eclipse.swt.layout.GridData');
-
-function loadNode(node) {
- var json = CmsScriptApp.toJson(node)
- var fromJson = JSON.parse(json)
- return fromJson
-}
-
-function newArea(parent, style, layout) {
- var control = new Composite(parent, SWT.NONE)
- control.setLayout(layout)
- CmsUtils.style(control, style)
- return control
-}
-
-function newLabel(parent, style, text) {
- var control = new Label(parent, SWT.WRAP)
- control.setText(text)
- CmsUtils.style(control, style)
- CmsUtils.markup(control)
- return control
-}
-
-function newButton(parent, style, text) {
- var control = new Button(parent, SWT.FLAT)
- control.setText(text)
- CmsUtils.style(control, style)
- CmsUtils.markup(control)
- return control
-}
-
-function newFormLabel(parent, style, text) {
- return newLabel(parent, style, '<b>' + text + '</b>')
-}
-
-function newText(parent, style, msg) {
- var control = new Text(parent, SWT.NONE)
- control.setMessage(msg)
- CmsUtils.style(control, style)
- return control
-}
-
-function newScrolledPage(parent) {
- var scrolled = new ScrolledPage(parent, SWT.NONE)
- scrolled.setLayoutData(CmsUtils.fillAll())
- scrolled.setLayout(CmsUtils.noSpaceGridLayout())
- var page = new Composite(scrolled, SWT.NONE)
- page.setLayout(CmsUtils.noSpaceGridLayout())
- page.setBackgroundMode(SWT.INHERIT_NONE)
- return page
-}
-
-function gridData(control) {
- var gridData = new GridData()
- control.setLayoutData(gridData)
- return gridData
-}
-
-function gridData(control, hAlign, vAlign) {
- var gridData = new GridData(hAlign, vAlign, false, false)
- control.setLayoutData(gridData)
- return gridData
-}
-
-// print(__FILE__, __LINE__, __DIR__)
+++ /dev/null
-/** Argeo CMS user interface scripting. */
-package org.argeo.cms.ui.script;
\ No newline at end of file
+++ /dev/null
-package org.argeo.cms.web;
-
-import static org.argeo.util.naming.SharedSecret.X_SHARED_SECRET;
-
-import java.io.IOException;
-import java.security.PrivilegedAction;
-import java.util.HashMap;
-import java.util.Map;
-
-import javax.jcr.Node;
-import javax.jcr.PathNotFoundException;
-import javax.jcr.Property;
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.nodetype.NodeType;
-import javax.security.auth.Subject;
-import javax.security.auth.callback.Callback;
-import javax.security.auth.callback.UnsupportedCallbackException;
-import javax.security.auth.login.LoginContext;
-import javax.security.auth.login.LoginException;
-import javax.servlet.http.HttpServletRequest;
-
-import org.argeo.api.cms.CmsView;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.api.cms.CmsAuth;
-import org.argeo.cms.CmsException;
-import org.argeo.cms.auth.CurrentUser;
-import org.argeo.cms.auth.RemoteAuthCallback;
-import org.argeo.cms.auth.RemoteAuthCallbackHandler;
-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.argeo.jcr.JcrUtils;
-import org.argeo.util.naming.AuthPassword;
-import org.argeo.util.naming.SharedSecret;
-import org.eclipse.rap.rwt.RWT;
-import org.eclipse.rap.rwt.application.AbstractEntryPoint;
-import org.eclipse.rap.rwt.client.WebClient;
-import org.eclipse.rap.rwt.client.service.BrowserNavigation;
-import org.eclipse.rap.rwt.client.service.BrowserNavigationEvent;
-import org.eclipse.rap.rwt.client.service.BrowserNavigationListener;
-import org.eclipse.rap.rwt.client.service.JavaScriptExecutor;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Display;
-import org.eclipse.swt.widgets.Shell;
-
-/** Manages history and navigation */
-@Deprecated
-public abstract class AbstractCmsEntryPoint extends AbstractEntryPoint implements CmsView {
- private static final long serialVersionUID = 906558779562569784L;
-
- private final CmsLog log = CmsLog.getLog(AbstractCmsEntryPoint.class);
-
- // private final Subject subject;
- private LoginContext loginContext;
-
- private final Repository repository;
- private final String workspace;
- private final String defaultPath;
- private final Map<String, String> factoryProperties;
-
- // Current state
- private Session session;
- private Node node;
- private String nodePath;// useful when changing auth
- private String state;
- private Throwable exception;
-
- // Client services
- private final JavaScriptExecutor jsExecutor;
- private final BrowserNavigation browserNavigation;
-
- public AbstractCmsEntryPoint(Repository repository, String workspace, String defaultPath,
- Map<String, String> factoryProperties) {
- this.repository = repository;
- this.workspace = workspace;
- this.defaultPath = defaultPath;
- this.factoryProperties = new HashMap<String, String>(factoryProperties);
- // subject = new Subject();
-
- // Initial login
- LoginContext lc;
- try {
- lc = new LoginContext(CmsAuth.LOGIN_CONTEXT_USER,
- new RemoteAuthCallbackHandler(new ServletHttpRequest(UiContext.getHttpRequest()),
- new ServletHttpResponse(UiContext.getHttpResponse())));
- lc.login();
- } catch (LoginException e) {
- try {
- lc = new LoginContext(CmsAuth.LOGIN_CONTEXT_ANONYMOUS);
- lc.login();
- } catch (LoginException e1) {
- throw new CmsException("Cannot log in as anonymous", e1);
- }
- }
- authChange(lc);
-
- jsExecutor = RWT.getClient().getService(JavaScriptExecutor.class);
- browserNavigation = RWT.getClient().getService(BrowserNavigation.class);
- if (browserNavigation != null)
- browserNavigation.addBrowserNavigationListener(new CmsNavigationListener());
- }
-
- @Override
- protected Shell createShell(Display display) {
- Shell shell = super.createShell(display);
- shell.setData(RWT.CUSTOM_VARIANT, CmsStyles.CMS_SHELL);
- display.disposeExec(new Runnable() {
-
- @Override
- public void run() {
- if (log.isTraceEnabled())
- log.trace("Logging out " + session);
- JcrUtils.logoutQuietly(session);
- }
- });
- return shell;
- }
-
- @Override
- protected final void createContents(final Composite parent) {
- // UiContext.setData(CmsView.KEY, this);
- CmsSwtUtils.registerCmsView(parent.getShell(), this);
- Subject.doAs(getSubject(), new PrivilegedAction<Void>() {
- @Override
- public Void run() {
- try {
- initUi(parent);
- } catch (Exception e) {
- throw new CmsException("Cannot create entrypoint contents", e);
- }
- return null;
- }
- });
- }
-
- /** Create UI */
- protected abstract void initUi(Composite parent);
-
- /** Recreate UI after navigation or auth change */
- protected abstract void refresh();
-
- /**
- * The node to return when no node was found (for authenticated users and
- * anonymous)
- */
-// private Node getDefaultNode(Session session) throws RepositoryException {
-// if (!session.hasPermission(defaultPath, "read")) {
-// String userId = session.getUserID();
-// if (userId.equals(NodeConstants.ROLE_ANONYMOUS))
-// // TODO throw a special exception
-// throw new CmsException("Login required");
-// else
-// throw new CmsException("Unauthorized");
-// }
-// return session.getNode(defaultPath);
-// }
-
- protected String getBaseTitle() {
- return factoryProperties.get(WebClient.PAGE_TITLE);
- }
-
- public void navigateTo(String state) {
- exception = null;
- String title = setState(state);
- doRefresh();
- if (browserNavigation != null)
- browserNavigation.pushState(state, title);
- }
-
- // @Override
- // public synchronized Subject getSubject() {
- // return subject;
- // }
-
- // @Override
- // public LoginContext getLoginContext() {
- // return loginContext;
- // }
- protected Subject getSubject() {
- return loginContext.getSubject();
- }
-
- @Override
- public boolean isAnonymous() {
- return CurrentUser.isAnonymous(getSubject());
- }
-
- @Override
- public synchronized void logout() {
- if (loginContext == null)
- throw new CmsException("Login context should not be null");
- try {
- CurrentUser.logoutCmsSession(loginContext.getSubject());
- loginContext.logout();
- LoginContext anonymousLc = new LoginContext(CmsAuth.LOGIN_CONTEXT_ANONYMOUS);
- anonymousLc.login();
- authChange(anonymousLc);
- } catch (LoginException e) {
- log.error("Cannot logout", e);
- }
- }
-
- @Override
- public synchronized void authChange(LoginContext lc) {
- if (lc == null)
- throw new CmsException("Login context cannot be null");
- // logout previous login context
- if (this.loginContext != null)
- try {
- this.loginContext.logout();
- } catch (LoginException e1) {
- log.warn("Could not log out: " + e1);
- }
- this.loginContext = lc;
- Subject.doAs(getSubject(), new PrivilegedAction<Void>() {
-
- @Override
- public Void run() {
- try {
- JcrUtils.logoutQuietly(session);
- session = repository.login(workspace);
- if (nodePath != null)
- try {
- node = session.getNode(nodePath);
- } catch (PathNotFoundException e) {
- navigateTo("~");
- }
-
- // refresh UI
- doRefresh();
- } catch (RepositoryException e) {
- throw new CmsException("Cannot perform auth change", e);
- }
- return null;
- }
-
- });
- }
-
- @Override
- public void exception(final Throwable e) {
- AbstractCmsEntryPoint.this.exception = e;
- log.error("Unexpected exception in CMS", e);
- doRefresh();
- }
-
- protected synchronized void doRefresh() {
- Subject.doAs(getSubject(), new PrivilegedAction<Void>() {
- @Override
- public Void run() {
- refresh();
- return null;
- }
- });
- }
-
- /** Sets the state of the entry point and retrieve the related JCR node. */
- protected synchronized String setState(String newState) {
- String previousState = this.state;
-
- String newNodePath = null;
- String prefix = null;
- this.state = newState;
- if (newState.equals("~"))
- this.state = "";
-
- try {
- int firstSlash = state.indexOf('/');
- if (firstSlash == 0) {
- newNodePath = state;
- prefix = "";
- } else if (firstSlash > 0) {
- prefix = state.substring(0, firstSlash);
- newNodePath = state.substring(firstSlash);
- } else {
- newNodePath = defaultPath;
- prefix = state;
-
- }
-
- // auth
- int colonIndex = prefix.indexOf('$');
- if (colonIndex > 0) {
- SharedSecret token = new SharedSecret(new AuthPassword(X_SHARED_SECRET + '$' + prefix)) {
-
- @Override
- public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
- super.handle(callbacks);
- // handle HTTP context
- for (Callback callback : callbacks) {
- if (callback instanceof RemoteAuthCallback) {
- ((RemoteAuthCallback) callback)
- .setRequest(new ServletHttpRequest(UiContext.getHttpRequest()));
- ((RemoteAuthCallback) callback)
- .setResponse(new ServletHttpResponse(UiContext.getHttpResponse()));
- }
- }
- }
- };
- LoginContext lc = new LoginContext(CmsAuth.LOGIN_CONTEXT_USER, token);
- lc.login();
- authChange(lc);// sets the node as well
- // } else {
- // // TODO check consistency
- // }
- } else {
- Node newNode = null;
- if (session.nodeExists(newNodePath))
- newNode = session.getNode(newNodePath);
- else {
-// throw new CmsException("Data " + newNodePath + " does not exist");
- newNode = null;
- }
- setNode(newNode);
- }
- String title = publishMetaData(getNode());
-
- if (log.isTraceEnabled())
- log.trace("node=" + newNodePath + ", state=" + state + " (prefix=" + prefix + ")");
-
- return title;
- } catch (Exception e) {
- log.error("Cannot set state '" + state + "'", e);
- if (state.equals("") || newState.equals("~") || newState.equals(previousState))
- return "Unrecoverable exception : " + e.getClass().getSimpleName();
- if (previousState.equals(""))
- previousState = "~";
- navigateTo(previousState);
- throw new CmsException("Unexpected issue when accessing #" + newState, e);
- }
- }
-
- private String publishMetaData(Node node) throws RepositoryException {
- // Title
- String title;
- if (node != null && node.isNodeType(NodeType.MIX_TITLE) && node.hasProperty(Property.JCR_TITLE))
- title = node.getProperty(Property.JCR_TITLE).getString() + " - " + getBaseTitle();
- else
- title = getBaseTitle();
-
- HttpServletRequest request = UiContext.getHttpRequest();
- if (request == null)
- return null;
-
- StringBuilder js = new StringBuilder();
- if (title == null)
- title = "";
- title = title.replace("'", "\\'");// sanitize
- js.append("document.title = '" + title + "';");
- jsExecutor.execute(js.toString());
- return title;
- }
-
- // Simply remove some illegal character
- // private String clean(String stringToClean) {
- // return stringToClean.replaceAll("'", "").replaceAll("\\n", "")
- // .replaceAll("\\t", "");
- // }
-
- protected synchronized Node getNode() {
- return node;
- }
-
- private synchronized void setNode(Node node) throws RepositoryException {
- this.node = node;
- this.nodePath = node == null ? null : node.getPath();
- }
-
- protected String getState() {
- return state;
- }
-
- protected Throwable getException() {
- return exception;
- }
-
- protected void resetException() {
- exception = null;
- }
-
- protected Session getSession() {
- return session;
- }
-
- private class CmsNavigationListener implements BrowserNavigationListener {
- private static final long serialVersionUID = -3591018803430389270L;
-
- @Override
- public void navigated(BrowserNavigationEvent event) {
- setState(event.getState());
- doRefresh();
- }
- }
-}
\ No newline at end of file
+++ /dev/null
-package org.argeo.cms.web;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.URL;
-
-import org.eclipse.rap.rwt.service.ResourceLoader;
-import org.osgi.framework.Bundle;
-
-/** {@link ResourceLoader} implementation wrapping an {@link Bundle}. */
-public class BundleResourceLoader implements ResourceLoader {
- private final Bundle bundle;
-
- public BundleResourceLoader(Bundle bundle) {
- this.bundle = bundle;
- }
-
- @Override
- public InputStream getResourceAsStream(String resourceName) throws IOException {
- URL res = bundle.getEntry(resourceName);
- if (res == null) {
- res = bundle.getResource(resourceName);
- if (res == null)
- throw new IllegalArgumentException(
- "Resource " + resourceName + " not found in bundle " + bundle.getSymbolicName());
- }
- return res.openStream();
- }
-
- public Bundle getBundle() {
- return bundle;
- }
-
-}
+++ /dev/null
-package org.argeo.cms.web;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-import org.argeo.api.cms.CmsTheme;
-import org.eclipse.rap.rwt.service.ResourceLoader;
-
-/** A RAP {@link ResourceLoader} based on a {@link CmsTheme}. */
-public class CmsThemeResourceLoader implements ResourceLoader {
- private final CmsTheme theme;
-
- public CmsThemeResourceLoader(CmsTheme theme) {
- super();
- this.theme = theme;
- }
-
- @Override
- public InputStream getResourceAsStream(String resourceName) throws IOException {
- return theme.getResourceAsStream(resourceName);
- }
-
-}
+++ /dev/null
-package org.argeo.cms.web;
-
-import java.util.Dictionary;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Set;
-
-import org.argeo.api.cms.CmsApp;
-import org.argeo.api.cms.CmsAppListener;
-import org.argeo.api.cms.CmsTheme;
-import org.argeo.api.cms.CmsView;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.util.LangUtils;
-import org.eclipse.rap.rwt.RWT;
-import org.eclipse.rap.rwt.application.Application;
-import org.eclipse.rap.rwt.application.Application.OperationMode;
-import org.eclipse.rap.rwt.application.ApplicationConfiguration;
-import org.eclipse.rap.rwt.application.ExceptionHandler;
-import org.eclipse.rap.rwt.client.WebClient;
-import org.eclipse.swt.widgets.Display;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.Constants;
-import org.osgi.framework.ServiceRegistration;
-import org.osgi.service.event.EventAdmin;
-
-/** An RWT web app integrating with a {@link CmsApp}. */
-public class CmsWebApp implements ApplicationConfiguration, ExceptionHandler, CmsAppListener {
- private final static CmsLog log = CmsLog.getLog(CmsWebApp.class);
-
- private BundleContext bundleContext;
- private CmsApp cmsApp;
- private String cmsAppId;
- private EventAdmin eventAdmin;
-
- private ServiceRegistration<ApplicationConfiguration> rwtAppReg;
-
- private final static String CONTEXT_NAME = "contextName";
- private String contextName;
-
- private final static String FAVICON_PNG = "favicon.png";
-
- public void init(BundleContext bundleContext, Map<String, String> properties) {
- this.bundleContext = bundleContext;
- contextName = properties.get(CONTEXT_NAME);
- if (cmsApp != null) {
- if (cmsApp.allThemesAvailable())
- publishWebApp();
- }
- }
-
- public void destroy(BundleContext bundleContext, Map<String, String> properties) {
- if (cmsApp != null) {
- cmsApp.removeCmsAppListener(this);
- cmsApp = null;
- }
- }
-
- @Override
- public void configure(Application application) {
- // TODO make it configurable?
- // SWT compatibility is required for:
- // - Browser.execute()
- // - blocking dialogs
- application.setOperationMode(OperationMode.SWT_COMPATIBILITY);
- for (String uiName : cmsApp.getUiNames()) {
- CmsTheme theme = cmsApp.getTheme(uiName);
- if (theme != null)
- WebThemeUtils.apply(application, theme);
- }
-
- Map<String, String> properties = new HashMap<>();
- addEntryPoints(application, properties);
- application.setExceptionHandler(this);
- }
-
- @Override
- public void handleException(Throwable throwable) {
- Display display = Display.getCurrent();
- if (display != null && !display.isDisposed()) {
- CmsView cmsView = CmsSwtUtils.getCmsView(display.getActiveShell());
- cmsView.exception(throwable);
- } else {
- log.error("Unexpected exception outside an UI thread", throwable);
- }
-
- }
-
- protected void addEntryPoints(Application application, Map<String, String> commonProperties) {
- for (String uiName : cmsApp.getUiNames()) {
- Map<String, String> properties = new HashMap<>(commonProperties);
- CmsTheme theme = cmsApp.getTheme(uiName);
- if (theme != null) {
- properties.put(WebClient.THEME_ID, theme.getThemeId());
- properties.put(WebClient.HEAD_HTML, theme.getHtmlHeaders());
- properties.put(WebClient.BODY_HTML, theme.getBodyHtml());
- Set<String> imagePaths = theme.getImagesPaths();
- if (imagePaths.contains(FAVICON_PNG)) {
- properties.put(WebClient.FAVICON, FAVICON_PNG);
- }
- } else {
- properties.put(WebClient.THEME_ID, RWT.DEFAULT_THEME_ID);
- }
- String entryPointName = !uiName.equals("") ? "/" + uiName : "/";
- application.addEntryPoint(entryPointName, () -> {
- CmsWebEntryPoint entryPoint = new CmsWebEntryPoint(this, uiName);
- entryPoint.setEventAdmin(eventAdmin);
- return entryPoint;
- }, properties);
- if (log.isDebugEnabled())
- log.info("Added web entry point " + (contextName != null ? "/" + contextName : "") + entryPointName);
- }
-// if (log.isDebugEnabled())
-// log.debug("Published CMS web app /" + (contextName != null ? contextName : ""));
- }
-
- CmsApp getCmsApp() {
- return cmsApp;
- }
-
- BundleContext getBundleContext() {
- return bundleContext;
- }
-
- public void setCmsApp(CmsApp cmsApp, Map<String, String> properties) {
- this.cmsApp = cmsApp;
- this.cmsAppId = properties.get(Constants.SERVICE_PID);
- this.cmsApp.addCmsAppListener(this);
- }
-
- public void unsetCmsApp(CmsApp cmsApp, Map<String, String> properties) {
- String cmsAppId = properties.get(Constants.SERVICE_PID);
- if (!cmsAppId.equals(this.cmsAppId))
- return;
- if (this.cmsApp != null) {
- this.cmsApp.removeCmsAppListener(this);
- }
- if (rwtAppReg != null)
- rwtAppReg.unregister();
- this.cmsApp = null;
- }
-
- @Override
- public void themingUpdated() {
- if (cmsApp != null && cmsApp.allThemesAvailable())
- publishWebApp();
- }
-
- protected void publishWebApp() {
- Dictionary<String, Object> regProps = LangUtils.dict(CONTEXT_NAME, contextName);
- if (rwtAppReg != null)
- rwtAppReg.unregister();
- if (bundleContext != null) {
- rwtAppReg = bundleContext.registerService(ApplicationConfiguration.class, this, regProps);
- if (log.isDebugEnabled())
- log.debug("Publishing CMS web app /" + (contextName != null ? contextName : "") + " ...");
- }
- }
-
- public void setEventAdmin(EventAdmin eventAdmin) {
- this.eventAdmin = eventAdmin;
- }
-
-}
+++ /dev/null
-package org.argeo.cms.web;
-
-import static org.eclipse.rap.rwt.internal.service.ContextProvider.getApplicationContext;
-
-import java.security.PrivilegedAction;
-import java.util.HashMap;
-import java.util.Locale;
-import java.util.Map;
-import java.util.UUID;
-
-import javax.security.auth.Subject;
-import javax.security.auth.login.LoginContext;
-import javax.security.auth.login.LoginException;
-
-import org.argeo.api.cms.CmsApp;
-import org.argeo.api.cms.CmsAuth;
-import org.argeo.api.cms.CmsImageManager;
-import org.argeo.api.cms.CmsSession;
-import org.argeo.api.cms.CmsUi;
-import org.argeo.api.cms.CmsView;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.api.cms.UxContext;
-import org.argeo.cms.LocaleUtils;
-import org.argeo.cms.auth.CurrentUser;
-import org.argeo.cms.auth.RemoteAuthCallbackHandler;
-import org.argeo.cms.osgi.CmsOsgiUtils;
-import org.argeo.cms.servlet.ServletHttpRequest;
-import org.argeo.cms.servlet.ServletHttpResponse;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.cms.swt.SimpleSwtUxContext;
-import org.argeo.cms.swt.dialogs.CmsFeedback;
-import org.argeo.cms.ui.util.DefaultImageManager;
-import org.argeo.eclipse.ui.specific.UiContext;
-import org.eclipse.rap.rwt.RWT;
-import org.eclipse.rap.rwt.application.EntryPoint;
-import org.eclipse.rap.rwt.client.service.BrowserNavigation;
-import org.eclipse.rap.rwt.client.service.BrowserNavigationEvent;
-import org.eclipse.rap.rwt.client.service.BrowserNavigationListener;
-import org.eclipse.rap.rwt.internal.lifecycle.RWTLifeCycle;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.SWTError;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Display;
-import org.eclipse.swt.widgets.Shell;
-import org.osgi.service.event.Event;
-import org.osgi.service.event.EventAdmin;
-
-/** The {@link CmsView} for a {@link CmsWebApp}. */
-@SuppressWarnings("restriction")
-public class CmsWebEntryPoint implements EntryPoint, CmsView, BrowserNavigationListener {
- private static final long serialVersionUID = 7733510691684570402L;
- private final static CmsLog log = CmsLog.getLog(CmsWebEntryPoint.class);
-
- private EventAdmin eventAdmin;
-
- private final CmsWebApp cmsWebApp;
- private final String uiName;
-
- private LoginContext loginContext;
- private String state;
- private Throwable exception;
- private UxContext uxContext;
- private CmsImageManager imageManager;
-
- private Display display;
- private CmsUi ui;
-
- private String uid;
-
- // Client services
- // private final JavaScriptExecutor jsExecutor;
- private final BrowserNavigation browserNavigation;
-
- /** Experimental OS-like multi windows. */
- private boolean multipleShells = false;
-
- public CmsWebEntryPoint(CmsWebApp cmsWebApp, String uiName) {
- assert cmsWebApp != null;
- assert uiName != null;
- this.cmsWebApp = cmsWebApp;
- this.uiName = uiName;
- uid = UUID.randomUUID().toString();
-
- // Initial login
- LoginContext lc;
- try {
- lc = new LoginContext(CmsAuth.LOGIN_CONTEXT_USER,
- new RemoteAuthCallbackHandler(new ServletHttpRequest(UiContext.getHttpRequest()),
- new ServletHttpResponse(UiContext.getHttpResponse())));
- lc.login();
- } catch (LoginException e) {
- try {
- lc = new LoginContext(CmsAuth.LOGIN_CONTEXT_ANONYMOUS);
- lc.login();
- } catch (LoginException e1) {
- throw new IllegalStateException("Cannot log in as anonymous", e1);
- }
- }
- authChange(lc);
-
- // jsExecutor = RWT.getClient().getService(JavaScriptExecutor.class);
- browserNavigation = RWT.getClient().getService(BrowserNavigation.class);
- if (browserNavigation != null)
- browserNavigation.addBrowserNavigationListener(this);
- }
-
- protected void createContents(Composite parent) {
- Subject.doAs(loginContext.getSubject(), new PrivilegedAction<Void>() {
- @Override
- public Void run() {
- try {
- uxContext = new SimpleSwtUxContext();
- imageManager = new DefaultImageManager();
- CmsSession cmsSession = getCmsSession();
- if (cmsSession != null) {
- UiContext.setLocale(cmsSession.getLocale());
- LocaleUtils.setThreadLocale(cmsSession.getLocale());
- } else {
- Locale rwtLocale = RWT.getUISession().getLocale();
- LocaleUtils.setThreadLocale(rwtLocale);
- }
- parent.setData(CmsApp.UI_NAME_PROPERTY, uiName);
- display = parent.getDisplay();
- ui = cmsWebApp.getCmsApp().initUi(parent);
- if (ui instanceof Composite)
- ((Composite) ui).setLayoutData(CmsSwtUtils.fillAll());
- // we need ui to be set before refresh so that CmsView can store UI context data
- // in it.
- cmsWebApp.getCmsApp().refreshUi(ui, null);
- } catch (Exception e) {
- throw new IllegalStateException("Cannot create entrypoint contents", e);
- }
- return null;
- }
- });
- }
-
- protected Subject getSubject() {
- return loginContext.getSubject();
- }
-
- public <T> T doAs(PrivilegedAction<T> action) {
- return Subject.doAs(getSubject(), action);
- }
-
- @Override
- public boolean isAnonymous() {
- return CurrentUser.isAnonymous(getSubject());
- }
-
- @Override
- public synchronized void logout() {
- if (loginContext == null)
- throw new IllegalArgumentException("Login context should not be null");
- try {
- CurrentUser.logoutCmsSession(loginContext.getSubject());
- loginContext.logout();
- LoginContext anonymousLc = new LoginContext(CmsAuth.LOGIN_CONTEXT_ANONYMOUS);
- anonymousLc.login();
- authChange(anonymousLc);
- } catch (LoginException e) {
- log.error("Cannot logout", e);
- }
- }
-
- @Override
- public synchronized void authChange(LoginContext lc) {
- if (lc == null)
- throw new IllegalArgumentException("Login context cannot be null");
- // logout previous login context
- if (this.loginContext != null)
- try {
- this.loginContext.logout();
- } catch (LoginException e1) {
- log.warn("Could not log out: " + e1);
- }
- this.loginContext = lc;
- doRefresh();
- }
-
- @Override
- public void exception(final Throwable e) {
- if (e instanceof SWTError) {
- SWTError swtError = (SWTError) e;
- if (swtError.code == SWT.ERROR_FUNCTION_DISPOSED)
- return;
- }
- display.syncExec(() -> {
-// CmsFeedback.show("Unexpected exception in CMS", e);
- exception = e;
-// log.error("Unexpected exception in CMS", e);
- doRefresh();
- });
- }
-
- protected synchronized void doRefresh() {
- if (ui != null)
- Subject.doAs(getSubject(), new PrivilegedAction<Void>() {
- @Override
- public Void run() {
- if (exception != null) {
- // TODO internationalise
- CmsFeedback.show("Unexpected exception", exception);
- exception = null;
- // TODO report
- }
- cmsWebApp.getCmsApp().refreshUi(ui, state);
- return null;
- }
- });
- }
-
- /** Sets the state of the entry point and retrieve the related JCR node. */
- protected String setState(String newState) {
- cmsWebApp.getCmsApp().setState(ui, newState);
- state = newState;
- return null;
- }
-
- @Override
- public UxContext getUxContext() {
- return uxContext;
- }
-
- @Override
- public String getUid() {
- return uid;
- }
-
- @Override
- public void navigateTo(String state) {
- exception = null;
- String title = setState(state);
- if (title != null)
- doRefresh();
- if (browserNavigation != null)
- browserNavigation.pushState(state, title);
- }
-
- public CmsImageManager getImageManager() {
- return imageManager;
- }
-
- @Override
- public void navigated(BrowserNavigationEvent event) {
- setState(event.getState());
- // doRefresh();
- }
-
- @Override
- public void sendEvent(String topic, Map<String, Object> properties) {
- if (properties == null)
- properties = new HashMap<>();
- if (properties.containsKey(CMS_VIEW_UID_PROPERTY) && !properties.get(CMS_VIEW_UID_PROPERTY).equals(uid))
- throw new IllegalArgumentException("Property " + CMS_VIEW_UID_PROPERTY + " is set to another CMS view uid ("
- + properties.get(CMS_VIEW_UID_PROPERTY) + ") then " + uid);
- properties.put(CMS_VIEW_UID_PROPERTY, uid);
- eventAdmin.sendEvent(new Event(topic, properties));
- }
-
- @Override
- public void stateChanged(String state, String title) {
- browserNavigation.pushState(state, title);
- }
-
- @Override
- public CmsSession getCmsSession() {
- CmsSession cmsSession = CmsOsgiUtils.getCmsSession(cmsWebApp.getBundleContext(), getSubject());
- return cmsSession;
- }
-
- @Override
- public Object getData(String key) {
- if (ui != null) {
- return ui.getData(key);
- } else {
- throw new IllegalStateException("UI is not initialized");
- }
- }
-
- @Override
- public void setData(String key, Object value) {
- if (ui != null) {
- ui.setData(key, value);
- } else {
- throw new IllegalStateException("UI is not initialized");
- }
- }
-
- /*
- * EntryPoint IMPLEMENTATION
- */
-
- @Override
- public int createUI() {
- Display display = new Display();
- Shell shell = createShell(display);
- shell.setLayout(CmsSwtUtils.noSpaceGridLayout());
- CmsSwtUtils.registerCmsView(shell, this);
- createContents(shell);
- shell.layout();
-// if (shell.getMaximized()) {
-// shell.layout();
-// } else {
-//// shell.pack();
-// }
- shell.open();
- if (getApplicationContext().getLifeCycleFactory().getLifeCycle() instanceof RWTLifeCycle) {
- eventLoop: while (!shell.isDisposed()) {
- try {
- if (!display.readAndDispatch()) {
- display.sleep();
- }
- } catch (Throwable e) {
- if (e instanceof SWTError) {
- SWTError swtError = (SWTError) e;
- if (swtError.code == SWT.ERROR_FUNCTION_DISPOSED) {
- log.error("Unexpected SWT error in event loop, ignoring it. " + e.getMessage());
- continue eventLoop;
- } else {
- log.error("Unexpected SWT error in event loop, shutting down...", e);
- break eventLoop;
- }
- } else if (e instanceof ThreadDeath) {
- throw (ThreadDeath) e;
- } else if (e instanceof Error) {
- log.error("Unexpected error in event loop, shutting down...", e);
- break eventLoop;
- } else {
- log.error("Unexpected exception in event loop, ignoring it. " + e.getMessage());
- continue eventLoop;
- }
- }
- }
- if (!display.isDisposed())
- display.dispose();
- }
- return 0;
- }
-
- protected Shell createShell(Display display) {
- Shell shell;
- if (!multipleShells) {
- shell = new Shell(display, SWT.NO_TRIM);
- shell.setMaximized(true);
- } else {
- shell = new Shell(display, SWT.SHELL_TRIM);
- shell.setSize(800, 600);
- }
- return shell;
- }
-
- public void setEventAdmin(EventAdmin eventAdmin) {
- this.eventAdmin = eventAdmin;
- }
-
-}
+++ /dev/null
-package org.argeo.cms.web;
-
-import static org.argeo.cms.osgi.BundleCmsTheme.CMS_THEME_BUNDLE_PROPERTY;
-
-import java.util.HashMap;
-import java.util.Map;
-
-import org.argeo.cms.osgi.BundleCmsTheme;
-import org.eclipse.rap.rwt.RWT;
-import org.eclipse.rap.rwt.application.Application;
-import org.eclipse.rap.rwt.application.ApplicationConfiguration;
-import org.eclipse.rap.rwt.client.WebClient;
-import org.osgi.framework.BundleContext;
-
-/** Lightweight web app using only RWT and not the whole Eclipse platform. */
-public class MinimalWebApp implements ApplicationConfiguration {
-
- private BundleCmsTheme theme;
-
- public void init(BundleContext bundleContext, Map<String, Object> properties) {
- if (properties.containsKey(CMS_THEME_BUNDLE_PROPERTY)) {
- String cmsThemeBundle = properties.get(CMS_THEME_BUNDLE_PROPERTY).toString();
- theme = new BundleCmsTheme(bundleContext, cmsThemeBundle);
- }
- }
-
- public void destroy() {
-
- }
-
- /** To be overridden. Does nothing by default. */
- protected void addEntryPoints(Application application, Map<String, String> properties) {
-
- }
-
- @Override
- public void configure(Application application) {
- if (theme != null)
- WebThemeUtils.apply(application, theme);
-
- Map<String, String> properties = new HashMap<>();
- if (theme != null) {
- properties.put(WebClient.THEME_ID, theme.getThemeId());
- properties.put(WebClient.HEAD_HTML, theme.getHtmlHeaders());
- } else {
- properties.put(WebClient.THEME_ID, RWT.DEFAULT_THEME_ID);
- }
- addEntryPoints(application, properties);
-
- }
-
- public void setTheme(BundleCmsTheme theme) {
- this.theme = theme;
- }
-
-}
+++ /dev/null
-package org.argeo.cms.web;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.URL;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Enumeration;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Hashtable;
-import java.util.LinkedHashMap;
-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.jcr.security.Privilege;
-import javax.jcr.version.VersionManager;
-
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.cms.CmsException;
-import org.argeo.cms.jcr.CmsJcrUtils;
-import org.argeo.cms.ui.CmsUiConstants;
-import org.argeo.cms.ui.CmsUiProvider;
-import org.argeo.cms.ui.LifeCycleUiProvider;
-import org.argeo.cms.ui.util.CmsUiUtils;
-import org.argeo.cms.ui.util.StyleSheetResourceLoader;
-import org.argeo.jcr.JcrUtils;
-import org.eclipse.rap.rwt.RWT;
-import org.eclipse.rap.rwt.application.Application;
-import org.eclipse.rap.rwt.application.Application.OperationMode;
-import org.eclipse.rap.rwt.application.ApplicationConfiguration;
-import org.eclipse.rap.rwt.application.EntryPoint;
-import org.eclipse.rap.rwt.application.EntryPointFactory;
-import org.eclipse.rap.rwt.application.ExceptionHandler;
-import org.eclipse.rap.rwt.client.WebClient;
-import org.eclipse.rap.rwt.client.service.JavaScriptExecutor;
-import org.eclipse.rap.rwt.service.ResourceLoader;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.events.SelectionAdapter;
-import org.eclipse.swt.events.SelectionEvent;
-import org.eclipse.swt.layout.FillLayout;
-import org.eclipse.swt.widgets.Button;
-import org.eclipse.swt.widgets.Composite;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.ServiceRegistration;
-
-/** A basic generic app based on {@link SimpleErgonomics}. */
-@Deprecated
-public class SimpleApp implements CmsUiConstants, ApplicationConfiguration {
- private final static CmsLog log = CmsLog.getLog(SimpleApp.class);
-
- private String contextName = null;
-
- private Map<String, Map<String, String>> branding = new HashMap<String, Map<String, String>>();
- private Map<String, List<String>> styleSheets = new HashMap<String, List<String>>();
-
- private List<String> resources = new ArrayList<String>();
-
- private BundleContext bundleContext;
-
- private Repository repository;
- private String workspace = null;
- private String jcrBasePath = "/";
- private List<String> roPrincipals = Arrays.asList(CmsConstants.ROLE_ANONYMOUS, CmsConstants.ROLE_USER);
- private List<String> rwPrincipals = Arrays.asList(CmsConstants.ROLE_USER);
-
- private CmsUiProvider header;
- private Map<String, CmsUiProvider> pages = new LinkedHashMap<String, CmsUiProvider>();
-
- private Integer headerHeight = 40;
-
- private ServiceRegistration<ApplicationConfiguration> appReg;
-
- public void configure(Application application) {
- try {
- BundleResourceLoader bundleRL = new BundleResourceLoader(bundleContext.getBundle());
-
- application.setOperationMode(OperationMode.SWT_COMPATIBILITY);
- // application.setOperationMode(OperationMode.JEE_COMPATIBILITY);
-
- application.setExceptionHandler(new CmsExceptionHandler());
-
- // loading animated gif
- application.addResource(LOADING_IMAGE, createResourceLoader(LOADING_IMAGE));
- // empty image
- application.addResource(NO_IMAGE, createResourceLoader(NO_IMAGE));
-
- for (String resource : resources) {
- application.addResource(resource, bundleRL);
- if (log.isTraceEnabled())
- log.trace("Resource " + resource);
- }
-
- Map<String, String> defaultBranding = null;
- if (branding.containsKey("*"))
- defaultBranding = branding.get("*");
- // String defaultTheme = defaultBranding.get(WebClient.THEME_ID);
-
- // entry points
- for (String page : pages.keySet()) {
- Map<String, String> properties = defaultBranding != null ? new HashMap<String, String>(defaultBranding)
- : new HashMap<String, String>();
- if (branding.containsKey(page)) {
- properties.putAll(branding.get(page));
- }
- // favicon
- if (properties.containsKey(WebClient.FAVICON)) {
- String themeId = defaultBranding.get(WebClient.THEME_ID);
- Bundle themeBundle = findThemeBundle(bundleContext, themeId);
- String faviconRelPath = properties.get(WebClient.FAVICON);
- application.addResource(faviconRelPath,
- new BundleResourceLoader(themeBundle != null ? themeBundle : bundleContext.getBundle()));
- if (log.isTraceEnabled())
- log.trace("Favicon " + faviconRelPath);
-
- }
-
- // page title
- if (!properties.containsKey(WebClient.PAGE_TITLE)) {
- if (page.length() > 0)
- properties.put(WebClient.PAGE_TITLE, Character.toUpperCase(page.charAt(0)) + page.substring(1));
- }
-
- // default body HTML
- if (!properties.containsKey(WebClient.BODY_HTML))
- properties.put(WebClient.BODY_HTML, DEFAULT_LOADING_BODY);
-
- //
- // ADD ENTRY POINT
- //
- application.addEntryPoint("/" + page,
- new CmsEntryPointFactory(pages.get(page), repository, workspace, properties), properties);
- log.info("Page /" + page);
- }
-
- // stylesheets and themes
- Set<Bundle> themeBundles = new HashSet<>();
- for (String themeId : styleSheets.keySet()) {
- Bundle themeBundle = findThemeBundle(bundleContext, themeId);
- StyleSheetResourceLoader styleSheetRL = new StyleSheetResourceLoader(
- themeBundle != null ? themeBundle : bundleContext.getBundle());
- if (themeBundle != null)
- themeBundles.add(themeBundle);
- List<String> cssLst = styleSheets.get(themeId);
- if (log.isDebugEnabled())
- log.debug("Theme " + themeId);
- for (String css : cssLst) {
- application.addStyleSheet(themeId, css, styleSheetRL);
- if (log.isDebugEnabled())
- log.debug(" CSS " + css);
- }
-
- }
- for (Bundle themeBundle : themeBundles) {
- BundleResourceLoader themeBRL = new BundleResourceLoader(themeBundle);
- SimpleApp.addThemeResources(application, themeBundle, themeBRL, "*.png");
- SimpleApp.addThemeResources(application, themeBundle, themeBRL, "*.gif");
- SimpleApp.addThemeResources(application, themeBundle, themeBRL, "*.jpg");
- }
- } catch (RuntimeException e) {
- // Easier access to initialisation errors
- log.error("Unexpected exception when configuring RWT application.", e);
- throw e;
- }
- }
-
- public void init() throws RepositoryException {
- Session session = null;
- try {
- session = CmsJcrUtils.openDataAdminSession(repository, workspace);
- // session = JcrUtils.loginOrCreateWorkspace(repository, workspace);
- VersionManager vm = session.getWorkspace().getVersionManager();
- JcrUtils.mkdirs(session, jcrBasePath);
- session.save();
- if (!vm.isCheckedOut(jcrBasePath))
- vm.checkout(jcrBasePath);
- for (String principal : rwPrincipals)
- JcrUtils.addPrivilege(session, jcrBasePath, principal, Privilege.JCR_WRITE);
- for (String principal : roPrincipals)
- JcrUtils.addPrivilege(session, jcrBasePath, principal, Privilege.JCR_READ);
-
- for (String pageName : pages.keySet()) {
- try {
- initPage(session, pages.get(pageName));
- session.save();
- } catch (Exception e) {
- throw new CmsException("Cannot initialize page " + pageName, e);
- }
- }
-
- } finally {
- JcrUtils.logoutQuietly(session);
- }
-
- // publish to OSGi
- register();
- }
-
- protected void initPage(Session adminSession, CmsUiProvider page) throws RepositoryException {
- if (page instanceof LifeCycleUiProvider)
- ((LifeCycleUiProvider) page).init(adminSession);
- }
-
- public void destroy() {
- for (String pageName : pages.keySet()) {
- try {
- CmsUiProvider page = pages.get(pageName);
- if (page instanceof LifeCycleUiProvider)
- ((LifeCycleUiProvider) page).destroy();
- } catch (Exception e) {
- log.error("Cannot destroy page " + pageName, e);
- }
- }
- }
-
- protected void register() {
- Hashtable<String, String> props = new Hashtable<String, String>();
- if (contextName != null)
- props.put("contextName", contextName);
- appReg = bundleContext.registerService(ApplicationConfiguration.class, this, props);
- if (log.isDebugEnabled())
- log.debug("Registered " + (contextName == null ? "/" : contextName));
- }
-
- protected void unregister() {
- appReg.unregister();
- if (log.isDebugEnabled())
- log.debug("Unregistered " + (contextName == null ? "/" : contextName));
- }
-
- public void setRepository(Repository repository) {
- this.repository = repository;
- }
-
- public void setWorkspace(String workspace) {
- this.workspace = workspace;
- }
-
- public void setHeader(CmsUiProvider header) {
- this.header = header;
- }
-
- public void setPages(Map<String, CmsUiProvider> pages) {
- this.pages = pages;
- }
-
- public void setJcrBasePath(String basePath) {
- this.jcrBasePath = basePath;
- }
-
- public void setRoPrincipals(List<String> roPrincipals) {
- this.roPrincipals = roPrincipals;
- }
-
- public void setRwPrincipals(List<String> rwPrincipals) {
- this.rwPrincipals = rwPrincipals;
- }
-
- public void setHeaderHeight(Integer headerHeight) {
- this.headerHeight = headerHeight;
- }
-
- public void setBranding(Map<String, Map<String, String>> branding) {
- this.branding = branding;
- }
-
- public void setStyleSheets(Map<String, List<String>> styleSheets) {
- this.styleSheets = styleSheets;
- }
-
- public void setBundleContext(BundleContext bundleContext) {
- this.bundleContext = bundleContext;
- }
-
- public void setResources(List<String> resources) {
- this.resources = resources;
- }
-
- public void setContextName(String contextName) {
- this.contextName = contextName;
- }
-
- private static void addThemeResources(Application application, Bundle themeBundle, BundleResourceLoader themeBRL,
- String pattern) {
- Enumeration<URL> themeResources = themeBundle.findEntries("/", pattern, true);
- if (themeResources == null)
- return;
- while (themeResources.hasMoreElements()) {
- String resource = themeResources.nextElement().getPath();
- // remove first '/' so that RWT registers it
- resource = resource.substring(1);
- if (!resource.endsWith("/")) {
- application.addResource(resource, themeBRL);
- if (log.isTraceEnabled())
- log.trace("Registered " + resource + " from theme " + themeBundle);
- }
-
- }
-
- }
-
- private static Bundle findThemeBundle(BundleContext bundleContext, String themeId) {
- if (themeId == null)
- return null;
- // TODO optimize
- // TODO deal with multiple versions
- Bundle themeBundle = null;
- if (themeId != null) {
- for (Bundle bundle : bundleContext.getBundles())
- if (themeId.equals(bundle.getSymbolicName())) {
- themeBundle = bundle;
- break;
- }
- }
- return themeBundle;
- }
-
- class CmsExceptionHandler implements ExceptionHandler {
-
- @Override
- public void handleException(Throwable throwable) {
- // TODO be smarter
- CmsUiUtils.getCmsView().exception(throwable);
- }
-
- }
-
- private class CmsEntryPointFactory implements EntryPointFactory {
- private final CmsUiProvider page;
- private final Repository repository;
- private final String workspace;
- private final Map<String, String> properties;
-
- public CmsEntryPointFactory(CmsUiProvider page, Repository repository, String workspace,
- Map<String, String> properties) {
- this.page = page;
- this.repository = repository;
- this.workspace = workspace;
- this.properties = properties;
- }
-
- @Override
- public EntryPoint create() {
- SimpleErgonomics entryPoint = new SimpleErgonomics(repository, workspace, jcrBasePath, page, properties) {
- private static final long serialVersionUID = -637940404865527290L;
-
- @Override
- protected void createAdminArea(Composite parent) {
- Composite adminArea = new Composite(parent, SWT.NONE);
- adminArea.setLayout(new FillLayout());
- Button refresh = new Button(adminArea, SWT.PUSH);
- refresh.setText("Reload App");
- refresh.addSelectionListener(new SelectionAdapter() {
- private static final long serialVersionUID = -7671999525536351366L;
-
- @Override
- public void widgetSelected(SelectionEvent e) {
- long timeBeforeReload = 1000;
- RWT.getClient().getService(JavaScriptExecutor.class).execute(
- "setTimeout(function() { " + "location.reload();" + "}," + timeBeforeReload + ");");
- reloadApp();
- }
- });
- }
- };
- // entryPoint.setState("");
- entryPoint.setHeader(header);
- entryPoint.setHeaderHeight(headerHeight);
- // CmsSession.current.set(entryPoint);
- return entryPoint;
- }
-
- private void reloadApp() {
- new Thread("Refresh app") {
- @Override
- public void run() {
- unregister();
- register();
- }
- }.start();
- }
- }
-
- private static ResourceLoader createResourceLoader(final String resourceName) {
- return new ResourceLoader() {
- public InputStream getResourceAsStream(String resourceName) throws IOException {
- return getClass().getClassLoader().getResourceAsStream(resourceName);
- }
- };
- }
-
- // private static ResourceLoader createUrlResourceLoader(final URL url) {
- // return new ResourceLoader() {
- // public InputStream getResourceAsStream(String resourceName)
- // throws IOException {
- // return url.openStream();
- // }
- // };
- // }
-
- /*
- * TEXTS
- */
- private static String DEFAULT_LOADING_BODY = "<div"
- + " style=\"position: absolute; left: 50%; top: 50%; margin: -32px -32px; width: 64px; height:64px\">"
- + "<img src=\"./rwt-resources/" + LOADING_IMAGE
- + "\" width=\"32\" height=\"32\" style=\"margin: 16px 16px\"/>" + "</div>";
-}
+++ /dev/null
-package org.argeo.cms.web;
-
-import java.util.Map;
-import java.util.UUID;
-
-import javax.jcr.Node;
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-
-import org.argeo.api.cms.CmsImageManager;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.api.cms.UxContext;
-import org.argeo.cms.CmsException;
-import org.argeo.cms.swt.CmsStyles;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.cms.swt.SimpleSwtUxContext;
-import org.argeo.cms.ui.CmsUiProvider;
-import org.argeo.cms.ui.util.DefaultImageManager;
-import org.argeo.cms.ui.util.SystemNotifications;
-import org.eclipse.rap.rwt.RWT;
-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.Control;
-
-/** Simple header/body ergonomics. */
-@Deprecated
-public class SimpleErgonomics extends AbstractCmsEntryPoint {
- private static final long serialVersionUID = 8743413921359548523L;
-
- private final static CmsLog log = CmsLog.getLog(SimpleErgonomics.class);
-
- private boolean uiInitialized = false;
- private Composite headerArea;
- private Composite leftArea;
- private Composite rightArea;
- private Composite footerArea;
- private Composite bodyArea;
- private final CmsUiProvider uiProvider;
-
- private CmsUiProvider header;
- private Integer headerHeight = 0;
- private Integer footerHeight = 0;
- private CmsUiProvider lead;
- private CmsUiProvider end;
- private CmsUiProvider footer;
-
- private CmsImageManager imageManager = new DefaultImageManager();
- private UxContext uxContext = null;
- private String uid;
-
- public SimpleErgonomics(Repository repository, String workspace, String defaultPath, CmsUiProvider uiProvider,
- Map<String, String> factoryProperties) {
- super(repository, workspace, defaultPath, factoryProperties);
- this.uiProvider = uiProvider;
- }
-
- @Override
- protected void initUi(Composite parent) {
- uid = UUID.randomUUID().toString();
- parent.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
- parent.setLayout(CmsSwtUtils.noSpaceGridLayout(new GridLayout(3, false)));
-
- uxContext = new SimpleSwtUxContext();
- if (!getUxContext().isMasterData())
- createAdminArea(parent);
- headerArea = new Composite(parent, SWT.NONE);
- headerArea.setLayout(new FillLayout());
- GridData headerData = new GridData(SWT.FILL, SWT.FILL, false, false, 3, 1);
- headerData.heightHint = headerHeight;
- headerArea.setLayoutData(headerData);
-
- // TODO: bi-directional
- leftArea = new Composite(parent, SWT.NONE);
- leftArea.setLayoutData(new GridData(SWT.LEAD, SWT.TOP, false, false));
- leftArea.setLayout(CmsSwtUtils.noSpaceGridLayout());
-
- bodyArea = new Composite(parent, SWT.NONE);
- bodyArea.setData(RWT.CUSTOM_VARIANT, CmsStyles.CMS_BODY);
- bodyArea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
- bodyArea.setLayout(CmsSwtUtils.noSpaceGridLayout());
-
- // TODO: bi-directional
- rightArea = new Composite(parent, SWT.NONE);
- rightArea.setLayoutData(new GridData(SWT.END, SWT.TOP, false, false));
- rightArea.setLayout(CmsSwtUtils.noSpaceGridLayout());
-
- footerArea = new Composite(parent, SWT.NONE);
- // footerArea.setLayout(new FillLayout());
- GridData footerData = new GridData(SWT.FILL, SWT.FILL, false, false, 3, 1);
- footerData.heightHint = footerHeight;
- footerArea.setLayoutData(footerData);
-
- uiInitialized = true;
- refresh();
- }
-
- @Override
- protected void refresh() {
- if (!uiInitialized)
- return;
- if (getState() == null)
- setState("");
- refreshSides();
- refreshBody();
- if (log.isTraceEnabled())
- log.trace("UI refreshed " + getNode());
- }
-
- protected void createAdminArea(Composite parent) {
- }
-
- @Deprecated
- protected void refreshHeader() {
- if (header == null)
- return;
-
- for (Control child : headerArea.getChildren())
- child.dispose();
- try {
- header.createUi(headerArea, getNode());
- } catch (RepositoryException e) {
- throw new CmsException("Cannot refresh header", e);
- }
- headerArea.layout(true, true);
- }
-
- protected void refreshSides() {
- refresh(headerArea, header, CmsStyles.CMS_HEADER);
- refresh(leftArea, lead, CmsStyles.CMS_LEAD);
- refresh(rightArea, end, CmsStyles.CMS_END);
- refresh(footerArea, footer, CmsStyles.CMS_FOOTER);
- }
-
- private void refresh(Composite area, CmsUiProvider uiProvider, String style) {
- if (uiProvider == null)
- return;
-
- for (Control child : area.getChildren())
- child.dispose();
- CmsSwtUtils.style(area, style);
- try {
- uiProvider.createUi(area, getNode());
- } catch (RepositoryException e) {
- throw new CmsException("Cannot refresh header", e);
- }
- area.layout(true, true);
- }
-
- protected void refreshBody() {
- // Exception
- Throwable exception = getException();
- if (exception != null) {
- SystemNotifications systemNotifications = new SystemNotifications(bodyArea);
- systemNotifications.notifyException(exception);
- resetException();
- return;
- // TODO report
- }
-
- // clear
- for (Control child : bodyArea.getChildren())
- child.dispose();
- bodyArea.setLayout(CmsSwtUtils.noSpaceGridLayout());
-
- try {
- Node node = getNode();
-// if (node == null)
-// log.error("Context cannot be null");
-// else
- uiProvider.createUi(bodyArea, node);
- } catch (RepositoryException e) {
- throw new CmsException("Cannot refresh body", e);
- }
-
- bodyArea.layout(true, true);
- }
-
- @Override
- public UxContext getUxContext() {
- return uxContext;
- }
- @Override
- public String getUid() {
- return uid;
- }
-
- public CmsImageManager getImageManager() {
- return imageManager;
- }
-
- public void setHeader(CmsUiProvider header) {
- this.header = header;
- }
-
- public void setHeaderHeight(Integer headerHeight) {
- this.headerHeight = headerHeight;
- }
-
- public void setImageManager(CmsImageManager imageManager) {
- this.imageManager = imageManager;
- }
-
- public CmsUiProvider getLead() {
- return lead;
- }
-
- public void setLead(CmsUiProvider lead) {
- this.lead = lead;
- }
-
- public CmsUiProvider getEnd() {
- return end;
- }
-
- public void setEnd(CmsUiProvider end) {
- this.end = end;
- }
-
- public CmsUiProvider getFooter() {
- return footer;
- }
-
- public void setFooter(CmsUiProvider footer) {
- this.footer = footer;
- }
-
- public CmsUiProvider getHeader() {
- return header;
- }
-
- public void setFooterHeight(Integer footerHeight) {
- this.footerHeight = footerHeight;
- }
-
-}
+++ /dev/null
-package org.argeo.cms.web;
-
-import org.argeo.api.cms.CmsTheme;
-import org.argeo.api.cms.CmsLog;
-import org.eclipse.rap.rwt.application.Application;
-import org.eclipse.rap.rwt.service.ResourceLoader;
-
-/** Web specific utilities around theming. */
-public class WebThemeUtils {
- private final static CmsLog log = CmsLog.getLog(WebThemeUtils.class);
-
- public static void apply(Application application, CmsTheme theme) {
- ResourceLoader resourceLoader = new CmsThemeResourceLoader(theme);
- resources: for (String path : theme.getImagesPaths()) {
- if (path.startsWith("target/"))
- continue resources; // skip maven output
- application.addResource(path, resourceLoader);
- if (log.isTraceEnabled())
- log.trace("Theme " + theme.getThemeId() + ": added resource " + path);
- }
- for (String path : theme.getRapCssPaths()) {
- application.addStyleSheet(theme.getThemeId(), path, resourceLoader);
- if (log.isDebugEnabled())
- log.debug("Theme " + theme.getThemeId() + ": added RAP CSS " + path);
- }
- }
-
-}
+++ /dev/null
-package org.argeo.eclipse.ui.jetty;
-
-import java.io.IOException;
-import java.lang.management.ManagementFactory;
-import java.nio.file.Files;
-import java.nio.file.Path;
-
-import javax.servlet.ServletContextEvent;
-import javax.servlet.ServletContextListener;
-
-import org.eclipse.jetty.server.Connector;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.ServerConnector;
-import org.eclipse.jetty.servlet.DefaultServlet;
-import org.eclipse.jetty.servlet.ServletContextHandler;
-import org.eclipse.jetty.servlet.ServletHolder;
-import org.eclipse.jetty.util.resource.Resource;
-import org.eclipse.jetty.util.thread.QueuedThreadPool;
-import org.eclipse.rap.rwt.application.AbstractEntryPoint;
-import org.eclipse.rap.rwt.application.ApplicationRunner;
-import org.eclipse.rap.rwt.engine.RWTServlet;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Label;
-
-/** A minimal RWT runner based on embedded Jetty. */
-public class RwtRunner {
-
- private final Server server;
- private final ServerConnector serverConnector;
- private Path tempDir;
-
- public RwtRunner() {
- server = new Server(new QueuedThreadPool(10, 1));
- serverConnector = new ServerConnector(server);
- serverConnector.setPort(0);
- server.setConnectors(new Connector[] { serverConnector });
- }
-
- protected Control createUi(Composite parent, Object context) {
- return new Label(parent, SWT.NONE);
- }
-
- public void init() {
- ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
- context.setContextPath("/");
- server.setHandler(context);
-
- String entryPoint = "app";
-
- // rwt-resources requires a file system
- try {
- tempDir = Files.createTempDirectory("argeo-rwtRunner");
- context.setBaseResource(Resource.newResource(tempDir.resolve("www").toString()));
- } catch (IOException e) {
- throw new IllegalStateException("Cannot create temporary directory", e);
- }
- context.addEventListener(new ServletContextListener() {
- ApplicationRunner applicationRunner;
-
- @Override
- public void contextInitialized(ServletContextEvent sce) {
- applicationRunner = new ApplicationRunner(
- (application) -> application.addEntryPoint("/" + entryPoint, () -> new AbstractEntryPoint() {
- private static final long serialVersionUID = 5678385921969090733L;
-
- @Override
- protected void createContents(Composite parent) {
- createUi(parent, null);
- }
- }, null), sce.getServletContext());
- applicationRunner.start();
- }
-
- @Override
- public void contextDestroyed(ServletContextEvent sce) {
- applicationRunner.stop();
- }
- });
-
- context.addServlet(new ServletHolder(new RWTServlet()), "/" + entryPoint);
-
- // Required to serve rwt-resources. It is important that this is last.
- ServletHolder holderPwd = new ServletHolder("default", DefaultServlet.class);
- context.addServlet(holderPwd, "/");
-
- try {
- server.start();
- } catch (Exception e) {
- throw new IllegalStateException("Cannot start Jetty server", e);
- }
- }
-
- public void destroy() {
- try {
- serverConnector.close();
- server.stop();
- // TODO delete temp dir
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
-
- public Integer getEffectivePort() {
- return serverConnector.getLocalPort();
- }
-
- public void waitFor() throws InterruptedException {
- server.join();
- }
-
- public static void main(String[] args) throws Exception {
- RwtRunner rwtRunner = new RwtRunner() {
-
- @Override
- protected Control createUi(Composite parent, Object context) {
- Label label = new Label(parent, SWT.NONE);
- label.setText("Hello world!");
- return label;
- }
- };
- rwtRunner.init();
- Runtime.getRuntime().addShutdownHook(new Thread(() -> rwtRunner.destroy(), "Jetty shutdown"));
-
- long jvmUptime = ManagementFactory.getRuntimeMXBean().getUptime();
- System.out.println("App available in " + jvmUptime + " ms, on port " + rwtRunner.getEffectivePort());
-
- // open browser in app mode
- Thread.sleep(2000);// wait for RWT to be ready
- Runtime.getRuntime().exec("google-chrome --app=http://localhost:" + rwtRunner.getEffectivePort() + "/app");
-
- rwtRunner.waitFor();
- }
-}
+++ /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.swt.specific.rap</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.dialogs,\
-org.eclipse.swt.events,\
-javax.servlet.http;version="[3,5)",\
-*
+++ /dev/null
-source.. = src/
-output.. = bin/
+++ /dev/null
-package org.argeo.eclipse.ui.specific;
-
-import org.eclipse.swt.widgets.FileDialog;
-import org.eclipse.swt.widgets.Shell;
-
-public class CmsFileDialog extends FileDialog {
- private static final long serialVersionUID = -7540791204102318801L;
-
- public CmsFileDialog(Shell parent, int style) {
- super(parent, style);
- }
-
- public CmsFileDialog(Shell parent) {
- super(parent);
- }
-
-}
+++ /dev/null
-package org.argeo.eclipse.ui.specific;
-
-import org.eclipse.rap.rwt.widgets.FileUpload;
-import org.eclipse.swt.events.SelectionListener;
-import org.eclipse.swt.widgets.Composite;
-
-public class CmsFileUpload extends FileUpload {
- private static final long serialVersionUID = 8027963992680980549L;
-
- public CmsFileUpload(Composite parent, int style) {
- super(parent, style);
- }
-
- @Override
- public void setText(String text) {
- super.setText(text);
- }
-
- @Override
- public String getFileName() {
- return super.getFileName();
- }
-
- @Override
- public String[] getFileNames() {
- return super.getFileNames();
- }
-
- @Override
- public void addSelectionListener(SelectionListener listener) {
- super.addSelectionListener(listener);
- }
-
-}
+++ /dev/null
-package org.argeo.eclipse.ui.specific;
-
-import org.eclipse.jface.viewers.AbstractTableViewer;
-import org.eclipse.jface.viewers.ColumnViewer;
-import org.eclipse.jface.viewers.ColumnViewerToolTipSupport;
-import org.eclipse.jface.viewers.Viewer;
-import org.eclipse.rap.rwt.RWT;
-import org.eclipse.swt.widgets.Widget;
-
-/** Static utilities to bridge differences between RCP and RAP */
-public class EclipseUiSpecificUtils {
-
- public static void setStyleData(Widget widget, Object data) {
- widget.setData(RWT.CUSTOM_VARIANT, data);
- }
-
- public static Object getStyleData(Widget widget) {
- return widget.getData(RWT.CUSTOM_VARIANT);
- }
-
- public static void setMarkupData(Widget widget) {
- widget.setData(RWT.MARKUP_ENABLED, true);
- }
-
- public static void setMarkupValidationDisabledData(Widget widget) {
- widget.setData("org.eclipse.rap.rwt.markupValidationDisabled", Boolean.TRUE);
- }
-
- /**
- * TootlTip support is supported only for {@link AbstractTableViewer} in RAP
- */
- public static void enableToolTipSupport(Viewer viewer) {
- if (viewer instanceof ColumnViewer)
- ColumnViewerToolTipSupport.enableFor((ColumnViewer) viewer);
- }
-
- private EclipseUiSpecificUtils() {
- }
-}
+++ /dev/null
-package org.argeo.eclipse.ui.specific;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-import org.eclipse.rap.fileupload.FileDetails;
-import org.eclipse.rap.fileupload.FileUploadHandler;
-import org.eclipse.rap.fileupload.FileUploadReceiver;
-import org.eclipse.rap.rwt.RWT;
-import org.eclipse.rap.rwt.client.ClientFile;
-import org.eclipse.rap.rwt.client.service.ClientFileUploader;
-import org.eclipse.rap.rwt.dnd.ClientFileTransfer;
-import org.eclipse.swt.dnd.DND;
-import org.eclipse.swt.dnd.DropTarget;
-import org.eclipse.swt.dnd.DropTargetAdapter;
-import org.eclipse.swt.dnd.DropTargetEvent;
-import org.eclipse.swt.dnd.Transfer;
-import org.eclipse.swt.widgets.Control;
-
-/** Configures a {@link Control} to receive files drop from the client OS. */
-public class FileDropAdapter {
-
- public void prepareDropTarget(Control control, DropTarget dropTarget) {
- dropTarget.setTransfer(new Transfer[] { ClientFileTransfer.getInstance() });
- dropTarget.addDropListener(new DropTargetAdapter() {
- private static final long serialVersionUID = 5361645765549463168L;
-
- @Override
- public void dropAccept(DropTargetEvent event) {
- if (!ClientFileTransfer.getInstance().isSupportedType(event.currentDataType)) {
- event.detail = DND.DROP_NONE;
- }
- }
-
- @Override
- public void drop(DropTargetEvent event) {
- handleFileDrop(control, event);
- }
- });
- }
-
- public void handleFileDrop(Control control, DropTargetEvent event) {
- ClientFile[] clientFiles = (ClientFile[]) event.data;
- ClientFileUploader service = RWT.getClient().getService(ClientFileUploader.class);
-// DiskFileUploadReceiver receiver = new DiskFileUploadReceiver();
- FileUploadReceiver receiver = new FileUploadReceiver() {
-
- @Override
- public void receive(InputStream stream, FileDetails details) throws IOException {
- control.getDisplay().syncExec(() -> {
- try {
- processUpload(stream, details.getFileName(), details.getContentType());
- } catch (IOException e) {
- throw new IllegalStateException("Cannot process upload of " + details.getFileName(), e);
- }
- });
- }
- };
- FileUploadHandler handler = new FileUploadHandler(receiver);
-// handler.setMaxFileSize( sizeLimit );
-// handler.setUploadTimeLimit( timeLimit );
- service.submit(handler.getUploadUrl(), clientFiles);
-// for (File file : receiver.getTargetFiles()) {
-// paths.add(file.toPath());
-// }
- }
-
- /** Executed in UI thread */
- protected void processUpload(InputStream in, String fileName, String contentType) throws IOException {
-
- }
-
-}
+++ /dev/null
-package org.argeo.eclipse.ui.specific;
-
-import java.util.Locale;
-
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.eclipse.rap.rwt.RWT;
-import org.eclipse.swt.widgets.Display;
-
-/** Singleton class providing single sources infos about the UI context. */
-public class UiContext {
- /** Can be null, thus indicating that we are not in a web context. */
- public static HttpServletRequest getHttpRequest() {
- return RWT.getRequest();
- }
-
- public static HttpServletResponse getHttpResponse() {
- return RWT.getResponse();
- }
-
- public static Locale getLocale() {
- if (Display.getCurrent() != null)
- return RWT.getUISession().getLocale();
- else
- return Locale.getDefault();
- }
-
- public static void setLocale(Locale locale) {
- if (Display.getCurrent() != null)
- RWT.getUISession().setLocale(locale);
- else
- Locale.setDefault(locale);
- }
-
- /** Can always be null */
- @SuppressWarnings("unchecked")
- public static <T> T getData(String key) {
- Display display = getDisplay();
- if (display == null)
- return null;
- return (T) display.getData(key);
- }
-
- public static void setData(String key, Object value) {
- Display display = getDisplay();
- if (display == null)
- throw new IllegalStateException("Not display available");
- display.setData(key, value);
- }
-
- private static Display getDisplay() {
- return Display.getCurrent();
- }
-
- private UiContext() {
- }
-
-}
+++ /dev/null
-/** Eclipse RAP-specific SWT/JFace utilities, to simplify single-sourcing. */
-package org.argeo.eclipse.ui.specific;
\ 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-1.8"/>
- <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
- <classpathentry kind="src" path="src"/>
- <classpathentry kind="output" path="bin"/>
-</classpath>
+++ /dev/null
-/bin/
-/target/
-/exec
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
- <name>org.argeo.cms.e4.rcp</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
-eclipse.preferences.version=1
-org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
-org.eclipse.jdt.core.compiler.compliance=1.8
-org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
-org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
-org.eclipse.jdt.core.compiler.source=1.8
+++ /dev/null
-eclipse.preferences.version=1
-pluginProject.extensions=false
-resolve.requirebundle=false
+++ /dev/null
-/MANIFEST.MF
+++ /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" xmi:id="_c4iAgCnCEei1F8sdBz8Mpw" elementId="org.argeo.cms.e4.rcp.application">
- <children xsi:type="basic:TrimmedWindow" xmi:id="_hSGBwCnCEei1F8sdBz8Mpw" elementId="org.argeo.cms.e4.rcp.trimmedwindow.argeocompanion" label="Argeo Companion">
- <children xsi:type="advanced:PerspectiveStack" xmi:id="_nxzQICnCEei1F8sdBz8Mpw" elementId="org.argeo.cms.e4.rcp.perspectivestack.0">
- <children xsi:type="advanced:Perspective" xmi:id="_oI_oICnCEei1F8sdBz8Mpw" elementId="org.argeo.cms.e4.rcp.perspective.cmsadmin" label="CMS Admin">
- <children xsi:type="basic:PartSashContainer" xmi:id="_qc16ECnCEei1F8sdBz8Mpw" elementId="org.argeo.cms.e4.rcp.partsashcontainer.0" horizontal="true">
- <children xsi:type="basic:PartStack" xmi:id="_RE87kDsXEeiUntFYWh-hFg" elementId="org.argeo.cms.e4.rcp.partstack.1">
- <children xsi:type="basic:Part" xmi:id="_V1WvgDsXEeiUntFYWh-hFg" elementId="org.argeo.cms.e4.rcp.part.files" contributionURI="bundleclass://org.argeo.cms.e4/org.argeo.cms.e4.files.NodeFsBrowserView" label="Files"/>
- <children xsi:type="basic:Part" xmi:id="_vOqDQCnCEei1F8sdBz8Mpw" elementId="org.argeo.cms.e4.rcp.part.jcr" containerData="4000" contributionURI="bundleclass://org.argeo.cms.e4/org.argeo.cms.e4.jcr.JcrBrowserView" label="JCR"/>
- </children>
- <children xsi:type="basic:PartStack" xmi:id="_0eRiwCnCEei1F8sdBz8Mpw" elementId="org.argeo.cms.e4.rcp.partstack.0" containerData="6000">
- <tags>editorArea</tags>
- </children>
- </children>
- </children>
- </children>
- </children>
- <descriptors xmi:id="__9SDsC8JEeq0koquN4xGyg" elementId="org.argeo.cms.e4.partdescriptor.nodeEditor" allowMultiple="true" category="editorArea" closeable="true" contributionURI="bundleclass://org.argeo.cms.e4/org.argeo.cms.e4.jcr.JcrNodeEditor"/>
- <addons xmi:id="_c4iAgSnCEei1F8sdBz8Mpw" elementId="org.eclipse.e4.core.commands.service" contributionURI="bundleclass://org.eclipse.e4.core.commands/org.eclipse.e4.core.commands.CommandServiceAddon"/>
- <addons xmi:id="_c4iAginCEei1F8sdBz8Mpw" elementId="org.eclipse.e4.ui.contexts.service" contributionURI="bundleclass://org.eclipse.e4.ui.services/org.eclipse.e4.ui.services.ContextServiceAddon"/>
- <addons xmi:id="_c4iAgynCEei1F8sdBz8Mpw" elementId="org.eclipse.e4.ui.bindings.service" contributionURI="bundleclass://org.eclipse.e4.ui.bindings/org.eclipse.e4.ui.bindings.BindingServiceAddon"/>
- <addons xmi:id="_c4iAhCnCEei1F8sdBz8Mpw" 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="_c4iAhSnCEei1F8sdBz8Mpw" 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="_c4iAhinCEei1F8sdBz8Mpw" 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="_c4iAhynCEei1F8sdBz8Mpw" elementId="org.eclipse.e4.ui.workbench.handler.model" contributionURI="bundleclass://org.eclipse.e4.ui.workbench/org.eclipse.e4.ui.internal.workbench.addons.HandlerProcessingAddon"/>
-</application:Application>
+++ /dev/null
-argeo.osgi.start.2.node=\
-org.eclipse.equinox.http.servlet,\
-org.eclipse.equinox.metatype,\
-org.eclipse.equinox.cm,\
-org.argeo.init
-
-argeo.osgi.start.3.node=\
-org.argeo.cms,\
-org.argeo.cms.jcr,\
-
-applicationXMI=org.argeo.cms.e4.rcp/argeo-companion.e4xmi
-lifeCycleURI=bundleclass://org.argeo.cms.e4.rcp/org.argeo.cms.e4.rcp.CmsRcpLifeCycle
-clearPersistedState=true
-#argeo.cms.desktop.inTray=true
-
-# Remote node:
-#argeo.node.repo.labeledUri=http://root:demo@localhost:7070/jcr/node
-
-# Logging
-log.org.argeo=DEBUG
-
-argeo.node.useradmin.uris=os:///
-eclipse.application=org.argeo.cms.e4.rcp.CmsE4Application
+++ /dev/null
-Bundle-SymbolicName: org.argeo.cms.e4.rcp;singleton=true
-
-Require-Bundle: org.eclipse.core.runtime
-
-Import-Package: !org.eclipse.core.runtime,\
-org.eclipse.swt,\
-*
+++ /dev/null
-output.. = bin/
-bin.includes = META-INF/,\
- .,\
- argeo-companion.e4xmi
-source.. = src/
+++ /dev/null
-log4j.rootLogger=WARN, development
-
-## Levels
-log4j.logger.org.argeo=DEBUG
-log4j.logger.org.argeo.jackrabbit.remote.ExtendedDispatcherServlet=WARN
-log4j.logger.org.argeo.server.webextender.TomcatDeployer=INFO
-
-#log4j.logger.org.springframework.security=DEBUG
-#log4j.logger.org.apache.commons.exec=DEBUG
-#log4j.logger.org.apache.jackrabbit.webdav=DEBUG
-#log4j.logger.org.apache.jackrabbit.remote=DEBUG
-#log4j.logger.org.apache.jackrabbit.core.observation=DEBUG
-
-log4j.logger.org.apache.catalina=INFO
-log4j.logger.org.apache.coyote=INFO
-
-log4j.logger.org.apache.directory=INFO
-log4j.logger.org.apache.directory.server=ERROR
-log4j.logger.org.apache.jackrabbit.core.query.lucene=ERROR
-
-## Appenders
-# console is set to be a ConsoleAppender.
-log4j.appender.console=org.apache.log4j.ConsoleAppender
-
-# console uses PatternLayout.
-log4j.appender.console.layout=org.apache.log4j.PatternLayout
-log4j.appender.console.layout.ConversionPattern= %-5p %d{ISO8601} %m - %c - [%t]%n
-
-# development appender (slow!)
-log4j.appender.development=org.apache.log4j.ConsoleAppender
-log4j.appender.development.layout=org.apache.log4j.PatternLayout
-log4j.appender.development.layout.ConversionPattern=%d{HH:mm:ss} [%16.16t] %5p %m (%F:%L) %c%n
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8"?>
-<?eclipse version="3.4"?>
-<plugin>
- <extension
- id="CmsE4Application"
- name="CMS E4 Application"
- point="org.eclipse.core.runtime.applications">
- <application
- cardinality="singleton-global"
- thread="main"
- visible="true">
- <run class="org.argeo.cms.e4.rcp.CmsE4Application"></run>
- </application>
- </extension>
-</plugin>
+++ /dev/null
-package org.argeo.cms.e4.rcp;
-
-import java.security.PrivilegedExceptionAction;
-import java.util.UUID;
-
-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.CmsImageManager;
-import org.argeo.api.cms.CmsView;
-import org.argeo.api.cms.UxContext;
-import org.argeo.cms.CmsException;
-import org.argeo.cms.auth.CurrentUser;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.cms.swt.SimpleSwtUxContext;
-import org.argeo.cms.swt.auth.CmsLoginShell;
-import org.eclipse.core.runtime.IConfigurationElement;
-import org.eclipse.core.runtime.IExtension;
-import org.eclipse.core.runtime.Platform;
-import org.eclipse.equinox.app.IApplication;
-import org.eclipse.equinox.app.IApplicationContext;
-import org.eclipse.swt.widgets.Display;
-
-public class CmsE4Application implements IApplication, CmsView {
- private LoginContext loginContext;
- private IApplication e4Application;
- private UxContext uxContext;
- private String uid;
-
- @Override
- public Object start(IApplicationContext context) throws Exception {
- // TODO wait for CMS to be ready
- Thread.sleep(5000);
-
- uid = UUID.randomUUID().toString();
- Subject subject = new Subject();
- Display display = createDisplay();
- CmsLoginShell loginShell = new CmsLoginShell(this, null);
- // TODO customize CmsLoginShell to be smaller and centered
- loginShell.setSubject(subject);
- try {
- // try pre-auth
- loginContext = new LoginContext(CmsAuth.LOGIN_CONTEXT_SINGLE_USER, subject, loginShell);
- loginContext.login();
- } catch (LoginException e) {
- e.printStackTrace();
- loginShell.createUi();
- loginShell.open();
-
- while (!loginShell.getShell().isDisposed()) {
- if (!display.readAndDispatch())
- display.sleep();
- }
- }
- if (CurrentUser.getUsername(getSubject()) == null)
- throw new CmsException("Cannot log in");
-
- // try {
- // CallbackHandler callbackHandler = new DefaultLoginDialog(
- // display.getActiveShell());
- // loginContext = new LoginContext(
- // NodeConstants.LOGIN_CONTEXT_SINGLE_USER, subject,
- // callbackHandler);
- // } catch (LoginException e1) {
- // throw new CmsException("Cannot initialize login context", e1);
- // }
- //
- // // login
- // try {
- // loginContext.login();
- // subject = loginContext.getSubject();
- // } catch (LoginException e) {
- // e.printStackTrace();
- // display.dispose();
- // try {
- // Thread.sleep(2000);
- // } catch (InterruptedException e1) {
- // // silent
- // }
- // return null;
- // }
-
- uxContext = new SimpleSwtUxContext();
- // UiContext.setData(CmsView.KEY, this);
- CmsSwtUtils.registerCmsView(loginShell.getShell(), this);
- e4Application = getApplication(null);
- Object res = Subject.doAs(subject, new PrivilegedExceptionAction<Object>() {
-
- @Override
- public Object run() throws Exception {
- return e4Application.start(context);
- }
-
- });
- return res;
- }
-
- @Override
- public void stop() {
- if (e4Application != null)
- e4Application.stop();
- }
-
- static IApplication getApplication(String[] args) {
- IExtension extension = Platform.getExtensionRegistry().getExtension(Platform.PI_RUNTIME,
- Platform.PT_APPLICATIONS, "org.eclipse.e4.ui.workbench.swt.E4Application");
- try {
- IConfigurationElement[] elements = extension.getConfigurationElements();
- if (elements.length > 0) {
- IConfigurationElement[] runs = elements[0].getChildren("run");
- if (runs.length > 0) {
- Object runnable;
- runnable = runs[0].createExecutableExtension("class");
- if (runnable instanceof IApplication)
- return (IApplication) runnable;
- }
- }
- } catch (Exception e) {
- throw new IllegalStateException("Cannot find e4 application", e);
- }
- throw new IllegalStateException("Cannot find e4 application");
- }
-
- public static Display createDisplay() {
- Display.setAppName("Argeo CMS RCP");
-
- // create the display
- Display newDisplay = Display.getCurrent();
- if (newDisplay == null) {
- newDisplay = new Display();
- }
- // Set the priority higher than normal so as to be higher
- // than the JobManager.
- Thread.currentThread().setPriority(Math.min(Thread.MAX_PRIORITY, Thread.NORM_PRIORITY + 1));
- return newDisplay;
- }
-
- //
- // CMS VIEW
- //
-
- @Override
- public UxContext getUxContext() {
- return uxContext;
- }
-
- @Override
- public void navigateTo(String state) {
- // TODO Auto-generated method stub
-
- }
-
- @Override
- public void authChange(LoginContext loginContext) {
- if (loginContext == null)
- throw new CmsException("Login context cannot be null");
- // logout previous login context
- // if (this.loginContext != null)
- // try {
- // this.loginContext.logout();
- // } catch (LoginException e1) {
- // System.err.println("Could not log out: " + e1);
- // }
- this.loginContext = loginContext;
- }
-
- @Override
- public void logout() {
- if (loginContext == null)
- throw new CmsException("Login context should not bet null");
- try {
- CurrentUser.logoutCmsSession(loginContext.getSubject());
- loginContext.logout();
- } catch (LoginException e) {
- throw new CmsException("Cannot log out", e);
- }
- }
-
- @Override
- public void exception(Throwable e) {
- // TODO Auto-generated method stub
-
- }
-
- @Override
- public CmsImageManager getImageManager() {
- // TODO Auto-generated method stub
- return null;
- }
-
- protected Subject getSubject() {
- return loginContext.getSubject();
- }
-
- @Override
- public boolean isAnonymous() {
- return CurrentUser.isAnonymous(getSubject());
- }
-
- @Override
- public String getUid() {
- return uid;
- }
-
-}
+++ /dev/null
-package org.argeo.cms.e4.rcp;
-
-import org.eclipse.e4.core.contexts.IEclipseContext;
-import org.eclipse.e4.ui.workbench.lifecycle.PostContextCreate;
-import org.eclipse.e4.ui.workbench.lifecycle.PreSave;
-import org.eclipse.e4.ui.workbench.lifecycle.ProcessAdditions;
-import org.eclipse.e4.ui.workbench.lifecycle.ProcessRemovals;
-
-@SuppressWarnings("restriction")
-public class CmsRcpLifeCycle {
-
- @PostContextCreate
- void postContextCreate(IEclipseContext workbenchContext) {
- }
-
- @PreSave
- void preSave(IEclipseContext workbenchContext) {
- }
-
- @ProcessAdditions
- void processAdditions(IEclipseContext workbenchContext) {
- }
-
- @ProcessRemovals
- void processRemovals(IEclipseContext workbenchContext) {
- }
-}
+++ /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
-/bin/
-/target/
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
- <name>org.argeo.cms.ui.rcp</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" activate="init" deactivate="destroy" name="CMS RCP App">
- <implementation class="org.argeo.cms.ui.rcp.CmsRcpApp"/>
- <reference bind="setCmsApp" cardinality="1..1" interface="org.argeo.api.cms.CmsApp" name="CmsApp" policy="dynamic"/>
- <reference bind="setEventAdmin" cardinality="1..1" interface="org.osgi.service.event.EventAdmin" name="EventAdmin" policy="static"/>
-</scr:component>
+++ /dev/null
-
-Service-Component: OSGI-INF/cmsRcpApp.xml
-
-Import-Package:\
-org.argeo.cms.auth,\
-org.eclipse.swt,\
-org.eclipse.swt.graphics,\
-org.w3c.css.sac,\
-*
+++ /dev/null
-output.. = bin/
-bin.includes = META-INF/,\
- .,\
- OSGI-INF/
-source.. = src/
+++ /dev/null
-package org.argeo.cms.ui.rcp;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.security.PrivilegedAction;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.UUID;
-
-import javax.security.auth.Subject;
-import javax.security.auth.login.LoginContext;
-import javax.security.auth.login.LoginException;
-
-import org.argeo.api.cms.CmsApp;
-import org.argeo.api.cms.CmsAuth;
-import org.argeo.api.cms.CmsImageManager;
-import org.argeo.api.cms.CmsSession;
-import org.argeo.api.cms.CmsTheme;
-import org.argeo.api.cms.CmsUi;
-import org.argeo.api.cms.CmsView;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.api.cms.UxContext;
-import org.argeo.cms.osgi.CmsOsgiUtils;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.eclipse.e4.ui.css.core.engine.CSSEngine;
-import org.eclipse.e4.ui.css.core.engine.CSSErrorHandler;
-import org.eclipse.e4.ui.css.swt.engine.CSSSWTEngineImpl;
-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.osgi.framework.BundleContext;
-import org.osgi.framework.FrameworkUtil;
-import org.osgi.service.event.Event;
-import org.osgi.service.event.EventAdmin;
-
-/** Runs a {@link CmsApp} as an SWT desktop application. */
-@SuppressWarnings("restriction")
-public class CmsRcpApp implements CmsView {
- private final static CmsLog log = CmsLog.getLog(CmsRcpApp.class);
-
- private BundleContext bundleContext = FrameworkUtil.getBundle(CmsRcpApp.class).getBundleContext();
-
- private Display display;
- private Shell shell;
- private CmsApp cmsApp;
- private CmsUiThread uiThread;
-
- // CMS View
- private String uid;
- private LoginContext loginContext;
-
- private EventAdmin eventAdmin;
-
- private CSSEngine cssEngine;
-
- private CmsUi ui;
- // TODO make it configurable
- private String uiName = "desktop";
-
- public CmsRcpApp() {
- uid = UUID.randomUUID().toString();
- }
-
- public void init(Map<String, String> properties) {
- try {
- Thread.sleep(5000);
- } catch (InterruptedException e) {
- // silent
- }
- uiThread = new CmsUiThread();
- uiThread.start();
-
- }
-
- public void destroy(Map<String, String> properties) {
- if (!shell.isDisposed())
- shell.dispose();
- try {
- uiThread.join();
- } catch (InterruptedException e) {
- // silent
- } finally {
- uiThread = null;
- }
- }
-
- class CmsUiThread extends Thread {
-
- public CmsUiThread() {
- super("CMS UI");
- }
-
- @Override
- public void run() {
- display = new Display();
- shell = new Shell(display);
- shell.setText("Argeo CMS");
- Composite parent = shell;
- parent.setLayout(new GridLayout());
- CmsSwtUtils.registerCmsView(shell, CmsRcpApp.this);
-
-// Subject subject = new Subject();
-// CmsLoginShell loginShell = new CmsLoginShell(CmsRcpApp.this);
-// loginShell.setSubject(subject);
- try {
- // try pre-auth
-// loginContext = new LoginContext(NodeConstants.LOGIN_CONTEXT_USER, subject, loginShell);
- loginContext = new LoginContext(CmsAuth.LOGIN_CONTEXT_SINGLE_USER);
- loginContext.login();
- } catch (LoginException e) {
- throw new IllegalStateException("Could not log in.", e);
-// loginShell.createUi();
-// loginShell.open();
-//
-// while (!loginShell.getShell().isDisposed()) {
-// if (!display.readAndDispatch())
-// display.sleep();
-// }
- }
- if (log.isDebugEnabled())
- log.debug("Logged in to desktop: " + loginContext.getSubject());
-
- Subject.doAs(loginContext.getSubject(), (PrivilegedAction<Void>) () -> {
-
- // TODO factorise with web app
- parent.setData(CmsApp.UI_NAME_PROPERTY, uiName);
- ui = cmsApp.initUi(parent);
- if (ui instanceof Composite)
- ((Composite) ui).setLayoutData(CmsSwtUtils.fillAll());
- // ui.setLayoutData(CmsUiUtils.fillAll());
- // we need ui to be set before refresh so that CmsView can store UI context data
- // in it.
- cmsApp.refreshUi(ui, null);
-
- // Styling
- CmsTheme theme = CmsSwtUtils.getCmsTheme(parent);
- if (theme != null) {
- cssEngine = new CSSSWTEngineImpl(display);
- for (String path : theme.getSwtCssPaths()) {
- try (InputStream in = theme.loadPath(path)) {
- cssEngine.parseStyleSheet(in);
- } catch (IOException e) {
- throw new IllegalStateException("Cannot load stylesheet " + path, e);
- }
- }
- cssEngine.setErrorHandler(new CSSErrorHandler() {
- public void error(Exception e) {
- log.error("SWT styling error: ", e);
- }
- });
- applyStyles(shell);
- }
- shell.layout(true, true);
-
- shell.open();
- while (!shell.isDisposed()) {
- if (!display.readAndDispatch())
- display.sleep();
- }
- display.dispose();
- return null;
- });
- }
-
- }
-
- /*
- * CMS VIEW
- */
-
- @Override
- public String getUid() {
- return uid;
- }
-
- @Override
- public UxContext getUxContext() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void navigateTo(String state) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void authChange(LoginContext loginContext) {
- }
-
- @Override
- public void logout() {
- if (loginContext != null)
- try {
- loginContext.logout();
- } catch (LoginException e) {
- log.error("Cannot log out", e);
- }
- }
-
- @Override
- public void exception(Throwable e) {
- log.error("Unexpected exception in CMS RCP", e);
- }
-
- @Override
- public CmsImageManager getImageManager() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public CmsSession getCmsSession() {
- CmsSession cmsSession = CmsOsgiUtils.getCmsSession(bundleContext, getSubject());
- return cmsSession;
- }
-
- @Override
- public Object getData(String key) {
- if (ui != null) {
- return ui.getData(key);
- } else {
- throw new IllegalStateException("UI is not initialized");
- }
- }
-
- @Override
- public void setData(String key, Object value) {
- if (ui != null) {
- ui.setData(key, value);
- } else {
- throw new IllegalStateException("UI is not initialized");
- }
- }
-
- @Override
- public boolean isAnonymous() {
- return false;
- }
-
- @Override
- public void applyStyles(Object node) {
- if (cssEngine != null)
- cssEngine.applyStyles(node, true);
- }
-
- @Override
- public void sendEvent(String topic, Map<String, Object> properties) {
- if (properties == null)
- properties = new HashMap<>();
- if (properties.containsKey(CMS_VIEW_UID_PROPERTY) && !properties.get(CMS_VIEW_UID_PROPERTY).equals(uid))
- throw new IllegalArgumentException("Property " + CMS_VIEW_UID_PROPERTY + " is set to another CMS view uid ("
- + properties.get(CMS_VIEW_UID_PROPERTY) + ") then " + uid);
- properties.put(CMS_VIEW_UID_PROPERTY, uid);
- eventAdmin.sendEvent(new Event(topic, properties));
- }
-
- public <T> T doAs(PrivilegedAction<T> action) {
- return Subject.doAs(getSubject(), action);
- }
-
- protected Subject getSubject() {
- return loginContext.getSubject();
- }
-
- /*
- * DEPENDENCY INJECTION
- */
- public void setCmsApp(CmsApp cmsApp) {
- this.cmsApp = cmsApp;
- }
-
- public void setEventAdmin(EventAdmin eventAdmin) {
- this.eventAdmin = eventAdmin;
- }
-
-}
+++ /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-1.8"/>
- <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
- <classpathentry kind="src" path="src"/>
- <classpathentry kind="output" path="bin"/>
-</classpath>
+++ /dev/null
-/bin/
-/target/
-*.log
\ No newline at end of file
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
- <name>org.argeo.swt.minidesktop</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,\
-*
+++ /dev/null
-source.. = src/
-output.. = bin/
-bin.includes = META-INF/,\
- .
+++ /dev/null
-package org.argeo.minidesktop;
-
-import java.util.Arrays;
-import java.util.List;
-
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.browser.Browser;
-import org.eclipse.swt.browser.LocationAdapter;
-import org.eclipse.swt.browser.LocationEvent;
-import org.eclipse.swt.events.KeyAdapter;
-import org.eclipse.swt.events.KeyEvent;
-import org.eclipse.swt.events.SelectionAdapter;
-import org.eclipse.swt.events.SelectionEvent;
-import org.eclipse.swt.graphics.Point;
-import org.eclipse.swt.graphics.Rectangle;
-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.Control;
-import org.eclipse.swt.widgets.Display;
-import org.eclipse.swt.widgets.Shell;
-import org.eclipse.swt.widgets.Text;
-
-/** A very minimalistic web browser based on {@link Browser}. */
-public class MiniBrowser {
- private static Point defaultShellSize = new Point(800, 480);
-
- private Browser browser;
- private Text addressT;
-
- private final boolean fullscreen;
- private final boolean appMode;
-
- public MiniBrowser(Composite composite, String url, boolean fullscreen, boolean appMode) {
- this.fullscreen = fullscreen;
- this.appMode = appMode;
- createUi(composite);
- setUrl(url);
- }
-
- public Control createUi(Composite parent) {
- parent.setLayout(noSpaceGridLayout(new GridLayout()));
- if (!isAppMode()) {
- Control toolBar = createToolBar(parent);
- toolBar.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
- }
- Control body = createBody(parent);
- body.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
- return body;
- }
-
- protected Control createToolBar(Composite parent) {
- Composite toolBar = new Composite(parent, SWT.NONE);
- toolBar.setLayout(new FillLayout());
- addressT = new Text(toolBar, SWT.SINGLE);
- addressT.addSelectionListener(new SelectionAdapter() {
-
- @Override
- public void widgetDefaultSelected(SelectionEvent e) {
- setUrl(addressT.getText().trim());
- }
- });
- return toolBar;
- }
-
- protected Control createBody(Composite parent) {
- browser = new Browser(parent, SWT.NONE);
- if (isFullScreen())
- browser.addKeyListener(new KeyAdapter() {
- @Override
- public void keyPressed(KeyEvent e) {
- if (e.keyCode == 0x77 && e.stateMask == 0x40000) {// Ctrl+W
- browser.getShell().dispose();
- }
- }
- });
- browser.addLocationListener(new LocationAdapter() {
- @Override
- public void changed(LocationEvent event) {
- System.out.println(event);
- if (addressT != null)
- addressT.setText(event.location);
- }
-
- });
- browser.addTitleListener(e -> titleChanged(e.title));
- browser.addOpenWindowListener((e) -> {
- e.browser = openNewBrowserWindow();
- });
- return browser;
- }
-
- protected Browser openNewBrowserWindow() {
-
- if (isFullScreen()) {
- // TODO manage multiple tabs?
- return browser;
- } else {
- Shell newShell = new Shell(browser.getDisplay(), SWT.SHELL_TRIM);
- MiniBrowser newMiniBrowser = new MiniBrowser(newShell, null, false, isAppMode());
- newShell.setSize(defaultShellSize);
- newShell.open();
- return newMiniBrowser.browser;
- }
- }
-
- protected boolean isFullScreen() {
- return fullscreen;
- }
-
- void setUrl(String url) {
- if (browser != null && url != null && !url.equals(browser.getUrl()))
- browser.setUrl(url.toString());
- }
-
- /** Called when URL changed; to be overridden, does nothing by default. */
- protected void urlChanged(String url) {
- }
-
- /** Called when title changed; to be overridden, does nothing by default. */
- protected void titleChanged(String title) {
- }
-
- protected Browser getBrowser() {
- return browser;
- }
-
- protected boolean isAppMode() {
- return appMode;
- }
-
- private static GridLayout noSpaceGridLayout(GridLayout layout) {
- layout.horizontalSpacing = 0;
- layout.verticalSpacing = 0;
- layout.marginWidth = 0;
- layout.marginHeight = 0;
- return layout;
- }
-
- public static void main(String[] args) {
- List<String> options = Arrays.asList(args);
- if (options.contains("--help")) {
- System.out.println("Usage: java " + MiniBrowser.class.getName().replace('.', '/') + " [OPTION] [URL]");
- System.out.println("A minimalistic web browser Eclipse SWT Browser integration.");
- System.out.println(" --fullscreen : take control of the whole screen (default is to run in a window)");
- System.out.println(" --app : open without an address bar and a toolbar");
- System.out.println(" --help : print this help and exit");
- System.exit(1);
- }
- boolean fullscreen = options.contains("--fullscreen");
- boolean appMode = options.contains("--app");
- String url = "https://start.duckduckgo.com/";
- if (options.size() > 0) {
- String last = options.get(options.size() - 1);
- if (!last.startsWith("--"))
- url = last.trim();
- }
-
- Display display = Display.getCurrent() == null ? new Display() : Display.getCurrent();
- Shell shell;
- if (fullscreen) {
- shell = new Shell(display, SWT.NO_TRIM);
- shell.setFullScreen(true);
- Rectangle bounds = display.getBounds();
- shell.setSize(bounds.width, bounds.height);
- } else {
- shell = new Shell(display, SWT.SHELL_TRIM);
- shell.setSize(defaultShellSize);
- }
-
- new MiniBrowser(shell, url, fullscreen, appMode) {
-
- @Override
- protected void titleChanged(String title) {
- shell.setText(title);
- }
- };
- shell.open();
-
- while (!shell.isDisposed()) {
- if (!display.readAndDispatch())
- display.sleep();
- }
- }
-
-}
+++ /dev/null
-package org.argeo.minidesktop;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-import org.eclipse.swt.SWTException;
-import org.eclipse.swt.graphics.Image;
-import org.eclipse.swt.widgets.Display;
-
-/** Icons. */
-public class MiniDesktopImages {
-
- public final Image homeIcon;
- public final Image exitIcon;
-
- public final Image terminalIcon;
- public final Image browserIcon;
- public final Image explorerIcon;
- public final Image textEditorIcon;
-
- public final Image folderIcon;
- public final Image fileIcon;
-
- public MiniDesktopImages(Display display) {
- homeIcon = loadImage(display, "nav_home@2x.png");
- exitIcon = loadImage(display, "delete@2x.png");
-
- terminalIcon = loadImage(display, "console_view@2x.png");
- browserIcon = loadImage(display, "external_browser@2x.png");
- explorerIcon = loadImage(display, "fldr_obj@2x.png");
- textEditorIcon = loadImage(display, "cheatsheet_obj@2x.png");
-
- folderIcon = loadImage(display, "fldr_obj@2x.png");
- fileIcon = loadImage(display, "file_obj@2x.png");
- }
-
- static Image loadImage(Display display, String path) {
- InputStream stream = MiniDesktopImages.class.getResourceAsStream(path);
- if (stream == null)
- throw new IllegalArgumentException("Image " + path + " not found");
- Image image = null;
- try {
- image = new Image(display, stream);
- } catch (SWTException ex) {
- } finally {
- try {
- stream.close();
- } catch (IOException ex) {
- }
- }
- return image;
- }
-}
+++ /dev/null
-package org.argeo.minidesktop;
-
-import java.lang.management.ManagementFactory;
-import java.net.InetAddress;
-import java.net.UnknownHostException;
-import java.nio.file.Path;
-import java.util.Arrays;
-import java.util.List;
-
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.custom.CTabFolder;
-import org.eclipse.swt.custom.CTabItem;
-import org.eclipse.swt.events.SelectionAdapter;
-import org.eclipse.swt.events.SelectionEvent;
-import org.eclipse.swt.graphics.Color;
-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.layout.GridLayout;
-import org.eclipse.swt.program.Program;
-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.ToolBar;
-import org.eclipse.swt.widgets.ToolItem;
-
-/** A very minimalistic desktop manager based on Java and Eclipse SWT. */
-public class MiniDesktopManager {
- private Display display;
-
- private Shell rootShell;
- private Shell toolBarShell;
- private CTabFolder tabFolder;
- private int maxTabTitleLength = 16;
-
- private final boolean fullscreen;
- private final boolean stacking;
-
- private MiniDesktopImages images;
-
- public MiniDesktopManager(boolean fullscreen, boolean stacking) {
- this.fullscreen = fullscreen;
- this.stacking = stacking;
- }
-
- public void init() {
- Display.setAppName("Mini SWT Desktop");
- display = Display.getCurrent();
- if (display != null)
- throw new IllegalStateException("Already a display " + display);
- display = new Display();
-
- if (display.getTouchEnabled()) {
- System.out.println("Touch enabled.");
- }
-
- images = new MiniDesktopImages(display);
-
- int toolBarSize = 48;
-
- if (isFullscreen()) {
- rootShell = new Shell(display, SWT.NO_TRIM);
- rootShell.setFullScreen(true);
- Rectangle bounds = display.getBounds();
- rootShell.setLocation(0, 0);
- rootShell.setSize(bounds.width, bounds.height);
- } else {
- rootShell = new Shell(display, SWT.CLOSE | SWT.RESIZE);
- Rectangle shellArea = rootShell.computeTrim(200, 200, 800, 480);
- rootShell.setSize(shellArea.width, shellArea.height);
- rootShell.setText(Display.getAppName());
- rootShell.setImage(images.terminalIcon);
- }
-
- rootShell.setLayout(noSpaceGridLayout(new GridLayout(2, false)));
- Composite toolBarArea = new Composite(rootShell, SWT.NONE);
- toolBarArea.setLayoutData(new GridData(toolBarSize, rootShell.getSize().y));
-
- ToolBar toolBar;
- if (isFullscreen()) {
- toolBarShell = new Shell(rootShell, SWT.NO_TRIM | SWT.ON_TOP);
- toolBar = new ToolBar(toolBarShell, SWT.VERTICAL | SWT.FLAT | SWT.BORDER);
- createDock(toolBar);
- toolBarShell.pack();
- toolBarArea.setLayoutData(new GridData(toolBar.getSize().x, toolBar.getSize().y));
- } else {
- toolBar = new ToolBar(toolBarArea, SWT.VERTICAL | SWT.FLAT | SWT.BORDER);
- createDock(toolBar);
- toolBarArea.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false));
- }
-
- if (isStacking()) {
- tabFolder = new CTabFolder(rootShell, SWT.MULTI | SWT.BORDER | SWT.BOTTOM);
- tabFolder.setLayout(noSpaceGridLayout(new GridLayout()));
- tabFolder.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
-
- Color selectionBackground = display.getSystemColor(SWT.COLOR_LIST_SELECTION);
- tabFolder.setSelectionBackground(selectionBackground);
-
- // background
- Control background = createBackground(tabFolder);
- CTabItem homeTabItem = new CTabItem(tabFolder, SWT.NONE);
- homeTabItem.setText("Home");
- homeTabItem.setImage(images.homeIcon);
- homeTabItem.setControl(background);
- tabFolder.setFocus();
- } else {
- createBackground(rootShell);
- }
-
- rootShell.open();
- // rootShell.layout(true, true);
-
- if (toolBarShell != null) {
- int toolBarShellY = (display.getBounds().height - toolBar.getSize().y) / 2;
- toolBarShell.setLocation(0, toolBarShellY);
- toolBarShell.open();
- }
-
- long jvmUptime = ManagementFactory.getRuntimeMXBean().getUptime();
- System.out.println("SWT Mini Desktop Manager available in " + jvmUptime + " ms.");
- }
-
- protected void createDock(ToolBar toolBar) {
- // Terminal
- addToolItem(toolBar, images.terminalIcon, "Terminal", () -> {
- String url = System.getProperty("user.home");
- AppContext appContext = createAppParent(images.terminalIcon);
- new MiniTerminal(appContext.getAppParent(), url) {
-
- @Override
- protected void exitCalled() {
- if (appContext.shell != null)
- appContext.shell.dispose();
- if (appContext.tabItem != null)
- appContext.tabItem.dispose();
- }
- };
- String title;
- try {
- title = System.getProperty("user.name") + "@" + InetAddress.getLocalHost().getHostName();
- } catch (UnknownHostException e) {
- title = System.getProperty("user.name") + "@localhost";
- }
- if (appContext.shell != null)
- appContext.shell.setText(title);
- if (appContext.tabItem != null) {
- appContext.tabItem.setText(tabTitle(title));
- appContext.tabItem.setToolTipText(title);
- }
- openApp(appContext);
- });
-
- // Web browser
- addToolItem(toolBar, images.browserIcon, "Browser", () -> {
- String url = "https://start.duckduckgo.com/";
- AppContext appContext = createAppParent(images.browserIcon);
- new MiniBrowser(appContext.getAppParent(), url, false, false) {
- @Override
- protected void titleChanged(String title) {
- if (appContext.shell != null)
- appContext.shell.setText(title);
- if (appContext.tabItem != null) {
- appContext.tabItem.setText(tabTitle(title));
- appContext.tabItem.setToolTipText(title);
- }
- }
- };
- openApp(appContext);
- });
-
- // File explorer
- addToolItem(toolBar, images.explorerIcon, "Explorer", () -> {
- String url = System.getProperty("user.home");
- AppContext appContext = createAppParent(images.explorerIcon);
- new MiniExplorer(appContext.getAppParent(), url) {
-
- @Override
- protected void pathChanged(Path path) {
- if (appContext.shell != null)
- appContext.shell.setText(path.toString());
- if (appContext.tabItem != null) {
- appContext.tabItem.setText(path.getFileName().toString());
- appContext.tabItem.setToolTipText(path.toString());
- }
- }
- };
- openApp(appContext);
- });
-
- // Separator
- new ToolItem(toolBar, SWT.SEPARATOR);
-
- // Exit
- addToolItem(toolBar, images.exitIcon, "Exit", () -> rootShell.dispose());
-
- toolBar.pack();
- }
-
- protected String tabTitle(String title) {
- return title.length() > maxTabTitleLength ? title.substring(0, maxTabTitleLength) : title;
- }
-
- protected void addToolItem(ToolBar toolBar, Image icon, String name, Runnable action) {
- ToolItem searchI = new ToolItem(toolBar, SWT.PUSH);
- searchI.setImage(icon);
- searchI.setToolTipText(name);
- searchI.addSelectionListener(new SelectionAdapter() {
-
- @Override
- public void widgetSelected(SelectionEvent e) {
- action.run();
- }
-
- });
- }
-
- protected AppContext createAppParent(Image icon) {
- if (isStacking()) {
- Composite appParent = new Composite(tabFolder, SWT.CLOSE);
- appParent.setLayout(noSpaceGridLayout(new GridLayout()));
- CTabItem item = new CTabItem(tabFolder, SWT.CLOSE);
- item.setImage(icon);
- item.setControl(appParent);
- return new AppContext(item);
- } else {
- Shell shell = isFullscreen() ? new Shell(rootShell, SWT.SHELL_TRIM)
- : new Shell(rootShell.getDisplay(), SWT.SHELL_TRIM);
- shell.setImage(icon);
- return new AppContext(shell);
- }
- }
-
- protected void openApp(AppContext appContext) {
- if (appContext.shell != null) {
- Shell shell = (Shell) appContext.shell;
- shell.open();
- shell.setSize(new Point(800, 480));
- }
- if (appContext.tabItem != null) {
- tabFolder.setFocus();
- tabFolder.setSelection(appContext.tabItem);
- }
- }
-
- protected Control createBackground(Composite parent) {
- Composite backgroundArea = new Composite(parent, SWT.NONE);
- backgroundArea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
- initBackground(backgroundArea);
- return backgroundArea;
- }
-
- protected void initBackground(Composite backgroundArea) {
- MiniHomePart homePart = new MiniHomePart() {
-
- @Override
- protected void fillAppsToolBar(ToolBar toolBar) {
- createDock(toolBar);
- }
- };
- homePart.createUiPart(backgroundArea, null);
- }
-
- public void run() {
- while (!rootShell.isDisposed()) {
- if (!display.readAndDispatch())
- display.sleep();
- }
- }
-
- public void dispose() {
- if (!rootShell.isDisposed())
- rootShell.dispose();
- }
-
- protected boolean isFullscreen() {
- return fullscreen;
- }
-
- protected boolean isStacking() {
- return stacking;
- }
-
- protected Image getIconForExt(String ext) {
- Program program = Program.findProgram(ext);
- if (program == null)
- return display.getSystemImage(SWT.ICON_INFORMATION);
-
- ImageData iconData = program.getImageData();
- if (iconData == null) {
- return display.getSystemImage(SWT.ICON_INFORMATION);
- } else {
- return new Image(display, iconData);
- }
-
- }
-
- private static GridLayout noSpaceGridLayout(GridLayout layout) {
- layout.horizontalSpacing = 0;
- layout.verticalSpacing = 0;
- layout.marginWidth = 0;
- layout.marginHeight = 0;
- return layout;
- }
-
- public static void main(String[] args) {
- List<String> options = Arrays.asList(args);
- if (options.contains("--help")) {
- System.out.println("Usage: java " + MiniDesktopManager.class.getName().replace('.', '/') + " [OPTION]");
- System.out.println("A minimalistic desktop manager based on Java and Eclipse SWT.");
- System.out.println(" --fullscreen : take control of the whole screen (default is to run in a window)");
- System.out.println(" --stacking : open apps as tabs (default is to create new windows)");
- System.out.println(" --help : print this help and exit");
- System.exit(1);
- }
- boolean fullscreen = options.contains("--fullscreen");
- boolean stacking = options.contains("--stacking");
-
- MiniDesktopManager desktopManager = new MiniDesktopManager(fullscreen, stacking);
- desktopManager.init();
- desktopManager.run();
- desktopManager.dispose();
- System.exit(0);
- }
-
- class AppContext {
- private Shell shell;
- private CTabItem tabItem;
-
- public AppContext(Shell shell) {
- this.shell = shell;
- }
-
- public AppContext(CTabItem tabItem) {
- this.tabItem = tabItem;
- }
-
- Composite getAppParent() {
- if (shell != null)
- return shell;
- if (tabItem != null)
- return (Composite) tabItem.getControl();
- throw new IllegalStateException();
- }
- }
-}
+++ /dev/null
-package org.argeo.minidesktop;
-
-import java.io.IOException;
-import java.nio.file.DirectoryStream;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-
-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.graphics.Point;
-import org.eclipse.swt.layout.FillLayout;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.program.Program;
-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.TableItem;
-import org.eclipse.swt.widgets.Text;
-
-public class MiniExplorer {
- private Path path;
- private Text addressT;
- private Table browser;
-
- private boolean showHidden = false;
-
- public MiniExplorer(Composite parent, String url) {
- this(parent);
- setUrl(url);
- }
-
- public MiniExplorer(Composite parent) {
- parent.setLayout(noSpaceGridLayout(new GridLayout()));
-
- Composite toolBar = new Composite(parent, SWT.NONE);
- toolBar.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
- toolBar.setLayout(new FillLayout());
- addressT = new Text(toolBar, SWT.SINGLE);
- addressT.addSelectionListener(new SelectionAdapter() {
-
- @Override
- public void widgetDefaultSelected(SelectionEvent e) {
- setUrl(addressT.getText().trim());
- }
- });
- browser = createTable(parent, this.path);
-
- }
-
- public void setPath(Path url) {
- this.path = url;
- if (addressT != null)
- addressT.setText(url.toString());
- if (browser != null) {
- Composite parent = browser.getParent();
- browser.dispose();
- browser = createTable(parent, this.path);
- parent.layout(true, true);
- }
- pathChanged(url);
- }
-
- protected void pathChanged(Path path) {
-
- }
-
- protected Table createTable(Composite parent, Path path) {
- Table table = new Table(parent, SWT.BORDER);
- table.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
- table.addMouseListener(new MouseAdapter() {
-
- @Override
- public void mouseDoubleClick(MouseEvent e) {
- Point pt = new Point(e.x, e.y);
- TableItem item = table.getItem(pt);
- Path path = (Path) item.getData();
- if (Files.isDirectory(path)) {
- setPath(path);
- } else {
- Program.launch(path.toString());
- }
- }
- });
-
- if (path != null) {
- if (path.getParent() != null) {
- TableItem parentTI = new TableItem(table, SWT.NONE);
- parentTI.setText("..");
- parentTI.setData(path.getParent());
- }
-
- try {
- // directories
- DirectoryStream<Path> ds = Files.newDirectoryStream(path, p -> Files.isDirectory(p) && isShown(p));
- ds.forEach(p -> {
- TableItem ti = new TableItem(table, SWT.NONE);
- ti.setText(p.getFileName().toString() + "/");
- ti.setData(p);
- });
- // files
- ds = Files.newDirectoryStream(path, p -> !Files.isDirectory(p) && isShown(p));
- ds.forEach(p -> {
- TableItem ti = new TableItem(table, SWT.NONE);
- ti.setText(p.getFileName().toString());
- ti.setData(p);
- });
- } catch (IOException e1) {
- // TODO Auto-generated catch block
- e1.printStackTrace();
- }
- }
- return table;
- }
-
- protected boolean isShown(Path path) {
- if (showHidden)
- return true;
- try {
- return !Files.isHidden(path);
- } catch (IOException e) {
- throw new IllegalArgumentException("Cannot check " + path, e);
- }
- }
-
- public void setUrl(String url) {
- setPath(Paths.get(url));
- }
-
- private static GridLayout noSpaceGridLayout(GridLayout layout) {
- layout.horizontalSpacing = 0;
- layout.verticalSpacing = 0;
- layout.marginWidth = 0;
- layout.marginHeight = 0;
- return layout;
- }
-
- public static void main(String[] args) {
- Display display = Display.getCurrent() == null ? new Display() : Display.getCurrent();
- Shell shell = new Shell(display, SWT.SHELL_TRIM);
-
- String url = args.length > 0 ? args[0] : System.getProperty("user.home");
- new MiniExplorer(shell, url) {
-
- @Override
- protected void pathChanged(Path path) {
- shell.setText(path.toString());
- }
-
- };
-
- shell.open();
- shell.setSize(new Point(800, 480));
- while (!shell.isDisposed()) {
- if (!display.readAndDispatch())
- display.sleep();
- }
- }
-
-}
+++ /dev/null
-package org.argeo.minidesktop;
-
-import java.net.InetAddress;
-import java.net.InterfaceAddress;
-import java.net.NetworkInterface;
-import java.net.SocketException;
-import java.net.UnknownHostException;
-import java.util.Enumeration;
-
-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.Group;
-import org.eclipse.swt.widgets.Label;
-import org.eclipse.swt.widgets.ProgressBar;
-import org.eclipse.swt.widgets.ToolBar;
-
-/** A start page displaying network information and resources. */
-public class MiniHomePart {
-
- public Control createUiPart(Composite parent, Object context) {
- parent.setLayout(new GridLayout(2, false));
- Display display = parent.getDisplay();
-
- // Apps
- Group appsGroup = new Group(parent, SWT.NONE);
- appsGroup.setText("Apps");
- appsGroup.setLayoutData(new GridData(SWT.FILL, SWT.TOP, false, false, 2, 1));
- ToolBar appsToolBar = new ToolBar(appsGroup, SWT.HORIZONTAL | SWT.FLAT);
- fillAppsToolBar(appsToolBar);
-
- // Host
- Group hostGroup = new Group(parent, SWT.NONE);
- hostGroup.setLayoutData(new GridData(SWT.FILL, SWT.TOP, false, false));
- hostGroup.setText("Host");
- hostGroup.setLayout(new GridLayout(2, false));
- label(hostGroup, "Hostname: ");
- try {
- InetAddress defaultAddr = InetAddress.getLocalHost();
- String hostname = defaultAddr.getHostName();
- label(hostGroup, hostname);
- label(hostGroup, "Address: ");
- label(hostGroup, defaultAddr.getHostAddress());
- } catch (UnknownHostException e) {
- label(hostGroup, e.getMessage());
- }
-
- Enumeration<NetworkInterface> netInterfaces = null;
- try {
- netInterfaces = NetworkInterface.getNetworkInterfaces();
- } catch (SocketException e) {
- label(hostGroup, "Interfaces: ");
- label(hostGroup, e.getMessage());
- }
- if (netInterfaces != null)
- while (netInterfaces.hasMoreElements()) {
- NetworkInterface netInterface = netInterfaces.nextElement();
- byte[] hardwareAddress = null;
- try {
- hardwareAddress = netInterface.getHardwareAddress();
- if (hardwareAddress != null) {
- label(hostGroup, convertHardwareAddress(hardwareAddress));
- label(hostGroup, netInterface.getName());
- for (InterfaceAddress addr : netInterface.getInterfaceAddresses()) {
- label(hostGroup, cleanHostAddress(addr.getAddress().getHostAddress()));
- label(hostGroup, Short.toString(addr.getNetworkPrefixLength()));
- }
- }
- } catch (SocketException e) {
- label(hostGroup, e.getMessage());
- }
- }
-
- // Resources
- Group resGroup = new Group(parent, SWT.NONE);
- resGroup.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false));
- resGroup.setText("Resources");
- resGroup.setLayout(new GridLayout(3, false));
-
- Runtime runtime = Runtime.getRuntime();
-
- String maxMemoryStr = Long.toString(runtime.maxMemory() / (1024 * 1024)) + " MB";
- label(resGroup, "Max Java memory: ");
- label(resGroup, maxMemoryStr);
- label(resGroup, "Java version: " + Runtime.version().toString());
-
- label(resGroup, "Usable Java memory: ");
- Label totalMemory = label(resGroup, maxMemoryStr);
- ProgressBar totalOnMax = new ProgressBar(resGroup, SWT.SMOOTH);
- totalOnMax.setMaximum(100);
- label(resGroup, "Used Java memory: ");
- Label usedMemory = label(resGroup, maxMemoryStr);
- ProgressBar usedOnTotal = new ProgressBar(resGroup, SWT.SMOOTH);
- totalOnMax.setMaximum(100);
- new Thread() {
- @Override
- public void run() {
- while (!totalOnMax.isDisposed()) {
- display.asyncExec(() -> {
- if (totalOnMax.isDisposed())
- return;
- totalOnMax.setSelection(javaTotalOnMaxPerct(runtime));
- usedOnTotal.setSelection(javaUsedOnTotalPerct(runtime));
- totalMemory.setText(Long.toString(runtime.totalMemory() / (1024 * 1024)) + " MB");
- usedMemory.setText(
- Long.toString((runtime.totalMemory() - runtime.freeMemory()) / (1024 * 1024)) + " MB");
- });
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- return;
- }
- }
- }
- }.start();
- return parent;
- }
-
- protected void fillAppsToolBar(ToolBar toolBar) {
-
- }
-
- protected int javaUsedOnTotalPerct(Runtime runtime) {
- return Math.toIntExact((runtime.totalMemory() - runtime.freeMemory()) * 100 / runtime.totalMemory());
- }
-
- protected int javaTotalOnMaxPerct(Runtime runtime) {
- return Math.toIntExact((runtime.totalMemory()) * 100 / runtime.maxMemory());
- }
-
- protected Label label(Composite parent, String text) {
- Label label = new Label(parent, SWT.WRAP);
- label.setText(text);
- return label;
- }
-
- protected String cleanHostAddress(String hostAddress) {
- // remove % from Ipv6 addresses
- int index = hostAddress.indexOf('%');
- if (index > 0)
- return hostAddress.substring(0, index);
- else
- return hostAddress;
- }
-
- protected String convertHardwareAddress(byte[] hardwareAddress) {
- if (hardwareAddress == null)
- return "";
- // from https://stackoverflow.com/a/2797498/7878010
- StringBuilder sb = new StringBuilder(18);
- for (byte b : hardwareAddress) {
- if (sb.length() > 0)
- sb.append(':');
- sb.append(String.format("%02x", b).toUpperCase());
- }
- return sb.toString();
- }
-}
+++ /dev/null
-package org.argeo.minidesktop;
-
-import java.io.IOException;
-import java.net.MalformedURLException;
-import java.net.URISyntaxException;
-import java.net.URL;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.events.PaintEvent;
-import org.eclipse.swt.events.PaintListener;
-import org.eclipse.swt.events.SelectionAdapter;
-import org.eclipse.swt.events.SelectionEvent;
-import org.eclipse.swt.graphics.Image;
-import org.eclipse.swt.graphics.ImageData;
-import org.eclipse.swt.graphics.ImageLoader;
-import org.eclipse.swt.graphics.Point;
-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.Canvas;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Display;
-import org.eclipse.swt.widgets.FileDialog;
-import org.eclipse.swt.widgets.Shell;
-
-public class MiniImageViewer implements PaintListener {
- private URL url;
- private Canvas area;
-
- private Image image;
-
- public MiniImageViewer(Composite parent, int style) {
- parent.setLayout(new GridLayout());
-
- Composite toolBar = new Composite(parent, SWT.NONE);
- toolBar.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
- toolBar.setLayout(new RowLayout());
- Button load = new Button(toolBar, SWT.FLAT);
- load.setText("\u2191");// up arrow
- load.addSelectionListener(new SelectionAdapter() {
-
- @Override
- public void widgetSelected(SelectionEvent e) {
- FileDialog fileDialog = new FileDialog(area.getShell());
- String path = fileDialog.open();
- if (path != null) {
- setUrl(path);
- }
- }
-
- });
-
- area = new Canvas(parent, SWT.NONE);
- area.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
- area.addPaintListener(this);
- }
-
- protected void load(URL url) {
- try {
- ImageLoader imageLoader = new ImageLoader();
- ImageData[] data = imageLoader.load(url.openStream());
- image = new Image(area.getDisplay(), data[0]);
- } catch (IOException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
-
- @Override
- public void paintControl(PaintEvent e) {
- e.gc.drawImage(image, 0, 0);
-
- }
-
- protected Path url2path(URL url) {
- try {
- Path path = Paths.get(url.toURI());
- return path;
- } catch (URISyntaxException e) {
- throw new IllegalStateException("Cannot convert " + url + " to uri", e);
- }
- }
-
- public void setUrl(URL url) {
- this.url = url;
- if (area != null)
- load(this.url);
- }
-
- public void setUrl(String url) {
- try {
- setUrl(new URL(url));
- } catch (MalformedURLException e) {
- // try with http
- try {
- setUrl(new URL("file://" + url));
- return;
- } catch (MalformedURLException e1) {
- // nevermind...
- }
- throw new IllegalArgumentException("Cannot interpret URL " + url, e);
- }
- }
-
- public static void main(String[] args) {
- Display display = Display.getCurrent() == null ? new Display() : Display.getCurrent();
- Shell shell = new Shell(display, SWT.SHELL_TRIM);
-
- MiniImageViewer miniBrowser = new MiniImageViewer(shell, SWT.NONE);
- String url = args.length > 0 ? args[0] : "";
- if (!url.trim().equals("")) {
- miniBrowser.setUrl(url);
- shell.setText(url);
- } else {
- shell.setText("*");
- }
-
- shell.open();
- shell.setSize(new Point(800, 480));
- while (!shell.isDisposed()) {
- if (!display.readAndDispatch())
- display.sleep();
- }
- }
-
-}
+++ /dev/null
-package org.argeo.minidesktop;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.OutputStream;
-import java.net.InetAddress;
-import java.net.UnknownHostException;
-import java.nio.charset.Charset;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.StringTokenizer;
-
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.events.KeyEvent;
-import org.eclipse.swt.events.KeyListener;
-import org.eclipse.swt.events.PaintEvent;
-import org.eclipse.swt.events.PaintListener;
-import org.eclipse.swt.graphics.Font;
-import org.eclipse.swt.graphics.GC;
-import org.eclipse.swt.graphics.Point;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.widgets.Canvas;
-import org.eclipse.swt.widgets.Caret;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Display;
-import org.eclipse.swt.widgets.Shell;
-
-public class MiniTerminal implements KeyListener, PaintListener {
-
- private Canvas area;
- private Caret caret;
-
- private StringBuffer buf = new StringBuffer("");
- private StringBuffer userInput = new StringBuffer("");
- private List<String> history = new ArrayList<>();
-
- private Point charExtent = null;
- private int charsPerLine = 0;
- private String[] lines = new String[0];
- private List<String> logicalLines = new ArrayList<>();
-
- private Font mono;
- private Charset charset;
-
- private Path currentDir;
- private Path homeDir;
- private String host = "localhost";
- private String username;
-
- // Sub process
- private Process process;
- private boolean running = false;
- private OutputStream stdIn = null;
-
- private Thread readOut;
-
- public MiniTerminal(Composite parent, String url) {
- this(parent);
- setPath(url);
- }
-
- public MiniTerminal(Composite parent) {
- charset = StandardCharsets.UTF_8;
-
- Display display = parent.getDisplay();
- // Linux-specific
- mono = new Font(display, "Monospace", 10, SWT.NONE);
-
- parent.setLayout(new GridLayout());
- area = new Canvas(parent, SWT.NONE);
- area.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
- caret = new Caret(area, SWT.NONE);
- area.setCaret(caret);
-
- area.addKeyListener(this);
- area.addPaintListener(this);
-
- username = System.getProperty("user.name");
- try {
- host = InetAddress.getLocalHost().getHostName();
- if (host.indexOf('.') > 0)
- host = host.substring(0, host.indexOf('.'));
- } catch (UnknownHostException e) {
- host = "localhost";
- }
- homeDir = Paths.get(System.getProperty("user.home"));
- currentDir = homeDir;
-
- buf = new StringBuffer(prompt());
- }
-
- @Override
- public void keyPressed(KeyEvent e) {
- }
-
- @Override
- public void keyReleased(KeyEvent e) {
- if (e.keyLocation != 0)
- return;// weird characters
- // System.out.println(e.character);
- if (e.keyCode == 0xd) {// return
- markLogicalLine();
- if (!running)
- processUserInput();
- // buf.append(prompt());
- } else if (e.keyCode == 0x8) {// delete
- if (userInput.length() == 0)
- return;
- userInput.setLength(userInput.length() - 1);
- if (!running && buf.length() > 0)
- buf.setLength(buf.length() - 1);
- } else if (e.stateMask == 0x40000 && e.keyCode == 0x63) {// Ctrl+C
- if (process != null)
- process.destroy();
- } else if (e.stateMask == 0x40000 && e.keyCode == 0xdf) {// Ctrl+\
- if (process != null) {
- process.destroyForcibly();
- }
- } else {
- // if (!running)
- buf.append(e.character);
- userInput.append(e.character);
- }
-
- if (area.isDisposed())
- return;
- area.redraw();
- // System.out.println("Append " + e);
-
- if (running) {
- if (stdIn != null) {
- try {
- stdIn.write(Character.toString(e.character).getBytes(charset));
- } catch (IOException e1) {
- // TODO Auto-generated catch block
- e1.printStackTrace();
- }
- }
- }
- }
-
- protected String prompt() {
- String fileName = currentDir.equals(homeDir) ? "~" : currentDir.getFileName().toString();
- String end = username.equals("root") ? "]# " : "]$ ";
- return "[" + username + "@" + host + " " + fileName + end;
- }
-
- private void displayPrompt() {
- buf.append(prompt() + userInput);
- }
-
- protected void markLogicalLine() {
- String str = buf.toString().trim();
- logicalLines.add(str);
- buf = new StringBuffer("");
- }
-
- private void processUserInput() {
- String cmd = userInput.toString();
- userInput = new StringBuffer("");
- processUserInput(cmd);
- history.add(cmd);
- }
-
- protected void processUserInput(String input) {
- try {
- StringTokenizer st = new StringTokenizer(input);
- List<String> args = new ArrayList<>();
- while (st.hasMoreTokens())
- args.add(st.nextToken());
- if (args.size() == 0) {
- displayPrompt();
- return;
- }
-
- // change directory
- if (args.get(0).equals("cd")) {
- if (args.size() == 1) {
- setPath(homeDir);
- } else {
- Path newPath = currentDir.resolve(args.get(1));
- if (!Files.exists(newPath) || !Files.isDirectory(newPath)) {
- println(newPath + ": No such file or directory");
- return;
- }
- setPath(newPath);
- }
- displayPrompt();
- return;
- }
- // show current directory
- else if (args.get(0).equals("pwd")) {
- println(currentDir);
- displayPrompt();
- return;
- }
- // exit
- else if (args.get(0).equals("exit")) {
- println("logout");
- exitCalled();
- return;
- }
-
- ProcessBuilder pb = new ProcessBuilder(args);
- pb.redirectErrorStream(true);
- pb.directory(currentDir.toFile());
-// Process process = Runtime.getRuntime().exec(input, null, currentPath.toFile());
- process = pb.start();
-
- stdIn = process.getOutputStream();
- readOut = new Thread("MiniTerminal read out") {
- @Override
- public void run() {
- running = true;
- try (BufferedReader in = new BufferedReader(
- new InputStreamReader(process.getInputStream(), charset))) {
- String line = null;
- while ((line = in.readLine()) != null) {
- println(line);
- }
- } catch (IOException e) {
- println(e.getMessage());
- }
- stdIn = null;
- displayPrompt();
- running = false;
- readOut = null;
- process = null;
- }
- };
- readOut.start();
- } catch (IOException e) {
- println(e.getMessage());
- displayPrompt();
- }
- }
-
- protected int linesForLogicalLine(char[] line) {
- return line.length / charsPerLine + 1;
- }
-
- protected void println(Object line) {
- buf.append(line);
- markLogicalLine();
- }
-
- protected void refreshLines(int charPerLine, int nbrOfLines) {
- if (lines.length != nbrOfLines) {
- lines = new String[nbrOfLines];
- Arrays.fill(lines, null);
- }
- if (this.charsPerLine != charPerLine)
- this.charsPerLine = charPerLine;
-
- int currentLine = nbrOfLines - 1;
- // current line
- if (buf.length() > 0) {
- lines[currentLine] = buf.toString();
- } else {
- lines[currentLine] = "";
- }
- currentLine--;
-
- logicalLines: for (int i = logicalLines.size() - 1; i >= 0; i--) {
- char[] logicalLine = logicalLines.get(i).toCharArray();
- int linesNeeded = linesForLogicalLine(logicalLine);
- for (int j = linesNeeded - 1; j >= 0; j--) {
- int from = j * charPerLine;
- int to = j == linesNeeded - 1 ? from + charPerLine : Math.min(from + charPerLine, logicalLine.length);
- lines[currentLine] = new String(Arrays.copyOfRange(logicalLine, from, to));
-// System.out.println("Set line " + currentLine + " to : " + lines[currentLine]);
- currentLine--;
- if (currentLine < 0)
- break logicalLines;
- }
- }
- }
-
- @Override
- public void paintControl(PaintEvent e) {
- GC gc = e.gc;
- gc.setFont(mono);
- if (charExtent == null)
- charExtent = gc.textExtent("a");
-
- Point areaSize = area.getSize();
- int charPerLine = areaSize.x / charExtent.x;
- int nbrOfLines = areaSize.y / charExtent.y;
- refreshLines(charPerLine, nbrOfLines);
-
- for (int i = 0; i < lines.length; i++) {
- String line = lines[i];
- if (line != null)
- gc.drawString(line, 0, i * charExtent.y);
- }
-// String toDraw = buf.toString();
-// gc.drawString(toDraw, 0, 0);
-// area.setCaret(caret);
- }
-
- protected void exitCalled() {
-
- }
-
- public void setPath(String path) {
- this.currentDir = Paths.get(path);
- }
-
- public void setPath(Path path) {
- this.currentDir = path;
- }
-
- public static void main(String[] args) {
- Display display = Display.getCurrent() == null ? new Display() : Display.getCurrent();
- Shell shell = new Shell(display, SWT.SHELL_TRIM);
-
- String url = args.length > 0 ? args[0] : System.getProperty("user.home");
- new MiniTerminal(shell, url) {
-
- @Override
- protected void exitCalled() {
- shell.dispose();
- System.exit(0);
- }
- };
-
- shell.open();
- shell.setSize(new Point(800, 480));
- while (!shell.isDisposed()) {
- if (!display.readAndDispatch())
- display.sleep();
- }
- }
-
-}
+++ /dev/null
-package org.argeo.minidesktop;
-
-import java.io.BufferedReader;
-import java.io.BufferedWriter;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.OutputStreamWriter;
-import java.net.MalformedURLException;
-import java.net.URISyntaxException;
-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 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.layout.RowLayout;
-import org.eclipse.swt.widgets.Button;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Display;
-import org.eclipse.swt.widgets.FileDialog;
-import org.eclipse.swt.widgets.Shell;
-import org.eclipse.swt.widgets.Text;
-
-public class MiniTextEditor {
- private URL url;
- private Text text;
-
- public MiniTextEditor(Composite parent, int style) {
- parent.setLayout(new GridLayout());
-
- Composite toolBar = new Composite(parent, SWT.NONE);
- toolBar.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
- toolBar.setLayout(new RowLayout());
- Button load = new Button(toolBar, SWT.FLAT);
- load.setText("\u2191");// up arrow
- load.addSelectionListener(new SelectionAdapter() {
-
- @Override
- public void widgetSelected(SelectionEvent e) {
- FileDialog fileDialog = new FileDialog(text.getShell());
- String path = fileDialog.open();
- if (path != null) {
- setUrl(path);
- }
- }
-
- });
-
- Button save = new Button(toolBar, SWT.FLAT);
- save.setText("\u2193");// down arrow
- // save.setText("\u1F609");// emoji
- save.addSelectionListener(new SelectionAdapter() {
-
- @Override
- public void widgetSelected(SelectionEvent e) {
- save(url);
- }
-
- });
-
- text = new Text(parent, SWT.WRAP | SWT.MULTI | SWT.BORDER | SWT.V_SCROLL);
- text.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
- }
-
- protected void load(URL url) {
- text.setText("");
- // TODO deal with encoding and binary data
- try (BufferedReader in = new BufferedReader(new InputStreamReader(url.openStream(), StandardCharsets.UTF_8))) {
- String line = null;
- while ((line = in.readLine()) != null) {
- text.append(line + "\n");
- }
- text.setEditable(true);
- } catch (IOException e) {
- if (e instanceof FileNotFoundException) {
- Path path = url2path(url);
- try {
- Files.createFile(path);
- load(url);
- return;
- } catch (IOException e1) {
- e = e1;
- }
- }
- text.setText(e.getMessage());
- text.setEditable(false);
- e.printStackTrace();
- // throw new IllegalStateException("Cannot load " + url, e);
- }
- }
-
- protected Path url2path(URL url) {
- try {
- Path path = Paths.get(url.toURI());
- return path;
- } catch (URISyntaxException e) {
- throw new IllegalStateException("Cannot convert " + url + " to uri", e);
- }
- }
-
- protected void save(URL url) {
- if (!url.getProtocol().equals("file"))
- throw new IllegalArgumentException(url.getProtocol() + " protocol is not supported for write");
- Path path = url2path(url);
- try (BufferedWriter out = new BufferedWriter(new OutputStreamWriter(Files.newOutputStream(path)))) {
- out.write(text.getText());
- } catch (IOException e) {
- throw new IllegalStateException("Cannot save " + url + " to " + path, e);
- }
- }
-
- public void setUrl(URL url) {
- this.url = url;
- if (text != null)
- load(url);
- }
-
- public void setUrl(String url) {
- try {
- setUrl(new URL(url));
- } catch (MalformedURLException e) {
- // try with http
- try {
- setUrl(new URL("file://" + url));
- return;
- } catch (MalformedURLException e1) {
- // nevermind...
- }
- throw new IllegalArgumentException("Cannot interpret URL " + url, e);
- }
- }
-
- public static void main(String[] args) {
- Display display = Display.getCurrent() == null ? new Display() : Display.getCurrent();
- Shell shell = new Shell(display, SWT.SHELL_TRIM);
-
- MiniTextEditor miniBrowser = new MiniTextEditor(shell, SWT.NONE);
- String url = args.length > 0 ? args[0] : "";
- if (!url.trim().equals("")) {
- miniBrowser.setUrl(url);
- shell.setText(url);
- } else {
- shell.setText("*");
- }
-
- shell.open();
- shell.setSize(new Point(800, 480));
- while (!shell.isDisposed()) {
- if (!display.readAndDispatch())
- display.sleep();
- }
- }
-
-}
+++ /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-1.8" />
- <classpathentry kind="output" path="bin" />
-</classpath>
+++ /dev/null
-/target/
-/bin/
-*.log
\ No newline at end of file
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
- <name>org.argeo.swt.specific.rcp</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: \
-!java.*,\
-org.apache.commons.io,\
-org.eclipse.core.commands,\
-!org.eclipse.core.runtime,\
-!org.eclipse.ui.plugin,\
-org.eclipse.swt,\
-javax.servlet.http;version="[3,5)",\
-javax.servlet;version="[3,5)",\
-*
-
-Export-Package: org.argeo.*,\
-org.eclipse.rap.fileupload.*;version="3.10",\
-org.eclipse.rap.rwt.*;version="3.10"
-
-# Was !org.eclipse.core.commands,\ why ?
-
-#Bundle-Activator: org.argeo.eclipse.ui.ArgeoUiPlugin
-#Bundle-ActivationPolicy: lazy
-#Ignore-Package: org.eclipse.core.commands
\ No newline at end of file
+++ /dev/null
-source.. = src/
-output.. = bin/
-bin.includes = META-INF/
+++ /dev/null
-package org.argeo.eclipse.ui.rcp.internal.rwt;
-
-import org.eclipse.rap.rwt.client.Client;
-import org.eclipse.rap.rwt.client.service.BrowserNavigation;
-import org.eclipse.rap.rwt.client.service.BrowserNavigationListener;
-import org.eclipse.rap.rwt.client.service.ClientService;
-import org.eclipse.rap.rwt.client.service.JavaScriptExecutor;
-
-public class RcpClient implements Client {
-
- @Override
- public <T extends ClientService> T getService(Class<T> type) {
- if (type.isAssignableFrom(JavaScriptExecutor.class))
- return (T) javaScriptExecutor;
- else if (type.isAssignableFrom(BrowserNavigation.class))
- return (T) browserNavigation;
- else
- return null;
- }
-
- private JavaScriptExecutor javaScriptExecutor = new JavaScriptExecutor() {
-
- @Override
- public void execute(String code) {
- // TODO Auto-generated method stub
-
- }
- };
- private BrowserNavigation browserNavigation = new BrowserNavigation() {
-
- @Override
- public void pushState(String state, String title) {
- // TODO Auto-generated method stub
-
- }
-
- @Override
- public void addBrowserNavigationListener(
- BrowserNavigationListener listener) {
- // TODO Auto-generated method stub
-
- }
- };
-}
+++ /dev/null
-package org.argeo.eclipse.ui.rcp.internal.rwt;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.Collections;
-import java.util.Map;
-import java.util.TreeMap;
-
-import org.apache.commons.io.IOUtils;
-import org.eclipse.rap.rwt.service.ResourceManager;
-
-public class RcpResourceManager implements ResourceManager {
- private Map<String, byte[]> register = Collections
- .synchronizedMap(new TreeMap<String, byte[]>());
-
- @Override
- public void register(String name, InputStream in) {
- try {
- register.put(name, IOUtils.toByteArray(in));
- } catch (IOException e) {
- throw new RuntimeException("Cannot register " + name, e);
- }
- }
-
- @Override
- public boolean unregister(String name) {
- return register.remove(name) != null;
- }
-
- @Override
- public InputStream getRegisteredContent(String name) {
- return new ByteArrayInputStream(register.get(name));
- }
-
- @Override
- public String getLocation(String name) {
- return name;
- }
-
- @Override
- public boolean isRegistered(String name) {
- return register.containsKey(name);
- }
-
-}
+++ /dev/null
-package org.argeo.eclipse.ui.specific;
-
-import org.eclipse.swt.widgets.FileDialog;
-import org.eclipse.swt.widgets.Shell;
-
-public class CmsFileDialog extends FileDialog {
- public CmsFileDialog(Shell parent, int style) {
- super(parent, style);
- }
-
- public CmsFileDialog(Shell parent) {
- super(parent);
- }
-
-}
+++ /dev/null
-package org.argeo.eclipse.ui.specific;
-
-import org.eclipse.rap.rwt.widgets.FileUpload;
-import org.eclipse.swt.events.SelectionListener;
-import org.eclipse.swt.widgets.Composite;
-
-public class CmsFileUpload extends FileUpload {
- public CmsFileUpload(Composite parent, int style) {
- super(parent, style);
- }
-
- @Override
- public void setText(String text) {
- super.setText(text);
- }
-
- @Override
- public String getFileName() {
- return super.getFileName();
- }
-
- @Override
- public String[] getFileNames() {
- return super.getFileNames();
- }
-
- @Override
- public void addSelectionListener(SelectionListener listener) {
- super.addSelectionListener(listener);
- }
-
-}
+++ /dev/null
-package org.argeo.eclipse.ui.specific;
-
-/** RCP specific {@link NLS} to be extended */
-public class DefaultNLS {// extends NLS {
-// public final static String DEFAULT_BUNDLE_LOCATION = "/properties/plugin";
-//
-// public DefaultNLS() {
-// this(DEFAULT_BUNDLE_LOCATION);
-// }
-//
-// public DefaultNLS(String bundleName) {
-// NLS.initializeMessages(bundleName, getClass());
-// }
-}
\ No newline at end of file
+++ /dev/null
-package org.argeo.eclipse.ui.specific;
-
-/** Constants which are specific to RWT.*/
-public interface EclipseUiConstants {
- final static String CSS_CLASS = "org.eclipse.e4.ui.css.CssClassName";
- final static String MARKUP_SUPPORT = null;
-}
+++ /dev/null
-package org.argeo.eclipse.ui.specific;
-
-import org.eclipse.jface.viewers.ColumnViewer;
-import org.eclipse.jface.viewers.ColumnViewerToolTipSupport;
-import org.eclipse.jface.viewers.Viewer;
-import org.eclipse.swt.widgets.Widget;
-
-/** Static utilities to bridge differences between RCP and RAP */
-public class EclipseUiSpecificUtils {
- private final static String CSS_CLASS = "org.eclipse.e4.ui.css.CssClassName";
-
- public static void setStyleData(Widget widget, Object data) {
- widget.setData(CSS_CLASS, data);
- }
-
- public static Object getStyleData(Widget widget) {
- return widget.getData(CSS_CLASS);
- }
-
- public static void setMarkupData(Widget widget) {
- // does nothing
- }
-
- public static void setMarkupValidationDisabledData(Widget widget) {
- // does nothing
- }
-
- /**
- * TootlTip support is supported for {@link ColumnViewer} in RCP
- *
- * @see ColumnViewerToolTipSupport#enableFor(Viewer)
- */
- public static void enableToolTipSupport(Viewer viewer) {
- if (viewer instanceof ColumnViewer)
- ColumnViewerToolTipSupport.enableFor((ColumnViewer) viewer);
- }
-
- private EclipseUiSpecificUtils() {
- }
-}
+++ /dev/null
-package org.argeo.eclipse.ui.specific;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.Arrays;
-
-import org.eclipse.swt.dnd.DND;
-import org.eclipse.swt.dnd.DropTarget;
-import org.eclipse.swt.dnd.DropTargetAdapter;
-import org.eclipse.swt.dnd.DropTargetEvent;
-import org.eclipse.swt.dnd.FileTransfer;
-import org.eclipse.swt.dnd.Transfer;
-import org.eclipse.swt.widgets.Control;
-
-public class FileDropAdapter {
-
- public void prepareDropTarget(Control control, DropTarget dropTarget) {
- dropTarget.setTransfer(new Transfer[] { FileTransfer.getInstance() });
- dropTarget.addDropListener(new DropTargetAdapter() {
- @Override
- public void dropAccept(DropTargetEvent event) {
- if (!FileTransfer.getInstance().isSupportedType(event.currentDataType)) {
- event.detail = DND.DROP_NONE;
- }
- }
-
- @Override
- public void drop(DropTargetEvent event) {
- handleFileDrop(control, event);
- }
- });
- }
-
- public void handleFileDrop(Control control, DropTargetEvent event) {
- String fileList[] = null;
- FileTransfer ft = FileTransfer.getInstance();
- if (ft.isSupportedType(event.currentDataType)) {
- fileList = (String[]) event.data;
- }
- System.out.println(Arrays.toString(fileList));
- }
-
- /** Executed in UI thread */
- protected void processUpload(InputStream in, String fileName, String contentType) throws IOException {
-
- }
-
-}
+++ /dev/null
-package org.argeo.eclipse.ui.specific;
-
-import java.util.Locale;
-
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.eclipse.swt.widgets.Display;
-
-/** Singleton class providing single sources infos about the UI context. */
-public class UiContext {
-
- public static HttpServletRequest getHttpRequest() {
- return null;
- }
-
- public static HttpServletResponse getHttpResponse() {
- return null;
- }
-
- public static Locale getLocale() {
- return Locale.getDefault();
- }
-
- public static void setLocale(Locale locale) {
- Locale.setDefault(locale);
- }
-
- /** Can always be null */
- @SuppressWarnings("unchecked")
- public static <T> T getData(String key) {
- Display display = getDisplay();
- if (display == null)
- return null;
- return (T) display.getData(key);
- }
-
- public static void setData(String key, Object value) {
- Display display = getDisplay();
- if (display == null)
- throw new IllegalStateException("Not display available");
- display.setData(key, value);
- }
-
- private static Display getDisplay() {
- return Display.getCurrent();
- }
-
- private UiContext() {
- }
-
-}
+++ /dev/null
-package org.eclipse.rap.fileupload;
-
-public interface FileDetails {
- String getContentType();
-
- long getContentLength();
-
- String getFileName();
-}
+++ /dev/null
-package org.eclipse.rap.fileupload;
-
-import java.util.EventObject;
-
-public abstract class FileUploadEvent extends EventObject {
-
- private static final long serialVersionUID = 1L;
-
- protected FileUploadEvent(FileUploadHandler source) {
- super(source);
- }
-
- public abstract FileDetails[] getFileDetails();
-
- public abstract long getContentLength();
-
- public abstract long getBytesRead();
-
- public abstract Exception getException();
-
-}
+++ /dev/null
-/*******************************************************************************
- * Copyright (c) 2011, 2012 EclipseSource and others.
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the Eclipse Public License v1.0
- * which accompanies this distribution, and is available at
- * http://www.eclipse.org/legal/epl-v10.html
- *
- * Contributors:
- * EclipseSource - initial API and implementation
- ******************************************************************************/
-package org.eclipse.rap.fileupload;
-
-/**
- * A file upload handler is used to accept file uploads from a client. After
- * creating a file upload handler, the server will accept file uploads to the
- * URL returned by <code>getUploadUrl()</code>. Upload listeners can be attached
- * to react on progress. When the upload has finished, a FileUploadHandler has
- * to be disposed of by calling its <code>dispose()</code> method.
- *
- * @noextend This class is not intended to be subclassed by clients.
- */
-public class FileUploadHandler {
-
- public FileUploadHandler(FileUploadReceiver fileUploadReceiver) {
- }
-
- public void dispose() {
-
- }
-
- public void addUploadListener(FileUploadListener listener) {
-
- }
-
- public String getUploadUrl() {
- return null;
- }
-}
+++ /dev/null
-/*******************************************************************************
- * Copyright (c) 2011, 2012 EclipseSource and others.
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the Eclipse Public License v1.0
- * which accompanies this distribution, and is available at
- * http://www.eclipse.org/legal/epl-v10.html
- *
- * Contributors:
- * EclipseSource - initial API and implementation
- ******************************************************************************/
-package org.eclipse.rap.fileupload;
-
-import org.eclipse.swt.widgets.Display;
-
-
-/**
- * Listener to react on progress and completion of a file upload.
- * <p>
- * <strong>Note:</strong> This listener will be called from a different thread than the UI thread.
- * Implementations must use {@link Display#asyncExec(Runnable)} to access the UI.
- * </p>
- *
- * @see FileUploadEvent
- */
-public interface FileUploadListener {
-
- /**
- * Called when new information about an in-progress upload is available.
- *
- * @param event event object that contains information about the uploaded file
- * @see FileUploadEvent#getBytesRead()
- */
- void uploadProgress( FileUploadEvent event );
-
- /**
- * Called when a file upload has finished successfully.
- *
- * @param event event object that contains information about the uploaded file
- * @see FileUploadEvent
- */
- void uploadFinished( FileUploadEvent event );
-
- /**
- * Called when a file upload failed.
- *
- * @param event event object that contains information about the uploaded file
- * @see FileUploadEvent#getErrorMessage()
- */
- void uploadFailed( FileUploadEvent event );
-
-}
+++ /dev/null
-/*******************************************************************************
- * Copyright (c) 2011, 2013 EclipseSource and others.
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the Eclipse Public License v1.0
- * which accompanies this distribution, and is available at
- * http://www.eclipse.org/legal/epl-v10.html
- *
- * Contributors:
- * EclipseSource - initial API and implementation
- ******************************************************************************/
-package org.eclipse.rap.fileupload;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-
-/**
- * Instances of this interface are responsible for reading and processing the data from a file
- * upload.
- */
-public abstract class FileUploadReceiver {
-
- /**
- * Reads and processes all data from the provided input stream.
- *
- * @param stream the stream to read from
- * @param details the details of the uploaded file like file name, content-type and size
- * @throws IOException if an input / output error occurs
- */
- public abstract void receive( InputStream stream, FileDetails details ) throws IOException;
-
-}
+++ /dev/null
-package org.eclipse.rap.rwt;
-
-import java.util.Locale;
-
-import javax.servlet.http.HttpServletRequest;
-
-import org.argeo.eclipse.ui.rcp.internal.rwt.RcpClient;
-import org.argeo.eclipse.ui.rcp.internal.rwt.RcpResourceManager;
-import org.eclipse.rap.rwt.client.Client;
-import org.eclipse.rap.rwt.service.ResourceManager;
-
-public class RWT {
- public final static String CUSTOM_VARIANT = "argeo-rcp:CUSTOM_VARIANT";
- public final static String MARKUP_ENABLED = "argeo-rcp:MARKUP_ENABLED";
- public static final String TOOLTIP_MARKUP_ENABLED = "argeo-rcp:TOOLTIP_MARKUP_ENABLED";
- public final static String CUSTOM_ITEM_HEIGHT = "argeo-rcp:CUSTOM_ITEM_HEIGHT";
- public final static String ACTIVE_KEYS = "argeo-rcp:ACTIVE_KEYS";
- public final static String CANCEL_KEYS = "argeo-rcp:CANCEL_KEYS";
- public final static String DEFAULT_THEME_ID = "argeo-rcp:DEFAULT_THEME_ID";
-
- public final static int HYPERLINK = 0;
-
- private static Locale locale = Locale.getDefault();
- private static RcpClient client = new RcpClient();
- private static ResourceManager resourceManager = new RcpResourceManager();
- static {
-
- }
-
- public static Locale getLocale() {
- return locale;
- }
-
- public static HttpServletRequest getRequest() {
- return null;
- }
-
- public static ResourceManager getResourceManager() {
- return resourceManager;
- }
-
- public static Client getClient() {
- return client;
- }
-}
+++ /dev/null
-package org.eclipse.rap.rwt;
-
-public class SingletonUtil {
- public static <T> T getSessionInstance(Class<T> clss) {
- return null;
- }
-}
+++ /dev/null
-package org.eclipse.rap.rwt.application;
-
-import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Display;
-import org.eclipse.swt.widgets.Shell;
-
-public abstract class AbstractEntryPoint implements EntryPoint {
- private Display display;
- private Shell shell;
-
- protected Shell createShell(Display display) {
- return new Shell(display);
- }
-
- protected void createContents(Composite parent) {
-
- }
-
- public int createUI() {
- display = new Display();
- shell = createShell(display);
- shell.setLayout(new GridLayout(1, false));
- createContents(shell);
- if (shell.getMaximized()) {
- shell.layout();
- } else {
- shell.pack();
- }
- shell.open();
- while (!shell.isDisposed()) {
- if (!display.readAndDispatch()) {
- display.sleep();
- }
- }
- display.dispose();
- return 0;
- }
-
- protected Shell getShell() {
- return shell;
- }
-}
+++ /dev/null
-package org.eclipse.rap.rwt.application;
-
-import java.util.Map;
-
-import org.eclipse.rap.rwt.service.ResourceLoader;
-
-public interface Application {
- public static enum OperationMode {
- JEE_COMPATIBILITY, SWT_COMPATIBILITY,
- }
-
- void setOperationMode(OperationMode operationMode);
-
- void addResource(String name, ResourceLoader resourceLoader);
-
- void setExceptionHandler(ExceptionHandler exceptionHandler);
-
- void addEntryPoint(String path, EntryPointFactory entryPointFactory,
- Map<String, String> properties);
-
- void addEntryPoint(String path, Class<? extends EntryPoint> entryPoint,
- Map<String, String> properties);
-
- void addStyleSheet(String themeId, String styleSheetLocation,
- ResourceLoader resourceLoader);
-
-}
+++ /dev/null
-package org.eclipse.rap.rwt.application;
-
-public interface ApplicationConfiguration {
- void configure(Application application);
-}
+++ /dev/null
-package org.eclipse.rap.rwt.application;
-
-public interface EntryPoint {
- int createUI();
-}
+++ /dev/null
-package org.eclipse.rap.rwt.application;
-
-public interface EntryPointFactory {
- public EntryPoint create();
-}
+++ /dev/null
-package org.eclipse.rap.rwt.application;
-
-public interface ExceptionHandler {
- public void handleException(Throwable throwable);
-}
+++ /dev/null
-package org.eclipse.rap.rwt.client;
-
-import java.io.Serializable;
-
-import org.eclipse.rap.rwt.client.service.ClientService;
-
-public interface Client extends Serializable {
-
- /**
- * Returns this client's implementation of a given service, if available.
- *
- * @param type the type of the requested service, must be a subtype of ClientService
- * @return the requested service if provided by this client, otherwise <code>null</code>
- * @see ClientService
- */
- <T extends ClientService> T getService( Class<T> type );
-
-}
\ No newline at end of file
+++ /dev/null
-package org.eclipse.rap.rwt.client;
-
-public interface WebClient {
- public final static String FAVICON = "rcp:FAVICON";
- public final static String PAGE_TITLE = "rcp:PAGE_TITLE";
- public final static String BODY_HTML = "rcp:BODY_HTML";
- public final static String THEME_ID = "rcp:THEME_ID";
- public final static String HEAD_HTML = "rcp:HEAD_HTML";
- public final static String PAGE_OVERFLOW = "rcp:PAGE_OVERFLOW";
-}
+++ /dev/null
-package org.eclipse.rap.rwt.client.service;
-
-public interface BrowserNavigation extends ClientService {
- void pushState(String state, String title);
-
- void addBrowserNavigationListener(BrowserNavigationListener listener);
-}
+++ /dev/null
-package org.eclipse.rap.rwt.client.service;
-
-public class BrowserNavigationEvent {
- private String state;
-
- public String getState() {
- return state;
- }
-
-}
+++ /dev/null
-package org.eclipse.rap.rwt.client.service;
-
-public interface BrowserNavigationListener {
- public void navigated(BrowserNavigationEvent event);
-}
+++ /dev/null
-package org.eclipse.rap.rwt.client.service;
-
-import java.io.Serializable;
-
-public interface ClientService extends Serializable {
-}
+++ /dev/null
-package org.eclipse.rap.rwt.client.service;
-
-public interface JavaScriptExecutor extends ClientService {
- public void execute( String code );
-}
+++ /dev/null
-/*******************************************************************************
- * Copyright (c) 2012 EclipseSource and others.
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the Eclipse Public License v1.0
- * which accompanies this distribution, and is available at
- * http://www.eclipse.org/legal/epl-v10.html
- *
- * Contributors:
- * EclipseSource - initial API and implementation
- ******************************************************************************/
-package org.eclipse.rap.rwt.client.service;
-
-/**
- * The UrlLauncher service allows loading an URL in an external window, application or save dialog.
- *
- * @since 2.0
- * @noimplement This interface is not intended to be implemented by clients.
- */
-public interface UrlLauncher extends ClientService {
-
- /**
- * Opens the given URL.
- *
- * Any HTTP URL or relative URL will be opened in a new window.
- * Modern browser may block any attempt to open new windows, but will usually prompt the user to
- * accept or ignore. Even if accepted, the decision may be applied to only this attempt, or only
- * to future attempts. It could also trigger a document reload, causing a session restart.
- *
- * Non-HTTP URLs like "mailto" will not create a new browser window, but require the client
- * to have a matching protocol handler registered.
- *
- * @param url the URL to open
- */
- void openURL( String url );
-
-}
+++ /dev/null
-package org.eclipse.rap.rwt.service;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-public interface ResourceLoader {
- public InputStream getResourceAsStream(String resourceName)
- throws IOException;
-}
+++ /dev/null
-package org.eclipse.rap.rwt.service;
-
-import java.io.InputStream;
-
-public interface ResourceManager {
- public void register(String name, InputStream in);
-
- boolean unregister(String name);
-
- public InputStream getRegisteredContent(String name);
-
- public String getLocation(String name);
-
- public boolean isRegistered(String name);
-}
+++ /dev/null
-package org.eclipse.rap.rwt.service;
-
-/** Mock, does nothing as this is irrelevant for RCP. */
-public class ServerPushSession {
- public void start() {
-
- }
-
- public void stop() {
-
- }
-}
+++ /dev/null
-package org.eclipse.rap.rwt.widgets;
-
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.widgets.Widget;
-
-public class DropDown {
- private boolean visible=false;
-
- public DropDown(Widget parent, int style) {
- // FIXME implement a shell
- }
-
- public DropDown(Widget parent) {
- this(parent, SWT.NONE);
- }
-
- public void setVisible(boolean visible) {
- this.visible = visible;
- }
-
- public boolean isVisible() {
- return visible;
- }
-
- public void setItems( String[] items ) {
-
- }
-
- public void setSelectionIndex( int selection ) {
-
- }
-
-}
+++ /dev/null
-package org.eclipse.rap.rwt.widgets;
-
-import org.eclipse.swt.events.SelectionListener;
-import org.eclipse.swt.graphics.Image;
-import org.eclipse.swt.widgets.Composite;
-
-public class FileUpload extends Composite {
-
- public FileUpload(Composite parent, int style) {
- super(parent, style);
- }
-
- public void addSelectionListener(SelectionListener listener) {
-
- }
-
- public void submit(String url) {
-
- }
-
- public void setImage(Image image) {
-
- }
-
- public void setText(String text) {
-
- }
-
- public String getFileName() {
- return null;
- }
-
- public String[] getFileNames() {
- return null;
- }
-
-}
-Subproject commit 5029b67ae930b82e822407b64d740173fe2d34c0
+Subproject commit a1d5c8e4bbee31d104fc1151cac4a8e19f5ef1fa
--- /dev/null
+major=2
+minor=1
+micro=106
+qualifier=.next
\ No newline at end of file
--- /dev/null
+major=2
+minor=3
+micro=14
+qualifier=.next
\ No newline at end of file
-argeo.osgi.start.2.node=\
+argeo.osgi.start.2=\
org.eclipse.equinox.http.servlet,\
-org.eclipse.equinox.metatype,\
-org.eclipse.equinox.cm,\
-org.eclipse.equinox.ds,\
+org.apache.felix.scr,\
org.eclipse.rap.rwt.osgi,\
org.argeo.init
-argeo.osgi.start.3.node=\
-org.argeo.cms
+argeo.osgi.start.3=\
+org.argeo.cms,\
+org.argeo.cms.swt.rap,\
+org.argeo.cms.swt.rcp,\
+org.argeo.cms.ee,\
+org.argeo.cms.lib.sshd,\
+org.argeo.cms.lib.equinox,\
+org.argeo.cms.lib.jetty,\
-argeo.osgi.start.4.node=\
-org.argeo.cms.servlet,\
-org.argeo.cms.jcr
-
-argeo.osgi.start.5.node=\
-org.argeo.cms.e4.rap
# Local
argeo.node.repo.type=h2
# DON'T CHANGE BELOW
org.eclipse.equinox.http.jetty.autostart=false
-org.osgi.framework.bootdelegation=com.sun.jndi.ldap,\
+org.osgi.framework.system.packages.extra=\
+com.sun.net.httpserver,\
+com.sun.jndi.ldap,\
com.sun.jndi.ldap.sasl,\
-com.sun.security.jgss,\
com.sun.jndi.dns,\
+com.sun.security.jgss,\
com.sun.nio.file,\
com.sun.nio.sctp
--- /dev/null
+argeo.osgi.start.2.node=\
+org.eclipse.equinox.metatype,\
+org.eclipse.equinox.cm,\
+org.eclipse.equinox.ds,\
+org.argeo.init
+
+argeo.osgi.start.3.node=\
+org.argeo.cms,\
+org.argeo.cms.jcr,\
+org.argeo.cms.ui.rcp
+
+
+# Local
+argeo.node.repo.type=h2
+org.osgi.service.http.port=7070
+#org.osgi.service.http.port.secure=7073
+
+argeo.node.useradmin.uris=os:///
+
+#argeo.node.useradmin.uris=ldap://cn=Directory%20Manager:argeoargeo@localhost:10389/dc=example,dc=com
+
+argeo.node.init=../../init
+
+argeo.i18n.locales=en,fr
+argeo.i18n.defaultLocale=en
+
+#tika.config=/home/mbaudier/dev/git/gpl/argeo-suite/sdk/exec/argeo-office-e4-rap/data/indexes/node/tika-config.xml
+
+# Logging
+log.org.argeo=DEBUG
+
+# DON'T CHANGE BELOW
+org.eclipse.equinox.http.jetty.autostart=false
+org.osgi.framework.bootdelegation=com.sun.jndi.ldap,\
+com.sun.jndi.ldap.sasl,\
+com.sun.security.jgss,\
+com.sun.jndi.dns,\
+com.sun.nio.file,\
+com.sun.nio.sctp
--- /dev/null
+!bin/
\ No newline at end of file
--- /dev/null
+#!/bin/sh
+curl $(cat $XDG_RUNTIME_DIR/argeo.rcp.url)$1
\ No newline at end of file
--- /dev/null
+#!/bin/sh
+java -Dorg.argeo.api.cli.rootCommand=$0 -jar /usr/share/a2/org.argeo.cms/org.argeo.cms.cli.2.3.jar "$@"
\ No newline at end of file
--- /dev/null
+-Dcom.sun.management.jmxremote.port=8099 -Dcom.sun.management.jmxremote.rmi.port=8099 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Djava.rmi.server.hostname=<hostname>
\ No newline at end of file
[Unit]
Description=Argeo node %I
-After=network.target
+After=network-online.target
Wants=postgresql.service
[Service]
Type=simple
+
+User=daemon
+Group=daemon
+
StateDirectory=argeo.d/%I
LogsDirectory=argeo.d/%I
ConfigurationDirectory=argeo.d/%I
CacheDirectory=argeo.d/%I
WorkingDirectory=/var/lib/argeo.d/%I
-ExecStart=/usr/lib/jvm/java-17-openjdk-amd64/bin/java \
+ExecStart=java \
-Dosgi.configuration.cascaded=true \
--Dosgi.sharedConfiguration.area=/etc/argeo.d/%I \
+-Dosgi.sharedConfiguration.area=/etc/argeo.d/%I/ \
-Dosgi.sharedConfiguration.area.readOnly=true \
--Dosgi.configuration.area=/var/lib/argeo.d/%I/state \
--Dosgi.instance.area=/var/lib/argeo.d/%I/data \
--Dargeo.node.repo.indexesBase=/var/cache/argeo.d/%I/indexes \
--Dorg.osgi.framework.bootdelegation=com.sun.jndi.ldap,com.sun.jndi.ldap.sasl,com.sun.security.jgss,com.sun.jndi.dns,com.sun.nio.file,com.sun.nio.sctp \
+-Dosgi.configuration.area=${STATE_DIRECTORY}/state/ \
+-Dosgi.instance.area=${STATE_DIRECTORY}/data/ \
+-Dargeo.node.repo.indexesBase=${CACHE_DIRECTORY}/indexes \
-Declipse.ignoreApp=true \
-Dosgi.noShutdown=true \
-Dorg.eclipse.equinox.http.jetty.autostart=false \
@/etc/argeo.d/jvm.args \
-@/etc/argeo.d/%I/jvm.args \
+@${CONFIGURATION_DIRECTORY}/jvm.args \
@/usr/share/argeo/jvm.args
+
# Exit codes of the JVM when SIGTERM or SIGINT have been caught:
SuccessExitStatus=143 130
+CPUAccounting=true
+MemoryAccounting=true
+TasksAccounting=true
+IOAccounting=true
+IPAccounting=true
+
[Install]
WantedBy=multi-user.target
--- /dev/null
+[Unit]
+Description=Argeo user node %I
+
+[Service]
+Type=simple
+StateDirectory=argeo.d/%I
+LogsDirectory=argeo.d/%I
+ConfigurationDirectory=argeo.d/%I
+CacheDirectory=argeo.d/%I
+#WorkingDirectory=
+
+ExecStart=java \
+-Dosgi.configuration.cascaded=true \
+-Dosgi.sharedConfiguration.area=/etc/argeo.user.d/%I/ \
+-Dosgi.sharedConfiguration.area.readOnly=true \
+-Dosgi.configuration.area=${STATE_DIRECTORY}/state/ \
+-Dosgi.instance.area=${STATE_DIRECTORY}/data/ \
+-Dargeo.node.repo.indexesBase=${CACHE_DIRECTORY}/indexes \
+-Declipse.ignoreApp=true \
+-Dosgi.noShutdown=true \
+-Dorg.eclipse.equinox.http.jetty.autostart=false \
+-Djava.library.path=/usr/lib/a2/swt/rcp/org.argeo.tp.swt/ \
+@/etc/argeo.user.d/jvm.args \
+@/etc/argeo.user.d/%I/jvm.args \
+@/usr/share/argeo/jvm.args
+# Exit codes of the JVM when SIGTERM or SIGINT have been caught:
+SuccessExitStatus=143 130
+
+[Install]
+WantedBy=multi-user.target
--cp /usr/share/a2/org.argeo.tp.eclipse.equinox/org.eclipse.osgi.3.17.jar:/usr/share/a2/org.argeo.cms/org.argeo.init.2.1.jar org.argeo.init.Service
\ No newline at end of file
+-cp /usr/share/a2/osgi/equinox/org.argeo.tp.osgi/org.eclipse.osgi.3.18.jar:/usr/share/a2/org.argeo.cms/org.argeo.init.2.3.jar org.argeo.init.Service
\ No newline at end of file
+++ /dev/null
-/krb5.keytab
-/krb5.keytab.old
-/*.p12
-/*.jks
\ No newline at end of file
+++ /dev/null
-dn: cn=admin,ou=roles,ou=node
-objectClass: groupOfNames
-objectClass: top
-cn: admin
-member: uid=root,ou=People,dc=example,dc=com
-
-dn: cn=userAdmin,ou=roles,ou=node
-objectClass: groupOfNames
-objectClass: top
-member: cn=admin,ou=roles,ou=node
-cn: userAdmin
-
--- /dev/null
+/krb5.keytab
+/krb5.keytab.old
+/*.p12
+/*.jks
\ No newline at end of file
--- /dev/null
+dn: cn=admin,ou=roles,ou=node
+objectClass: groupOfNames
+objectClass: top
+cn: admin
+member: uid=root,ou=People,dc=example,dc=com
+
+dn: cn=userAdmin,ou=roles,ou=node
+objectClass: groupOfNames
+objectClass: top
+member: cn=admin,ou=roles,ou=node
+cn: userAdmin
+
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-17"/>
+ <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+ <classpathentry kind="src" path="src"/>
+ <classpathentry kind="output" path="bin"/>
+</classpath>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>org.argeo.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
+<?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
+Service-Component: OSGI-INF/defaultCallbackHandler.xml
+Bundle-ActivationPolicy: lazy
+
+Import-Package: \
+org.argeo.api.acr,\
+org.eclipse.swt,\
+org.eclipse.swt.widgets;version="0.0.0",\
+org.eclipse.e4.ui.model.application.ui;resolution:=optional,\
+org.eclipse.e4.ui.model.application;resolution:=optional,\
+org.argeo.cms,\
+org.eclipse.core.commands.common,\
+org.eclipse.jface.window,\
+org.eclipse.jface.dialogs,\
+org.argeo.cms.swt.auth,\
+org.argeo.cms.ux.widgets,\
+javax.servlet.*;version="[3,5)",\
+org.eclipse.*;resolution:=optional,\
+javax.*;resolution:=optional,\
+*
+
--- /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
+package org.argeo.cms.e4;
+
+import java.util.List;
+
+import org.argeo.cms.swt.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.swt.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.CurrentUser;
+import org.argeo.cms.swt.CmsException;
+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);
+ }
+
+ // FIXME make it more generic
+ 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.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.api.cms.keyring.CryptoKeyring;
+import org.argeo.api.cms.transaction.WorkTransaction;
+import org.argeo.cms.CurrentUser;
+import org.argeo.cms.swt.dialogs.CmsFeedback;
+import org.argeo.cms.swt.dialogs.CmsMessageDialog;
+import org.argeo.cms.ux.widgets.CmsDialog;
+import org.eclipse.e4.core.di.annotations.Execute;
+import org.eclipse.e4.core.di.annotations.Optional;
+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() == CmsDialog.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 IllegalArgumentException("Invalid user dn " + name, e);
+ }
+ User user = (User) userAdmin.getRole(dn.toString());
+ if (!user.hasCredential(null, oldPassword))
+ throw new IllegalArgumentException("Invalid password");
+ if (Arrays.equals(newPassword, new char[0]))
+ throw new IllegalArgumentException("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 IllegalStateException("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 IllegalArgumentException("New passwords are different");
+ changePassword(oldPassword.getTextChars(), newPassword1.getTextChars());
+ closeShell(CmsDialog.OK);
+ } catch (Exception e) {
+ CmsFeedback.error("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 javax.security.auth.Subject;
+
+import org.argeo.cms.CurrentUser;
+import org.argeo.cms.util.CurrentSubject;
+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 = CurrentSubject.current();
+ try {
+ CurrentUser.logoutCmsSession(subject);
+ } catch (Exception e) {
+ throw new IllegalStateException("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
+/** Eclipse 4 user interfaces. */
+package org.argeo.cms.e4;
\ No newline at end of file
--- /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-17" />
+ <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>
+ <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="CMS User App">
+ <implementation class="org.argeo.cms.swt.app.CmsUserApp"/>
+ <property name="argeo.cms.app.contextName" type="String" value="cms/user"/>
+ <service>
+ <provide interface="org.argeo.api.cms.CmsApp"/>
+ </service>
+ <reference bind="setCmsContext" cardinality="1..1" interface="org.argeo.api.cms.CmsContext" name="CmsContext" policy="static"/>
+ <reference bind="setContentRepository" cardinality="1..1" interface="org.argeo.api.acr.ContentRepository" name="ContentRepository" policy="static"/>
+</scr:component>
--- /dev/null
+Import-Package: org.eclipse.swt,\
+org.eclipse.jface.window,\
+org.eclipse.jface.dialogs,\
+org.eclipse.core.commands.common,\
+javax.servlet.*;version="[3,5)",\
+*
+
+Bundle-ActivationPolicy: lazy
+
+Service-Component: \
+OSGI-INF/cmsUserApp.xml
+
\ No newline at end of file
--- /dev/null
+output.. = bin/
+bin.includes = META-INF/,\
+ .,\
+ OSGI-INF/cmsUserApp.xml
+source.. = src/
--- /dev/null
+package org.argeo.cms.jface.dialog;
+
+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.cms.swt.dialogs.LightweightDialog;
+import org.argeo.cms.ux.widgets.CmsDialog;
+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(CmsDialog.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 {
+ // FIXME it creates a dependency to Eclipse Core Runtime
+ // 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(CmsDialog.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;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.ExecutionException;
+
+import javax.security.auth.Subject;
+import javax.security.auth.login.LoginContext;
+
+import org.argeo.api.cms.CmsApp;
+import org.argeo.api.cms.CmsEventBus;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.api.cms.ux.CmsImageManager;
+import org.argeo.api.cms.ux.CmsUi;
+import org.argeo.api.cms.ux.CmsView;
+import org.argeo.api.cms.ux.UxContext;
+import org.argeo.cms.CurrentUser;
+import org.argeo.cms.util.CurrentSubject;
+import org.eclipse.swt.widgets.Display;
+
+public abstract class AbstractSwtCmsView implements CmsView {
+ private final static CmsLog log = CmsLog.getLog(AbstractSwtCmsView.class);
+
+ protected final String uiName;
+
+ protected LoginContext loginContext;
+ protected String state;
+// protected Throwable exception;
+ protected UxContext uxContext;
+ protected CmsImageManager imageManager;
+
+ protected Display display;
+ protected CmsUi ui;
+
+ protected String uid;
+
+ public AbstractSwtCmsView(String uiName) {
+ this.uiName = uiName;
+ }
+
+ public abstract CmsEventBus getCmsEventBus();
+
+ public abstract CmsApp getCmsApp();
+
+ @Override
+ public void sendEvent(String topic, Map<String, Object> properties) {
+ if (properties == null)
+ properties = new HashMap<>();
+ if (properties.containsKey(CMS_VIEW_UID_PROPERTY) && !properties.get(CMS_VIEW_UID_PROPERTY).equals(uid))
+ throw new IllegalArgumentException("Property " + CMS_VIEW_UID_PROPERTY + " is set to another CMS view uid ("
+ + properties.get(CMS_VIEW_UID_PROPERTY) + ") then " + uid);
+ properties.put(CMS_VIEW_UID_PROPERTY, uid);
+
+ log.trace(() -> uid + ": send event to " + topic);
+
+ getCmsEventBus().sendEvent(topic, properties);
+ // getCmsApp().onEvent(topic, properties);
+ }
+
+// public void runAs(Runnable runnable) {
+// display.asyncExec(() -> doAs(Executors.callable(runnable)));
+// }
+
+ public <T> T doAs(Callable<T> action) {
+ try {
+ CompletableFuture<T> result = new CompletableFuture<>();
+ Runnable toDo = () -> {
+ log.trace(() -> uid + ": process doAs");
+ Subject subject = CurrentSubject.current();
+ T res;
+ if (subject != null) {
+ assert subject == getSubject();
+ try {
+ res = action.call();
+ } catch (Exception e) {
+ throw new CompletionException("Failed to execute action for " + subject, e);
+ }
+ } else {
+ res = CurrentSubject.callAs(getSubject(), action);
+ }
+ result.complete(res);
+ };
+ if (Thread.currentThread() == display.getThread())
+ toDo.run();
+ else {
+ display.asyncExec(toDo);
+ display.wake();
+ }
+// throw new IllegalStateException("Must be called from UI thread");
+ return result.get();
+ } catch (InterruptedException | ExecutionException e) {
+ throw new IllegalStateException("Cannot execute action ins CMS view " + uid, e);
+ }
+ }
+
+ @Override
+ public UxContext getUxContext() {
+ return uxContext;
+ }
+
+ @Override
+ public String getUid() {
+ return uid;
+ }
+
+ @Override
+ public CmsImageManager<?, ?> getImageManager() {
+ return imageManager;
+ }
+
+ @Override
+ public boolean isAnonymous() {
+ return CurrentUser.isAnonymous(getSubject());
+ }
+
+ protected Subject getSubject() {
+ return loginContext.getSubject();
+ }
+
+ @Override
+ public Object getData(String key) {
+ if (ui != null) {
+ return ui.getData(key);
+ } else {
+ throw new IllegalStateException("UI is not initialized");
+ }
+ }
+
+ @Override
+ public void setData(String key, Object value) {
+ if (ui != null) {
+ ui.setData(key, value);
+ } else {
+ throw new IllegalStateException("UI is not initialized");
+ }
+ }
+
+}
--- /dev/null
+package org.argeo.cms.swt;
+
+import org.argeo.api.cms.ux.Cms2DSize;
+import org.argeo.cms.ux.AbstractImageManager;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+
+/** Manages only public images so far. */
+public abstract class AbstractSwtImageManager<M> extends AbstractImageManager<Control, M> {
+ protected abstract Image getSwtImage(M node);
+
+ protected abstract String noImg(Cms2DSize size);
+
+ public Boolean load(M 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 = 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 = 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;
+ }
+
+ public Cms2DSize getImageSize(M node) {
+ // TODO optimise
+ Image image = getSwtImage(node);
+ return new Cms2DSize(image.getBounds().width, image.getBounds().height);
+ }
+
+}
--- /dev/null
+package org.argeo.cms.swt;
+
+/** @deprecated Use standard Java {@link RuntimeException} instead. */
+@Deprecated
+public class CmsException extends RuntimeException {
+ private static final long serialVersionUID = -5341764743356771313L;
+
+ public CmsException(String message) {
+ super(message);
+ }
+
+ public CmsException(String message, Throwable e) {
+ super(message, e);
+ }
+
+}
--- /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.ux.CmsIcon;
+import org.argeo.api.cms.ux.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);
+
+ Image getSmallIcon(CmsIcon icon);
+
+ Image getBigIcon(CmsIcon icon);
+}
--- /dev/null
+package org.argeo.cms.swt;
+
+import org.argeo.api.cms.ux.CmsUi;
+import org.argeo.api.cms.ux.CmsView;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+
+/** A basic {@link CmsUi}, based on an SWT {@link Composite}. */
+public class CmsSwtUi extends Composite implements CmsUi {
+
+ private static final long serialVersionUID = -107939076610406448L;
+
+ private CmsView cmsView;
+
+ public CmsSwtUi(Composite parent, int style) {
+ super(parent, style);
+ cmsView = CmsSwtUtils.getCmsView(parent);
+
+ setLayout(new GridLayout());
+ }
+
+ public CmsView getCmsView() {
+ return cmsView;
+ }
+
+}
\ No newline at end of file
--- /dev/null
+package org.argeo.cms.swt;
+
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.StringTokenizer;
+
+import org.argeo.api.cms.ux.CmsIcon;
+import org.argeo.api.cms.ux.CmsStyle;
+import org.argeo.api.cms.ux.CmsTheme;
+import org.argeo.api.cms.ux.CmsView;
+import org.argeo.eclipse.ui.specific.EclipseUiSpecificUtils;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.graphics.Image;
+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.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.Layout;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Text;
+import org.eclipse.swt.widgets.Widget;
+
+/** SWT utilities. */
+public class CmsSwtUtils {
+ /*
+ * THEME AND VIEW
+ */
+
+ public static CmsSwtTheme getCmsTheme(Composite parent) {
+ CmsSwtTheme theme = (CmsSwtTheme) parent.getData(CmsTheme.class.getName());
+ if (theme == null) {
+ // find parent shell
+ Shell topShell = parent.getShell();
+ while (topShell.getParent() != null)
+ topShell = (Shell) topShell.getParent();
+ theme = (CmsSwtTheme) 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);
+ }
+
+ /*
+ * EVENTS
+ */
+
+ /** 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);
+ }
+
+ /*
+ * ICONS
+ */
+ /** Get a small icon from this theme. */
+ public static Image getSmallIcon(CmsTheme theme, CmsIcon icon) {
+ return ((CmsSwtTheme) theme).getSmallIcon(icon);
+ }
+
+ /** Get a big icon from this theme. */
+ public static Image getBigIcon(CmsTheme theme, CmsIcon icon) {
+ return ((CmsSwtTheme) theme).getBigIcon(icon);
+ }
+
+ /*
+ * LAYOUT INDEPENDENT
+ */
+ /** Takes the most space possible, depending on parent layout. */
+ public static void fill(Control control) {
+ Layout parentLayout = control.getParent().getLayout();
+ if (parentLayout == null)
+ throw new IllegalStateException("Parent layout is not set");
+ if (parentLayout instanceof GridLayout) {
+ control.setLayoutData(fillAll());
+ } else if (parentLayout instanceof FormLayout) {
+ control.setLayoutData(coverAll());
+ } else {
+ throw new IllegalArgumentException("Unsupported parent layout " + parentLayout.getClass().getName());
+ }
+ }
+
+ /*
+ * GRID LAYOUT
+ */
+ /** A {@link GridLayout} without any spacing and one column. */
+ public static GridLayout noSpaceGridLayout() {
+ return noSpaceGridLayout(new GridLayout());
+ }
+
+ /**
+ * A {@link GridLayout} without any spacing and multiple columns of unequal
+ * width.
+ */
+ 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();
+ }
+
+ /** 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();
+ }
+
+ /** Singleton. */
+ private CmsSwtUtils() {
+ }
+
+}
--- /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#mouseDown(MouseEvent)} 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.ux.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;
+
+import org.argeo.cms.ux.widgets.EditablePart;
+import org.eclipse.swt.widgets.Control;
+
+/** Manages whether an editable or non editable control is shown. */
+public interface SwtEditablePart extends EditablePart {
+ public Control getControl();
+}
--- /dev/null
+package org.argeo.cms.swt.acr;
+
+import java.security.PrivilegedAction;
+
+import javax.security.auth.Subject;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.acr.ContentSession;
+import org.argeo.api.acr.spi.ProvidedContent;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.api.cms.ux.CmsEditable;
+import org.argeo.cms.CurrentUser;
+import org.argeo.cms.swt.SwtEditablePart;
+import org.argeo.cms.swt.widgets.ScrolledPage;
+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 {
+
+ 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 SwtEditablePart edited;
+// private ISelection selection = StructuredSelection.EMPTY;
+
+ private Subject viewerSubject;
+
+ protected AbstractPageViewer(Composite 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();
+ viewerSubject = CurrentUser.getCmsSession().getSubject();
+ }
+
+ public abstract Control getControl();
+
+// /**
+// * 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) {
+ }
+
+ /** To be overridden.Save the edited part. */
+ protected void save(SwtEditablePart part) {
+ }
+
+ /** Prepare the edited part */
+ protected void prepare(SwtEditablePart part, Object caretPosition) {
+ }
+
+ /** Notified when the editing state changed. Does nothing, to be overridden */
+ protected void editingStateChanged(CmsEditable cmsEditable) {
+ }
+
+ public void refresh() {
+ // TODO check actual context in order to notice a discrepancy
+ Subject viewerSubject = getViewerSubject();
+ Subject.doAs(viewerSubject, (PrivilegedAction<Void>) () -> {
+ if (cmsEditable.canEdit() && !readOnly)
+ mouseListener = createMouseListener();
+ else
+ mouseListener = null;
+ refresh(getControl());
+ // layout(getControl());
+ if (!getControl().isDisposed())
+ layoutPage();
+ return null;
+ });
+ }
+
+// @Override
+// public void setSelection(ISelection selection, boolean reveal) {
+// this.selection = selection;
+// }
+
+ protected void updateContent(SwtEditablePart part) {
+ }
+
+ // LOW LEVEL EDITION
+ protected void edit(SwtEditablePart part, Object caretPosition) {
+ if (edited == part)
+ return;
+
+ if (edited != null && edited != part) {
+ SwtEditablePart 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());
+ }
+
+ 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();
+ SwtEditablePart editablePart = edited;
+ Control control = ((SwtEditablePart) edited).getControl();
+ edited = null;
+ // TODO make edited state management more robust
+ updateContent(editablePart);
+ layout(control);
+ } 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 SwtEditablePart} in the parents hierarchy of this
+ * control
+ */
+ protected SwtEditablePart findDataParent(Control parent) {
+ if (parent instanceof SwtEditablePart) {
+ return (SwtEditablePart) 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(ContentSession session) {
+// 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(Content anyNode) {
+ persistChanges(((ProvidedContent) 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() {
+ return viewerSubject;
+// 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 SwtEditablePart 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.swt.acr;
+
+import java.io.InputStream;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.api.cms.ux.Cms2DSize;
+import org.argeo.cms.swt.AbstractSwtImageManager;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.ux.CmsUxUtils;
+import org.eclipse.swt.graphics.Image;
+
+public class AcrSwtImageManager extends AbstractSwtImageManager<Content> {
+
+ @Override
+ public String getImageUrl(Content node) {
+ return getDataPathForUrl(node);
+ }
+
+ @Override
+ public String uploadImage(Content context, Content uploadFolder, String fileName, InputStream in,
+ String contentType) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ protected Image getSwtImage(Content node) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ protected String noImg(Cms2DSize size) {
+ String dataPath = "";
+ return CmsUxUtils.img(dataPath, size);
+ }
+
+ protected String getDataPathForUrl(Content content) {
+ return CmsSwtUtils.cleanPathForUrl(getDataPath(content));
+ }
+
+ /** A path in the node repository */
+ protected String getDataPath(Content node) {
+ // TODO make it more configurable?
+ StringBuilder buf = new StringBuilder(CmsConstants.PATH_API_ACR);
+ buf.append(node.getPath());
+ return buf.toString();
+ }
+}
--- /dev/null
+package org.argeo.cms.swt.acr;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.acr.spi.ProvidedContent;
+import org.eclipse.swt.widgets.Composite;
+
+/** A composite which can (optionally) manage a content. */
+public class ContentComposite extends Composite {
+ private static final long serialVersionUID = -1447009015451153367L;
+
+ public ContentComposite(Composite parent, int style, Content item) {
+ super(parent, style);
+ if (item != null)
+ setData(item);
+ }
+
+ public boolean hasContent() {
+ if (getData() == null)
+ return false;
+ return getData() instanceof Content;
+ }
+
+ public Content getContent() {
+ return (Content) getData();
+ }
+
+ @Deprecated
+ public Content getNode() {
+ return getContent();
+ }
+
+ protected ProvidedContent getProvidedContent() {
+ return (ProvidedContent) getContent();
+ }
+
+ public String getSessionLocalId() {
+ return getProvidedContent().getSessionLocalId();
+ }
+
+ protected void itemUpdated() {
+ layout();
+ }
+
+ public void setContent(Content content) {
+ setData(content);
+ itemUpdated();
+ }
+}
--- /dev/null
+package org.argeo.cms.swt.acr;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.acr.NamespaceUtils;
+import org.argeo.api.acr.spi.ProvidedContent;
+import org.argeo.api.cms.ux.Cms2DSize;
+import org.argeo.api.cms.ux.CmsImageManager;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.swt.widgets.EditableImage;
+import org.argeo.cms.ux.acr.ContentPart;
+import org.argeo.eclipse.ui.specific.CmsFileUpload;
+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 SwtSectionPart, ContentPart {
+ private static final long serialVersionUID = 6233572783968188476L;
+
+ private final SwtSection section;
+
+ private final CmsImageManager<Control, Content> imageManager;
+// private FileUploadHandler currentUploadHandler = null;
+// private FileUploadListener fileUploadListener;
+
+ public Img(Composite parent, int swtStyle, Content imgNode, Cms2DSize preferredImageSize) {
+ this(SwtSection.findSection(parent), parent, swtStyle, imgNode, preferredImageSize, null);
+// setStyle(TextStyles.TEXT_IMAGE);
+ }
+
+ public Img(Composite parent, int swtStyle, Content imgNode) {
+ this(SwtSection.findSection(parent), parent, swtStyle, imgNode, null, null);
+// setStyle(TextStyles.TEXT_IMAGE);
+ }
+
+ public Img(Composite parent, int swtStyle, Content imgNode, CmsImageManager<Control, Content> imageManager) {
+ this(SwtSection.findSection(parent), parent, swtStyle, imgNode, null, imageManager);
+// setStyle(TextStyles.TEXT_IMAGE);
+ }
+
+ Img(SwtSection section, Composite parent, int swtStyle, Content imgNode, Cms2DSize preferredImageSize,
+ CmsImageManager<Control, Content> imageManager) {
+ super(parent, swtStyle, preferredImageSize);
+ this.section = section;
+ this.imageManager = imageManager != null ? imageManager
+ : (CmsImageManager<Control, Content>) CmsSwtUtils.getCmsView(section).getImageManager();
+// CmsSwtUtils.style(this, TextStyles.TEXT_IMG);
+ setData(imgNode);
+ }
+
+ @Override
+ protected Control createControl(Composite box, String style) {
+ if (isEditing()) {
+ return createImageChooser(box, style);
+ } else {
+ return createLabel(box, style);
+ }
+ }
+
+ @Override
+ public synchronized void stopEditing() {
+ super.stopEditing();
+// fileUploadListener = null;
+ }
+
+ @Override
+ protected synchronized Boolean load(Control lbl) {
+ Content imgNode = getContent();
+ boolean loaded = imageManager.load(imgNode, lbl, getPreferredImageSize());
+ // getParent().layout();
+ return loaded;
+ }
+
+ protected Content getUploadFolder() {
+ return getContent().getParent();
+ }
+
+ protected String getUploadName() {
+ Content node = getContent();
+ // TODO centralise pattern?
+ return NamespaceUtils.toPrefixedName(node.getName()) + '[' + node.getSiblingIndex() + ']';
+ }
+
+ protected CmsImageManager<Control, Content> getImageManager() {
+ return imageManager;
+ }
+
+ protected Control createImageChooser(Composite box, String style) {
+// JcrFileUploadReceiver receiver = new JcrFileUploadReceiver(this, getUploadFolder(), getUploadName(),
+// imageManager);
+// if (currentUploadHandler != null)
+// currentUploadHandler.dispose();
+// currentUploadHandler = prepareUpload(receiver);
+// final ServerPushSession pushSession = new ServerPushSession();
+ final CmsFileUpload fileUpload = new CmsFileUpload(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 SwtSection getSection() {
+ return section;
+ }
+
+// public void setFileUploadListener(FileUploadListener fileUploadListener) {
+// this.fileUploadListener = fileUploadListener;
+// if (currentUploadHandler != null)
+// currentUploadHandler.addUploadListener(fileUploadListener);
+// }
+
+ @Override
+ public Content getContent() {
+ return (Content) getData();
+ }
+
+ @Override
+ public String getPartId() {
+ return ((ProvidedContent) getContent()).getSessionLocalId();
+ }
+
+ @Override
+ public String toString() {
+ return "Img #" + getPartId();
+ }
+
+}
--- /dev/null
+package org.argeo.cms.swt.acr;
+
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.argeo.api.acr.Content;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.ux.widgets.EditablePart;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+
+/** A structured UI related to an ACR context. */
+public class SwtSection extends ContentComposite {
+ private static final long serialVersionUID = -5933796173755739207L;
+
+ private final SwtSection parentSection;
+ private Composite sectionHeader;
+ private final Integer relativeDepth;
+
+ public SwtSection(Composite parent, int style, Content node) {
+ this(parent, findSection(parent), style, node);
+ }
+
+ public SwtSection(SwtSection section, int style, Content node) {
+ this(section, section, style, node);
+ }
+
+ public SwtSection(SwtSection section, int style) {
+ this(section, style, null);
+ }
+
+ protected SwtSection(Composite parent, SwtSection parentSection, int style, Content node) {
+ super(parent, style, node);
+ this.parentSection = parentSection;
+ if (parentSection != null && hasContent() && parentSection.hasContent()) {
+ relativeDepth = getProvidedContent().getDepth() - parentSection.getProvidedContent().getDepth();
+ } else {
+ relativeDepth = 0;
+ }
+ setLayout(CmsSwtUtils.noSpaceGridLayout());
+ }
+
+ public Map<String, SwtSection> getSubSections() {
+ LinkedHashMap<String, SwtSection> result = new LinkedHashMap<String, SwtSection>();
+ for (Control child : getChildren()) {
+ if (child instanceof Composite) {
+ collectDirectSubSections((Composite) child, result);
+ }
+ }
+ return Collections.unmodifiableMap(result);
+ }
+
+ private void collectDirectSubSections(Composite composite, LinkedHashMap<String, SwtSection> subSections) {
+ if (composite == sectionHeader || composite instanceof EditablePart)
+ return;
+ if (composite instanceof SwtSection) {
+ SwtSection section = (SwtSection) composite;
+ subSections.put(section.getProvidedContent().getSessionLocalId(), 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 SwtSectionPart getSectionPart(String partId) {
+ for (Control child : getChildren()) {
+ if (child instanceof SwtSectionPart) {
+ SwtSectionPart sectionPart = (SwtSectionPart) child;
+ if (sectionPart.getPartId().equals(partId))
+ return sectionPart;
+ }
+ }
+ return null;
+ }
+
+ public SwtSectionPart nextSectionPart(SwtSectionPart 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 SwtSectionPart) {
+ return (SwtSectionPart) children[i + 1];
+ }
+ }
+
+// if (i + 1 < children.length) {
+// Composite next = (Composite) children[i + 1];
+// return (SectionPart) next;
+// } else {
+// // next section
+// }
+ }
+ }
+ return null;
+ }
+
+ public SwtSectionPart previousSectionPart(SwtSectionPart 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 (SwtSectionPart) previous;
+ } else {
+ // previous section
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public String toString() {
+ if (parentSection == null)
+ return "Main section " + getContent();
+ return "Section " + getContent();
+ }
+
+ public SwtSection getParentSection() {
+ return parentSection;
+ }
+
+ public Integer getRelativeDepth() {
+ return relativeDepth;
+ }
+
+ /** Recursively finds the related section in the parents (can be itself) */
+ public static SwtSection findSection(Control control) {
+ if (control == null)
+ return null;
+ if (control instanceof SwtSection)
+ return (SwtSection) control;
+ else
+ return findSection(control.getParent());
+ }
+}
--- /dev/null
+package org.argeo.cms.swt.acr;
+
+import org.argeo.cms.ux.acr.ContentPart;
+import org.argeo.cms.ux.widgets.EditablePart;
+
+/** An editable part dynamically related to a Section */
+public interface SwtSectionPart extends EditablePart, ContentPart {
+ public String getPartId();
+
+ public SwtSection getSection();
+}
--- /dev/null
+package org.argeo.cms.swt.acr;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.acr.spi.ProvidedContent;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.swt.Selected;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.StackLayout;
+import org.eclipse.swt.graphics.Image;
+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.ToolBar;
+import org.eclipse.swt.widgets.ToolItem;
+
+/** Manages {@link SwtSection} in a tab-like structure. */
+public class SwtTabbedArea extends Composite {
+ private static final long serialVersionUID = 8659669229482033444L;
+
+ private Composite headers;
+ private Composite body;
+
+ private List<SwtSection> sections = new ArrayList<>();
+
+ private ProvidedContent previousNode;
+ private SwtUiProvider previousUiProvider;
+ private SwtUiProvider currentUiProvider;
+
+ private String tabStyle;
+ private String tabSelectedStyle;
+ private String bodyStyle;
+ private Image closeIcon;
+
+ private StackLayout stackLayout;
+
+ private boolean singleTab = false;
+ private String singleTabTitle = null;
+
+ public SwtTabbedArea(Composite parent, int style) {
+ super(parent, SWT.NONE);
+ CmsSwtUtils.style(parent, bodyStyle);
+
+ setLayout(CmsSwtUtils.noSpaceGridLayout());
+
+ // TODO manage tabs at bottom or sides
+ headers = new Composite(this, SWT.NONE);
+ headers.setLayoutData(CmsSwtUtils.fillWidth());
+ body = new Composite(this, SWT.NONE);
+ body.setLayoutData(CmsSwtUtils.fillAll());
+ // body.setLayout(new FormLayout());
+ stackLayout = new StackLayout();
+ body.setLayout(stackLayout);
+ emptyState();
+ }
+
+ protected void refreshTabHeaders() {
+ int tabCount = sections.size() > 0 ? sections.size() : 1;
+ for (Control tab : headers.getChildren())
+ tab.dispose();
+
+ headers.setLayout(CmsSwtUtils.noSpaceGridLayout(new GridLayout(tabCount, true)));
+
+ if (sections.size() == 0) {
+ Composite emptyHeader = new Composite(headers, SWT.NONE);
+ emptyHeader.setLayoutData(CmsSwtUtils.fillAll());
+ emptyHeader.setLayout(new GridLayout());
+ Label lbl = new Label(emptyHeader, SWT.NONE);
+ lbl.setText("");
+ lbl.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, true, false));
+
+ }
+
+ SwtSection currentSection = getCurrentSection();
+ for (SwtSection section : sections) {
+ boolean selected = section == currentSection;
+ Composite sectionHeader = section.createHeader(headers);
+ CmsSwtUtils.style(sectionHeader, selected ? tabSelectedStyle : tabStyle);
+ int headerColumns = singleTab ? 1 : 2;
+ sectionHeader.setLayout(new GridLayout(headerColumns, false));
+ sectionHeader.setLayout(CmsSwtUtils.noSpaceGridLayout(headerColumns));
+ Button title = new Button(sectionHeader, SWT.FLAT);
+ CmsSwtUtils.style(title, selected ? tabSelectedStyle : tabStyle);
+ title.setLayoutData(CmsSwtUtils.fillWidth());
+ title.addSelectionListener((Selected) (e) -> showTab(tabIndex(section.getContent())));
+ Content node = section.getContent();
+
+ // FIXME find a standard way to display titles
+ String titleStr = node.getName().getLocalPart();
+ if (singleTab && singleTabTitle != null)
+ titleStr = singleTabTitle;
+
+ // TODO internationalise
+ title.setText(titleStr);
+ if (!singleTab) {
+ ToolBar toolBar = new ToolBar(sectionHeader, SWT.NONE);
+ ToolItem closeItem = new ToolItem(toolBar, SWT.FLAT);
+ if (closeIcon != null)
+ closeItem.setImage(closeIcon);
+ else
+ closeItem.setText("X");
+ CmsSwtUtils.style(closeItem, selected ? tabSelectedStyle : tabStyle);
+ closeItem.addSelectionListener((Selected) (e) -> closeTab(section));
+ }
+ }
+
+ }
+
+ public void view(SwtUiProvider uiProvider, Content context) {
+ if (body.isDisposed())
+ return;
+ int index = tabIndex(context);
+ if (index >= 0) {
+ showTab(index);
+ previousNode = (ProvidedContent) context;
+ previousUiProvider = uiProvider;
+ return;
+ }
+ SwtSection section = (SwtSection) body.getChildren()[0];
+ previousNode = (ProvidedContent) section.getContent();
+ if (previousNode == null) {// empty state
+ previousNode = (ProvidedContent) context;
+ previousUiProvider = uiProvider;
+ } else {
+ previousUiProvider = currentUiProvider;
+ }
+ currentUiProvider = uiProvider;
+ section.setContent(context);
+ // section.setLayoutData(CmsUiUtils.coverAll());
+ build(section, uiProvider, context);
+ if (sections.size() == 0)
+ sections.add(section);
+ refreshTabHeaders();
+ index = tabIndex(context);
+ showTab(index);
+ layout(true, true);
+ }
+
+ public void open(SwtUiProvider uiProvider, Content context) {
+ if (singleTab)
+ throw new UnsupportedOperationException("Open is not supported in single tab mode.");
+
+ if (previousNode != null
+ && previousNode.getSessionLocalId().equals(((ProvidedContent) context).getSessionLocalId())) {
+ // does nothing
+ return;
+ }
+ if (sections.size() == 0)
+ CmsSwtUtils.clear(body);
+ SwtSection currentSection = getCurrentSection();
+ int currentIndex = sections.indexOf(currentSection);
+ SwtSection previousSection = new SwtSection(body, SWT.NONE, context);
+ build(previousSection, previousUiProvider, previousNode);
+ // previousSection.setLayoutData(CmsUiUtils.coverAll());
+ int newIndex = currentIndex + 1;
+ sections.add(currentIndex, previousSection);
+// sections.add(newIndex, previousSection);
+ showTab(newIndex);
+ refreshTabHeaders();
+ layout(true, true);
+ }
+
+ public void showTab(int index) {
+ SwtSection sectionToShow = sections.get(index);
+ // sectionToShow.moveAbove(null);
+ stackLayout.topControl = sectionToShow;
+ refreshTabHeaders();
+ layout(true, true);
+ }
+
+ protected void build(SwtSection section, SwtUiProvider uiProvider, Content context) {
+ for (Control child : section.getChildren())
+ child.dispose();
+ CmsSwtUtils.style(section, bodyStyle);
+ section.setContent(context);
+ uiProvider.createUiPart(section, context);
+
+ }
+
+ private int tabIndex(Content context) {
+ for (int i = 0; i < sections.size(); i++) {
+ SwtSection section = sections.get(i);
+ if (section.getSessionLocalId().equals(((ProvidedContent) context).getSessionLocalId()))
+ return i;
+ }
+ return -1;
+ }
+
+ public void closeTab(SwtSection section) {
+ int currentIndex = sections.indexOf(section);
+ int nextIndex = currentIndex == 0 ? 0 : currentIndex - 1;
+ sections.remove(section);
+ section.dispose();
+ if (sections.size() == 0) {
+ emptyState();
+ refreshTabHeaders();
+ layout(true, true);
+ return;
+ }
+ refreshTabHeaders();
+ showTab(nextIndex);
+ }
+
+ public void closeAllTabs() {
+ for (SwtSection section : sections) {
+ section.dispose();
+ }
+ sections.clear();
+ emptyState();
+ refreshTabHeaders();
+ layout(true, true);
+ }
+
+ protected void emptyState() {
+ new SwtSection(body, SWT.NONE, null);
+ refreshTabHeaders();
+ }
+
+ public Composite getCurrent() {
+ return getCurrentSection();
+ }
+
+ protected SwtSection getCurrentSection() {
+ return (SwtSection) stackLayout.topControl;
+ }
+
+ public Content getCurrentContext() {
+ SwtSection section = getCurrentSection();
+ if (section != null) {
+ return section.getContent();
+ } else {
+ return null;
+ }
+ }
+
+ public void setTabStyle(String tabStyle) {
+ this.tabStyle = tabStyle;
+ }
+
+ public void setTabSelectedStyle(String tabSelectedStyle) {
+ this.tabSelectedStyle = tabSelectedStyle;
+ }
+
+ public void setBodyStyle(String bodyStyle) {
+ this.bodyStyle = bodyStyle;
+ }
+
+ public void setCloseIcon(Image closeIcon) {
+ this.closeIcon = closeIcon;
+ }
+
+ public void setSingleTab(boolean singleTab) {
+ this.singleTab = singleTab;
+ }
+
+ public void setSingleTabTitle(String singleTabTitle) {
+ this.singleTabTitle = singleTabTitle;
+ }
+
+}
--- /dev/null
+package org.argeo.cms.swt.acr;
+
+import org.argeo.api.acr.Content;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+
+@FunctionalInterface
+public interface SwtUiProvider {
+ Control createUiPart(Composite parent, Content context);
+}
--- /dev/null
+package org.argeo.cms.swt.app;
+
+import static org.argeo.api.acr.NamespaceUtils.toPrefixedName;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.xml.namespace.QName;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.acr.NamespaceUtils;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.swt.widgets.SwtTableView;
+import org.argeo.cms.swt.widgets.SwtTreeView;
+import org.argeo.cms.ux.acr.ContentHierarchicalPart;
+import org.argeo.cms.ux.widgets.Column;
+import org.argeo.cms.ux.widgets.DefaultTabularPart;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.SashForm;
+import org.eclipse.swt.widgets.Composite;
+
+/** A simple ACR browser. */
+public class AcrContentTreeView extends Composite {
+ private static final long serialVersionUID = -3707881216246077323L;
+
+ private Content rootContent;
+
+// private Content selected;
+
+ public AcrContentTreeView(Composite parent, int style, Content content) {
+ super(parent, style);
+ this.rootContent = content;
+ // this.selected = rootContent;
+ setLayout(CmsSwtUtils.noSpaceGridLayout());
+
+ SashForm split = new SashForm(this, SWT.HORIZONTAL);
+ split.setLayoutData(CmsSwtUtils.fillAll());
+
+ ContentHierarchicalPart contentPart = new ContentHierarchicalPart();
+ contentPart.addColumn((model) -> {
+ try {
+ return NamespaceUtils.toPrefixedName(model.getName());
+ } catch (IllegalStateException e) {
+ return model.getName().toString();
+ }
+ });
+ contentPart.setInput(rootContent);
+
+ new SwtTreeView<>(split, getStyle(), contentPart);
+
+ Composite area = new Composite(split, SWT.BORDER);
+ area.setLayout(CmsSwtUtils.noSpaceGridLayout(2));
+ split.setWeights(new int[] { 30, 70 });
+
+ // attributes
+ DefaultTabularPart<Content, QName> attributesPart = new DefaultTabularPart<>() {
+
+ @Override
+ protected List<QName> asList(Content input) {
+ return new ArrayList<>(input.keySet());
+ }
+ };
+
+ attributesPart.addColumn(new Column<QName>() {
+
+ @Override
+ public String getText(QName model) {
+ try {
+ return NamespaceUtils.toPrefixedName(model);
+ } catch (IllegalStateException e) {
+ return model.toString();
+ }
+ }
+ });
+ attributesPart.addColumn(new Column<QName>() {
+
+ @Override
+ public String getText(QName model) {
+ return attributesPart.getInput().get(model).toString();
+ }
+
+ @Override
+ public int getWidth() {
+ return 400;
+ }
+
+ });
+ // attributesPart.setInput(selected);
+
+ SwtTableView<Content, QName> attributeTable = new SwtTableView<>(area, style, attributesPart);
+ attributeTable.setLayoutData(CmsSwtUtils.fillAll());
+
+ // types
+ DefaultTabularPart<Content, QName> typesPart = new DefaultTabularPart<>() {
+
+ @Override
+ protected List<QName> asList(Content input) {
+ return input.getContentClasses();
+ }
+ };
+ typesPart.addColumn(new Column<QName>() {
+
+ @Override
+ public String getText(QName model) {
+ return toPrefixedName(model);
+ }
+
+ });
+
+ // typesPart.setInput(selected);
+
+ SwtTableView<Content, QName> typesTable = new SwtTableView<>(area, style, typesPart);
+ typesTable.setLayoutData(CmsSwtUtils.fillAll());
+
+ // controller
+ contentPart.setInput(rootContent);
+ contentPart.onSelected((o) -> {
+ Content c = (Content) o;
+// selected = c;
+ attributesPart.setInput(c);
+ typesPart.setInput(c);
+ });
+
+ attributesPart.refresh();
+ typesPart.refresh();
+ }
+
+// 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.app;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.acr.ContentRepository;
+import org.argeo.api.cms.CmsContext;
+import org.argeo.api.cms.ux.CmsUi;
+import org.argeo.api.cms.ux.CmsView;
+import org.argeo.cms.AbstractCmsApp;
+import org.argeo.cms.swt.CmsSwtUi;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.swt.auth.CmsLogin;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.Composite;
+
+public class CmsUserApp extends AbstractCmsApp {
+ private ContentRepository contentRepository;
+
+ @Override
+ public Set<String> getUiNames() {
+ Set<String> uiNames = new HashSet<>();
+ uiNames.add("login");
+ uiNames.add("data");
+ return uiNames;
+ }
+
+ @Override
+ public CmsUi initUi(Object uiParent) {
+ Composite parent = (Composite) uiParent;
+ String uiName = parent.getData(UI_NAME_PROPERTY) != null ? parent.getData(UI_NAME_PROPERTY).toString() : null;
+ CmsSwtUi cmsUi = new CmsSwtUi(parent, SWT.NONE);
+ if ("login".equals(uiName)) {
+ CmsView cmsView = CmsSwtUtils.getCmsView(cmsUi);
+ CmsLogin cmsLogin = new CmsLogin(cmsView, getCmsContext());
+ cmsLogin.createUi(cmsUi);
+
+ } else if ("data".equals(uiName)) {
+ Content rootContent = contentRepository.get().get("/");
+ AcrContentTreeView view = new AcrContentTreeView(cmsUi, 0, rootContent);
+ view.setLayoutData(CmsSwtUtils.fillAll());
+
+ }
+ return cmsUi;
+ }
+
+ @Override
+ public void refreshUi(CmsUi cmsUi, String state) {
+ }
+
+ @Override
+ public void setState(CmsUi cmsUi, String state) {
+ // TODO Auto-generated method stub
+
+ }
+
+ public void setContentRepository(ContentRepository contentRepository) {
+ this.contentRepository = contentRepository;
+ }
+
+}
\ No newline at end of file
--- /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.Collections;
+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.ux.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;
+
+ private CmsContext cmsContext;
+
+ public CmsLogin(CmsView cmsView, CmsContext cmsContext) {
+ this.cmsView = cmsView;
+ this.cmsContext = cmsContext;
+ if (this.cmsContext != null) {
+ defaultLocale = this.cmsContext.getDefaultLocale();
+ List<Locale> locales = this.cmsContext.getLocales();
+ if (locales != null && locales.size() > 1)
+ 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 = 200;
+ 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);
+ cmsContext.getCmsEventBus().sendEvent("cms", Collections.singletonMap("msg", "New login"));
+ 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.CmsContext;
+import org.argeo.api.cms.ux.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, CmsContext cmsContext) {
+ super(cmsView, cmsContext);
+ 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.cms.swt.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.swt.CmsException;
+
+/** Choose in a list of locales. TODO: replace with {@link LanguageCallback} */
+@Deprecated
+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.util.Arrays;
+import java.util.concurrent.Callable;
+
+import org.argeo.api.cms.CmsLog;
+import org.argeo.api.cms.directory.CmsUserManager;
+import org.argeo.api.cms.ux.CmsView;
+import org.argeo.cms.CmsMsg;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.ux.widgets.CmsDialog;
+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 Callable<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 CmsDialog.OK;
+ } catch (Exception e1) {
+ log.error("Could not change password", e1);
+ cancel();
+ CmsMessageDialog.openError(CmsMsg.invalidPassword.lead());
+ return CmsDialog.CANCEL;
+ }
+ } else {
+ cancel();
+ CmsMessageDialog.openError(CmsMsg.repeatNewPassword.lead());
+ return CmsDialog.CANCEL;
+ }
+ };
+
+ pack();
+ return previousPassword;
+ }
+
+ @Override
+ protected void okPressed() {
+ Integer returnCode = cmsView.doAs(doIt);
+ if (returnCode.equals(CmsDialog.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.IOException;
+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.argeo.cms.ux.widgets.CmsDialog;
+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.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;
+
+/** 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;
+
+ private Text stack;
+
+ private CmsFeedback(Shell parentShell, String message, Throwable e) {
+ super(parentShell);
+ this.message = message;
+ this.exception = e;
+ }
+
+ public static CmsFeedback error(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;
+
+ log.error(message, e);
+ try {
+ Display display = LightweightDialog.findDisplay();
+
+ CmsFeedback current = (CmsFeedback) display.getData(CmsFeedback.class.getName());
+ if (current != null) {// already one open
+ current.append("");
+ if (message != null)
+ current.append(message);
+ if (e != null)
+ current.append(e);
+ // FIXME set a limit to the size of the text
+ return current;
+ }
+
+ CmsFeedback cmsFeedback = new CmsFeedback(null, message, e);
+ cmsFeedback.setBlockOnOpen(false);
+ cmsFeedback.open();
+ cmsFeedback.getDisplay().setData(CmsFeedback.class.getName(), cmsFeedback);
+ 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;
+ }
+
+ protected Control createDialogArea(Composite parent) {
+ parent.setLayout(new GridLayout(2, false));
+
+ Label messageLbl = new Label(parent, SWT.WRAP);
+ messageLbl.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
+ 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(CmsDialog.OK));
+
+ if (exception != null) {
+ 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));
+ append(exception);
+ }
+ return messageLbl;
+ }
+
+ protected Point getInitialSize() {
+ if (exception != null)
+ return new Point(800, 600);
+ else
+ return new Point(600, 400);
+ }
+
+ protected void append(String message) {
+ stack.append(message);
+ stack.append("\n");
+ }
+
+ protected void append(Throwable exception) {
+ try (StringWriter sw = new StringWriter()) {
+ exception.printStackTrace(new PrintWriter(sw));
+ stack.append(sw.toString());
+ } catch (IOException e) {
+ // ignore
+ }
+
+ }
+
+ @Override
+ protected void onClose() {
+ getDisplay().setData(CmsFeedback.class.getName(), null);
+ }
+
+}
--- /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.cms.ux.widgets.CmsDialog;
+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(CmsDialog.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(CmsDialog.OK);
+ }
+
+ protected void cancelPressed() {
+ closeShell(CmsDialog.CANCEL);
+ }
+
+ protected void cancel() {
+ closeShell(CmsDialog.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 org.argeo.api.cms.CmsLog;
+import org.argeo.cms.ux.widgets.CmsDialog;
+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);
+
+ private Shell parentShell;
+ private Shell backgroundShell;
+ private Shell foregoundShell;
+
+ private Display display;
+
+ private Integer returnCode = null;
+ private boolean block = true;
+
+ private String title;
+
+ /** Tries to find a display */
+ static Display findDisplay() {
+ 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() {
+ display = findDisplay();
+ 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(display.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.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(CmsDialog.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(CmsDialog.CANCEL);
+ }
+ });
+ backgroundShell.addDisposeListener((event) -> onClose());
+
+ if (block) {
+ block();
+ }
+ if (returnCode == null)
+ returnCode = CmsDialog.OK;
+ return returnCode;
+ }
+
+ public void block() {
+ try {
+ runEventLoop(foregoundShell);
+ } catch (ThreadDeath t) {
+ returnCode = CmsDialog.CANCEL;
+ if (log.isTraceEnabled())
+ log.error("Thread death, canceling dialog", t);
+ } catch (Throwable t) {
+ returnCode = CmsDialog.CANCEL;
+ log.error("Cannot open blocking lightweight dialog", t);
+ }
+ }
+
+ private boolean hasChildShells() {
+ if (foregoundShell == null)
+ return false;
+ return foregoundShell.getShells().length != 0;
+ }
+
+ protected void onClose() {
+
+ }
+
+ // 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 = CmsDialog.CANCEL;
+ notifyAll();
+ }
+
+ protected void closeShell(int returnCode) {
+ this.returnCode = returnCode;
+ if (CmsDialog.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() {
+ 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;
+ }
+
+ Display getDisplay() {
+ return display;
+ }
+
+}
\ 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.osgi;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.argeo.api.cms.ux.CmsIcon;
+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<>();
+
+ protected 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 #getSmallIconSize()} will be tried.
+ */
+ public Image getIcon(String name, Integer preferredSize) {
+ if (preferredSize == null)
+ preferredSize = getSmallIconSize();
+ 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;
+ String ext = "";
+ if (lastSlash >= 0)
+ fileName = p.substring(lastSlash + 1);
+ int lastDot = fileName.lastIndexOf('.');
+ if (lastDot >= 0) {
+ ext = fileName.substring(lastDot + 1);
+ fileName = fileName.substring(0, lastDot);
+ }
+
+ if ("svg".equals(ext))
+ continue paths;
+
+ 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;
+ }
+
+ @Override
+ public Image getSmallIcon(CmsIcon icon) {
+ return getIcon(icon.name(), getSmallIconSize());
+ }
+
+ @Override
+ public Image getBigIcon(CmsIcon icon) {
+ return getIcon(icon.name(), getBigIconSize());
+ }
+
+}
--- /dev/null
+package org.argeo.cms.swt.osgi;
+
+import java.awt.Color;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.System.Logger;
+import java.lang.System.Logger.Level;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.batik.transcoder.TranscoderException;
+import org.apache.batik.transcoder.TranscoderInput;
+import org.apache.batik.transcoder.TranscoderOutput;
+import org.apache.batik.transcoder.image.ImageTranscoder;
+import org.apache.batik.transcoder.image.PNGTranscoder;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.ImageData;
+import org.eclipse.swt.widgets.Display;
+import org.osgi.framework.BundleContext;
+
+/** Theme which can dynamically create icons from SVG data. */
+public class BundleSvgTheme extends BundleCmsSwtTheme {
+ private final static Logger logger = System.getLogger(BundleSvgTheme.class.getName());
+
+ private Map<String, Map<Integer, Image>> imageCache = Collections.synchronizedMap(new HashMap<>());
+
+ private Map<Integer, ImageTranscoder> transcoders = Collections.synchronizedMap(new HashMap<>());
+
+ @Override
+ public Image getIcon(String name, Integer preferredSize) {
+ String path = "icons/types/svg/" + name + ".svg";
+ return createImageFromSvg(path, preferredSize);
+ }
+
+ protected Image createImageFromSvg(String path, Integer preferredSize) {
+ Image image = null;
+ if (imageCache.containsKey(path)) {
+ image = imageCache.get(path).get(preferredSize);
+ }
+ if (image != null)
+ return image;
+ ImageData imageData = loadFromSvg(path, preferredSize);
+ image = new Image(Display.getDefault(), imageData);
+ if (!imageCache.containsKey(path))
+ imageCache.put(path, Collections.synchronizedMap(new HashMap<>()));
+ imageCache.get(path).put(preferredSize, image);
+ return image;
+ }
+
+ protected ImageData loadFromSvg(String path, int size) {
+ ImageTranscoder transcoder = null;
+ synchronized (this) {
+ transcoder = transcoders.get(size);
+ if (transcoder == null) {
+ transcoder = new PNGTranscoder();
+ transcoder.addTranscodingHint(PNGTranscoder.KEY_WIDTH, (float) size);
+ transcoder.addTranscodingHint(PNGTranscoder.KEY_HEIGHT, (float) size);
+ transcoder.addTranscodingHint(PNGTranscoder.KEY_BACKGROUND_COLOR, new Color(255, 255, 255, 0));
+ transcoders.put(size, transcoder);
+ }
+ }
+ ImageData imageData;
+ try (InputStream in = getResourceAsStream(path); ByteArrayOutputStream out = new ByteArrayOutputStream();) {
+ if (in == null)
+ throw new IllegalArgumentException(path + " not found");
+ TranscoderInput input = new TranscoderInput(in);
+ TranscoderOutput output = new TranscoderOutput(out);
+ transcoder.transcode(input, output);
+ try (InputStream imageIn = new ByteArrayInputStream(out.toByteArray())) {
+ imageData = new ImageData(imageIn);
+ }
+ logger.log(Level.DEBUG, () -> "Generated " + size + "x" + size + " PNG icon from " + path);
+ } catch (IOException | TranscoderException e) {
+ throw new RuntimeException("Cannot transcode SVG " + path, e);
+ }
+
+ return imageData;
+ }
+
+ @Override
+ public void init(BundleContext bundleContext, Map<String, String> properties) {
+ super.init(bundleContext, properties);
+
+ // preload all icons
+// paths: for (String p : getImagesPaths()) {
+// if (!p.endsWith(".svg"))
+// continue paths;
+// createImageFromSvg(p, getDefaultIconSize());
+// }
+ }
+
+ @Override
+ public void destroy(BundleContext bundleContext, Map<String, String> properties) {
+ Display display = Display.getDefault();
+ if (display != null)
+ for (String path : imageCache.keySet()) {
+ for (Image image : imageCache.get(path).values()) {
+ display.syncExec(() -> image.dispose());
+ }
+ }
+ super.destroy(bundleContext, properties);
+ }
+
+}
--- /dev/null
+package org.argeo.cms.swt.widgets;
+
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.ux.widgets.DataPart;
+import org.argeo.cms.ux.widgets.DataView;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.widgets.Composite;
+
+/** Base class for {@link DataView}s based on an SWT {@link Composite}. */
+public abstract class AbstractSwtView<INPUT, TYPE> extends Composite implements DataView<INPUT, TYPE> {
+ private static final long serialVersionUID = -1999179054267812170L;
+
+ protected DataPart<INPUT, TYPE> dataPart;
+
+ protected final SelectionListener selectionListener;
+
+ @SuppressWarnings("unchecked")
+ public AbstractSwtView(Composite parent, DataPart<INPUT, TYPE> dataPart) {
+ super(parent, SWT.NONE);
+ setLayout(CmsSwtUtils.noSpaceGridLayout());
+
+ this.dataPart = dataPart;
+
+ selectionListener = new SelectionListener() {
+
+ private static final long serialVersionUID = 4334785560035009330L;
+
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ if (dataPart.getOnSelected() != null)
+ dataPart.getOnSelected().accept((TYPE) e.item.getData());
+ }
+
+ @Override
+ public void widgetDefaultSelected(SelectionEvent e) {
+ if (dataPart.getOnAction() != null)
+ dataPart.getOnAction().accept((TYPE) e.item.getData());
+ }
+ };
+
+ dataPart.addView(this);
+ addDisposeListener((e) -> dataPart.removeView(this));
+ }
+
+ public abstract void refresh();
+}
--- /dev/null
+package org.argeo.cms.swt.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.swt.widgets;
+
+import org.argeo.api.cms.CmsLog;
+import org.argeo.api.cms.ux.Cms2DSize;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.ux.AbstractImageManager;
+import org.argeo.cms.ux.CmsUxUtils;
+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;
+ }
+
+ @Override
+ protected void setContainerLayoutData(Composite composite) {
+ // composite.setLayoutData(fillWidth());
+ }
+
+ @Override
+ protected void setControlLayoutData(Control control) {
+ // control.setLayoutData(fillWidth());
+ }
+
+ /** To be overriden. */
+ protected String createImgTag() {
+ return 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 = noImg(preferredImageSize);
+ loaded = false;
+ }
+
+ if (imgTag == null) {
+ loaded = false;
+ imgTag = 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;
+ }
+
+ public static String noImg(Cms2DSize size) {
+// ResourceManager rm = RWT.getResourceManager();
+// String noImgPath=rm.getLocation(AbstractImageManager.NO_IMAGE);
+ // FIXME load it via package service
+ String noImgPath = "";
+ return CmsUxUtils.img(noImgPath, size);
+ }
+
+ public static String noImg() {
+ return noImg(AbstractImageManager.NO_IMAGE_SIZE);
+ }
+
+}
--- /dev/null
+package org.argeo.cms.swt.widgets;
+
+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 boolean multiLine = 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));
+ multiLine = !(SWT.SINGLE == (style & SWT.SINGLE));
+ highlightColor = parent.getDisplay().getSystemColor(SWT.COLOR_GRAY);
+ useTextAsLabel = SWT.FLAT == (style & SWT.FLAT);
+ }
+
+ @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() | (multiLine ? SWT.MULTI : SWT.SINGLE));
+ 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() | (multiLine ? SWT.MULTI : SWT.SINGLE) | 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.swt.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.swt.widgets;
+
+import org.argeo.api.cms.ux.CmsStyle;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.swt.SwtEditablePart;
+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 Composite implements SwtEditablePart {
+ 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());
+ }
+
+ 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;
+ }
+
+ @Override
+ public Control getControl() {
+ return control;
+ }
+
+ protected synchronized Boolean isEditing() {
+ return editing;
+ }
+
+ @Override
+ 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);
+ }
+
+ @Override
+ 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(CmsStyle style) {
+ setStyle(style.style());
+ }
+
+ 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.swt.widgets;
+
+import java.util.List;
+
+import org.argeo.cms.CmsMsg;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.swt.Selected;
+import org.argeo.cms.swt.dialogs.LightweightDialog;
+import org.argeo.cms.ux.widgets.CmsDialog;
+import org.argeo.cms.ux.widgets.GuidedForm;
+import org.argeo.cms.ux.widgets.GuidedForm.Page;
+import org.argeo.eclipse.ui.EclipseUiUtils;
+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 SwtGuidedFormDialog extends LightweightDialog implements GuidedForm.View {
+ private GuidedForm guidedForm;
+ private GuidedForm.Page 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 SwtGuidedFormDialog(Shell parentShell, GuidedForm guidedForm) {
+ super(parentShell);
+ this.guidedForm = guidedForm;
+ guidedForm.setView(this);
+ // create the pages
+ guidedForm.addPages();
+ for (Page page : guidedForm.getPages()) {
+ if (!(page instanceof SwtGuidedFormPage))
+ throw new IllegalArgumentException("Pages form must implement " + SwtGuidedFormPage.class);
+ }
+ currentPage = guidedForm.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(CmsDialog.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[guidedForm.getPageCount()];
+ List<GuidedForm.Page> pages = guidedForm.getPages();
+ for (int i = 0; i < pages.size(); i++) {
+ pageBodies[i] = new Composite(body, SWT.NONE);
+ pageBodies[i].setLayout(CmsSwtUtils.noSpaceGridLayout());
+ setSwitchingFormData(pageBodies[i]);
+ // !! SWT specific
+ SwtGuidedFormPage page = (SwtGuidedFormPage) pages.get(i);
+ page.createControl(pageBodies[i]);
+ }
+ showPage(currentPage);
+
+ buttons = new Composite(parent, SWT.NONE);
+ buttons.setLayoutData(new GridData(SWT.END, SWT.FILL, true, false));
+ {
+ boolean singlePage = guidedForm.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;
+ }
+
+ public GuidedForm.Page getCurrentPage() {
+ return currentPage;
+ }
+
+ public Shell getShell() {
+ return getForegoundShell();
+ }
+
+ public void showPage(GuidedForm.Page page) {
+ List<GuidedForm.Page> pages = guidedForm.getPages();
+ int index = -1;
+ for (int i = 0; i < pages.size(); i++) {
+ if (page == pages.get(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(guidedForm.getPreviousPage(currentPage) != null);
+ if (next != null)
+ next.setEnabled(guidedForm.getNextPage(currentPage) != null && currentPage.canFlipToNextPage());
+ if (finish != null) {
+ finish.setEnabled(guidedForm.canFinish());
+ }
+ }
+
+ public void updateMessage() {
+ if (currentPage.getMessage() != null)
+ message.setText(currentPage.getMessage());
+ }
+
+ public void updateTitleBar() {
+ if (currentPage.getTitle() != null)
+ titleBar.setText(currentPage.getTitle());
+ }
+
+ public void updateWindowTitle() {
+ setTitle(guidedForm.getFormTitle());
+ }
+
+ protected boolean onCancel() {
+ return guidedForm.performCancel();
+ }
+
+ protected void nextPressed() {
+ GuidedForm.Page page = guidedForm.getNextPage(currentPage);
+ showPage(page);
+ updateButtons();
+ }
+
+ protected void backPressed() {
+ GuidedForm.Page page = guidedForm.getPreviousPage(currentPage);
+ showPage(page);
+ updateButtons();
+ }
+
+ protected void finishPressed() {
+ if (guidedForm.performFinish())
+ closeShell(CmsDialog.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.widgets;
+
+import org.argeo.cms.ux.widgets.AbstractGuidedFormPage;
+import org.eclipse.swt.widgets.Composite;
+
+public abstract class SwtGuidedFormPage extends AbstractGuidedFormPage {
+
+ public SwtGuidedFormPage(String pageName) {
+ super(pageName);
+ }
+
+ public abstract void createControl(Composite parent);
+}
--- /dev/null
+package org.argeo.cms.swt.widgets;
+
+import org.argeo.api.cms.ux.CmsIcon;
+import org.argeo.cms.swt.CmsSwtTheme;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.ux.widgets.Column;
+import org.argeo.cms.ux.widgets.TabularPart;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Table;
+import org.eclipse.swt.widgets.TableColumn;
+import org.eclipse.swt.widgets.TableItem;
+
+/** View of a {@link TabularPart} based on a {@link Table}. */
+public class SwtTableView<INPUT, T> extends AbstractSwtView<INPUT, T> {
+ private static final long serialVersionUID = -1114155772446357750L;
+ private final Table table;
+ private TabularPart<INPUT, T> tabularPart;
+
+ private CmsSwtTheme theme;
+
+ public SwtTableView(Composite parent, int style, TabularPart<INPUT, T> tabularPart) {
+ super(parent, tabularPart);
+ theme = CmsSwtUtils.getCmsTheme(parent);
+
+ table = new Table(this, SWT.VIRTUAL | style);
+ table.setLinesVisible(true);
+ table.setLayoutData(CmsSwtUtils.fillAll());
+
+ this.tabularPart = tabularPart;
+ }
+
+ @Override
+ public void refresh() {
+ // TODO optimise
+ table.clearAll();
+ table.addListener(SWT.SetData, event -> {
+ TableItem item = (TableItem) event.item;
+ refreshItem(item);
+ });
+ table.setItemCount(tabularPart.getItemCount());
+ for (int i = 0; i < tabularPart.getColumnCount(); i++) {
+ TableColumn swtColumn = new TableColumn(table, SWT.NONE);
+ swtColumn.setWidth(tabularPart.getColumn(i).getWidth());
+ }
+ CmsSwtUtils.fill(table);
+
+ table.addSelectionListener(selectionListener);
+
+ }
+
+ protected Object getDataFromEvent(SelectionEvent e) {
+ Object data = e.item.getData();
+ if (data == null)
+ data = tabularPart.getData(getTable().indexOf((TableItem) e.item));
+ return data;
+ }
+
+ protected void refreshItem(TableItem item) {
+ int row = getTable().indexOf(item);
+ T data = tabularPart.getData(row);
+ for (int i = 0; i < tabularPart.getColumnCount(); i++) {
+ Column<T> column = tabularPart.getColumn(i);
+ item.setData(data);
+ String text = data != null ? column.getText(data) : "";
+ if (text != null)
+ item.setText(i, text);
+ CmsIcon icon = column.getIcon(data);
+ if (icon != null) {
+ Image image = theme.getSmallIcon(icon);
+ item.setImage(i, image);
+ }
+ }
+ }
+
+ @Override
+ public void notifyItemCountChange() {
+ table.setItemCount(tabularPart.getItemCount());
+ }
+
+ protected Table getTable() {
+ return table;
+ }
+
+}
--- /dev/null
+package org.argeo.cms.swt.widgets;
+
+import java.util.List;
+
+import org.argeo.api.cms.CmsLog;
+import org.argeo.api.cms.ux.CmsIcon;
+import org.argeo.cms.swt.CmsSwtTheme;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.ux.widgets.Column;
+import org.argeo.cms.ux.widgets.HierarchicalPart;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Tree;
+import org.eclipse.swt.widgets.TreeItem;
+
+/** View of a {@link HierarchicalPart} based on a {@link Tree}. */
+public class SwtTreeView<T> extends AbstractSwtView<T, T> {
+ private final static CmsLog log = CmsLog.getLog(SwtTreeView.class);
+
+ private static final long serialVersionUID = -6247710601465713047L;
+
+ private final Tree tree;
+
+ private HierarchicalPart<T> hierarchicalPart;
+ private CmsSwtTheme theme;
+
+ public SwtTreeView(Composite parent, int style, HierarchicalPart<T> hierarchicalPart) {
+ super(parent, hierarchicalPart);
+ theme = CmsSwtUtils.getCmsTheme(parent);
+
+ tree = new Tree(this, style);
+ tree.setLayoutData(CmsSwtUtils.fillAll());
+ this.hierarchicalPart = hierarchicalPart;
+
+ tree.addSelectionListener(selectionListener);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public void refresh() {
+ // TODO optimise
+ for (TreeItem rootItem : tree.getItems()) {
+ rootItem.dispose();
+ }
+
+ List<T> rootItems = hierarchicalPart.getChildren(hierarchicalPart.getInput());
+ for (T child : rootItems) {
+ try {
+ addTreeItem(null, child);
+ } catch (Exception e) {
+ if (log.isTraceEnabled())
+ log.error("Cannot retrieve child", e);
+ }
+ }
+
+ 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();
+ }
+
+ List<T> children = hierarchicalPart.getChildren((T) root.getData());
+ for (T child : children) {
+ addTreeItem(root, child);
+ }
+ });
+
+ CmsSwtUtils.fill(tree);
+
+ }
+
+ protected TreeItem addTreeItem(TreeItem parent, T data) {
+ TreeItem item = parent == null ? new TreeItem(tree, SWT.NONE) : new TreeItem(parent, SWT.NONE);
+ item.setData(data);
+ for (int i = 0; i < hierarchicalPart.getColumnCount(); i++) {
+ Column<T> column = hierarchicalPart.getColumn(i);
+ String txt = column.getText(data);
+ if (txt != null)
+ item.setText(txt);
+ CmsIcon icon = column.getIcon(data);
+ if (icon != null) {
+ Image image = theme.getSmallIcon(icon);
+ item.setImage(image);
+ }
+ }
+ // TODO optimise
+ List<T> grandChildren = hierarchicalPart.getChildren(data);
+ if (grandChildren.size() != 0)
+ new TreeItem(item, SWT.NONE);
+ return item;
+ }
+
+ @Override
+ public void notifyItemCountChange() {
+ // TODO what to update ?
+
+ }
+
+ protected Tree getTree() {
+ return tree;
+ }
+
+}
--- /dev/null
+package org.argeo.eclipse.ui;
+
+import org.argeo.cms.ux.widgets.TreeParent;
+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.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
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-17"/>
+ <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+ <classpathentry kind="src" path="src"/>
+ <classpathentry kind="output" path="bin"/>
+</classpath>
--- /dev/null
+/bin/
+/target/
+*.log
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>org.argeo.swt.minidesktop</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,\
+*
--- /dev/null
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+ .
--- /dev/null
+package org.argeo.minidesktop;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.browser.Browser;
+import org.eclipse.swt.browser.LocationAdapter;
+import org.eclipse.swt.browser.LocationEvent;
+import org.eclipse.swt.events.KeyAdapter;
+import org.eclipse.swt.events.KeyEvent;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.Rectangle;
+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.Control;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Text;
+
+/** A very minimalistic web browser based on {@link Browser}. */
+public class MiniBrowser {
+ private static Point defaultShellSize = new Point(800, 480);
+
+ private Browser browser;
+ private Text addressT;
+
+ private final boolean fullscreen;
+ private final boolean appMode;
+
+ public MiniBrowser(Composite composite, String url, boolean fullscreen, boolean appMode) {
+ this.fullscreen = fullscreen;
+ this.appMode = appMode;
+ createUi(composite);
+ setUrl(url);
+ }
+
+ public Control createUi(Composite parent) {
+ parent.setLayout(noSpaceGridLayout(new GridLayout()));
+ if (!isAppMode()) {
+ Control toolBar = createToolBar(parent);
+ toolBar.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
+ }
+ Control body = createBody(parent);
+ body.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
+ return body;
+ }
+
+ protected Control createToolBar(Composite parent) {
+ Composite toolBar = new Composite(parent, SWT.NONE);
+ toolBar.setLayout(new FillLayout());
+ addressT = new Text(toolBar, SWT.SINGLE);
+ addressT.addSelectionListener(new SelectionAdapter() {
+
+ @Override
+ public void widgetDefaultSelected(SelectionEvent e) {
+ setUrl(addressT.getText().trim());
+ }
+ });
+ return toolBar;
+ }
+
+ protected Control createBody(Composite parent) {
+ browser = new Browser(parent, SWT.NONE);
+ if (isFullScreen())
+ browser.addKeyListener(new KeyAdapter() {
+ @Override
+ public void keyPressed(KeyEvent e) {
+ if (e.keyCode == 0x77 && e.stateMask == 0x40000) {// Ctrl+W
+ browser.getShell().dispose();
+ }
+ }
+ });
+ browser.addLocationListener(new LocationAdapter() {
+ @Override
+ public void changed(LocationEvent event) {
+ System.out.println(event);
+ if (addressT != null)
+ addressT.setText(event.location);
+ }
+
+ });
+
+ MiniDesktopSpecific.getMiniDesktopSpecific().addBrowserTitleListener(this, browser);
+ MiniDesktopSpecific.getMiniDesktopSpecific().addBrowserOpenWindowListener(this, browser);
+ return browser;
+ }
+
+ public Browser openNewBrowserWindow() {
+
+ if (isFullScreen()) {
+ // TODO manage multiple tabs?
+ return browser;
+ } else {
+ Shell newShell = new Shell(browser.getDisplay(), SWT.SHELL_TRIM);
+ MiniBrowser newMiniBrowser = new MiniBrowser(newShell, null, false, isAppMode());
+ newShell.setSize(defaultShellSize);
+ newShell.open();
+ return newMiniBrowser.browser;
+ }
+ }
+
+ protected boolean isFullScreen() {
+ return fullscreen;
+ }
+
+ void setUrl(String url) {
+ if (browser != null && url != null && !url.equals(browser.getUrl()))
+ browser.setUrl(url.toString());
+ }
+
+ /** Called when URL changed; to be overridden, does nothing by default. */
+ protected void urlChanged(String url) {
+ }
+
+ /** Called when title changed; to be overridden, does nothing by default. */
+ public void titleChanged(String title) {
+ }
+
+ protected Browser getBrowser() {
+ return browser;
+ }
+
+ protected boolean isAppMode() {
+ return appMode;
+ }
+
+ private static GridLayout noSpaceGridLayout(GridLayout layout) {
+ layout.horizontalSpacing = 0;
+ layout.verticalSpacing = 0;
+ layout.marginWidth = 0;
+ layout.marginHeight = 0;
+ return layout;
+ }
+
+ public static void main(String[] args) {
+ List<String> options = Arrays.asList(args);
+ if (options.contains("--help")) {
+ System.out.println("Usage: java " + MiniBrowser.class.getName().replace('.', '/') + " [OPTION] [URL]");
+ System.out.println("A minimalistic web browser Eclipse SWT Browser integration.");
+ System.out.println(" --fullscreen : take control of the whole screen (default is to run in a window)");
+ System.out.println(" --app : open without an address bar and a toolbar");
+ System.out.println(" --help : print this help and exit");
+ System.exit(1);
+ }
+ boolean fullscreen = options.contains("--fullscreen");
+ boolean appMode = options.contains("--app");
+ String url = "https://start.duckduckgo.com/";
+ if (options.size() > 0) {
+ String last = options.get(options.size() - 1);
+ if (!last.startsWith("--"))
+ url = last.trim();
+ }
+
+ Display display = Display.getCurrent() == null ? new Display() : Display.getCurrent();
+ Shell shell;
+ if (fullscreen) {
+ shell = new Shell(display, SWT.NO_TRIM);
+ shell.setFullScreen(true);
+ Rectangle bounds = display.getBounds();
+ shell.setSize(bounds.width, bounds.height);
+ } else {
+ shell = new Shell(display, SWT.SHELL_TRIM);
+ shell.setSize(defaultShellSize);
+ }
+
+ new MiniBrowser(shell, url, fullscreen, appMode) {
+
+ @Override
+ public void titleChanged(String title) {
+ shell.setText(title);
+ }
+ };
+ shell.open();
+
+ while (!shell.isDisposed()) {
+ if (!display.readAndDispatch())
+ display.sleep();
+ }
+ }
+
+}
--- /dev/null
+package org.argeo.minidesktop;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.eclipse.swt.SWTException;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.widgets.Display;
+
+/** Icons. */
+public class MiniDesktopImages {
+
+ public final Image homeIcon;
+ public final Image exitIcon;
+
+ public final Image terminalIcon;
+ public final Image browserIcon;
+ public final Image explorerIcon;
+ public final Image textEditorIcon;
+
+ public final Image folderIcon;
+ public final Image fileIcon;
+
+ public MiniDesktopImages(Display display) {
+ homeIcon = loadImage(display, "nav_home@2x.png");
+ exitIcon = loadImage(display, "delete@2x.png");
+
+ terminalIcon = loadImage(display, "console_view@2x.png");
+ browserIcon = loadImage(display, "external_browser@2x.png");
+ explorerIcon = loadImage(display, "fldr_obj@2x.png");
+ textEditorIcon = loadImage(display, "cheatsheet_obj@2x.png");
+
+ folderIcon = loadImage(display, "fldr_obj@2x.png");
+ fileIcon = loadImage(display, "file_obj@2x.png");
+ }
+
+ static Image loadImage(Display display, String path) {
+ InputStream stream = MiniDesktopImages.class.getResourceAsStream(path);
+ if (stream == null)
+ throw new IllegalArgumentException("Image " + path + " not found");
+ Image image = null;
+ try {
+ image = new Image(display, stream);
+ } catch (SWTException ex) {
+ } finally {
+ try {
+ stream.close();
+ } catch (IOException ex) {
+ }
+ }
+ return image;
+ }
+}
--- /dev/null
+package org.argeo.minidesktop;
+
+import java.lang.management.ManagementFactory;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.List;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.CTabFolder;
+import org.eclipse.swt.custom.CTabItem;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.Image;
+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;
+import org.eclipse.swt.widgets.ToolBar;
+import org.eclipse.swt.widgets.ToolItem;
+
+/** A very minimalistic desktop manager based on Java and Eclipse SWT. */
+public class MiniDesktopManager {
+ private Display display;
+
+ private Shell rootShell;
+ private Shell toolBarShell;
+ private CTabFolder tabFolder;
+ private int maxTabTitleLength = 16;
+
+ private final boolean fullscreen;
+ private final boolean stacking;
+
+ private MiniDesktopImages images;
+
+ public MiniDesktopManager(boolean fullscreen, boolean stacking) {
+ this.fullscreen = fullscreen;
+ this.stacking = stacking;
+ }
+
+ public void init() {
+ Display.setAppName("Mini SWT Desktop");
+ display = Display.getCurrent();
+ if (display != null)
+ throw new IllegalStateException("Already a display " + display);
+ display = new Display();
+
+ if (display.getTouchEnabled()) {
+ System.out.println("Touch enabled.");
+ }
+
+ images = new MiniDesktopImages(display);
+
+ int toolBarSize = 48;
+
+ if (isFullscreen()) {
+ rootShell = new Shell(display, SWT.NO_TRIM);
+ rootShell.setFullScreen(true);
+ Rectangle bounds = display.getBounds();
+ rootShell.setLocation(0, 0);
+ rootShell.setSize(bounds.width, bounds.height);
+ } else {
+ rootShell = new Shell(display, SWT.CLOSE | SWT.RESIZE);
+ Rectangle shellArea = rootShell.computeTrim(200, 200, 800, 480);
+ rootShell.setSize(shellArea.width, shellArea.height);
+ rootShell.setText(Display.getAppName());
+ rootShell.setImage(images.terminalIcon);
+ }
+
+ rootShell.setLayout(noSpaceGridLayout(new GridLayout(2, false)));
+ Composite toolBarArea = new Composite(rootShell, SWT.NONE);
+ toolBarArea.setLayoutData(new GridData(toolBarSize, rootShell.getSize().y));
+
+ ToolBar toolBar;
+ if (isFullscreen()) {
+ toolBarShell = new Shell(rootShell, SWT.NO_TRIM | SWT.ON_TOP);
+ toolBar = new ToolBar(toolBarShell, SWT.VERTICAL | SWT.FLAT | SWT.BORDER);
+ createDock(toolBar);
+ toolBarShell.pack();
+ toolBarArea.setLayoutData(new GridData(toolBar.getSize().x, toolBar.getSize().y));
+ } else {
+ toolBar = new ToolBar(toolBarArea, SWT.VERTICAL | SWT.FLAT | SWT.BORDER);
+ createDock(toolBar);
+ toolBarArea.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false));
+ }
+
+ if (isStacking()) {
+ tabFolder = new CTabFolder(rootShell, SWT.MULTI | SWT.BORDER | SWT.BOTTOM);
+ tabFolder.setLayout(noSpaceGridLayout(new GridLayout()));
+ tabFolder.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
+
+ Color selectionBackground = display.getSystemColor(SWT.COLOR_LIST_SELECTION);
+ tabFolder.setSelectionBackground(selectionBackground);
+
+ // background
+ Control background = createBackground(tabFolder);
+ CTabItem homeTabItem = new CTabItem(tabFolder, SWT.NONE);
+ homeTabItem.setText("Home");
+ homeTabItem.setImage(images.homeIcon);
+ homeTabItem.setControl(background);
+ tabFolder.setFocus();
+ } else {
+ createBackground(rootShell);
+ }
+
+ rootShell.open();
+ // rootShell.layout(true, true);
+
+ if (toolBarShell != null) {
+ int toolBarShellY = (display.getBounds().height - toolBar.getSize().y) / 2;
+ toolBarShell.setLocation(0, toolBarShellY);
+ toolBarShell.open();
+ }
+
+ long jvmUptime = ManagementFactory.getRuntimeMXBean().getUptime();
+ System.out.println("SWT Mini Desktop Manager available in " + jvmUptime + " ms.");
+ }
+
+ protected void createDock(ToolBar toolBar) {
+ // Terminal
+ addToolItem(toolBar, images.terminalIcon, "Terminal", () -> {
+ String url = System.getProperty("user.home");
+ AppContext appContext = createAppParent(images.terminalIcon);
+ new MiniTerminal(appContext.getAppParent(), url) {
+
+ @Override
+ protected void exitCalled() {
+ if (appContext.shell != null)
+ appContext.shell.dispose();
+ if (appContext.tabItem != null)
+ appContext.tabItem.dispose();
+ }
+ };
+ String title;
+ try {
+ title = System.getProperty("user.name") + "@" + InetAddress.getLocalHost().getHostName();
+ } catch (UnknownHostException e) {
+ title = System.getProperty("user.name") + "@localhost";
+ }
+ if (appContext.shell != null)
+ appContext.shell.setText(title);
+ if (appContext.tabItem != null) {
+ appContext.tabItem.setText(tabTitle(title));
+ appContext.tabItem.setToolTipText(title);
+ }
+ openApp(appContext);
+ });
+
+ // Web browser
+ addToolItem(toolBar, images.browserIcon, "Browser", () -> {
+ String url = "https://start.duckduckgo.com/";
+ AppContext appContext = createAppParent(images.browserIcon);
+ new MiniBrowser(appContext.getAppParent(), url, false, false) {
+ @Override
+ public void titleChanged(String title) {
+ if (appContext.shell != null)
+ appContext.shell.setText(title);
+ if (appContext.tabItem != null) {
+ appContext.tabItem.setText(tabTitle(title));
+ appContext.tabItem.setToolTipText(title);
+ }
+ }
+ };
+ openApp(appContext);
+ });
+
+ // File explorer
+ addToolItem(toolBar, images.explorerIcon, "Explorer", () -> {
+ String url = System.getProperty("user.home");
+ AppContext appContext = createAppParent(images.explorerIcon);
+ new MiniExplorer(appContext.getAppParent(), url) {
+
+ @Override
+ protected void pathChanged(Path path) {
+ if (appContext.shell != null)
+ appContext.shell.setText(path.toString());
+ if (appContext.tabItem != null) {
+ appContext.tabItem.setText(path.getFileName().toString());
+ appContext.tabItem.setToolTipText(path.toString());
+ }
+ }
+ };
+ openApp(appContext);
+ });
+
+ // Separator
+ new ToolItem(toolBar, SWT.SEPARATOR);
+
+ // Exit
+ addToolItem(toolBar, images.exitIcon, "Exit", () -> rootShell.dispose());
+
+ toolBar.pack();
+ }
+
+ protected String tabTitle(String title) {
+ return title.length() > maxTabTitleLength ? title.substring(0, maxTabTitleLength) : title;
+ }
+
+ protected void addToolItem(ToolBar toolBar, Image icon, String name, Runnable action) {
+ ToolItem searchI = new ToolItem(toolBar, SWT.PUSH);
+ searchI.setImage(icon);
+ searchI.setToolTipText(name);
+ searchI.addSelectionListener(new SelectionAdapter() {
+
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ action.run();
+ }
+
+ });
+ }
+
+ protected AppContext createAppParent(Image icon) {
+ if (isStacking()) {
+ Composite appParent = new Composite(tabFolder, SWT.CLOSE);
+ appParent.setLayout(noSpaceGridLayout(new GridLayout()));
+ CTabItem item = new CTabItem(tabFolder, SWT.CLOSE);
+ item.setImage(icon);
+ item.setControl(appParent);
+ return new AppContext(item);
+ } else {
+ Shell shell = isFullscreen() ? new Shell(rootShell, SWT.SHELL_TRIM)
+ : new Shell(rootShell.getDisplay(), SWT.SHELL_TRIM);
+ shell.setImage(icon);
+ return new AppContext(shell);
+ }
+ }
+
+ protected void openApp(AppContext appContext) {
+ if (appContext.shell != null) {
+ Shell shell = (Shell) appContext.shell;
+ shell.open();
+ shell.setSize(new Point(800, 480));
+ }
+ if (appContext.tabItem != null) {
+ tabFolder.setFocus();
+ tabFolder.setSelection(appContext.tabItem);
+ }
+ }
+
+ protected Control createBackground(Composite parent) {
+ Composite backgroundArea = new Composite(parent, SWT.NONE);
+ backgroundArea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
+ initBackground(backgroundArea);
+ return backgroundArea;
+ }
+
+ protected void initBackground(Composite backgroundArea) {
+ MiniHomePart homePart = new MiniHomePart() {
+
+ @Override
+ protected void fillAppsToolBar(ToolBar toolBar) {
+ createDock(toolBar);
+ }
+ };
+ homePart.createUiPart(backgroundArea, null);
+ }
+
+ public void run() {
+ while (!rootShell.isDisposed()) {
+ if (!display.readAndDispatch())
+ display.sleep();
+ }
+ }
+
+ public void dispose() {
+ if (!rootShell.isDisposed())
+ rootShell.dispose();
+ }
+
+ protected boolean isFullscreen() {
+ return fullscreen;
+ }
+
+ protected boolean isStacking() {
+ return stacking;
+ }
+
+ protected Image getIconForExt(String ext) {
+// Program program = Program.findProgram(ext);
+// if (program == null)
+ return display.getSystemImage(SWT.ICON_INFORMATION);
+
+// ImageData iconData = program.getImageData();
+// if (iconData == null) {
+// return display.getSystemImage(SWT.ICON_INFORMATION);
+// } else {
+// return new Image(display, iconData);
+// }
+
+ }
+
+ private static GridLayout noSpaceGridLayout(GridLayout layout) {
+ layout.horizontalSpacing = 0;
+ layout.verticalSpacing = 0;
+ layout.marginWidth = 0;
+ layout.marginHeight = 0;
+ return layout;
+ }
+
+ public static void main(String[] args) {
+ List<String> options = Arrays.asList(args);
+ if (options.contains("--help")) {
+ System.out.println("Usage: java " + MiniDesktopManager.class.getName().replace('.', '/') + " [OPTION]");
+ System.out.println("A minimalistic desktop manager based on Java and Eclipse SWT.");
+ System.out.println(" --fullscreen : take control of the whole screen (default is to run in a window)");
+ System.out.println(" --stacking : open apps as tabs (default is to create new windows)");
+ System.out.println(" --help : print this help and exit");
+ System.exit(1);
+ }
+ boolean fullscreen = options.contains("--fullscreen");
+ boolean stacking = options.contains("--stacking");
+
+ MiniDesktopManager desktopManager = new MiniDesktopManager(fullscreen, stacking);
+ desktopManager.init();
+ desktopManager.run();
+ desktopManager.dispose();
+ System.exit(0);
+ }
+
+ class AppContext {
+ private Shell shell;
+ private CTabItem tabItem;
+
+ public AppContext(Shell shell) {
+ this.shell = shell;
+ }
+
+ public AppContext(CTabItem tabItem) {
+ this.tabItem = tabItem;
+ }
+
+ Composite getAppParent() {
+ if (shell != null)
+ return shell;
+ if (tabItem != null)
+ return (Composite) tabItem.getControl();
+ throw new IllegalStateException();
+ }
+ }
+}
--- /dev/null
+package org.argeo.minidesktop;
+
+import java.util.Objects;
+
+import org.eclipse.swt.browser.Browser;
+
+/**
+ * This customiser is available to all components, in order to be extended with
+ * low-level specific capabilities, which depend on the context (typically
+ * differences between RAP and RCP). It does nothing by default.
+ */
+public class MiniDesktopSpecific {
+ protected void addBrowserTitleListener(MiniBrowser miniBrowser, Browser browser) {
+ }
+
+ protected void addBrowserOpenWindowListener(MiniBrowser miniBrowser, Browser browser) {
+ }
+
+ private static MiniDesktopSpecific SINGLETON = new MiniDesktopSpecific();
+
+ public static void setMiniDesktopSpecific(MiniDesktopSpecific miniDesktopSpecific) {
+ Objects.requireNonNull(miniDesktopSpecific);
+ SINGLETON = miniDesktopSpecific;
+ }
+
+ static MiniDesktopSpecific getMiniDesktopSpecific() {
+ return SINGLETON;
+ }
+}
--- /dev/null
+package org.argeo.minidesktop;
+
+import java.io.IOException;
+import java.nio.file.DirectoryStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+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.graphics.Point;
+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.TableItem;
+import org.eclipse.swt.widgets.Text;
+
+public class MiniExplorer {
+ private Path path;
+ private Text addressT;
+ private Table browser;
+
+ private boolean showHidden = false;
+
+ public MiniExplorer(Composite parent, String url) {
+ this(parent);
+ setUrl(url);
+ }
+
+ public MiniExplorer(Composite parent) {
+ parent.setLayout(noSpaceGridLayout(new GridLayout()));
+
+ Composite toolBar = new Composite(parent, SWT.NONE);
+ toolBar.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
+ toolBar.setLayout(new FillLayout());
+ addressT = new Text(toolBar, SWT.SINGLE);
+ addressT.addSelectionListener(new SelectionAdapter() {
+
+ @Override
+ public void widgetDefaultSelected(SelectionEvent e) {
+ setUrl(addressT.getText().trim());
+ }
+ });
+ browser = createTable(parent, this.path);
+
+ }
+
+ public void setPath(Path url) {
+ this.path = url;
+ if (addressT != null)
+ addressT.setText(url.toString());
+ if (browser != null) {
+ Composite parent = browser.getParent();
+ browser.dispose();
+ browser = createTable(parent, this.path);
+ parent.layout(true, true);
+ }
+ pathChanged(url);
+ }
+
+ protected void pathChanged(Path path) {
+
+ }
+
+ protected Table createTable(Composite parent, Path path) {
+ Table table = new Table(parent, SWT.BORDER);
+ table.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
+ table.addMouseListener(new MouseAdapter() {
+
+ @Override
+ public void mouseDoubleClick(MouseEvent e) {
+ Point pt = new Point(e.x, e.y);
+ TableItem item = table.getItem(pt);
+ Path path = (Path) item.getData();
+ if (Files.isDirectory(path)) {
+ setPath(path);
+ } else {
+ //Program.launch(path.toString());
+ }
+ }
+ });
+
+ if (path != null) {
+ if (path.getParent() != null) {
+ TableItem parentTI = new TableItem(table, SWT.NONE);
+ parentTI.setText("..");
+ parentTI.setData(path.getParent());
+ }
+
+ try {
+ // directories
+ DirectoryStream<Path> ds = Files.newDirectoryStream(path, p -> Files.isDirectory(p) && isShown(p));
+ ds.forEach(p -> {
+ TableItem ti = new TableItem(table, SWT.NONE);
+ ti.setText(p.getFileName().toString() + "/");
+ ti.setData(p);
+ });
+ // files
+ ds = Files.newDirectoryStream(path, p -> !Files.isDirectory(p) && isShown(p));
+ ds.forEach(p -> {
+ TableItem ti = new TableItem(table, SWT.NONE);
+ ti.setText(p.getFileName().toString());
+ ti.setData(p);
+ });
+ } catch (IOException e1) {
+ // TODO Auto-generated catch block
+ e1.printStackTrace();
+ }
+ }
+ return table;
+ }
+
+ protected boolean isShown(Path path) {
+ if (showHidden)
+ return true;
+ try {
+ return !Files.isHidden(path);
+ } catch (IOException e) {
+ throw new IllegalArgumentException("Cannot check " + path, e);
+ }
+ }
+
+ public void setUrl(String url) {
+ setPath(Paths.get(url));
+ }
+
+ private static GridLayout noSpaceGridLayout(GridLayout layout) {
+ layout.horizontalSpacing = 0;
+ layout.verticalSpacing = 0;
+ layout.marginWidth = 0;
+ layout.marginHeight = 0;
+ return layout;
+ }
+
+ public static void main(String[] args) {
+ Display display = Display.getCurrent() == null ? new Display() : Display.getCurrent();
+ Shell shell = new Shell(display, SWT.SHELL_TRIM);
+
+ String url = args.length > 0 ? args[0] : System.getProperty("user.home");
+ new MiniExplorer(shell, url) {
+
+ @Override
+ protected void pathChanged(Path path) {
+ shell.setText(path.toString());
+ }
+
+ };
+
+ shell.open();
+ shell.setSize(new Point(800, 480));
+ while (!shell.isDisposed()) {
+ if (!display.readAndDispatch())
+ display.sleep();
+ }
+ }
+
+}
--- /dev/null
+package org.argeo.minidesktop;
+
+import java.net.InetAddress;
+import java.net.InterfaceAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.net.UnknownHostException;
+import java.util.Enumeration;
+
+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.Group;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.ProgressBar;
+import org.eclipse.swt.widgets.ToolBar;
+
+/** A start page displaying network information and resources. */
+public class MiniHomePart {
+
+ public Control createUiPart(Composite parent, Object context) {
+ parent.setLayout(new GridLayout(2, false));
+ Display display = parent.getDisplay();
+
+ // Apps
+ Group appsGroup = new Group(parent, SWT.NONE);
+ appsGroup.setText("Apps");
+ appsGroup.setLayoutData(new GridData(SWT.FILL, SWT.TOP, false, false, 2, 1));
+ ToolBar appsToolBar = new ToolBar(appsGroup, SWT.HORIZONTAL | SWT.FLAT);
+ fillAppsToolBar(appsToolBar);
+
+ // Host
+ Group hostGroup = new Group(parent, SWT.NONE);
+ hostGroup.setLayoutData(new GridData(SWT.FILL, SWT.TOP, false, false));
+ hostGroup.setText("Host");
+ hostGroup.setLayout(new GridLayout(2, false));
+ label(hostGroup, "Hostname: ");
+ try {
+ InetAddress defaultAddr = InetAddress.getLocalHost();
+ String hostname = defaultAddr.getHostName();
+ label(hostGroup, hostname);
+ label(hostGroup, "Address: ");
+ label(hostGroup, defaultAddr.getHostAddress());
+ } catch (UnknownHostException e) {
+ label(hostGroup, e.getMessage());
+ }
+
+ Enumeration<NetworkInterface> netInterfaces = null;
+ try {
+ netInterfaces = NetworkInterface.getNetworkInterfaces();
+ } catch (SocketException e) {
+ label(hostGroup, "Interfaces: ");
+ label(hostGroup, e.getMessage());
+ }
+ if (netInterfaces != null)
+ while (netInterfaces.hasMoreElements()) {
+ NetworkInterface netInterface = netInterfaces.nextElement();
+ byte[] hardwareAddress = null;
+ try {
+ hardwareAddress = netInterface.getHardwareAddress();
+ if (hardwareAddress != null) {
+ label(hostGroup, convertHardwareAddress(hardwareAddress));
+ label(hostGroup, netInterface.getName());
+ for (InterfaceAddress addr : netInterface.getInterfaceAddresses()) {
+ label(hostGroup, cleanHostAddress(addr.getAddress().getHostAddress()));
+ label(hostGroup, Short.toString(addr.getNetworkPrefixLength()));
+ }
+ }
+ } catch (SocketException e) {
+ label(hostGroup, e.getMessage());
+ }
+ }
+
+ // Resources
+ Group resGroup = new Group(parent, SWT.NONE);
+ resGroup.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false));
+ resGroup.setText("Resources");
+ resGroup.setLayout(new GridLayout(3, false));
+
+ Runtime runtime = Runtime.getRuntime();
+
+ String maxMemoryStr = Long.toString(runtime.maxMemory() / (1024 * 1024)) + " MB";
+ label(resGroup, "Max Java memory: ");
+ label(resGroup, maxMemoryStr);
+ label(resGroup, "Java version: " + Runtime.version().toString());
+
+ label(resGroup, "Usable Java memory: ");
+ Label totalMemory = label(resGroup, maxMemoryStr);
+ ProgressBar totalOnMax = new ProgressBar(resGroup, SWT.SMOOTH);
+ totalOnMax.setMaximum(100);
+ label(resGroup, "Used Java memory: ");
+ Label usedMemory = label(resGroup, maxMemoryStr);
+ ProgressBar usedOnTotal = new ProgressBar(resGroup, SWT.SMOOTH);
+ totalOnMax.setMaximum(100);
+ new Thread() {
+ @Override
+ public void run() {
+ while (!totalOnMax.isDisposed()) {
+ display.asyncExec(() -> {
+ if (totalOnMax.isDisposed())
+ return;
+ totalOnMax.setSelection(javaTotalOnMaxPerct(runtime));
+ usedOnTotal.setSelection(javaUsedOnTotalPerct(runtime));
+ totalMemory.setText(Long.toString(runtime.totalMemory() / (1024 * 1024)) + " MB");
+ usedMemory.setText(
+ Long.toString((runtime.totalMemory() - runtime.freeMemory()) / (1024 * 1024)) + " MB");
+ });
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ return;
+ }
+ }
+ }
+ }.start();
+ return parent;
+ }
+
+ protected void fillAppsToolBar(ToolBar toolBar) {
+
+ }
+
+ protected int javaUsedOnTotalPerct(Runtime runtime) {
+ return Math.toIntExact((runtime.totalMemory() - runtime.freeMemory()) * 100 / runtime.totalMemory());
+ }
+
+ protected int javaTotalOnMaxPerct(Runtime runtime) {
+ return Math.toIntExact((runtime.totalMemory()) * 100 / runtime.maxMemory());
+ }
+
+ protected Label label(Composite parent, String text) {
+ Label label = new Label(parent, SWT.WRAP);
+ label.setText(text);
+ return label;
+ }
+
+ protected String cleanHostAddress(String hostAddress) {
+ // remove % from Ipv6 addresses
+ int index = hostAddress.indexOf('%');
+ if (index > 0)
+ return hostAddress.substring(0, index);
+ else
+ return hostAddress;
+ }
+
+ protected String convertHardwareAddress(byte[] hardwareAddress) {
+ if (hardwareAddress == null)
+ return "";
+ // from https://stackoverflow.com/a/2797498/7878010
+ StringBuilder sb = new StringBuilder(18);
+ for (byte b : hardwareAddress) {
+ if (sb.length() > 0)
+ sb.append(':');
+ sb.append(String.format("%02x", b).toUpperCase());
+ }
+ return sb.toString();
+ }
+}
--- /dev/null
+package org.argeo.minidesktop;
+
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.PaintEvent;
+import org.eclipse.swt.events.PaintListener;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.ImageData;
+import org.eclipse.swt.graphics.ImageLoader;
+import org.eclipse.swt.graphics.Point;
+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.Canvas;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.FileDialog;
+import org.eclipse.swt.widgets.Shell;
+
+public class MiniImageViewer implements PaintListener {
+ private URL url;
+ private Canvas area;
+
+ private Image image;
+
+ public MiniImageViewer(Composite parent, int style) {
+ parent.setLayout(new GridLayout());
+
+ Composite toolBar = new Composite(parent, SWT.NONE);
+ toolBar.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
+ toolBar.setLayout(new RowLayout());
+ Button load = new Button(toolBar, SWT.FLAT);
+ load.setText("\u2191");// up arrow
+ load.addSelectionListener(new SelectionAdapter() {
+
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ FileDialog fileDialog = new FileDialog(area.getShell());
+ String path = fileDialog.open();
+ if (path != null) {
+ setUrl(path);
+ }
+ }
+
+ });
+
+ area = new Canvas(parent, SWT.NONE);
+ area.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
+ area.addPaintListener(this);
+ }
+
+ protected void load(URL url) {
+ try {
+ ImageLoader imageLoader = new ImageLoader();
+ ImageData[] data = imageLoader.load(url.openStream());
+ image = new Image(area.getDisplay(), data[0]);
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ public void paintControl(PaintEvent e) {
+ e.gc.drawImage(image, 0, 0);
+
+ }
+
+ protected Path url2path(URL url) {
+ try {
+ Path path = Paths.get(url.toURI());
+ return path;
+ } catch (URISyntaxException e) {
+ throw new IllegalStateException("Cannot convert " + url + " to uri", e);
+ }
+ }
+
+ public void setUrl(URL url) {
+ this.url = url;
+ if (area != null)
+ load(this.url);
+ }
+
+ public void setUrl(String url) {
+ try {
+ setUrl(new URL(url));
+ } catch (MalformedURLException e) {
+ // try with http
+ try {
+ setUrl(new URL("file://" + url));
+ return;
+ } catch (MalformedURLException e1) {
+ // nevermind...
+ }
+ throw new IllegalArgumentException("Cannot interpret URL " + url, e);
+ }
+ }
+
+ public static void main(String[] args) {
+ Display display = Display.getCurrent() == null ? new Display() : Display.getCurrent();
+ Shell shell = new Shell(display, SWT.SHELL_TRIM);
+
+ MiniImageViewer miniBrowser = new MiniImageViewer(shell, SWT.NONE);
+ String url = args.length > 0 ? args[0] : "";
+ if (!url.trim().equals("")) {
+ miniBrowser.setUrl(url);
+ shell.setText(url);
+ } else {
+ shell.setText("*");
+ }
+
+ shell.open();
+ shell.setSize(new Point(800, 480));
+ while (!shell.isDisposed()) {
+ if (!display.readAndDispatch())
+ display.sleep();
+ }
+ }
+
+}
--- /dev/null
+package org.argeo.minidesktop;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.StringTokenizer;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.KeyEvent;
+import org.eclipse.swt.events.KeyListener;
+import org.eclipse.swt.events.PaintEvent;
+import org.eclipse.swt.events.PaintListener;
+import org.eclipse.swt.graphics.Font;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Canvas;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Shell;
+
+public class MiniTerminal implements KeyListener, PaintListener {
+
+ private Canvas area;
+// private Caret caret;
+
+ private StringBuffer buf = new StringBuffer("");
+ private StringBuffer userInput = new StringBuffer("");
+ private List<String> history = new ArrayList<>();
+
+ private Point charExtent = null;
+ private int charsPerLine = 0;
+ private String[] lines = new String[0];
+ private List<String> logicalLines = new ArrayList<>();
+
+ private Font mono;
+ private Charset charset;
+
+ private Path currentDir;
+ private Path homeDir;
+ private String host = "localhost";
+ private String username;
+
+ // Sub process
+ private Process process;
+ private boolean running = false;
+ private OutputStream stdIn = null;
+
+ private Thread readOut;
+
+ public MiniTerminal(Composite parent, String url) {
+ this(parent);
+ setPath(url);
+ }
+
+ public MiniTerminal(Composite parent) {
+ charset = StandardCharsets.UTF_8;
+
+ Display display = parent.getDisplay();
+ // Linux-specific
+ mono = new Font(display, "Monospace", 10, SWT.NONE);
+
+ parent.setLayout(new GridLayout());
+ area = new Canvas(parent, SWT.NONE);
+ area.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
+// caret = new Caret(area, SWT.NONE);
+// area.setCaret(caret);
+
+ area.addKeyListener(this);
+ area.addPaintListener(this);
+
+ username = System.getProperty("user.name");
+ try {
+ host = InetAddress.getLocalHost().getHostName();
+ if (host.indexOf('.') > 0)
+ host = host.substring(0, host.indexOf('.'));
+ } catch (UnknownHostException e) {
+ host = "localhost";
+ }
+ homeDir = Paths.get(System.getProperty("user.home"));
+ currentDir = homeDir;
+
+ buf = new StringBuffer(prompt());
+ }
+
+ @Override
+ public void keyPressed(KeyEvent e) {
+ }
+
+ @Override
+ public void keyReleased(KeyEvent e) {
+// if (e.keyLocation != 0)
+// return;// weird characters
+
+ // System.out.println(e.character);
+ if (e.keyCode == 0xd) {// return
+ markLogicalLine();
+ if (!running)
+ processUserInput();
+ // buf.append(prompt());
+ } else if (e.keyCode == 0x8) {// delete
+ if (userInput.length() == 0)
+ return;
+ userInput.setLength(userInput.length() - 1);
+ if (!running && buf.length() > 0)
+ buf.setLength(buf.length() - 1);
+ } else if (e.stateMask == 0x40000 && e.keyCode == 0x63) {// Ctrl+C
+ if (process != null)
+ process.destroy();
+ } else if (e.stateMask == 0x40000 && e.keyCode == 0xdf) {// Ctrl+\
+ if (process != null) {
+ process.destroyForcibly();
+ }
+ } else {
+ // if (!running)
+ buf.append(e.character);
+ userInput.append(e.character);
+ }
+
+ if (area.isDisposed())
+ return;
+ area.redraw();
+ // System.out.println("Append " + e);
+
+ if (running) {
+ if (stdIn != null) {
+ try {
+ stdIn.write(Character.toString(e.character).getBytes(charset));
+ } catch (IOException e1) {
+ // TODO Auto-generated catch block
+ e1.printStackTrace();
+ }
+ }
+ }
+ }
+
+ protected String prompt() {
+ String fileName = currentDir.equals(homeDir) ? "~" : currentDir.getFileName().toString();
+ String end = username.equals("root") ? "]# " : "]$ ";
+ return "[" + username + "@" + host + " " + fileName + end;
+ }
+
+ private void displayPrompt() {
+ buf.append(prompt() + userInput);
+ }
+
+ protected void markLogicalLine() {
+ String str = buf.toString().trim();
+ logicalLines.add(str);
+ buf = new StringBuffer("");
+ }
+
+ private void processUserInput() {
+ String cmd = userInput.toString();
+ userInput = new StringBuffer("");
+ processUserInput(cmd);
+ history.add(cmd);
+ }
+
+ protected void processUserInput(String input) {
+ try {
+ StringTokenizer st = new StringTokenizer(input);
+ List<String> args = new ArrayList<>();
+ while (st.hasMoreTokens())
+ args.add(st.nextToken());
+ if (args.size() == 0) {
+ displayPrompt();
+ return;
+ }
+
+ // change directory
+ if (args.get(0).equals("cd")) {
+ if (args.size() == 1) {
+ setPath(homeDir);
+ } else {
+ Path newPath = currentDir.resolve(args.get(1));
+ if (!Files.exists(newPath) || !Files.isDirectory(newPath)) {
+ println(newPath + ": No such file or directory");
+ return;
+ }
+ setPath(newPath);
+ }
+ displayPrompt();
+ return;
+ }
+ // show current directory
+ else if (args.get(0).equals("pwd")) {
+ println(currentDir);
+ displayPrompt();
+ return;
+ }
+ // exit
+ else if (args.get(0).equals("exit")) {
+ println("logout");
+ exitCalled();
+ return;
+ }
+
+ ProcessBuilder pb = new ProcessBuilder(args);
+ pb.redirectErrorStream(true);
+ pb.directory(currentDir.toFile());
+// Process process = Runtime.getRuntime().exec(input, null, currentPath.toFile());
+ process = pb.start();
+
+ stdIn = process.getOutputStream();
+ readOut = new Thread("MiniTerminal read out") {
+ @Override
+ public void run() {
+ running = true;
+ try (BufferedReader in = new BufferedReader(
+ new InputStreamReader(process.getInputStream(), charset))) {
+ String line = null;
+ while ((line = in.readLine()) != null) {
+ println(line);
+ }
+ } catch (IOException e) {
+ println(e.getMessage());
+ }
+ stdIn = null;
+ displayPrompt();
+ running = false;
+ readOut = null;
+ process = null;
+ }
+ };
+ readOut.start();
+ } catch (IOException e) {
+ println(e.getMessage());
+ displayPrompt();
+ }
+ }
+
+ protected int linesForLogicalLine(char[] line) {
+ return line.length / charsPerLine + 1;
+ }
+
+ protected void println(Object line) {
+ buf.append(line);
+ markLogicalLine();
+ }
+
+ protected void refreshLines(int charPerLine, int nbrOfLines) {
+ if (lines.length != nbrOfLines) {
+ lines = new String[nbrOfLines];
+ Arrays.fill(lines, null);
+ }
+ if (this.charsPerLine != charPerLine)
+ this.charsPerLine = charPerLine;
+
+ int currentLine = nbrOfLines - 1;
+ // current line
+ if (buf.length() > 0) {
+ lines[currentLine] = buf.toString();
+ } else {
+ lines[currentLine] = "";
+ }
+ currentLine--;
+
+ logicalLines: for (int i = logicalLines.size() - 1; i >= 0; i--) {
+ char[] logicalLine = logicalLines.get(i).toCharArray();
+ int linesNeeded = linesForLogicalLine(logicalLine);
+ for (int j = linesNeeded - 1; j >= 0; j--) {
+ int from = j * charPerLine;
+ int to = j == linesNeeded - 1 ? from + charPerLine : Math.min(from + charPerLine, logicalLine.length);
+ lines[currentLine] = new String(Arrays.copyOfRange(logicalLine, from, to));
+// System.out.println("Set line " + currentLine + " to : " + lines[currentLine]);
+ currentLine--;
+ if (currentLine < 0)
+ break logicalLines;
+ }
+ }
+ }
+
+ @Override
+ public void paintControl(PaintEvent e) {
+ GC gc = e.gc;
+ gc.setFont(mono);
+ if (charExtent == null)
+ charExtent = gc.textExtent("a");
+
+ Point areaSize = area.getSize();
+ int charPerLine = areaSize.x / charExtent.x;
+ int nbrOfLines = areaSize.y / charExtent.y;
+ refreshLines(charPerLine, nbrOfLines);
+
+ for (int i = 0; i < lines.length; i++) {
+ String line = lines[i];
+ if (line != null)
+ gc.drawString(line, 0, i * charExtent.y);
+ }
+// String toDraw = buf.toString();
+// gc.drawString(toDraw, 0, 0);
+// area.setCaret(caret);
+ }
+
+ protected void exitCalled() {
+
+ }
+
+ public void setPath(String path) {
+ this.currentDir = Paths.get(path);
+ }
+
+ public void setPath(Path path) {
+ this.currentDir = path;
+ }
+
+ public static void main(String[] args) {
+ Display display = Display.getCurrent() == null ? new Display() : Display.getCurrent();
+ Shell shell = new Shell(display, SWT.SHELL_TRIM);
+
+ String url = args.length > 0 ? args[0] : System.getProperty("user.home");
+ new MiniTerminal(shell, url) {
+
+ @Override
+ protected void exitCalled() {
+ shell.dispose();
+ System.exit(0);
+ }
+ };
+
+ shell.open();
+ shell.setSize(new Point(800, 480));
+ while (!shell.isDisposed()) {
+ if (!display.readAndDispatch())
+ display.sleep();
+ }
+ }
+
+}
--- /dev/null
+package org.argeo.minidesktop;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.net.MalformedURLException;
+import java.net.URISyntaxException;
+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 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.layout.RowLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.FileDialog;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Text;
+
+public class MiniTextEditor {
+ private URL url;
+ private Text text;
+
+ public MiniTextEditor(Composite parent, int style) {
+ parent.setLayout(new GridLayout());
+
+ Composite toolBar = new Composite(parent, SWT.NONE);
+ toolBar.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
+ toolBar.setLayout(new RowLayout());
+ Button load = new Button(toolBar, SWT.FLAT);
+ load.setText("\u2191");// up arrow
+ load.addSelectionListener(new SelectionAdapter() {
+
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ FileDialog fileDialog = new FileDialog(text.getShell());
+ String path = fileDialog.open();
+ if (path != null) {
+ setUrl(path);
+ }
+ }
+
+ });
+
+ Button save = new Button(toolBar, SWT.FLAT);
+ save.setText("\u2193");// down arrow
+ // save.setText("\u1F609");// emoji
+ save.addSelectionListener(new SelectionAdapter() {
+
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ save(url);
+ }
+
+ });
+
+ text = new Text(parent, SWT.WRAP | SWT.MULTI | SWT.BORDER | SWT.V_SCROLL);
+ text.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
+ }
+
+ protected void load(URL url) {
+ text.setText("");
+ // TODO deal with encoding and binary data
+ try (BufferedReader in = new BufferedReader(new InputStreamReader(url.openStream(), StandardCharsets.UTF_8))) {
+ String line = null;
+ while ((line = in.readLine()) != null) {
+ text.append(line + "\n");
+ }
+ text.setEditable(true);
+ } catch (IOException e) {
+ if (e instanceof FileNotFoundException) {
+ Path path = url2path(url);
+ try {
+ Files.createFile(path);
+ load(url);
+ return;
+ } catch (IOException e1) {
+ e = e1;
+ }
+ }
+ text.setText(e.getMessage());
+ text.setEditable(false);
+ e.printStackTrace();
+ // throw new IllegalStateException("Cannot load " + url, e);
+ }
+ }
+
+ protected Path url2path(URL url) {
+ try {
+ Path path = Paths.get(url.toURI());
+ return path;
+ } catch (URISyntaxException e) {
+ throw new IllegalStateException("Cannot convert " + url + " to uri", e);
+ }
+ }
+
+ protected void save(URL url) {
+ if (!url.getProtocol().equals("file"))
+ throw new IllegalArgumentException(url.getProtocol() + " protocol is not supported for write");
+ Path path = url2path(url);
+ try (BufferedWriter out = new BufferedWriter(new OutputStreamWriter(Files.newOutputStream(path)))) {
+ out.write(text.getText());
+ } catch (IOException e) {
+ throw new IllegalStateException("Cannot save " + url + " to " + path, e);
+ }
+ }
+
+ public void setUrl(URL url) {
+ this.url = url;
+ if (text != null)
+ load(url);
+ }
+
+ public void setUrl(String url) {
+ try {
+ setUrl(new URL(url));
+ } catch (MalformedURLException e) {
+ // try with http
+ try {
+ setUrl(new URL("file://" + url));
+ return;
+ } catch (MalformedURLException e1) {
+ // nevermind...
+ }
+ throw new IllegalArgumentException("Cannot interpret URL " + url, e);
+ }
+ }
+
+ public static void main(String[] args) {
+ Display display = Display.getCurrent() == null ? new Display() : Display.getCurrent();
+ Shell shell = new Shell(display, SWT.SHELL_TRIM);
+
+ MiniTextEditor miniBrowser = new MiniTextEditor(shell, SWT.NONE);
+ String url = args.length > 0 ? args[0] : "";
+ if (!url.trim().equals("")) {
+ miniBrowser.setUrl(url);
+ shell.setText(url);
+ } else {
+ shell.setText("*");
+ }
+
+ shell.open();
+ shell.setSize(new Point(800, 480));
+ while (!shell.isDisposed()) {
+ if (!display.readAndDispatch())
+ display.sleep();
+ }
+ }
+
+}
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-17"/>
+ <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+ <classpathentry kind="src" path="src"/>
+ <classpathentry kind="output" path="bin"/>
+</classpath>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>org.argeo.cms.e4.rap</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
+Import-Package: \
+org.argeo.api.acr, \
+org.eclipse.swt,\
+org.eclipse.swt.graphics,\
+org.eclipse.e4.ui.workbench,\
+org.eclipse.rap.rwt.client,\
+org.eclipse.nebula.widgets.richtext;resolution:=optional,\
+org.eclipse.*;resolution:=optional,\
+*
--- /dev/null
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+ .,\
+ OSGI-INF/
--- /dev/null
+package org.argeo.cms.e4.rap;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.argeo.cms.swt.dialogs.CmsFeedback;
+import org.eclipse.rap.e4.E4ApplicationConfig;
+import org.eclipse.rap.rwt.application.Application;
+import org.eclipse.rap.rwt.application.Application.OperationMode;
+import org.eclipse.rap.rwt.application.ApplicationConfiguration;
+import org.eclipse.rap.rwt.application.ExceptionHandler;
+import org.eclipse.rap.rwt.client.WebClient;
+import org.osgi.framework.BundleContext;
+
+/** Base class for CMS RAP applications. */
+public abstract class AbstractRapE4App implements ApplicationConfiguration {
+ private String e4Xmi;
+ private String path;
+ private String lifeCycleUri = "bundleclass://org.argeo.cms.e4.rap/org.argeo.cms.e4.rap.CmsLoginLifecycle";
+
+ private Map<String, String> baseProperties = new HashMap<String, String>();
+
+ private BundleContext bundleContext;
+ public final static String CONTEXT_NAME_PROPERTY = "contextName";
+ private String contextName;
+
+ /**
+ * To be overridden in order to add multiple entry points, directly or using
+ * {@link #addE4EntryPoint(Application, String, String, Map)}.
+ */
+ protected void addEntryPoints(Application application) {
+ }
+
+ public void configure(Application application) {
+ application.setExceptionHandler(new ExceptionHandler() {
+
+ @Override
+ public void handleException(Throwable throwable) {
+ CmsFeedback.error("Unexpected RWT exception", throwable);
+ }
+ });
+
+ if (e4Xmi != null) {// backward compatibility
+ addE4EntryPoint(application, path, e4Xmi, getBaseProperties());
+ } else {
+ addEntryPoints(application);
+ }
+ }
+
+ protected Map<String, String> getBaseProperties() {
+ return baseProperties;
+ }
+
+// protected void addEntryPoint(Application application, E4ApplicationConfig config, Map<String, String> properties) {
+// CmsE4EntryPointFactory entryPointFactory = new CmsE4EntryPointFactory(config);
+// application.addEntryPoint(path, entryPointFactory, properties);
+// application.setOperationMode(OperationMode.SWT_COMPATIBILITY);
+// }
+
+ protected void addE4EntryPoint(Application application, String path, String e4Xmi, Map<String, String> properties) {
+ E4ApplicationConfig config = createE4ApplicationConfig(e4Xmi);
+ CmsE4EntryPointFactory entryPointFactory = new CmsE4EntryPointFactory(config);
+ application.addEntryPoint(path, entryPointFactory, properties);
+ application.setOperationMode(OperationMode.SWT_COMPATIBILITY);
+ }
+
+ /**
+ * To be overridden for further configuration.
+ *
+ * @see E4ApplicationConfig
+ */
+ protected E4ApplicationConfig createE4ApplicationConfig(String e4Xmi) {
+ return new E4ApplicationConfig(e4Xmi, lifeCycleUri, null, null, false, true, true);
+ }
+
+ @Deprecated
+ public void setPageTitle(String pageTitle) {
+ if (pageTitle != null)
+ baseProperties.put(WebClient.PAGE_TITLE, pageTitle);
+ }
+
+ /** Returns a new map used to customise and entry point. */
+ public Map<String, String> customise(String pageTitle) {
+ Map<String, String> custom = new HashMap<>(getBaseProperties());
+ if (pageTitle != null)
+ custom.put(WebClient.PAGE_TITLE, pageTitle);
+ return custom;
+ }
+
+ @Deprecated
+ public void setE4Xmi(String e4Xmi) {
+ this.e4Xmi = e4Xmi;
+ }
+
+ @Deprecated
+ public void setPath(String path) {
+ this.path = path;
+ }
+
+ public void setLifeCycleUri(String lifeCycleUri) {
+ this.lifeCycleUri = lifeCycleUri;
+ }
+
+ protected BundleContext getBundleContext() {
+ return bundleContext;
+ }
+
+ protected void setBundleContext(BundleContext bundleContext) {
+ this.bundleContext = bundleContext;
+ }
+
+ public String getContextName() {
+ return contextName;
+ }
+
+ public void setContextName(String contextName) {
+ this.contextName = contextName;
+ }
+
+ public void init(BundleContext bundleContext, Map<String, Object> properties) {
+ this.bundleContext = bundleContext;
+ for (String key : properties.keySet()) {
+ Object value = properties.get(key);
+ if (value != null)
+ baseProperties.put(key, value.toString());
+ }
+
+ if (properties.containsKey(CONTEXT_NAME_PROPERTY)) {
+ assert properties.get(CONTEXT_NAME_PROPERTY) != null;
+ contextName = properties.get(CONTEXT_NAME_PROPERTY).toString();
+ } else {
+ contextName = "<unknown context>";
+ }
+ }
+
+ public void destroy(Map<String, Object> properties) {
+
+ }
+}
--- /dev/null
+package org.argeo.cms.e4.rap;
+
+import java.security.PrivilegedAction;
+
+import javax.security.auth.Subject;
+
+import org.eclipse.rap.e4.E4ApplicationConfig;
+import org.eclipse.rap.e4.E4EntryPointFactory;
+import org.eclipse.rap.rwt.RWT;
+import org.eclipse.rap.rwt.application.EntryPoint;
+import org.eclipse.rap.rwt.client.service.JavaScriptExecutor;
+
+public class CmsE4EntryPointFactory extends E4EntryPointFactory {
+ public final static String DEFAULT_LIFECYCLE_URI = "bundleclass://org.argeo.cms.e4.rap/org.argeo.cms.e4.rap.CmsLoginLifecycle";
+
+ public CmsE4EntryPointFactory(E4ApplicationConfig config) {
+ super(config);
+ }
+
+ public CmsE4EntryPointFactory(String e4Xmi, String lifeCycleUri) {
+ super(defaultConfig(e4Xmi, lifeCycleUri));
+ }
+
+ public CmsE4EntryPointFactory(String e4Xmi) {
+ this(e4Xmi, DEFAULT_LIFECYCLE_URI);
+ }
+
+ public static E4ApplicationConfig defaultConfig(String e4Xmi, String lifeCycleUri) {
+ E4ApplicationConfig config = new E4ApplicationConfig(e4Xmi, lifeCycleUri, null, null, false, true, true);
+ return config;
+ }
+
+ @Override
+ public EntryPoint create() {
+ EntryPoint ep = createEntryPoint();
+ EntryPoint authEp = new EntryPoint() {
+
+ @Override
+ public int createUI() {
+ Subject subject = new Subject();
+ return Subject.doAs(subject, new PrivilegedAction<Integer>() {
+
+ @Override
+ public Integer run() {
+ // SPNEGO
+ // HttpServletRequest request = RWT.getRequest();
+ // String authorization = request.getHeader(HEADER_AUTHORIZATION);
+ // if (authorization == null || !authorization.startsWith("Negotiate")) {
+ // HttpServletResponse response = RWT.getResponse();
+ // response.setStatus(401);
+ // response.setHeader(HEADER_WWW_AUTHENTICATE, "Negotiate");
+ // 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");
+ // }
+
+ JavaScriptExecutor jsExecutor = RWT.getClient().getService(JavaScriptExecutor.class);
+ Integer exitCode = ep.createUI();
+ jsExecutor.execute("location.reload()");
+ return exitCode;
+ }
+
+ });
+ }
+ };
+ return authEp;
+ }
+
+ protected EntryPoint createEntryPoint() {
+ return super.create();
+ }
+}
--- /dev/null
+package org.argeo.cms.e4.rap;
+
+import java.security.AccessController;
+import java.util.UUID;
+import java.util.concurrent.Callable;
+
+import javax.inject.Inject;
+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.CmsContext;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.api.cms.ux.CmsImageManager;
+import org.argeo.api.cms.ux.CmsView;
+import org.argeo.api.cms.ux.UxContext;
+import org.argeo.cms.CurrentUser;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.swt.SimpleSwtUxContext;
+import org.argeo.cms.swt.acr.AcrSwtImageManager;
+import org.argeo.cms.swt.auth.CmsLoginShell;
+import org.argeo.cms.swt.dialogs.CmsFeedback;
+import org.eclipse.e4.core.services.events.IEventBroker;
+import org.eclipse.e4.ui.workbench.UIEvents;
+import org.eclipse.e4.ui.workbench.lifecycle.PostContextCreate;
+import org.eclipse.e4.ui.workbench.lifecycle.PreSave;
+import org.eclipse.rap.rwt.RWT;
+import org.eclipse.rap.rwt.client.service.BrowserNavigation;
+import org.eclipse.rap.rwt.client.service.BrowserNavigationEvent;
+import org.eclipse.rap.rwt.client.service.BrowserNavigationListener;
+import org.eclipse.swt.widgets.Display;
+import org.osgi.service.event.Event;
+import org.osgi.service.event.EventHandler;
+
+@SuppressWarnings("restriction")
+public class CmsLoginLifecycle implements CmsView {
+ private final static CmsLog log = CmsLog.getLog(CmsLoginLifecycle.class);
+
+ @Inject
+ private CmsContext cmsContext;
+
+ private UxContext uxContext;
+ private CmsImageManager imageManager;
+
+ private LoginContext loginContext;
+ private BrowserNavigation browserNavigation;
+
+ private String state = null;
+ private String uid;
+
+ @PostContextCreate
+ boolean login(final IEventBroker eventBroker) {
+ uid = UUID.randomUUID().toString();
+ browserNavigation = RWT.getClient().getService(BrowserNavigation.class);
+ if (browserNavigation != null)
+ browserNavigation.addBrowserNavigationListener(new BrowserNavigationListener() {
+ private static final long serialVersionUID = -3668136623771902865L;
+
+ @Override
+ public void navigated(BrowserNavigationEvent event) {
+ state = event.getState();
+ if (uxContext != null)// is logged in
+ stateChanged();
+ }
+ });
+
+ Subject subject = Subject.getSubject(AccessController.getContext());
+ Display display = Display.getCurrent();
+// UiContext.setData(CmsView.KEY, this);
+ CmsLoginShell loginShell = new CmsLoginShell(this, cmsContext);
+ CmsSwtUtils.registerCmsView(loginShell.getShell(), this);
+ loginShell.setSubject(subject);
+ try {
+ // try pre-auth
+ loginContext = new LoginContext(CmsAuth.LOGIN_CONTEXT_USER, subject, loginShell);
+ loginContext.login();
+ } catch (LoginException e) {
+ loginShell.createUi();
+ loginShell.open();
+
+ while (!loginShell.getShell().isDisposed()) {
+ if (!display.readAndDispatch())
+ display.sleep();
+ }
+ }
+ if (CurrentUser.getUsername(getSubject()) == null)
+ return false;
+ uxContext = new SimpleSwtUxContext();
+ imageManager = (CmsImageManager) new AcrSwtImageManager();
+
+ eventBroker.subscribe(UIEvents.UILifeCycle.APP_STARTUP_COMPLETE, new EventHandler() {
+ @Override
+ public void handleEvent(Event event) {
+ startupComplete();
+ eventBroker.unsubscribe(this);
+ }
+ });
+
+ // lcs.changeApplicationLocale(Locale.FRENCH);
+ return true;
+ }
+
+ @PreSave
+ void destroy() {
+ // logout();
+ }
+
+ @Override
+ public UxContext getUxContext() {
+ return uxContext;
+ }
+
+ @Override
+ public void navigateTo(String state) {
+ browserNavigation.pushState(state, state);
+ }
+
+ @Override
+ public void authChange(LoginContext loginContext) {
+ if (loginContext == null)
+ throw new IllegalArgumentException("Login context cannot be null");
+ // logout previous login context
+ // if (this.loginContext != null)
+ // try {
+ // this.loginContext.logout();
+ // } catch (LoginException e1) {
+ // System.err.println("Could not log out: " + e1);
+ // }
+ this.loginContext = loginContext;
+ }
+
+ @Override
+ public void logout() {
+ if (loginContext == null)
+ throw new IllegalStateException("Login context should not be null");
+ try {
+ CurrentUser.logoutCmsSession(loginContext.getSubject());
+ loginContext.logout();
+ } catch (LoginException e) {
+ throw new IllegalStateException("Cannot log out", e);
+ }
+ }
+
+ @Override
+ public void exception(Throwable e) {
+ String msg = "Unexpected exception in Eclipse 4 RAP";
+ log.error(msg, e);
+ CmsFeedback.error(msg, e);
+ }
+
+ @Override
+ public CmsImageManager getImageManager() {
+ return imageManager;
+ }
+
+ protected Subject getSubject() {
+ return loginContext.getSubject();
+ }
+
+ @Override
+ public boolean isAnonymous() {
+ return CurrentUser.isAnonymous(getSubject());
+ }
+
+ @Override
+ public String getUid() {
+ return uid;
+ }
+
+ // CALLBACKS
+ protected void startupComplete() {
+ }
+
+ protected void stateChanged() {
+
+ }
+
+ // GETTERS
+ protected BrowserNavigation getBrowserNavigation() {
+ return browserNavigation;
+ }
+
+ protected String getState() {
+ return state;
+ }
+
+ @Override
+ public <T> T doAs(Callable<T> action) {
+ throw new UnsupportedOperationException();
+ }
+
+}
--- /dev/null
+package org.argeo.cms.e4.rap;
+
+import java.util.Enumeration;
+
+import org.argeo.api.cms.CmsLog;
+import org.eclipse.rap.rwt.application.Application;
+import org.osgi.framework.Bundle;
+
+/** Simple RAP app which loads all e4xmi files. */
+public class SimpleRapE4App extends AbstractRapE4App {
+ private final static CmsLog log = CmsLog.getLog(SimpleRapE4App.class);
+
+ private String baseE4xmi = "/e4xmi";
+
+ @Override
+ protected void addEntryPoints(Application application) {
+ Bundle bundle = getBundleContext().getBundle();
+ Enumeration<String> paths = bundle.getEntryPaths(baseE4xmi);
+ while (paths.hasMoreElements()) {
+ String p = paths.nextElement();
+ if (p.endsWith(".e4xmi")) {
+ String e4xmiPath = bundle.getSymbolicName() + '/' + p;
+ // FIXME deal with base name
+ String name=null;// = '/' + FilenameUtils.removeExtension(FilenameUtils.getName(p));
+ addE4EntryPoint(application, name, e4xmiPath, getBaseProperties());
+ if (log.isDebugEnabled())
+ log.debug("Registered " + e4xmiPath + " as " + getContextName() + name);
+ }
+ }
+ }
+
+}
--- /dev/null
+/** Eclipse 4 RAP specific extensions. */
+package org.argeo.cms.e4.rap;
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-17"/>
+ <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+ <classpathentry kind="src" path="src"/>
+ <classpathentry kind="output" path="bin"/>
+</classpath>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>org.argeo.cms.swt.rap</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="CMS Web App Factory">
+ <implementation class="org.argeo.cms.web.osgi.CmsWebAppFactory"/>
+ <reference bind="addCmsApp" cardinality="0..n" interface="org.argeo.api.cms.CmsApp" name="CmsApp" policy="dynamic" unbind="removeCmsApp"/>
+ <reference bind="setCmsEventBus" cardinality="1..1" interface="org.argeo.api.cms.CmsEventBus" name="CmsEventBus" policy="static"/>
+</scr:component>
--- /dev/null
+Import-Package:\
+org.argeo.api.acr,\
+org.eclipse.swt,\
+org.argeo.eclipse.ui,\
+org.eclipse.swt.graphics,\
+javax.servlet.*;version="[3,5)",\
+*
+
+Service-Component: OSGI-INF/cmsWebAppFactory.xml
+
--- /dev/null
+output.. = bin/
+bin.includes = META-INF/,\
+ .,\
+ OSGI-INF/cmsWebAppFactory.xml
+source.. = src/
+additional.bundles = org.argeo.ext.slf4j,\
+ org.slf4j.api
--- /dev/null
+package org.argeo.cms.web;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+
+import org.eclipse.rap.rwt.service.ResourceLoader;
+import org.osgi.framework.Bundle;
+
+/** {@link ResourceLoader} implementation wrapping an {@link Bundle}. */
+public class BundleResourceLoader implements ResourceLoader {
+ private final Bundle bundle;
+
+ public BundleResourceLoader(Bundle bundle) {
+ this.bundle = bundle;
+ }
+
+ @Override
+ public InputStream getResourceAsStream(String resourceName) throws IOException {
+ URL res = bundle.getEntry(resourceName);
+ if (res == null) {
+ res = bundle.getResource(resourceName);
+ if (res == null)
+ throw new IllegalArgumentException(
+ "Resource " + resourceName + " not found in bundle " + bundle.getSymbolicName());
+ }
+ return res.openStream();
+ }
+
+ public Bundle getBundle() {
+ return bundle;
+ }
+
+}
--- /dev/null
+package org.argeo.cms.web;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.argeo.api.cms.ux.CmsTheme;
+import org.eclipse.rap.rwt.service.ResourceLoader;
+
+/** A RAP {@link ResourceLoader} based on a {@link CmsTheme}. */
+public class CmsThemeResourceLoader implements ResourceLoader {
+ private final CmsTheme theme;
+
+ public CmsThemeResourceLoader(CmsTheme theme) {
+ super();
+ this.theme = theme;
+ }
+
+ @Override
+ public InputStream getResourceAsStream(String resourceName) throws IOException {
+ return theme.getResourceAsStream(resourceName);
+ }
+
+}
--- /dev/null
+package org.argeo.cms.web;
+
+import java.util.Dictionary;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import org.argeo.api.cms.CmsApp;
+import org.argeo.api.cms.CmsAppListener;
+import org.argeo.api.cms.CmsEventBus;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.api.cms.ux.CmsTheme;
+import org.argeo.api.cms.ux.CmsView;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.util.LangUtils;
+import org.eclipse.rap.rwt.RWT;
+import org.eclipse.rap.rwt.application.Application;
+import org.eclipse.rap.rwt.application.Application.OperationMode;
+import org.eclipse.rap.rwt.application.ApplicationConfiguration;
+import org.eclipse.rap.rwt.application.ExceptionHandler;
+import org.eclipse.rap.rwt.client.WebClient;
+import org.eclipse.swt.widgets.Display;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceRegistration;
+
+/** An RWT web app integrating with a {@link CmsApp}. */
+public class CmsWebApp implements ApplicationConfiguration, ExceptionHandler, CmsAppListener {
+ private final static CmsLog log = CmsLog.getLog(CmsWebApp.class);
+
+ private BundleContext bundleContext;
+ private CmsApp cmsApp;
+
+ private CmsEventBus cmsEventBus;
+
+ private ServiceRegistration<ApplicationConfiguration> rwtAppReg;
+
+ private final static String CONTEXT_NAME = "contextName";
+ private String contextName;
+
+ private final static String FAVICON_PNG = "favicon.png";
+
+ public void init(BundleContext bundleContext, Map<String, String> properties) {
+ this.bundleContext = bundleContext;
+ contextName = properties.get(CONTEXT_NAME);
+ if (cmsApp != null) {
+ if (cmsApp.allThemesAvailable())
+ publishWebApp();
+ }
+ }
+
+ public void destroy(BundleContext bundleContext, Map<String, String> properties) {
+ if (cmsApp != null) {
+ cmsApp.removeCmsAppListener(this);
+ cmsApp = null;
+ }
+ }
+
+ @Override
+ public void configure(Application application) {
+ // TODO make it configurable?
+ // SWT compatibility is required for:
+ // - Browser.execute()
+ // - blocking dialogs
+ application.setOperationMode(OperationMode.SWT_COMPATIBILITY);
+ for (String uiName : cmsApp.getUiNames()) {
+ CmsTheme theme = cmsApp.getTheme(uiName);
+ if (theme != null)
+ WebThemeUtils.apply(application, theme);
+ }
+
+ Map<String, String> properties = new HashMap<>();
+ addEntryPoints(application, properties);
+ application.setExceptionHandler(this);
+ }
+
+ @Override
+ public void handleException(Throwable throwable) {
+ Display display = Display.getCurrent();
+ if (display != null && !display.isDisposed()) {
+ CmsView cmsView = CmsSwtUtils.getCmsView(display.getActiveShell());
+ cmsView.exception(throwable);
+ } else {
+ log.error("Unexpected exception outside an UI thread", throwable);
+ }
+
+ }
+
+ protected void addEntryPoints(Application application, Map<String, String> commonProperties) {
+ for (String uiName : cmsApp.getUiNames()) {
+ Map<String, String> properties = new HashMap<>(commonProperties);
+ CmsTheme theme = cmsApp.getTheme(uiName);
+ if (theme != null) {
+ properties.put(WebClient.THEME_ID, theme.getThemeId());
+ properties.put(WebClient.HEAD_HTML, theme.getHtmlHeaders());
+ properties.put(WebClient.BODY_HTML, theme.getBodyHtml());
+ Set<String> imagePaths = theme.getImagesPaths();
+ if (imagePaths.contains(FAVICON_PNG)) {
+ properties.put(WebClient.FAVICON, FAVICON_PNG);
+ }
+ } else {
+ properties.put(WebClient.THEME_ID, RWT.DEFAULT_THEME_ID);
+ }
+ String entryPointName = !uiName.equals("") ? "/" + uiName : "/";
+ application.addEntryPoint(entryPointName, () -> {
+ CmsWebEntryPoint entryPoint = new CmsWebEntryPoint(this, uiName);
+ return entryPoint;
+ }, properties);
+ if (log.isDebugEnabled())
+ log.info("Added web entry point " + (contextName != null ? "/" + contextName : "") + entryPointName);
+ }
+// if (log.isDebugEnabled())
+// log.debug("Published CMS web app /" + (contextName != null ? contextName : ""));
+ }
+
+ public CmsApp getCmsApp() {
+ return cmsApp;
+ }
+
+ BundleContext getBundleContext() {
+ return bundleContext;
+ }
+
+ public void setCmsApp(CmsApp cmsApp) {
+ this.cmsApp = cmsApp;
+// this.cmsAppId = properties.get(Constants.SERVICE_PID);
+ this.cmsApp.addCmsAppListener(this);
+ }
+
+ public void unsetCmsApp(CmsApp cmsApp, Map<String, String> properties) {
+ String contextName = properties.get(CmsApp.CONTEXT_NAME_PROPERTY);
+ if (!contextName.equals(this.contextName))
+ return;
+ if (this.cmsApp != null) {
+ this.cmsApp.removeCmsAppListener(this);
+ }
+ if (rwtAppReg != null)
+ rwtAppReg.unregister();
+ this.cmsApp = null;
+ }
+
+ @Override
+ public void themingUpdated() {
+ if (cmsApp != null && cmsApp.allThemesAvailable())
+ publishWebApp();
+ }
+
+ protected void publishWebApp() {
+ Dictionary<String, Object> regProps = LangUtils.dict(CONTEXT_NAME, contextName);
+ if (rwtAppReg != null)
+ rwtAppReg.unregister();
+ if (bundleContext != null) {
+ rwtAppReg = bundleContext.registerService(ApplicationConfiguration.class, this, regProps);
+ if (log.isDebugEnabled())
+ log.debug("Publishing CMS web app /" + (contextName != null ? contextName : "") + " ...");
+ }
+ }
+
+ public void setCmsEventBus(CmsEventBus cmsEventBus) {
+ this.cmsEventBus = cmsEventBus;
+ }
+
+ public CmsEventBus getCmsEventBus() {
+ return cmsEventBus;
+ }
+
+ public void setContextName(String contextName) {
+ this.contextName = contextName;
+ }
+
+}
--- /dev/null
+package org.argeo.cms.web;
+
+import static org.eclipse.rap.rwt.internal.service.ContextProvider.getApplicationContext;
+
+import java.security.PrivilegedAction;
+import java.util.Locale;
+import java.util.UUID;
+
+import javax.security.auth.Subject;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+
+import org.argeo.api.cms.CmsApp;
+import org.argeo.api.cms.CmsAuth;
+import org.argeo.api.cms.CmsEventBus;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.api.cms.CmsSession;
+import org.argeo.api.cms.ux.CmsImageManager;
+import org.argeo.api.cms.ux.CmsView;
+import org.argeo.cms.CurrentUser;
+import org.argeo.cms.LocaleUtils;
+import org.argeo.cms.auth.RemoteAuthCallbackHandler;
+import org.argeo.cms.servlet.ServletHttpRequest;
+import org.argeo.cms.servlet.ServletHttpResponse;
+import org.argeo.cms.swt.AbstractSwtCmsView;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.swt.SimpleSwtUxContext;
+import org.argeo.cms.swt.acr.AcrSwtImageManager;
+import org.argeo.cms.swt.dialogs.CmsFeedback;
+import org.argeo.eclipse.ui.specific.UiContext;
+import org.eclipse.rap.rwt.RWT;
+import org.eclipse.rap.rwt.application.EntryPoint;
+import org.eclipse.rap.rwt.client.service.BrowserNavigation;
+import org.eclipse.rap.rwt.client.service.BrowserNavigationEvent;
+import org.eclipse.rap.rwt.client.service.BrowserNavigationListener;
+import org.eclipse.rap.rwt.internal.lifecycle.RWTLifeCycle;
+import org.eclipse.rap.rwt.service.ServerPushSession;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.SWTError;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Shell;
+
+/** The {@link CmsView} for a {@link CmsWebApp}. */
+@SuppressWarnings("restriction")
+public class CmsWebEntryPoint extends AbstractSwtCmsView implements EntryPoint, CmsView, BrowserNavigationListener {
+ private static final long serialVersionUID = 7733510691684570402L;
+ private final static CmsLog log = CmsLog.getLog(CmsWebEntryPoint.class);
+
+ private final CmsWebApp cmsWebApp;
+
+ // Client services
+ // private final JavaScriptExecutor jsExecutor;
+ private final BrowserNavigation browserNavigation;
+
+ /** Experimental OS-like multi windows. */
+ private boolean multipleShells = false;
+
+ private ServerPushSession serverPushSession;
+
+ public CmsWebEntryPoint(CmsWebApp cmsWebApp, String uiName) {
+ super(uiName);
+ assert cmsWebApp != null;
+ assert uiName != null;
+ this.cmsWebApp = cmsWebApp;
+ uid = UUID.randomUUID().toString();
+
+ // Initial login
+ LoginContext lc;
+ try {
+ lc = new LoginContext(CmsAuth.LOGIN_CONTEXT_USER,
+ new RemoteAuthCallbackHandler(new ServletHttpRequest(UiContext.getHttpRequest()),
+ new ServletHttpResponse(UiContext.getHttpResponse())));
+ lc.login();
+ } catch (LoginException e) {
+ try {
+ lc = new LoginContext(CmsAuth.LOGIN_CONTEXT_ANONYMOUS,
+ new RemoteAuthCallbackHandler(new ServletHttpRequest(UiContext.getHttpRequest()),
+ new ServletHttpResponse(UiContext.getHttpResponse())));
+ lc.login();
+ } catch (LoginException e1) {
+ throw new IllegalStateException("Cannot log in as anonymous", e1);
+ }
+ }
+ authChange(lc);
+
+ // jsExecutor = RWT.getClient().getService(JavaScriptExecutor.class);
+ browserNavigation = RWT.getClient().getService(BrowserNavigation.class);
+ if (browserNavigation != null)
+ browserNavigation.addBrowserNavigationListener(this);
+
+ }
+
+ protected void createContents(Composite parent) {
+ Subject.doAs(loginContext.getSubject(), new PrivilegedAction<Void>() {
+ @Override
+ public Void run() {
+ try {
+ uxContext = new SimpleSwtUxContext();
+ imageManager = (CmsImageManager) new AcrSwtImageManager();
+ CmsSession cmsSession = getCmsSession();
+ if (cmsSession != null) {
+ UiContext.setLocale(cmsSession.getLocale());
+ LocaleUtils.setThreadLocale(cmsSession.getLocale());
+ } else {
+ Locale rwtLocale = RWT.getUISession().getLocale();
+ LocaleUtils.setThreadLocale(rwtLocale);
+ }
+ parent.setData(CmsApp.UI_NAME_PROPERTY, uiName);
+ display = parent.getDisplay();
+ ui = cmsWebApp.getCmsApp().initUi(parent);
+ if (ui instanceof Composite)
+ ((Composite) ui).setLayoutData(CmsSwtUtils.fillAll());
+ serverPushSession = new ServerPushSession();
+
+ // required in order to doAs to work
+ // TODO check whether it would be worth optimising
+ serverPushSession.start();
+ // we need ui to be set before refresh so that CmsView can store UI context data
+ // in it.
+ cmsWebApp.getCmsApp().refreshUi(ui, null);
+ } catch (Exception e) {
+ throw new IllegalStateException("Cannot create entrypoint contents", e);
+ }
+ return null;
+ }
+ });
+ }
+
+ @Override
+ public synchronized void logout() {
+ if (loginContext == null)
+ throw new IllegalArgumentException("Login context should not be null");
+ try {
+ CurrentUser.logoutCmsSession(loginContext.getSubject());
+ loginContext.logout();
+ LoginContext anonymousLc = new LoginContext(CmsAuth.LOGIN_CONTEXT_ANONYMOUS,
+ new RemoteAuthCallbackHandler(new ServletHttpRequest(UiContext.getHttpRequest()),
+ new ServletHttpResponse(UiContext.getHttpResponse())));
+ anonymousLc.login();
+ authChange(anonymousLc);
+ } catch (LoginException e) {
+ log.error("Cannot logout", e);
+ }
+ }
+
+ @Override
+ public synchronized void authChange(LoginContext lc) {
+ if (lc == null)
+ throw new IllegalArgumentException("Login context cannot be null");
+ // logout previous login context
+ if (this.loginContext != null)
+ try {
+ this.loginContext.logout();
+ } catch (LoginException e1) {
+ log.warn("Could not log out: " + e1);
+ }
+ this.loginContext = lc;
+ doRefresh();
+ }
+
+ @Override
+ public void exception(final Throwable e) {
+ if (e instanceof SWTError) {
+ SWTError swtError = (SWTError) e;
+ if (swtError.code == SWT.ERROR_FUNCTION_DISPOSED)
+ return;
+ }
+ display.syncExec(() -> {
+ // TODO internationalise
+ CmsFeedback.error("Unexpected exception", e);
+ // TODO report
+// doRefresh();
+ });
+ }
+
+ protected synchronized void doRefresh() {
+ if (ui != null)
+ Subject.doAs(getSubject(), new PrivilegedAction<Void>() {
+ @Override
+ public Void run() {
+// if (exception != null) {
+// // TODO internationalise
+// CmsFeedback.error("Unexpected exception", exception);
+// exception = null;
+// // TODO report
+// }
+ cmsWebApp.getCmsApp().refreshUi(ui, state);
+ return null;
+ }
+ });
+ }
+
+ /** Sets the state of the entry point and retrieve the related content. */
+ protected String setState(String newState) {
+ cmsWebApp.getCmsApp().setState(ui, newState);
+ state = newState;
+ return null;
+ }
+
+ @Override
+ public void navigateTo(String state) {
+// exception = null;
+ String title = setState(state);
+ if (title != null)
+ doRefresh();
+ if (browserNavigation != null)
+ browserNavigation.pushState(state, title);
+ }
+
+ public CmsImageManager getImageManager() {
+ return imageManager;
+ }
+
+ @Override
+ public void navigated(BrowserNavigationEvent event) {
+ setState(event.getState());
+ // doRefresh();
+ }
+
+ @Override
+ public CmsEventBus getCmsEventBus() {
+ return cmsWebApp.getCmsEventBus();
+ }
+
+ @Override
+ public CmsApp getCmsApp() {
+ return cmsWebApp.getCmsApp();
+ }
+
+ @Override
+ public void stateChanged(String state, String title) {
+ browserNavigation.pushState(state, title);
+ }
+
+ @Override
+ public CmsSession getCmsSession() {
+ CmsSession cmsSession = cmsWebApp.getCmsApp().getCmsContext().getCmsSession(getSubject());
+ if (cmsSession == null)
+ throw new IllegalStateException("No CMS session available for " + getSubject());
+ return cmsSession;
+ }
+
+ /*
+ * EntryPoint IMPLEMENTATION
+ */
+
+ @Override
+ public int createUI() {
+ Display display = new Display();
+ Shell shell = createShell(display);
+ shell.setLayout(CmsSwtUtils.noSpaceGridLayout());
+ CmsSwtUtils.registerCmsView(shell, this);
+ createContents(shell);
+ shell.layout();
+// if (shell.getMaximized()) {
+// shell.layout();
+// } else {
+//// shell.pack();
+// }
+ shell.open();
+ if (getApplicationContext().getLifeCycleFactory().getLifeCycle() instanceof RWTLifeCycle) {
+ eventLoop: while (!shell.isDisposed()) {
+ try {
+ Subject.doAs(loginContext.getSubject(), new PrivilegedAction<Void>() {
+ @Override
+ public Void run() {
+ if (!display.readAndDispatch()) {
+ display.sleep();
+ }
+ return null;
+ }
+ });
+ } catch (Throwable e) {
+ if (e instanceof SWTError) {
+ SWTError swtError = (SWTError) e;
+ if (swtError.code == SWT.ERROR_FUNCTION_DISPOSED) {
+ log.error("Unexpected SWT error in event loop, ignoring it. " + e.getMessage());
+ continue eventLoop;
+ } else {
+ log.error("Unexpected SWT error in event loop, shutting down...", e);
+ break eventLoop;
+ }
+ } else if (e instanceof ThreadDeath) {
+ throw (ThreadDeath) e;
+// } else if (e instanceof Error) {
+// log.error("Unexpected error in event loop, shutting down...", e);
+// break eventLoop;
+ } else {
+ log.error("Unexpected exception in event loop, ignoring it. " + e.getMessage());
+ continue eventLoop;
+ }
+ }
+ }
+ if (!display.isDisposed())
+ display.dispose();
+ }
+ return 0;
+ }
+
+ protected Shell createShell(Display display) {
+ Shell shell;
+ if (!multipleShells) {
+ shell = new Shell(display, SWT.NO_TRIM);
+ shell.setMaximized(true);
+ } else {
+ shell = new Shell(display, SWT.SHELL_TRIM);
+ shell.setSize(800, 600);
+ }
+ return shell;
+ }
+}
--- /dev/null
+package org.argeo.cms.web;
+
+import static org.argeo.cms.osgi.BundleCmsTheme.CMS_THEME_BUNDLE_PROPERTY;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.argeo.cms.osgi.BundleCmsTheme;
+import org.eclipse.rap.rwt.RWT;
+import org.eclipse.rap.rwt.application.Application;
+import org.eclipse.rap.rwt.application.ApplicationConfiguration;
+import org.eclipse.rap.rwt.client.WebClient;
+import org.osgi.framework.BundleContext;
+
+/** Lightweight web app using only RWT and not the whole Eclipse platform. */
+public class MinimalWebApp implements ApplicationConfiguration {
+
+ private BundleCmsTheme theme;
+
+ public void init(BundleContext bundleContext, Map<String, Object> properties) {
+ if (properties.containsKey(CMS_THEME_BUNDLE_PROPERTY)) {
+ String cmsThemeBundle = properties.get(CMS_THEME_BUNDLE_PROPERTY).toString();
+ theme = new BundleCmsTheme(bundleContext, cmsThemeBundle);
+ }
+ }
+
+ public void destroy() {
+
+ }
+
+ /** To be overridden. Does nothing by default. */
+ protected void addEntryPoints(Application application, Map<String, String> properties) {
+
+ }
+
+ @Override
+ public void configure(Application application) {
+ if (theme != null)
+ WebThemeUtils.apply(application, theme);
+
+ Map<String, String> properties = new HashMap<>();
+ if (theme != null) {
+ properties.put(WebClient.THEME_ID, theme.getThemeId());
+ properties.put(WebClient.HEAD_HTML, theme.getHtmlHeaders());
+ } else {
+ properties.put(WebClient.THEME_ID, RWT.DEFAULT_THEME_ID);
+ }
+ addEntryPoints(application, properties);
+
+ }
+
+ public void setTheme(BundleCmsTheme theme) {
+ this.theme = theme;
+ }
+
+}
--- /dev/null
+package org.argeo.cms.web;
+
+import org.argeo.api.cms.CmsLog;
+import org.argeo.api.cms.ux.CmsTheme;
+import org.eclipse.rap.rwt.application.Application;
+import org.eclipse.rap.rwt.service.ResourceLoader;
+
+/** Web specific utilities around theming. */
+public class WebThemeUtils {
+ private final static CmsLog log = CmsLog.getLog(WebThemeUtils.class);
+
+ public static void apply(Application application, CmsTheme theme) {
+ ResourceLoader resourceLoader = new CmsThemeResourceLoader(theme);
+ resources: for (String path : theme.getImagesPaths()) {
+ if (path.startsWith("target/"))
+ continue resources; // skip maven output
+ application.addResource(path, resourceLoader);
+ if (log.isTraceEnabled())
+ log.trace("Theme " + theme.getThemeId() + ": added resource " + path);
+ }
+ for (String path : theme.getRapCssPaths()) {
+ application.addStyleSheet(theme.getThemeId(), path, resourceLoader);
+ if (log.isDebugEnabled())
+ log.debug("Theme " + theme.getThemeId() + ": added RAP CSS " + path);
+ }
+ }
+
+}
--- /dev/null
+package org.argeo.cms.web.osgi;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Map;
+
+import org.argeo.api.cms.CmsApp;
+import org.argeo.api.cms.CmsEventBus;
+import org.argeo.cms.web.CmsWebApp;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
+
+/** Publish a CmsApp as a RAP application. */
+public class CmsWebAppFactory {
+ private BundleContext bundleContext = FrameworkUtil.getBundle(CmsWebAppFactory.class).getBundleContext();
+ private final static String CONTEXT_NAME = "contextName";
+
+ private CmsEventBus cmsEventBus;
+
+ private Map<String, CmsWebApp> registrations = Collections.synchronizedMap(new HashMap<>());
+
+ public void addCmsApp(CmsApp cmsApp, Map<String, String> properties) {
+ String contextName = properties.get(CmsApp.CONTEXT_NAME_PROPERTY);
+ if (contextName != null) {
+ CmsWebApp cmsWebApp = new CmsWebApp();
+ cmsWebApp.setCmsEventBus(cmsEventBus);
+ cmsWebApp.setCmsApp(cmsApp);
+ Hashtable<String, String> serviceProperties = new Hashtable<>();
+ if (!contextName.equals(""))
+ serviceProperties.put(CONTEXT_NAME, contextName);
+ cmsWebApp.init(bundleContext, serviceProperties);
+ registrations.put(contextName, cmsWebApp);
+ }
+ }
+
+ public void removeCmsApp(CmsApp cmsApp, Map<String, String> properties) {
+ String contextName = properties.get(CmsApp.CONTEXT_NAME_PROPERTY);
+ if (contextName != null) {
+ CmsWebApp cmsWebApp = registrations.get(contextName);
+ if (cmsWebApp != null) {
+ cmsWebApp.destroy(bundleContext, new HashMap<>());
+ cmsWebApp.unsetCmsApp(cmsApp, properties);
+ } else {
+ // TODO log warning
+ }
+ }
+ }
+
+ public void setCmsEventBus(CmsEventBus cmsEventBus) {
+ this.cmsEventBus = cmsEventBus;
+ }
+
+
+}
--- /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-17" />
+ <classpathentry kind="output" path="bin" />
+</classpath>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>org.argeo.swt.specific.rap</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.dialogs,\
+org.eclipse.swt.events,\
+javax.servlet.http;version="[3,5)",\
+*
--- /dev/null
+source.. = src/
+output.. = bin/
--- /dev/null
+package org.argeo.eclipse.ui.specific;
+
+import java.awt.image.BufferedImage;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.Composite;
+
+public class BufferedImageDisplay extends Composite {
+ private static final long serialVersionUID = 4541163690514461514L;
+ private BufferedImage image;
+
+ public BufferedImageDisplay(Composite parent, int style) {
+ super(parent, SWT.NO_BACKGROUND);
+ }
+
+ public void setImage(BufferedImage image) {
+ this.image = image;
+ }
+
+}
--- /dev/null
+package org.argeo.eclipse.ui.specific;
+
+import org.eclipse.swt.widgets.FileDialog;
+import org.eclipse.swt.widgets.Shell;
+
+public class CmsFileDialog extends FileDialog {
+ private static final long serialVersionUID = -7540791204102318801L;
+
+ public CmsFileDialog(Shell parent, int style) {
+ super(parent, style);
+ }
+
+ public CmsFileDialog(Shell parent) {
+ super(parent);
+ }
+
+}
--- /dev/null
+package org.argeo.eclipse.ui.specific;
+
+import org.eclipse.rap.rwt.widgets.FileUpload;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.widgets.Composite;
+
+public class CmsFileUpload extends FileUpload {
+ private static final long serialVersionUID = 8027963992680980549L;
+
+ public CmsFileUpload(Composite parent, int style) {
+ super(parent, style);
+ }
+
+ @Override
+ public void setText(String text) {
+ super.setText(text);
+ }
+
+ @Override
+ public String getFileName() {
+ return super.getFileName();
+ }
+
+ @Override
+ public String[] getFileNames() {
+ return super.getFileNames();
+ }
+
+ @Override
+ public void addSelectionListener(SelectionListener listener) {
+ super.addSelectionListener(listener);
+ }
+
+}
--- /dev/null
+package org.argeo.eclipse.ui.specific;
+
+import org.eclipse.jface.viewers.AbstractTableViewer;
+import org.eclipse.jface.viewers.ColumnViewer;
+import org.eclipse.jface.viewers.ColumnViewerToolTipSupport;
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.rap.rwt.RWT;
+import org.eclipse.swt.widgets.Widget;
+
+/** Static utilities to bridge differences between RCP and RAP */
+public class EclipseUiSpecificUtils {
+
+ public static void setStyleData(Widget widget, Object data) {
+ widget.setData(RWT.CUSTOM_VARIANT, data);
+ }
+
+ public static Object getStyleData(Widget widget) {
+ return widget.getData(RWT.CUSTOM_VARIANT);
+ }
+
+ public static void setMarkupData(Widget widget) {
+ widget.setData(RWT.MARKUP_ENABLED, true);
+ }
+
+ public static void setMarkupValidationDisabledData(Widget widget) {
+ widget.setData("org.eclipse.rap.rwt.markupValidationDisabled", Boolean.TRUE);
+ }
+
+ /**
+ * TootlTip support is supported only for {@link AbstractTableViewer} in RAP
+ */
+ public static void enableToolTipSupport(Viewer viewer) {
+ if (viewer instanceof ColumnViewer)
+ ColumnViewerToolTipSupport.enableFor((ColumnViewer) viewer);
+ }
+
+ private EclipseUiSpecificUtils() {
+ }
+}
--- /dev/null
+package org.argeo.eclipse.ui.specific;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.eclipse.rap.fileupload.FileDetails;
+import org.eclipse.rap.fileupload.FileUploadHandler;
+import org.eclipse.rap.fileupload.FileUploadReceiver;
+import org.eclipse.rap.rwt.RWT;
+import org.eclipse.rap.rwt.client.ClientFile;
+import org.eclipse.rap.rwt.client.service.ClientFileUploader;
+import org.eclipse.rap.rwt.dnd.ClientFileTransfer;
+import org.eclipse.swt.dnd.DND;
+import org.eclipse.swt.dnd.DropTarget;
+import org.eclipse.swt.dnd.DropTargetAdapter;
+import org.eclipse.swt.dnd.DropTargetEvent;
+import org.eclipse.swt.dnd.Transfer;
+import org.eclipse.swt.widgets.Control;
+
+/** Configures a {@link Control} to receive files drop from the client OS. */
+public class FileDropAdapter {
+
+ public void prepareDropTarget(Control control, DropTarget dropTarget) {
+ dropTarget.setTransfer(new Transfer[] { ClientFileTransfer.getInstance() });
+ dropTarget.addDropListener(new DropTargetAdapter() {
+ private static final long serialVersionUID = 5361645765549463168L;
+
+ @Override
+ public void dropAccept(DropTargetEvent event) {
+ if (!ClientFileTransfer.getInstance().isSupportedType(event.currentDataType)) {
+ event.detail = DND.DROP_NONE;
+ }
+ }
+
+ @Override
+ public void drop(DropTargetEvent event) {
+ handleFileDrop(control, event);
+ }
+ });
+ }
+
+ public void handleFileDrop(Control control, DropTargetEvent event) {
+ ClientFile[] clientFiles = (ClientFile[]) event.data;
+ ClientFileUploader service = RWT.getClient().getService(ClientFileUploader.class);
+// DiskFileUploadReceiver receiver = new DiskFileUploadReceiver();
+ FileUploadReceiver receiver = new FileUploadReceiver() {
+
+ @Override
+ public void receive(InputStream stream, FileDetails details) throws IOException {
+ control.getDisplay().syncExec(() -> {
+ try {
+ processUpload(stream, details.getFileName(), details.getContentType());
+ } catch (IOException e) {
+ throw new IllegalStateException("Cannot process upload of " + details.getFileName(), e);
+ }
+ });
+ }
+ };
+ FileUploadHandler handler = new FileUploadHandler(receiver);
+// handler.setMaxFileSize( sizeLimit );
+// handler.setUploadTimeLimit( timeLimit );
+ service.submit(handler.getUploadUrl(), clientFiles);
+// for (File file : receiver.getTargetFiles()) {
+// paths.add(file.toPath());
+// }
+ }
+
+ /** Executed in UI thread */
+ protected void processUpload(InputStream in, String fileName, String contentType) throws IOException {
+
+ }
+
+}
--- /dev/null
+package org.argeo.eclipse.ui.specific;
+
+import java.util.Locale;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.rap.rwt.RWT;
+import org.eclipse.swt.widgets.Display;
+
+/** Singleton class providing single sources infos about the UI context. */
+public class UiContext {
+ /** Can be null, thus indicating that we are not in a web context. */
+ public static HttpServletRequest getHttpRequest() {
+ return RWT.getRequest();
+ }
+
+ public static HttpServletResponse getHttpResponse() {
+ return RWT.getResponse();
+ }
+
+ public static Locale getLocale() {
+ if (Display.getCurrent() != null)
+ return RWT.getUISession().getLocale();
+ else
+ return Locale.getDefault();
+ }
+
+ public static void setLocale(Locale locale) {
+ if (Display.getCurrent() != null)
+ RWT.getUISession().setLocale(locale);
+ else
+ Locale.setDefault(locale);
+ }
+
+ /** Can always be null */
+ @SuppressWarnings("unchecked")
+ public static <T> T getData(String key) {
+ Display display = getDisplay();
+ if (display == null)
+ return null;
+ return (T) display.getData(key);
+ }
+
+ public static void setData(String key, Object value) {
+ Display display = getDisplay();
+ if (display == null)
+ throw new IllegalStateException("Not display available");
+ display.setData(key, value);
+ }
+
+ private static Display getDisplay() {
+ return Display.getCurrent();
+ }
+
+ private UiContext() {
+ }
+
+}
--- /dev/null
+/** Eclipse RAP-specific SWT/JFace utilities, to simplify single-sourcing. */
+package org.argeo.eclipse.ui.specific;
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-17"/>
+ <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+ <classpathentry kind="src" path="src"/>
+ <classpathentry kind="output" path="bin"/>
+</classpath>
--- /dev/null
+/bin/
+/target/
+/exec
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>org.argeo.cms.e4.rcp</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
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
+org.eclipse.jdt.core.compiler.compliance=1.8
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.source=1.8
--- /dev/null
+eclipse.preferences.version=1
+pluginProject.extensions=false
+resolve.requirebundle=false
--- /dev/null
+/MANIFEST.MF
--- /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" xmi:id="_c4iAgCnCEei1F8sdBz8Mpw" elementId="org.argeo.cms.e4.rcp.application">
+ <children xsi:type="basic:TrimmedWindow" xmi:id="_hSGBwCnCEei1F8sdBz8Mpw" elementId="org.argeo.cms.e4.rcp.trimmedwindow.argeocompanion" label="Argeo Companion">
+ <children xsi:type="advanced:PerspectiveStack" xmi:id="_nxzQICnCEei1F8sdBz8Mpw" elementId="org.argeo.cms.e4.rcp.perspectivestack.0">
+ <children xsi:type="advanced:Perspective" xmi:id="_oI_oICnCEei1F8sdBz8Mpw" elementId="org.argeo.cms.e4.rcp.perspective.cmsadmin" label="CMS Admin">
+ <children xsi:type="basic:PartSashContainer" xmi:id="_qc16ECnCEei1F8sdBz8Mpw" elementId="org.argeo.cms.e4.rcp.partsashcontainer.0" horizontal="true">
+ <children xsi:type="basic:PartStack" xmi:id="_RE87kDsXEeiUntFYWh-hFg" elementId="org.argeo.cms.e4.rcp.partstack.1">
+ <children xsi:type="basic:Part" xmi:id="_V1WvgDsXEeiUntFYWh-hFg" elementId="org.argeo.cms.e4.rcp.part.files" contributionURI="bundleclass://org.argeo.cms.jcr.e4/org.argeo.cms.e4.files.NodeFsBrowserView" label="Files"/>
+ <children xsi:type="basic:Part" xmi:id="_vOqDQCnCEei1F8sdBz8Mpw" elementId="org.argeo.cms.e4.rcp.part.jcr" containerData="4000" contributionURI="bundleclass://org.argeo.cms.jcr.e4/org.argeo.cms.e4.jcr.JcrBrowserView" label="JCR"/>
+ </children>
+ <children xsi:type="basic:PartStack" xmi:id="_0eRiwCnCEei1F8sdBz8Mpw" elementId="org.argeo.cms.e4.rcp.partstack.0" containerData="6000">
+ <tags>editorArea</tags>
+ </children>
+ </children>
+ </children>
+ </children>
+ </children>
+ <descriptors xmi:id="__9SDsC8JEeq0koquN4xGyg" elementId="org.argeo.cms.e4.partdescriptor.nodeEditor" allowMultiple="true" category="editorArea" closeable="true" contributionURI="bundleclass://org.argeo.cms.jcr.e4/org.argeo.cms.e4.jcr.JcrNodeEditor"/>
+ <addons xmi:id="_c4iAgSnCEei1F8sdBz8Mpw" elementId="org.eclipse.e4.core.commands.service" contributionURI="bundleclass://org.eclipse.e4.core.commands/org.eclipse.e4.core.commands.CommandServiceAddon"/>
+ <addons xmi:id="_c4iAginCEei1F8sdBz8Mpw" elementId="org.eclipse.e4.ui.contexts.service" contributionURI="bundleclass://org.eclipse.e4.ui.services/org.eclipse.e4.ui.services.ContextServiceAddon"/>
+ <addons xmi:id="_c4iAgynCEei1F8sdBz8Mpw" elementId="org.eclipse.e4.ui.bindings.service" contributionURI="bundleclass://org.eclipse.e4.ui.bindings/org.eclipse.e4.ui.bindings.BindingServiceAddon"/>
+ <addons xmi:id="_c4iAhCnCEei1F8sdBz8Mpw" 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="_c4iAhSnCEei1F8sdBz8Mpw" 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="_c4iAhinCEei1F8sdBz8Mpw" 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="_c4iAhynCEei1F8sdBz8Mpw" elementId="org.eclipse.e4.ui.workbench.handler.model" contributionURI="bundleclass://org.eclipse.e4.ui.workbench/org.eclipse.e4.ui.internal.workbench.addons.HandlerProcessingAddon"/>
+</application:Application>
--- /dev/null
+argeo.osgi.start.2.node=\
+org.eclipse.equinox.http.servlet,\
+org.eclipse.equinox.metatype,\
+org.eclipse.equinox.cm,\
+org.argeo.init
+
+argeo.osgi.start.3.node=\
+org.argeo.cms,\
+org.argeo.cms.jcr,\
+
+applicationXMI=org.argeo.cms.e4.rcp/argeo-companion.e4xmi
+lifeCycleURI=bundleclass://org.argeo.cms.e4.rcp/org.argeo.cms.e4.rcp.CmsRcpLifeCycle
+clearPersistedState=true
+#argeo.cms.desktop.inTray=true
+
+# Remote node:
+#argeo.node.repo.labeledUri=http://root:demo@localhost:7070/jcr/node
+
+# Logging
+log.org.argeo=DEBUG
+
+argeo.node.useradmin.uris=os:///
+eclipse.application=org.argeo.cms.e4.rcp.CmsE4Application
--- /dev/null
+Bundle-SymbolicName: org.argeo.cms.e4.rcp;singleton=true
+
+Require-Bundle: org.eclipse.core.runtime
+
+Import-Package: !org.eclipse.core.runtime,\
+org.eclipse.swt,\
+org.eclipse.*;resolution:=optional,\
+*
--- /dev/null
+output.. = bin/
+bin.includes = META-INF/,\
+ .,\
+ argeo-companion.e4xmi
+source.. = src/
--- /dev/null
+log4j.rootLogger=WARN, development
+
+## Levels
+log4j.logger.org.argeo=DEBUG
+log4j.logger.org.argeo.jackrabbit.remote.ExtendedDispatcherServlet=WARN
+log4j.logger.org.argeo.server.webextender.TomcatDeployer=INFO
+
+#log4j.logger.org.springframework.security=DEBUG
+#log4j.logger.org.apache.commons.exec=DEBUG
+#log4j.logger.org.apache.jackrabbit.webdav=DEBUG
+#log4j.logger.org.apache.jackrabbit.remote=DEBUG
+#log4j.logger.org.apache.jackrabbit.core.observation=DEBUG
+
+log4j.logger.org.apache.catalina=INFO
+log4j.logger.org.apache.coyote=INFO
+
+log4j.logger.org.apache.directory=INFO
+log4j.logger.org.apache.directory.server=ERROR
+log4j.logger.org.apache.jackrabbit.core.query.lucene=ERROR
+
+## Appenders
+# console is set to be a ConsoleAppender.
+log4j.appender.console=org.apache.log4j.ConsoleAppender
+
+# console uses PatternLayout.
+log4j.appender.console.layout=org.apache.log4j.PatternLayout
+log4j.appender.console.layout.ConversionPattern= %-5p %d{ISO8601} %m - %c - [%t]%n
+
+# development appender (slow!)
+log4j.appender.development=org.apache.log4j.ConsoleAppender
+log4j.appender.development.layout=org.apache.log4j.PatternLayout
+log4j.appender.development.layout.ConversionPattern=%d{HH:mm:ss} [%16.16t] %5p %m (%F:%L) %c%n
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<?eclipse version="3.4"?>
+<plugin>
+ <extension
+ id="CmsE4Application"
+ name="CMS E4 Application"
+ point="org.eclipse.core.runtime.applications">
+ <application
+ cardinality="singleton-global"
+ thread="main"
+ visible="true">
+ <run class="org.argeo.cms.e4.rcp.CmsE4Application"></run>
+ </application>
+ </extension>
+</plugin>
--- /dev/null
+package org.argeo.cms.e4.rcp;
+
+import java.security.PrivilegedExceptionAction;
+import java.util.UUID;
+import java.util.concurrent.Callable;
+
+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.ux.CmsImageManager;
+import org.argeo.api.cms.ux.CmsView;
+import org.argeo.api.cms.ux.UxContext;
+import org.argeo.cms.CurrentUser;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.swt.SimpleSwtUxContext;
+import org.argeo.cms.swt.auth.CmsLoginShell;
+import org.eclipse.core.runtime.IConfigurationElement;
+import org.eclipse.core.runtime.IExtension;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.equinox.app.IApplication;
+import org.eclipse.equinox.app.IApplicationContext;
+import org.eclipse.swt.widgets.Display;
+
+public class CmsE4Application implements IApplication, CmsView {
+ private LoginContext loginContext;
+ private IApplication e4Application;
+ private UxContext uxContext;
+ private String uid;
+
+ @Override
+ public Object start(IApplicationContext context) throws Exception {
+ // TODO wait for CMS to be ready
+ Thread.sleep(5000);
+
+ uid = UUID.randomUUID().toString();
+ Subject subject = new Subject();
+ Display display = createDisplay();
+ CmsLoginShell loginShell = new CmsLoginShell(this, null);
+ // TODO customize CmsLoginShell to be smaller and centered
+ loginShell.setSubject(subject);
+ try {
+ // try pre-auth
+ loginContext = new LoginContext(CmsAuth.LOGIN_CONTEXT_SINGLE_USER, subject, loginShell);
+ loginContext.login();
+ } catch (LoginException e) {
+ e.printStackTrace();
+ loginShell.createUi();
+ loginShell.open();
+
+ while (!loginShell.getShell().isDisposed()) {
+ if (!display.readAndDispatch())
+ display.sleep();
+ }
+ }
+ if (CurrentUser.getUsername(getSubject()) == null)
+ throw new IllegalStateException("Cannot log in");
+
+ // try {
+ // CallbackHandler callbackHandler = new DefaultLoginDialog(
+ // display.getActiveShell());
+ // loginContext = new LoginContext(
+ // NodeConstants.LOGIN_CONTEXT_SINGLE_USER, subject,
+ // callbackHandler);
+ // } catch (LoginException e1) {
+ // throw new CmsException("Cannot initialize login context", e1);
+ // }
+ //
+ // // login
+ // try {
+ // loginContext.login();
+ // subject = loginContext.getSubject();
+ // } catch (LoginException e) {
+ // e.printStackTrace();
+ // display.dispose();
+ // try {
+ // Thread.sleep(2000);
+ // } catch (InterruptedException e1) {
+ // // silent
+ // }
+ // return null;
+ // }
+
+ uxContext = new SimpleSwtUxContext();
+ // UiContext.setData(CmsView.KEY, this);
+ CmsSwtUtils.registerCmsView(loginShell.getShell(), this);
+ e4Application = getApplication(null);
+ Object res = Subject.doAs(subject, new PrivilegedExceptionAction<Object>() {
+
+ @Override
+ public Object run() throws Exception {
+ return e4Application.start(context);
+ }
+
+ });
+ return res;
+ }
+
+ @Override
+ public void stop() {
+ if (e4Application != null)
+ e4Application.stop();
+ }
+
+ static IApplication getApplication(String[] args) {
+ IExtension extension = Platform.getExtensionRegistry().getExtension(Platform.PI_RUNTIME,
+ Platform.PT_APPLICATIONS, "org.eclipse.e4.ui.workbench.swt.E4Application");
+ try {
+ IConfigurationElement[] elements = extension.getConfigurationElements();
+ if (elements.length > 0) {
+ IConfigurationElement[] runs = elements[0].getChildren("run");
+ if (runs.length > 0) {
+ Object runnable;
+ runnable = runs[0].createExecutableExtension("class");
+ if (runnable instanceof IApplication)
+ return (IApplication) runnable;
+ }
+ }
+ } catch (Exception e) {
+ throw new IllegalStateException("Cannot find e4 application", e);
+ }
+ throw new IllegalStateException("Cannot find e4 application");
+ }
+
+ public static Display createDisplay() {
+ Display.setAppName("Argeo CMS RCP");
+
+ // create the display
+ Display newDisplay = Display.getCurrent();
+ if (newDisplay == null) {
+ newDisplay = new Display();
+ }
+ // Set the priority higher than normal so as to be higher
+ // than the JobManager.
+ Thread.currentThread().setPriority(Math.min(Thread.MAX_PRIORITY, Thread.NORM_PRIORITY + 1));
+ return newDisplay;
+ }
+
+ //
+ // CMS VIEW
+ //
+
+ @Override
+ public UxContext getUxContext() {
+ return uxContext;
+ }
+
+ @Override
+ public void navigateTo(String state) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void authChange(LoginContext loginContext) {
+ if (loginContext == null)
+ throw new IllegalStateException("Login context cannot be null");
+ // logout previous login context
+ // if (this.loginContext != null)
+ // try {
+ // this.loginContext.logout();
+ // } catch (LoginException e1) {
+ // System.err.println("Could not log out: " + e1);
+ // }
+ this.loginContext = loginContext;
+ }
+
+ @Override
+ public void logout() {
+ if (loginContext == null)
+ throw new IllegalStateException("Login context should not bet null");
+ try {
+ CurrentUser.logoutCmsSession(loginContext.getSubject());
+ loginContext.logout();
+ } catch (LoginException e) {
+ throw new IllegalStateException("Cannot log out", e);
+ }
+ }
+
+ @Override
+ public void exception(Throwable e) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public CmsImageManager getImageManager() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ protected Subject getSubject() {
+ return loginContext.getSubject();
+ }
+
+ @Override
+ public boolean isAnonymous() {
+ return CurrentUser.isAnonymous(getSubject());
+ }
+
+ @Override
+ public String getUid() {
+ return uid;
+ }
+
+ @Override
+ public <T> T doAs(Callable<T> action) {
+ throw new UnsupportedOperationException();
+ }
+
+}
--- /dev/null
+package org.argeo.cms.e4.rcp;
+
+import org.eclipse.e4.core.contexts.IEclipseContext;
+import org.eclipse.e4.ui.workbench.lifecycle.PostContextCreate;
+import org.eclipse.e4.ui.workbench.lifecycle.PreSave;
+import org.eclipse.e4.ui.workbench.lifecycle.ProcessAdditions;
+import org.eclipse.e4.ui.workbench.lifecycle.ProcessRemovals;
+
+@SuppressWarnings("restriction")
+public class CmsRcpLifeCycle {
+
+ @PostContextCreate
+ void postContextCreate(IEclipseContext workbenchContext) {
+ }
+
+ @PreSave
+ void preSave(IEclipseContext workbenchContext) {
+ }
+
+ @ProcessAdditions
+ void processAdditions(IEclipseContext workbenchContext) {
+ }
+
+ @ProcessRemovals
+ void processRemovals(IEclipseContext workbenchContext) {
+ }
+}
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-17"/>
+ <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+ <classpathentry kind="src" path="src"/>
+ <classpathentry kind="output" path="bin"/>
+</classpath>
--- /dev/null
+/bin/
+/target/
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>org.argeo.cms.swt.rcp</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" activate="init" deactivate="destroy" immediate="true" name="CMS RCP Display Factory">
+ <implementation class="org.argeo.cms.ui.rcp.CmsRcpDisplayFactory"/>
+</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" immediate="true" name="CMS RCP Servlet Factory">
+ <implementation class="org.argeo.cms.ui.rcp.CmsRcpHttpLauncher"/>
+ <reference bind="setHttpServer" cardinality="1..1" interface="com.sun.net.httpserver.HttpServer" name="HttpServer" policy="static"/>
+ <reference bind="addCmsApp" cardinality="0..n" interface="org.argeo.api.cms.CmsApp" name="CmsApp" policy="dynamic" unbind="removeCmsApp"/>
+</scr:component>
--- /dev/null
+Bundle-SymbolicName: org.argeo.cms.swt.rcp;singleton=true
+
+Import-Package:\
+org.argeo.cms.auth,\
+org.eclipse.swt,\
+org.eclipse.swt.graphics,\
+org.w3c.css.sac,\
+*
+
+Service-Component:\
+OSGI-INF/cmsRcpDisplayFactory.xml,\
+OSGI-INF/cmsRcpHttpLauncher.xml
+
--- /dev/null
+output.. = bin/
+bin.includes = META-INF/,\
+ .,\
+ OSGI-INF/,\
+ OSGI-INF/cmsRcpHttpLauncher.xml
+source.. = src/
--- /dev/null
+package org.argeo.cms.ui.rcp;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.PrivilegedAction;
+import java.util.Map;
+import java.util.UUID;
+
+import javax.security.auth.Subject;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+
+import org.argeo.api.cms.CmsApp;
+import org.argeo.api.cms.CmsAuth;
+import org.argeo.api.cms.CmsEventBus;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.api.cms.CmsSession;
+import org.argeo.api.cms.ux.CmsTheme;
+import org.argeo.api.cms.ux.CmsView;
+import org.argeo.cms.swt.AbstractSwtCmsView;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.eclipse.e4.ui.css.core.engine.CSSEngine;
+import org.eclipse.e4.ui.css.core.engine.CSSErrorHandler;
+import org.eclipse.e4.ui.css.swt.engine.CSSSWTEngineImpl;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Shell;
+
+/** Runs a {@link CmsApp} as an SWT desktop application. */
+@SuppressWarnings("restriction")
+public class CmsRcpApp extends AbstractSwtCmsView implements CmsView {
+ private final static CmsLog log = CmsLog.getLog(CmsRcpApp.class);
+
+ private Shell shell;
+ private CmsApp cmsApp;
+
+ private CSSEngine cssEngine;
+
+ public CmsRcpApp(String uiName) {
+ super(uiName);
+ uid = UUID.randomUUID().toString();
+ }
+
+ public void initRcpApp() {
+ display = Display.getCurrent();
+ shell = new Shell(display);
+ shell.setText("Argeo CMS");
+ Composite parent = shell;
+ parent.setLayout(CmsSwtUtils.noSpaceGridLayout());
+ CmsSwtUtils.registerCmsView(shell, CmsRcpApp.this);
+
+ try {
+ loginContext = new LoginContext(CmsAuth.SINGLE_USER.getLoginContextName());
+ loginContext.login();
+ } catch (LoginException e) {
+ throw new IllegalStateException("Could not log in.", e);
+ }
+ if (log.isDebugEnabled())
+ log.debug("Logged in to desktop: " + loginContext.getSubject());
+
+ Subject.doAs(loginContext.getSubject(), (PrivilegedAction<Void>) () -> {
+
+ // TODO factorise with web app
+ parent.setData(CmsApp.UI_NAME_PROPERTY, uiName);
+ ui = cmsApp.initUi(parent);
+ if (ui instanceof Composite)
+ ((Composite) ui).setLayoutData(CmsSwtUtils.fillAll());
+ // we need ui to be set before refresh so that CmsView can store UI context data
+ // in it.
+ cmsApp.refreshUi(ui, null);
+
+ // Styling
+ CmsTheme theme = CmsSwtUtils.getCmsTheme(parent);
+ if (theme != null) {
+ cssEngine = new CSSSWTEngineImpl(display);
+ for (String path : theme.getSwtCssPaths()) {
+ try (InputStream in = theme.loadPath(path)) {
+ cssEngine.parseStyleSheet(in);
+ } catch (IOException e) {
+ throw new IllegalStateException("Cannot load stylesheet " + path, e);
+ }
+ }
+ cssEngine.setErrorHandler(new CSSErrorHandler() {
+ public void error(Exception e) {
+ log.error("SWT styling error: ", e);
+ }
+ });
+ applyStyles(shell);
+ }
+ shell.layout(true, true);
+
+ shell.open();
+ return null;
+ });
+ }
+
+ /*
+ * CMS VIEW
+ */
+
+ @Override
+ public void navigateTo(String state) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void authChange(LoginContext loginContext) {
+ }
+
+ @Override
+ public void logout() {
+ if (loginContext != null)
+ try {
+ loginContext.logout();
+ } catch (LoginException e) {
+ log.error("Cannot log out", e);
+ }
+ }
+
+ @Override
+ public void exception(Throwable e) {
+ log.error("Unexpected exception in CMS RCP", e);
+ }
+
+ @Override
+ public CmsSession getCmsSession() {
+ CmsSession cmsSession = cmsApp.getCmsContext().getCmsSession(getSubject());
+ return cmsSession;
+ }
+
+ @Override
+ public boolean isAnonymous() {
+ return false;
+ }
+
+ @Override
+ public void applyStyles(Object node) {
+ if (cssEngine != null)
+ cssEngine.applyStyles(node, true);
+ }
+
+ public Shell getShell() {
+ return shell;
+ }
+
+ @Override
+ public CmsEventBus getCmsEventBus() {
+ return cmsApp.getCmsContext().getCmsEventBus();
+ }
+
+ @Override
+ public CmsApp getCmsApp() {
+ return cmsApp;
+ }
+
+ /*
+ * DEPENDENCY INJECTION
+ */
+ public void setCmsApp(CmsApp cmsApp, Map<String, String> properties) {
+ this.cmsApp = cmsApp;
+ }
+
+ public void unsetCmsApp(CmsApp cmsApp, Map<String, String> properties) {
+ this.cmsApp = null;
+ }
+
+}
--- /dev/null
+package org.argeo.cms.ui.rcp;
+
+import java.lang.System.Logger;
+import java.lang.System.Logger.Level;
+import java.nio.file.Path;
+
+import org.argeo.api.cms.CmsApp;
+import org.argeo.cms.util.OS;
+import org.eclipse.swt.events.DisposeListener;
+import org.eclipse.swt.widgets.Display;
+
+/** Creates the SWT {@link Display} in a dedicated thread. */
+public class CmsRcpDisplayFactory {
+ private final static Logger logger = System.getLogger(CmsRcpDisplayFactory.class.getName());
+
+ /** File name in a run directory */
+ private final static String ARGEO_RCP_URL = "argeo.rcp.url";
+
+ /** There is only one display in RCP mode */
+ private static Display display;
+
+ private CmsUiThread uiThread;
+
+ private boolean shutdown = false;
+
+ public void init() {
+ uiThread = new CmsUiThread();
+ uiThread.start();
+ while (display == null)
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException e) {
+ // silent
+ }
+ }
+
+ public void destroy() {
+ shutdown = true;
+ display.wake();
+ try {
+ uiThread.join();
+ } catch (InterruptedException e) {
+ // silent
+ } finally {
+ uiThread = null;
+ }
+ }
+
+ class CmsUiThread extends Thread {
+
+ public CmsUiThread() {
+ super("CMS UI");
+ }
+
+ @Override
+ public void run() {
+ try {
+ display = Display.getDefault();
+ display.setRuntimeExceptionHandler((e) -> e.printStackTrace());
+ display.setErrorHandler((e) -> e.printStackTrace());
+
+ while (!shutdown) {
+ if (!display.readAndDispatch())
+ display.sleep();
+ }
+ display.dispose();
+ display = null;
+ } catch (UnsatisfiedLinkError e) {
+ logger.log(Level.ERROR,
+ "Cannot load SWT, probably because the OSGi framework has been refresh. Restart the application.",
+ e);
+ }
+ }
+ }
+
+ public static Display getDisplay() {
+ return display;
+ }
+
+ public static void openCmsApp(CmsApp cmsApp, String uiName, DisposeListener disposeListener) {
+ CmsRcpDisplayFactory.getDisplay().syncExec(() -> {
+ CmsRcpApp cmsRcpApp = new CmsRcpApp(uiName);
+ cmsRcpApp.setCmsApp(cmsApp, null);
+ cmsRcpApp.initRcpApp();
+ if (disposeListener != null)
+ cmsRcpApp.getShell().addDisposeListener(disposeListener);
+ });
+ }
+
+ public static Path getUrlRunFile() {
+ return OS.getRunDir().resolve(CmsRcpDisplayFactory.ARGEO_RCP_URL);
+ }
+}
--- /dev/null
+package org.argeo.cms.ui.rcp;
+
+import static java.lang.System.Logger.Level.DEBUG;
+
+import java.io.IOException;
+import java.lang.System.Logger;
+import java.lang.System.Logger.Level;
+import java.net.DatagramSocket;
+import java.net.ServerSocket;
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+
+import org.argeo.api.cms.CmsApp;
+
+import com.sun.net.httpserver.HttpExchange;
+import com.sun.net.httpserver.HttpHandler;
+import com.sun.net.httpserver.HttpServer;
+
+/** Publishes one {@link CmsRcpServlet} per {@link CmsApp}. */
+public class CmsRcpHttpLauncher {
+ private final static Logger logger = System.getLogger(CmsRcpHttpLauncher.class.getName());
+ private CompletableFuture<HttpServer> httpServer = new CompletableFuture<>();
+
+ public void init() {
+
+ }
+
+ public void destroy() {
+ Path runFile = CmsRcpDisplayFactory.getUrlRunFile();
+ try {
+ if (Files.exists(runFile)) {
+ Files.delete(runFile);
+ }
+ } catch (IOException e) {
+ logger.log(Level.ERROR, "Cannot delete " + runFile, e);
+ }
+ }
+
+ public void addCmsApp(CmsApp cmsApp, Map<String, String> properties) {
+ final String contextName = properties.get(CmsApp.CONTEXT_NAME_PROPERTY);
+ if (contextName != null) {
+ httpServer.thenAcceptAsync((httpServer) -> {
+ httpServer.createContext("/" + contextName, new HttpHandler() {
+
+ @Override
+ public void handle(HttpExchange exchange) throws IOException {
+ String path = exchange.getRequestURI().getPath();
+ String uiName = path != null ? path.substring(path.lastIndexOf('/') + 1) : "";
+ CmsRcpDisplayFactory.openCmsApp(cmsApp, uiName, null);
+ exchange.sendResponseHeaders(200, -1);
+ logger.log(Level.DEBUG, "Opened RCP UI " + uiName + " of CMS App /" + contextName);
+ }
+ });
+ }).exceptionally(e -> {
+ logger.log(Level.ERROR, "Cannot register RCO app " + contextName, e);
+ return null;
+ });
+ logger.log(Level.DEBUG, "Registered RCP CMS APP /" + contextName);
+ }
+ }
+
+ public void removeCmsApp(CmsApp cmsApp, Map<String, String> properties) {
+ String contextName = properties.get(CmsApp.CONTEXT_NAME_PROPERTY);
+ if (contextName != null) {
+ httpServer.thenAcceptAsync((httpServer) -> {
+ httpServer.removeContext("/" + contextName);
+ });
+ }
+ }
+
+ public void setHttpServer(HttpServer httpServer) {
+ Integer httpPort = httpServer.getAddress().getPort();
+ String baseUrl = "http://localhost:" + httpPort + "/";
+ Path runFile = CmsRcpDisplayFactory.getUrlRunFile();
+ try {
+ if (!Files.exists(runFile)) {
+ Files.createDirectories(runFile.getParent());
+ // TODO give read permission only to the owner
+ Files.createFile(runFile);
+ } else {
+ URI uri = URI.create(Files.readString(runFile));
+ if (!httpPort.equals(uri.getPort()))
+ if (!isPortAvailable(uri.getPort())) {
+ throw new IllegalStateException("Another CMS is running on " + runFile);
+ } else {
+ logger.log(Level.WARNING,
+ "Run file " + runFile + " found but port of " + uri + " is available. Overwriting...");
+ }
+ }
+ Files.writeString(runFile, baseUrl, StandardCharsets.UTF_8);
+ } catch (IOException e) {
+ throw new RuntimeException("Cannot write run file to " + runFile, e);
+ }
+ logger.log(DEBUG, "RCP available under " + baseUrl + ", written to " + runFile);
+ this.httpServer.complete(httpServer);
+ }
+
+ protected boolean isPortAvailable(int port) {
+ ServerSocket ss = null;
+ DatagramSocket ds = null;
+ try {
+ ss = new ServerSocket(port);
+ ss.setReuseAddress(true);
+ ds = new DatagramSocket(port);
+ ds.setReuseAddress(true);
+ return true;
+ } catch (IOException e) {
+ } finally {
+ if (ds != null) {
+ ds.close();
+ }
+
+ if (ss != null) {
+ try {
+ ss.close();
+ } catch (IOException e) {
+ /* should not be thrown */
+ }
+ }
+ }
+
+ return false;
+ }
+}
--- /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-17" />
+ <classpathentry kind="output" path="bin" />
+</classpath>
--- /dev/null
+/target/
+/bin/
+*.log
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>org.argeo.swt.specific.rcp</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: \
+!java.*,\
+org.apache.commons.io,\
+org.eclipse.core.commands,\
+!org.eclipse.core.runtime,\
+!org.eclipse.ui.plugin,\
+org.eclipse.swt,\
+javax.servlet.http;version="[3,5)",\
+javax.servlet;version="[3,5)",\
+*
+
+Export-Package: org.argeo.*,\
+org.eclipse.rap.fileupload.*;version="3.10",\
+org.eclipse.rap.rwt.*;version="3.10"
+
+# Was !org.eclipse.core.commands,\ why ?
+
+#Bundle-Activator: org.argeo.eclipse.ui.ArgeoUiPlugin
+#Bundle-ActivationPolicy: lazy
+#Ignore-Package: org.eclipse.core.commands
\ No newline at end of file
--- /dev/null
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/
--- /dev/null
+package org.argeo.eclipse.ui.rcp.internal.rwt;
+
+import org.eclipse.rap.rwt.client.Client;
+import org.eclipse.rap.rwt.client.service.BrowserNavigation;
+import org.eclipse.rap.rwt.client.service.BrowserNavigationListener;
+import org.eclipse.rap.rwt.client.service.ClientService;
+import org.eclipse.rap.rwt.client.service.JavaScriptExecutor;
+
+public class RcpClient implements Client {
+
+ @Override
+ public <T extends ClientService> T getService(Class<T> type) {
+ if (type.isAssignableFrom(JavaScriptExecutor.class))
+ return (T) javaScriptExecutor;
+ else if (type.isAssignableFrom(BrowserNavigation.class))
+ return (T) browserNavigation;
+ else
+ return null;
+ }
+
+ private JavaScriptExecutor javaScriptExecutor = new JavaScriptExecutor() {
+
+ @Override
+ public void execute(String code) {
+ // TODO Auto-generated method stub
+
+ }
+ };
+ private BrowserNavigation browserNavigation = new BrowserNavigation() {
+
+ @Override
+ public void pushState(String state, String title) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void addBrowserNavigationListener(
+ BrowserNavigationListener listener) {
+ // TODO Auto-generated method stub
+
+ }
+ };
+}
--- /dev/null
+package org.argeo.eclipse.ui.rcp.internal.rwt;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Collections;
+import java.util.Map;
+import java.util.TreeMap;
+
+import org.argeo.cms.util.StreamUtils;
+import org.eclipse.rap.rwt.service.ResourceManager;
+
+public class RcpResourceManager implements ResourceManager {
+ private Map<String, byte[]> register = Collections.synchronizedMap(new TreeMap<String, byte[]>());
+
+ @Override
+ public void register(String name, InputStream in) {
+ try {
+ register.put(name, StreamUtils.toByteArray(in));
+ } catch (IOException e) {
+ throw new RuntimeException("Cannot register " + name, e);
+ }
+ }
+
+ @Override
+ public boolean unregister(String name) {
+ return register.remove(name) != null;
+ }
+
+ @Override
+ public InputStream getRegisteredContent(String name) {
+ return new ByteArrayInputStream(register.get(name));
+ }
+
+ @Override
+ public String getLocation(String name) {
+ return name;
+ }
+
+ @Override
+ public boolean isRegistered(String name) {
+ return register.containsKey(name);
+ }
+
+}
--- /dev/null
+package org.argeo.eclipse.ui.specific;
+
+import java.awt.BorderLayout;
+import java.awt.Frame;
+import java.awt.Graphics;
+import java.awt.image.BufferedImage;
+
+import javax.swing.JPanel;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.awt.SWT_AWT;
+import org.eclipse.swt.widgets.Composite;
+
+public class BufferedImageDisplay extends Composite {
+ private BufferedImage image;
+
+ public BufferedImageDisplay(Composite parent, int style) {
+ super(parent, SWT.EMBEDDED | SWT.NO_BACKGROUND);
+ Frame frame = SWT_AWT.new_Frame(this);
+ frame.setLayout(new BorderLayout());
+ frame.add(new JPanel() {
+ private static final long serialVersionUID = 8924410573598922364L;
+
+ public void paintComponent(Graphics g) {
+ super.paintComponent(g);
+ if (image != null)
+ g.drawImage(image, 0, 0, this);
+ }
+
+ }, BorderLayout.CENTER);
+ frame.setVisible(true);
+ }
+
+ public void setImage(BufferedImage image) {
+ this.image = image;
+ }
+
+}
--- /dev/null
+package org.argeo.eclipse.ui.specific;
+
+import org.eclipse.swt.widgets.FileDialog;
+import org.eclipse.swt.widgets.Shell;
+
+public class CmsFileDialog extends FileDialog {
+ public CmsFileDialog(Shell parent, int style) {
+ super(parent, style);
+ }
+
+ public CmsFileDialog(Shell parent) {
+ super(parent);
+ }
+
+}
--- /dev/null
+package org.argeo.eclipse.ui.specific;
+
+import org.eclipse.rap.rwt.widgets.FileUpload;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.widgets.Composite;
+
+public class CmsFileUpload extends FileUpload {
+ public CmsFileUpload(Composite parent, int style) {
+ super(parent, style);
+ }
+
+ @Override
+ public void setText(String text) {
+ super.setText(text);
+ }
+
+ @Override
+ public String getFileName() {
+ return super.getFileName();
+ }
+
+ @Override
+ public String[] getFileNames() {
+ return super.getFileNames();
+ }
+
+ @Override
+ public void addSelectionListener(SelectionListener listener) {
+ super.addSelectionListener(listener);
+ }
+
+}
--- /dev/null
+package org.argeo.eclipse.ui.specific;
+
+/** RCP specific {@link NLS} to be extended */
+public class DefaultNLS {// extends NLS {
+// public final static String DEFAULT_BUNDLE_LOCATION = "/properties/plugin";
+//
+// public DefaultNLS() {
+// this(DEFAULT_BUNDLE_LOCATION);
+// }
+//
+// public DefaultNLS(String bundleName) {
+// NLS.initializeMessages(bundleName, getClass());
+// }
+}
\ No newline at end of file
--- /dev/null
+package org.argeo.eclipse.ui.specific;
+
+/** Constants which are specific to RWT.*/
+public interface EclipseUiConstants {
+ final static String CSS_CLASS = "org.eclipse.e4.ui.css.CssClassName";
+ final static String MARKUP_SUPPORT = null;
+}
--- /dev/null
+package org.argeo.eclipse.ui.specific;
+
+import org.eclipse.jface.viewers.ColumnViewer;
+import org.eclipse.jface.viewers.ColumnViewerToolTipSupport;
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.swt.widgets.Widget;
+
+/** Static utilities to bridge differences between RCP and RAP */
+public class EclipseUiSpecificUtils {
+ private final static String CSS_CLASS = "org.eclipse.e4.ui.css.CssClassName";
+
+ public static void setStyleData(Widget widget, Object data) {
+ widget.setData(CSS_CLASS, data);
+ }
+
+ public static Object getStyleData(Widget widget) {
+ return widget.getData(CSS_CLASS);
+ }
+
+ public static void setMarkupData(Widget widget) {
+ // does nothing
+ }
+
+ public static void setMarkupValidationDisabledData(Widget widget) {
+ // does nothing
+ }
+
+ /**
+ * TootlTip support is supported for {@link ColumnViewer} in RCP
+ *
+ * @see ColumnViewerToolTipSupport#enableFor(Viewer)
+ */
+ public static void enableToolTipSupport(Viewer viewer) {
+ if (viewer instanceof ColumnViewer)
+ ColumnViewerToolTipSupport.enableFor((ColumnViewer) viewer);
+ }
+
+ private EclipseUiSpecificUtils() {
+ }
+}
--- /dev/null
+package org.argeo.eclipse.ui.specific;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+
+import org.eclipse.swt.dnd.DND;
+import org.eclipse.swt.dnd.DropTarget;
+import org.eclipse.swt.dnd.DropTargetAdapter;
+import org.eclipse.swt.dnd.DropTargetEvent;
+import org.eclipse.swt.dnd.FileTransfer;
+import org.eclipse.swt.dnd.Transfer;
+import org.eclipse.swt.widgets.Control;
+
+public class FileDropAdapter {
+
+ public void prepareDropTarget(Control control, DropTarget dropTarget) {
+ dropTarget.setTransfer(new Transfer[] { FileTransfer.getInstance() });
+ dropTarget.addDropListener(new DropTargetAdapter() {
+ @Override
+ public void dropAccept(DropTargetEvent event) {
+ if (!FileTransfer.getInstance().isSupportedType(event.currentDataType)) {
+ event.detail = DND.DROP_NONE;
+ }
+ }
+
+ @Override
+ public void drop(DropTargetEvent event) {
+ handleFileDrop(control, event);
+ }
+ });
+ }
+
+ public void handleFileDrop(Control control, DropTargetEvent event) {
+ String fileList[] = null;
+ FileTransfer ft = FileTransfer.getInstance();
+ if (ft.isSupportedType(event.currentDataType)) {
+ fileList = (String[]) event.data;
+ }
+ System.out.println(Arrays.toString(fileList));
+ }
+
+ /** Executed in UI thread */
+ protected void processUpload(InputStream in, String fileName, String contentType) throws IOException {
+
+ }
+
+}
--- /dev/null
+package org.argeo.eclipse.ui.specific;
+
+import java.util.Locale;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.swt.widgets.Display;
+
+/** Singleton class providing single sources infos about the UI context. */
+public class UiContext {
+
+ public static HttpServletRequest getHttpRequest() {
+ return null;
+ }
+
+ public static HttpServletResponse getHttpResponse() {
+ return null;
+ }
+
+ public static Locale getLocale() {
+ return Locale.getDefault();
+ }
+
+ public static void setLocale(Locale locale) {
+ Locale.setDefault(locale);
+ }
+
+ /** Can always be null */
+ @SuppressWarnings("unchecked")
+ public static <T> T getData(String key) {
+ Display display = getDisplay();
+ if (display == null)
+ return null;
+ return (T) display.getData(key);
+ }
+
+ public static void setData(String key, Object value) {
+ Display display = getDisplay();
+ if (display == null)
+ throw new IllegalStateException("Not display available");
+ display.setData(key, value);
+ }
+
+ private static Display getDisplay() {
+ return Display.getCurrent();
+ }
+
+ private UiContext() {
+ }
+
+}
--- /dev/null
+package org.eclipse.rap.fileupload;
+
+public interface FileDetails {
+ String getContentType();
+
+ long getContentLength();
+
+ String getFileName();
+}
--- /dev/null
+package org.eclipse.rap.fileupload;
+
+import java.util.EventObject;
+
+public abstract class FileUploadEvent extends EventObject {
+
+ private static final long serialVersionUID = 1L;
+
+ protected FileUploadEvent(FileUploadHandler source) {
+ super(source);
+ }
+
+ public abstract FileDetails[] getFileDetails();
+
+ public abstract long getContentLength();
+
+ public abstract long getBytesRead();
+
+ public abstract Exception getException();
+
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2011, 2012 EclipseSource and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * EclipseSource - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.rap.fileupload;
+
+/**
+ * A file upload handler is used to accept file uploads from a client. After
+ * creating a file upload handler, the server will accept file uploads to the
+ * URL returned by <code>getUploadUrl()</code>. Upload listeners can be attached
+ * to react on progress. When the upload has finished, a FileUploadHandler has
+ * to be disposed of by calling its <code>dispose()</code> method.
+ *
+ * @noextend This class is not intended to be subclassed by clients.
+ */
+public class FileUploadHandler {
+
+ public FileUploadHandler(FileUploadReceiver fileUploadReceiver) {
+ }
+
+ public void dispose() {
+
+ }
+
+ public void addUploadListener(FileUploadListener listener) {
+
+ }
+
+ public String getUploadUrl() {
+ return null;
+ }
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2011, 2012 EclipseSource and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * EclipseSource - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.rap.fileupload;
+
+import org.eclipse.swt.widgets.Display;
+
+
+/**
+ * Listener to react on progress and completion of a file upload.
+ * <p>
+ * <strong>Note:</strong> This listener will be called from a different thread than the UI thread.
+ * Implementations must use {@link Display#asyncExec(Runnable)} to access the UI.
+ * </p>
+ *
+ * @see FileUploadEvent
+ */
+public interface FileUploadListener {
+
+ /**
+ * Called when new information about an in-progress upload is available.
+ *
+ * @param event event object that contains information about the uploaded file
+ * @see FileUploadEvent#getBytesRead()
+ */
+ void uploadProgress( FileUploadEvent event );
+
+ /**
+ * Called when a file upload has finished successfully.
+ *
+ * @param event event object that contains information about the uploaded file
+ * @see FileUploadEvent
+ */
+ void uploadFinished( FileUploadEvent event );
+
+ /**
+ * Called when a file upload failed.
+ *
+ * @param event event object that contains information about the uploaded file
+ * @see FileUploadEvent#getErrorMessage()
+ */
+ void uploadFailed( FileUploadEvent event );
+
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2011, 2013 EclipseSource and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * EclipseSource - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.rap.fileupload;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+
+/**
+ * Instances of this interface are responsible for reading and processing the data from a file
+ * upload.
+ */
+public abstract class FileUploadReceiver {
+
+ /**
+ * Reads and processes all data from the provided input stream.
+ *
+ * @param stream the stream to read from
+ * @param details the details of the uploaded file like file name, content-type and size
+ * @throws IOException if an input / output error occurs
+ */
+ public abstract void receive( InputStream stream, FileDetails details ) throws IOException;
+
+}
--- /dev/null
+package org.eclipse.rap.rwt;
+
+import java.util.Locale;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.argeo.eclipse.ui.rcp.internal.rwt.RcpClient;
+import org.argeo.eclipse.ui.rcp.internal.rwt.RcpResourceManager;
+import org.eclipse.rap.rwt.client.Client;
+import org.eclipse.rap.rwt.service.ResourceManager;
+
+public class RWT {
+ public final static String CUSTOM_VARIANT = "argeo-rcp:CUSTOM_VARIANT";
+ public final static String MARKUP_ENABLED = "argeo-rcp:MARKUP_ENABLED";
+ public static final String TOOLTIP_MARKUP_ENABLED = "argeo-rcp:TOOLTIP_MARKUP_ENABLED";
+ public final static String CUSTOM_ITEM_HEIGHT = "argeo-rcp:CUSTOM_ITEM_HEIGHT";
+ public final static String ACTIVE_KEYS = "argeo-rcp:ACTIVE_KEYS";
+ public final static String CANCEL_KEYS = "argeo-rcp:CANCEL_KEYS";
+ public final static String DEFAULT_THEME_ID = "argeo-rcp:DEFAULT_THEME_ID";
+
+ public final static int HYPERLINK = 0;
+
+ private static Locale locale = Locale.getDefault();
+ private static RcpClient client = new RcpClient();
+ private static ResourceManager resourceManager = new RcpResourceManager();
+ static {
+
+ }
+
+ public static Locale getLocale() {
+ return locale;
+ }
+
+ public static HttpServletRequest getRequest() {
+ return null;
+ }
+
+ public static ResourceManager getResourceManager() {
+ return resourceManager;
+ }
+
+ public static Client getClient() {
+ return client;
+ }
+}
--- /dev/null
+package org.eclipse.rap.rwt;
+
+public class SingletonUtil {
+ public static <T> T getSessionInstance(Class<T> clss) {
+ return null;
+ }
+}
--- /dev/null
+package org.eclipse.rap.rwt.application;
+
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Shell;
+
+public abstract class AbstractEntryPoint implements EntryPoint {
+ private Display display;
+ private Shell shell;
+
+ protected Shell createShell(Display display) {
+ return new Shell(display);
+ }
+
+ protected void createContents(Composite parent) {
+
+ }
+
+ public int createUI() {
+ display = new Display();
+ shell = createShell(display);
+ shell.setLayout(new GridLayout(1, false));
+ createContents(shell);
+ if (shell.getMaximized()) {
+ shell.layout();
+ } else {
+ shell.pack();
+ }
+ shell.open();
+ while (!shell.isDisposed()) {
+ if (!display.readAndDispatch()) {
+ display.sleep();
+ }
+ }
+ display.dispose();
+ return 0;
+ }
+
+ protected Shell getShell() {
+ return shell;
+ }
+}
--- /dev/null
+package org.eclipse.rap.rwt.application;
+
+import java.util.Map;
+
+import org.eclipse.rap.rwt.service.ResourceLoader;
+
+public interface Application {
+ public static enum OperationMode {
+ JEE_COMPATIBILITY, SWT_COMPATIBILITY,
+ }
+
+ void setOperationMode(OperationMode operationMode);
+
+ void addResource(String name, ResourceLoader resourceLoader);
+
+ void setExceptionHandler(ExceptionHandler exceptionHandler);
+
+ void addEntryPoint(String path, EntryPointFactory entryPointFactory,
+ Map<String, String> properties);
+
+ void addEntryPoint(String path, Class<? extends EntryPoint> entryPoint,
+ Map<String, String> properties);
+
+ void addStyleSheet(String themeId, String styleSheetLocation,
+ ResourceLoader resourceLoader);
+
+}
--- /dev/null
+package org.eclipse.rap.rwt.application;
+
+public interface ApplicationConfiguration {
+ void configure(Application application);
+}
--- /dev/null
+package org.eclipse.rap.rwt.application;
+
+public interface EntryPoint {
+ int createUI();
+}
--- /dev/null
+package org.eclipse.rap.rwt.application;
+
+public interface EntryPointFactory {
+ public EntryPoint create();
+}
--- /dev/null
+package org.eclipse.rap.rwt.application;
+
+public interface ExceptionHandler {
+ public void handleException(Throwable throwable);
+}
--- /dev/null
+package org.eclipse.rap.rwt.client;
+
+import java.io.Serializable;
+
+import org.eclipse.rap.rwt.client.service.ClientService;
+
+public interface Client extends Serializable {
+
+ /**
+ * Returns this client's implementation of a given service, if available.
+ *
+ * @param type the type of the requested service, must be a subtype of ClientService
+ * @return the requested service if provided by this client, otherwise <code>null</code>
+ * @see ClientService
+ */
+ <T extends ClientService> T getService( Class<T> type );
+
+}
\ No newline at end of file
--- /dev/null
+package org.eclipse.rap.rwt.client;
+
+public interface WebClient {
+ public final static String FAVICON = "rcp:FAVICON";
+ public final static String PAGE_TITLE = "rcp:PAGE_TITLE";
+ public final static String BODY_HTML = "rcp:BODY_HTML";
+ public final static String THEME_ID = "rcp:THEME_ID";
+ public final static String HEAD_HTML = "rcp:HEAD_HTML";
+ public final static String PAGE_OVERFLOW = "rcp:PAGE_OVERFLOW";
+}
--- /dev/null
+package org.eclipse.rap.rwt.client.service;
+
+public interface BrowserNavigation extends ClientService {
+ void pushState(String state, String title);
+
+ void addBrowserNavigationListener(BrowserNavigationListener listener);
+}
--- /dev/null
+package org.eclipse.rap.rwt.client.service;
+
+public class BrowserNavigationEvent {
+ private String state;
+
+ public String getState() {
+ return state;
+ }
+
+}
--- /dev/null
+package org.eclipse.rap.rwt.client.service;
+
+public interface BrowserNavigationListener {
+ public void navigated(BrowserNavigationEvent event);
+}
--- /dev/null
+package org.eclipse.rap.rwt.client.service;
+
+import java.io.Serializable;
+
+public interface ClientService extends Serializable {
+}
--- /dev/null
+package org.eclipse.rap.rwt.client.service;
+
+public interface JavaScriptExecutor extends ClientService {
+ public void execute( String code );
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2012 EclipseSource and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * EclipseSource - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.rap.rwt.client.service;
+
+/**
+ * The UrlLauncher service allows loading an URL in an external window, application or save dialog.
+ *
+ * @since 2.0
+ * @noimplement This interface is not intended to be implemented by clients.
+ */
+public interface UrlLauncher extends ClientService {
+
+ /**
+ * Opens the given URL.
+ *
+ * Any HTTP URL or relative URL will be opened in a new window.
+ * Modern browser may block any attempt to open new windows, but will usually prompt the user to
+ * accept or ignore. Even if accepted, the decision may be applied to only this attempt, or only
+ * to future attempts. It could also trigger a document reload, causing a session restart.
+ *
+ * Non-HTTP URLs like "mailto" will not create a new browser window, but require the client
+ * to have a matching protocol handler registered.
+ *
+ * @param url the URL to open
+ */
+ void openURL( String url );
+
+}
--- /dev/null
+package org.eclipse.rap.rwt.service;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+public interface ResourceLoader {
+ public InputStream getResourceAsStream(String resourceName)
+ throws IOException;
+}
--- /dev/null
+package org.eclipse.rap.rwt.service;
+
+import java.io.InputStream;
+
+public interface ResourceManager {
+ public void register(String name, InputStream in);
+
+ boolean unregister(String name);
+
+ public InputStream getRegisteredContent(String name);
+
+ public String getLocation(String name);
+
+ public boolean isRegistered(String name);
+}
--- /dev/null
+package org.eclipse.rap.rwt.service;
+
+/** Mock, does nothing as this is irrelevant for RCP. */
+public class ServerPushSession {
+ public void start() {
+
+ }
+
+ public void stop() {
+
+ }
+}
--- /dev/null
+package org.eclipse.rap.rwt.widgets;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.Widget;
+
+public class DropDown {
+ private boolean visible=false;
+
+ public DropDown(Widget parent, int style) {
+ // FIXME implement a shell
+ }
+
+ public DropDown(Widget parent) {
+ this(parent, SWT.NONE);
+ }
+
+ public void setVisible(boolean visible) {
+ this.visible = visible;
+ }
+
+ public boolean isVisible() {
+ return visible;
+ }
+
+ public void setItems( String[] items ) {
+
+ }
+
+ public void setSelectionIndex( int selection ) {
+
+ }
+
+}
--- /dev/null
+package org.eclipse.rap.rwt.widgets;
+
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.widgets.Composite;
+
+public class FileUpload extends Composite {
+
+ public FileUpload(Composite parent, int style) {
+ super(parent, style);
+ }
+
+ public void addSelectionListener(SelectionListener listener) {
+
+ }
+
+ public void submit(String url) {
+
+ }
+
+ public void setImage(Image image) {
+
+ }
+
+ public void setText(String text) {
+
+ }
+
+ public String getFileName() {
+ return null;
+ }
+
+ public String[] getFileNames() {
+ return null;
+ }
+
+}