Merge remote-tracking branch 'origin/unstable' into testing
authorMathieu <mbaudier@argeo.org>
Tue, 6 Dec 2022 05:12:22 +0000 (06:12 +0100)
committerMathieu <mbaudier@argeo.org>
Tue, 6 Dec 2022 05:12:22 +0000 (06:12 +0100)
568 files changed:
COPYING.LESSER [deleted file]
Makefile
Makefile-ext.mk [deleted file]
Makefile-rcp.mk [new file with mode: 0644]
branch.mk
cms/.gitignore [deleted file]
cms/org.argeo.cms.integration/.classpath [deleted file]
cms/org.argeo.cms.integration/.gitignore [deleted file]
cms/org.argeo.cms.integration/.project [deleted file]
cms/org.argeo.cms.integration/.settings/org.eclipse.jdt.core.prefs [deleted file]
cms/org.argeo.cms.integration/META-INF/.gitignore [deleted file]
cms/org.argeo.cms.integration/bnd.bnd [deleted file]
cms/org.argeo.cms.integration/build.properties [deleted file]
cms/org.argeo.cms.integration/src/org/argeo/cms/integration/CmsExceptionsChain.java [deleted file]
cms/org.argeo.cms.integration/src/org/argeo/cms/integration/CmsLoginServlet.java [deleted file]
cms/org.argeo.cms.integration/src/org/argeo/cms/integration/CmsLogoutServlet.java [deleted file]
cms/org.argeo.cms.integration/src/org/argeo/cms/integration/CmsPrivateServletContext.java [deleted file]
cms/org.argeo.cms.integration/src/org/argeo/cms/integration/CmsSessionDescriptor.java [deleted file]
cms/org.argeo.cms.integration/src/org/argeo/cms/integration/CmsTokenServlet.java [deleted file]
cms/org.argeo.cms.integration/src/org/argeo/cms/integration/JcrReadServlet.java [deleted file]
cms/org.argeo.cms.integration/src/org/argeo/cms/integration/JcrWriteServlet.java [deleted file]
cms/org.argeo.cms.integration/src/org/argeo/cms/integration/TokenDescriptor.java [deleted file]
cms/org.argeo.cms.integration/src/org/argeo/cms/integration/XslTemplate.java [deleted file]
cms/org.argeo.cms.integration/src/org/argeo/cms/integration/package-info.java [deleted file]
cms/org.argeo.cms.integration/src/org/argeo/cms/websocket/CmsWebSocketConfigurator.java [deleted file]
cms/org.argeo.cms.integration/src/org/argeo/cms/websocket/package-info.java [deleted file]
cms/org.argeo.cms.integration/src/org/argeo/maintenance/backup/vfs/AbstractAtomicBackup.java [deleted file]
cms/org.argeo.cms.integration/src/org/argeo/maintenance/backup/vfs/AtomicBackup.java [deleted file]
cms/org.argeo.cms.integration/src/org/argeo/maintenance/backup/vfs/BackupContext.java [deleted file]
cms/org.argeo.cms.integration/src/org/argeo/maintenance/backup/vfs/BackupFileSystemManager.java [deleted file]
cms/org.argeo.cms.integration/src/org/argeo/maintenance/backup/vfs/BackupPurge.java [deleted file]
cms/org.argeo.cms.integration/src/org/argeo/maintenance/backup/vfs/BackupUtils.java [deleted file]
cms/org.argeo.cms.integration/src/org/argeo/maintenance/backup/vfs/MaintenanceException.java [deleted file]
cms/org.argeo.cms.integration/src/org/argeo/maintenance/backup/vfs/MySqlBackup.java [deleted file]
cms/org.argeo.cms.integration/src/org/argeo/maintenance/backup/vfs/OpenLdapBackup.java [deleted file]
cms/org.argeo.cms.integration/src/org/argeo/maintenance/backup/vfs/OsCallBackup.java [deleted file]
cms/org.argeo.cms.integration/src/org/argeo/maintenance/backup/vfs/PostgreSqlBackup.java [deleted file]
cms/org.argeo.cms.integration/src/org/argeo/maintenance/backup/vfs/SimpleBackupContext.java [deleted file]
cms/org.argeo.cms.integration/src/org/argeo/maintenance/backup/vfs/SimpleBackupPurge.java [deleted file]
cms/org.argeo.cms.integration/src/org/argeo/maintenance/backup/vfs/SvnBackup.java [deleted file]
cms/org.argeo.cms.integration/src/org/argeo/maintenance/backup/vfs/SystemBackup.java [deleted file]
cms/org.argeo.cms.integration/src/org/argeo/maintenance/backup/vfs/package-info.java [deleted file]
cms/org.argeo.cms.integration/src/org/argeo/ssh/AbstractSsh.java [deleted file]
cms/org.argeo.cms.integration/src/org/argeo/ssh/BasicSshServer.java [deleted file]
cms/org.argeo.cms.integration/src/org/argeo/ssh/Sftp.java [deleted file]
cms/org.argeo.cms.integration/src/org/argeo/ssh/Ssh.java [deleted file]
cms/org.argeo.cms.integration/src/org/argeo/ssh/SshKeyPair.java [deleted file]
cms/org.argeo.cms.integration/src/org/argeo/ssh/SshSync.java [deleted file]
cms/org.argeo.cms.integration/src/org/argeo/ssh/package-info.java [deleted file]
cnf/build.bnd [deleted file]
cnf/testing.bnd [deleted file]
cnf/unstable.bnd [deleted file]
configure [changed mode: 0644->0755]
ext/org.argeo.ext.equinox.jetty/.classpath [deleted file]
ext/org.argeo.ext.equinox.jetty/.gitignore [deleted file]
ext/org.argeo.ext.equinox.jetty/.project [deleted file]
ext/org.argeo.ext.equinox.jetty/META-INF/.gitignore [deleted file]
ext/org.argeo.ext.equinox.jetty/bnd.bnd [deleted file]
ext/org.argeo.ext.equinox.jetty/build.properties [deleted file]
ext/org.argeo.ext.equinox.jetty/src/org/argeo/equinox/jetty/CmsJettyCustomizer.java [deleted file]
ext/org.argeo.ext.equinox.jetty/src/org/argeo/equinox/jetty/package-info.java [deleted file]
ext/org.argeo.ext.slf4j/.classpath [deleted file]
ext/org.argeo.ext.slf4j/.project [deleted file]
ext/org.argeo.ext.slf4j/bnd.bnd [deleted file]
ext/org.argeo.ext.slf4j/build.properties [deleted file]
ext/org.argeo.ext.slf4j/src/org/slf4j/impl/ArgeoLogger.java [deleted file]
ext/org.argeo.ext.slf4j/src/org/slf4j/impl/StaticLoggerBinder.java [deleted file]
ext/org.argeo.ext.slf4j/src/org/slf4j/impl/SystemLoggingAdapter.java [deleted file]
ext/osgi.system/.classpath [new file with mode: 0644]
ext/osgi.system/.project [new file with mode: 0644]
ext/osgi.system/.settings/org.eclipse.jdt.core.prefs [new file with mode: 0644]
ext/osgi.system/.settings/org.eclipse.pde.core.prefs [new file with mode: 0644]
ext/osgi.system/.settings/org.eclipse.pde.prefs [new file with mode: 0644]
ext/osgi.system/JavaSE-9.profile [new file with mode: 0644]
ext/osgi.system/build.properties [new file with mode: 0644]
ext/osgi.system/profile.list [new file with mode: 0644]
graalvm/org.argeo.slc.graalvm/.classpath [new file with mode: 0644]
graalvm/org.argeo.slc.graalvm/.project [new file with mode: 0644]
graalvm/org.argeo.slc.graalvm/bnd.bnd [new file with mode: 0644]
graalvm/org.argeo.slc.graalvm/build.properties [new file with mode: 0644]
graalvm/org.argeo.slc.graalvm/src/org/argeo/slc/graalvm/feature/ArgeoToolFeature.java [new file with mode: 0644]
jni/.cproject [new file with mode: 0644]
jni/.project [new file with mode: 0644]
jni/.settings/language.settings.xml [new file with mode: 0644]
jni/.settings/org.eclipse.cdt.core.prefs [new file with mode: 0644]
jni/Makefile [new file with mode: 0644]
jni/jni.mk [new file with mode: 0644]
jni/org_argeo_api_uuid_libuuid/.gitignore [new file with mode: 0644]
jni/org_argeo_api_uuid_libuuid/Makefile [new file with mode: 0644]
jni/org_argeo_api_uuid_libuuid/org_argeo_api_uuid_libuuid_DirectLibuuidFactory.c [new file with mode: 0644]
jni/org_argeo_api_uuid_libuuid/org_argeo_api_uuid_libuuid_DirectLibuuidFactory.h [new file with mode: 0644]
jni/org_argeo_api_uuid_libuuid/org_argeo_api_uuid_libuuid_LibuuidFactory.c [new file with mode: 0644]
jni/org_argeo_api_uuid_libuuid/org_argeo_api_uuid_libuuid_LibuuidFactory.h [new file with mode: 0644]
lib/linux/org.argeo.slc.systemd/.classpath [new file with mode: 0644]
lib/linux/org.argeo.slc.systemd/.project [new file with mode: 0644]
lib/linux/org.argeo.slc.systemd/OSGI-INF/systemdServiceStatistics.xml [new file with mode: 0644]
lib/linux/org.argeo.slc.systemd/bnd.bnd [new file with mode: 0644]
lib/linux/org.argeo.slc.systemd/build.properties [new file with mode: 0644]
lib/linux/org.argeo.slc.systemd/src/org/argeo/slc/systemd/dbus/PlainDBusTest.java [new file with mode: 0644]
lib/linux/org.argeo.slc.systemd/src/org/argeo/slc/systemd/dbus/ServiceStatistics.java [new file with mode: 0644]
org.argeo.slc.api/src/org/argeo/slc/ManifestConstants.java
org.argeo.slc.api/src/org/argeo/slc/WellKnownConstants.java [new file with mode: 0644]
org.argeo.slc.cms/.classpath [new file with mode: 0644]
org.argeo.slc.cms/.project [new file with mode: 0644]
org.argeo.slc.cms/bnd.bnd [new file with mode: 0644]
org.argeo.slc.cms/build.properties [new file with mode: 0644]
org.argeo.slc.cms/src/org/argeo/cms/sql/postgres/CheckPg.java [new file with mode: 0644]
org.argeo.slc.cms/src/org/argeo/slc/backup/vfs/AbstractAtomicBackup.java [new file with mode: 0644]
org.argeo.slc.cms/src/org/argeo/slc/backup/vfs/AtomicBackup.java [new file with mode: 0644]
org.argeo.slc.cms/src/org/argeo/slc/backup/vfs/BackupContext.java [new file with mode: 0644]
org.argeo.slc.cms/src/org/argeo/slc/backup/vfs/BackupFileSystemManager.java [new file with mode: 0644]
org.argeo.slc.cms/src/org/argeo/slc/backup/vfs/BackupPurge.java [new file with mode: 0644]
org.argeo.slc.cms/src/org/argeo/slc/backup/vfs/BackupUtils.java [new file with mode: 0644]
org.argeo.slc.cms/src/org/argeo/slc/backup/vfs/MaintenanceException.java [new file with mode: 0644]
org.argeo.slc.cms/src/org/argeo/slc/backup/vfs/MySqlBackup.java [new file with mode: 0644]
org.argeo.slc.cms/src/org/argeo/slc/backup/vfs/OpenLdapBackup.java [new file with mode: 0644]
org.argeo.slc.cms/src/org/argeo/slc/backup/vfs/OsCallBackup.java [new file with mode: 0644]
org.argeo.slc.cms/src/org/argeo/slc/backup/vfs/PostgreSqlBackup.java [new file with mode: 0644]
org.argeo.slc.cms/src/org/argeo/slc/backup/vfs/SimpleBackupContext.java [new file with mode: 0644]
org.argeo.slc.cms/src/org/argeo/slc/backup/vfs/SimpleBackupPurge.java [new file with mode: 0644]
org.argeo.slc.cms/src/org/argeo/slc/backup/vfs/SvnBackup.java [new file with mode: 0644]
org.argeo.slc.cms/src/org/argeo/slc/backup/vfs/SystemBackup.java [new file with mode: 0644]
org.argeo.slc.cms/src/org/argeo/slc/backup/vfs/package-info.java [new file with mode: 0644]
org.argeo.slc.cms/src/org/argeo/slc/cms/deploy/CmsDeployedSystem.java [new file with mode: 0644]
org.argeo.slc.cms/src/org/argeo/slc/cms/deploy/CmsDeploymentData.java [new file with mode: 0644]
org.argeo.slc.cms/src/org/argeo/slc/cms/deploy/CmsTargetData.java [new file with mode: 0644]
org.argeo.slc.cms/src/org/argeo/slc/cms/deploy/SimpleCmsDeploymentData.java [new file with mode: 0644]
org.argeo.slc.cms/src/org/argeo/slc/cms/deploy/SimpleCmsTargetData.java [new file with mode: 0644]
org.argeo.slc.cms/src/org/argeo/slc/cms/deploy/osgi/CmsOsgiDeployedSystem.java [new file with mode: 0644]
org.argeo.slc.cms/src/org/argeo/slc/cms/deploy/osgi/CmsOsgiDeployment.java [new file with mode: 0644]
org.argeo.slc.cms/src/org/argeo/slc/cms/distribution/A2Distribution.java [new file with mode: 0644]
org.argeo.slc.cms/src/org/argeo/slc/cms/distribution/A2ModuleDistribution.java [new file with mode: 0644]
org.argeo.slc.cms/src/org/argeo/slc/cms/test/CmsSmokeTest.java [new file with mode: 0644]
org.argeo.slc.cms/src/org/argeo/slc/cms/test/MinimalJvm.java [new file with mode: 0644]
org.argeo.slc.factory/.classpath [deleted file]
org.argeo.slc.factory/.project [deleted file]
org.argeo.slc.factory/bnd.bnd [deleted file]
org.argeo.slc.factory/build.properties [deleted file]
org.argeo.slc.factory/src/org/argeo/slc/factory/A2Factory.java [deleted file]
org.argeo.slc.factory/src/org/argeo/slc/factory/m2/Artifact.java [deleted file]
org.argeo.slc.factory/src/org/argeo/slc/factory/m2/DefaultArtifact.java [deleted file]
org.argeo.slc.factory/src/org/argeo/slc/factory/m2/MavenConventionsUtils.java [deleted file]
org.argeo.slc.jcr/build.properties
org.argeo.slc.jcr/src/org/argeo/cli/jcr/JcrCommands.java
org.argeo.slc.jcr/src/org/argeo/cli/jcr/JcrSync.java
org.argeo.slc.jcr/src/org/argeo/slc/jcr/SlcJcrResultUtils.java
org.argeo.slc.repo/src/org/argeo/slc/repo/RepoUtils.java
org.argeo.slc.repo/src/org/argeo/slc/repo/core/RepoServiceImpl.java
org.argeo.slc.runtime/.classpath
org.argeo.slc.runtime/bnd.bnd
org.argeo.slc.runtime/build.properties
org.argeo.slc.runtime/ext/test/org/argeo/fs/FsUtilsTest.java [deleted file]
org.argeo.slc.runtime/src/org/argeo/slc/cli/CommandArgsException.java [deleted file]
org.argeo.slc.runtime/src/org/argeo/slc/cli/CommandRuntimeException.java [deleted file]
org.argeo.slc.runtime/src/org/argeo/slc/cli/CommandsCli.java [deleted file]
org.argeo.slc.runtime/src/org/argeo/slc/cli/DescribedCommand.java [deleted file]
org.argeo.slc.runtime/src/org/argeo/slc/cli/HelpCommand.java [deleted file]
org.argeo.slc.runtime/src/org/argeo/slc/cli/fs/FileSync.java [deleted file]
org.argeo.slc.runtime/src/org/argeo/slc/cli/fs/FsCommands.java [deleted file]
org.argeo.slc.runtime/src/org/argeo/slc/cli/fs/PathSync.java [deleted file]
org.argeo.slc.runtime/src/org/argeo/slc/cli/fs/SyncFileVisitor.java [deleted file]
org.argeo.slc.runtime/src/org/argeo/slc/cli/fs/package-info.java [deleted file]
org.argeo.slc.runtime/src/org/argeo/slc/cli/package-info.java [deleted file]
org.argeo.slc.runtime/src/org/argeo/slc/cli/posix/Echo.java [deleted file]
org.argeo.slc.runtime/src/org/argeo/slc/cli/posix/PosixCommands.java [deleted file]
org.argeo.slc.runtime/src/org/argeo/slc/cli/posix/package-info.java [deleted file]
org.argeo.slc.runtime/src/org/argeo/slc/runtime/ArgeoCli.java [deleted file]
org.argeo.slc.runtime/src/org/argeo/slc/runtime/ProcessThread.java
org.argeo.slc.runtime/src/org/argeo/slc/sync/BasicSyncFileVisitor.java [deleted file]
org.argeo.slc.runtime/src/org/argeo/slc/sync/FsSyncUtils.java [deleted file]
org.argeo.slc.runtime/src/org/argeo/slc/sync/SyncResult.java [deleted file]
sdk/argeo-tp-rap.target [deleted file]
sdk/branches/testing.bnd [new file with mode: 0644]
sdk/branches/unstable.bnd [new file with mode: 0644]
sdk/init/node/.gitignore [deleted file]
sdk/init/node/ou=roles,ou=node.ldif [deleted file]
sdk/init/private/.gitignore [new file with mode: 0644]
sdk/init/private/ou=roles,ou=node.ldif [new file with mode: 0644]
sdk/output-argeo-tp-rap.target [deleted file]
sdk/output-argeo-tp-rcp.target [deleted file]
swt/org.argeo.tool.devops.e4/.classpath [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/.project [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/OSGI-INF/cmsAdminRap.xml [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/OSGI-INF/homeRepository.xml [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/OSGI-INF/userAdminWrapper.xml [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/bnd.bnd [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/build.properties [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/e4xmi/devops.e4xmi [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/files/NodeFsBrowserView.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/files/package-info.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/jcr/EclipseJcrMonitor.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/jcr/GenericPropertyPage.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/jcr/JcrBrowserView.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/jcr/JcrE4DClickListener.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/jcr/JcrNodeEditor.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/jcr/SimplePart.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/jcr/handlers/AddFolderNode.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/jcr/handlers/AddRemoteRepository.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/jcr/handlers/DeleteNodes.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/jcr/handlers/Refresh.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/jcr/handlers/RenameNode.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/jcr/handlers/package-info.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/jcr/package-info.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/maintenance/AbstractOsgiComposite.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/maintenance/Browse.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/maintenance/ConnectivityDeploymentUi.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/maintenance/DataDeploymentUi.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/maintenance/DeploymentEntryPoint.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/maintenance/LogDeploymentUi.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/maintenance/MaintenanceStyles.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/maintenance/NonAdminPage.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/maintenance/SecurityDeploymentUi.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/maintenance/package-info.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/monitoring/BundleNode.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/monitoring/BundlesView.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/monitoring/CmsSessionsView.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/monitoring/ModulesView.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/monitoring/OsgiConfigurationsView.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/monitoring/OsgiExplorerImages.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/monitoring/ServiceReferenceNode.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/monitoring/StateLabelProvider.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/monitoring/package-info.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/parts/EgoDashboard.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/users/AbstractRoleEditor.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/users/CmsWorkbenchStyles.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/users/GroupEditor.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/users/GroupsView.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/users/SecurityAdminImages.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/users/UiAdminUtils.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/users/UiUserAdminListener.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/users/UserAdminWrapper.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/users/UserBatchUpdateWizard.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/users/UserEditor.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/users/UserTableDefaultDClickListener.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/users/UsersView.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/users/handlers/DeleteGroups.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/users/handlers/DeleteUsers.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/users/handlers/NewGroup.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/users/handlers/NewUser.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/users/handlers/package-info.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/users/package-info.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/users/providers/CommonNameLP.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/users/providers/DomainNameLP.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/users/providers/MailLP.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/users/providers/RoleIconLP.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/users/providers/UserAdminAbstractLP.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/users/providers/UserDragListener.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/users/providers/UserFilter.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/users/providers/UserNameLP.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/users/providers/package-info.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/jcr/e4/rap/CmsE4AdminApp.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/swt/useradmin/LdifUsersTable.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/swt/useradmin/PickUpUserDialog.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/swt/useradmin/UserLP.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/swt/useradmin/UsersImages.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/swt/useradmin/ViewerUtils.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/swt/useradmin/package-info.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/eclipse/forms/AbstractFormPart.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/eclipse/forms/FormColors.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/eclipse/forms/FormFonts.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/eclipse/forms/FormToolkit.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/eclipse/forms/FormUtil.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/eclipse/forms/IFormColors.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/eclipse/forms/IFormPart.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/eclipse/forms/IManagedForm.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/eclipse/forms/IPartSelectionListener.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/eclipse/forms/ManagedForm.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/eclipse/forms/editor/FormEditor.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/eclipse/forms/editor/FormPage.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/eclipse/forms/editor/IFormPage.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/jcr/DefaultRepositoryRegister.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/jcr/FullVersioningTreeContentProvider.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/jcr/JcrBrowserUtils.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/jcr/JcrDClickListener.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/jcr/JcrImages.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/jcr/JcrTreeContentProvider.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/jcr/NodeContentProvider.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/jcr/NodeLabelProvider.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/jcr/OsgiRepositoryRegister.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/jcr/PropertiesContentProvider.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/jcr/PropertyLabelProvider.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/jcr/RepositoryRegister.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/jcr/VersionLabelProvider.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/jcr/model/MaintainedRepositoryElem.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/jcr/model/RemoteRepositoryElem.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/jcr/model/RepositoriesElem.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/jcr/model/RepositoryElem.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/jcr/model/SingleJcrNodeElem.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/jcr/model/WorkspaceElem.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/jcr/model/package-info.java [new file with mode: 0644]
swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/jcr/package-info.java [new file with mode: 0644]
swt/org.argeo.tool.swt/.classpath [new file with mode: 0644]
swt/org.argeo.tool.swt/.project [new file with mode: 0644]
swt/org.argeo.tool.swt/bnd.bnd [new file with mode: 0644]
swt/org.argeo.tool.swt/build.properties [new file with mode: 0644]
swt/org.argeo.tool.swt/icons/actions/add.png [new file with mode: 0644]
swt/org.argeo.tool.swt/icons/actions/close-all.png [new file with mode: 0644]
swt/org.argeo.tool.swt/icons/actions/delete.png [new file with mode: 0644]
swt/org.argeo.tool.swt/icons/actions/edit.png [new file with mode: 0644]
swt/org.argeo.tool.swt/icons/actions/save-all.png [new file with mode: 0644]
swt/org.argeo.tool.swt/icons/actions/save.png [new file with mode: 0644]
swt/org.argeo.tool.swt/icons/active.gif [new file with mode: 0644]
swt/org.argeo.tool.swt/icons/add.gif [new file with mode: 0644]
swt/org.argeo.tool.swt/icons/add.png [new file with mode: 0644]
swt/org.argeo.tool.swt/icons/addFolder.gif [new file with mode: 0644]
swt/org.argeo.tool.swt/icons/addPrivileges.gif [new file with mode: 0644]
swt/org.argeo.tool.swt/icons/addRepo.gif [new file with mode: 0644]
swt/org.argeo.tool.swt/icons/addWorkspace.png [new file with mode: 0644]
swt/org.argeo.tool.swt/icons/adminLog.gif [new file with mode: 0644]
swt/org.argeo.tool.swt/icons/batch.gif [new file with mode: 0644]
swt/org.argeo.tool.swt/icons/begin.gif [new file with mode: 0755]
swt/org.argeo.tool.swt/icons/binary.png [new file with mode: 0644]
swt/org.argeo.tool.swt/icons/browser.gif [new file with mode: 0644]
swt/org.argeo.tool.swt/icons/bundles.gif [new file with mode: 0644]
swt/org.argeo.tool.swt/icons/changePassword.gif [new file with mode: 0644]
swt/org.argeo.tool.swt/icons/clear.gif [new file with mode: 0644]
swt/org.argeo.tool.swt/icons/close-all.png [new file with mode: 0644]
swt/org.argeo.tool.swt/icons/commit.gif [new file with mode: 0755]
swt/org.argeo.tool.swt/icons/delete.png [new file with mode: 0644]
swt/org.argeo.tool.swt/icons/dumpNode.gif [new file with mode: 0644]
swt/org.argeo.tool.swt/icons/file.gif [new file with mode: 0644]
swt/org.argeo.tool.swt/icons/folder.gif [new file with mode: 0644]
swt/org.argeo.tool.swt/icons/getSize.gif [new file with mode: 0644]
swt/org.argeo.tool.swt/icons/group.png [new file with mode: 0644]
swt/org.argeo.tool.swt/icons/home.gif [new file with mode: 0644]
swt/org.argeo.tool.swt/icons/home.png [new file with mode: 0644]
swt/org.argeo.tool.swt/icons/import_fs.png [new file with mode: 0644]
swt/org.argeo.tool.swt/icons/installed.gif [new file with mode: 0644]
swt/org.argeo.tool.swt/icons/log.gif [new file with mode: 0644]
swt/org.argeo.tool.swt/icons/logout.png [new file with mode: 0644]
swt/org.argeo.tool.swt/icons/maintenance.gif [new file with mode: 0644]
swt/org.argeo.tool.swt/icons/node.gif [new file with mode: 0644]
swt/org.argeo.tool.swt/icons/nodes.gif [new file with mode: 0644]
swt/org.argeo.tool.swt/icons/osgi_explorer.gif [new file with mode: 0644]
swt/org.argeo.tool.swt/icons/password.gif [new file with mode: 0644]
swt/org.argeo.tool.swt/icons/person-logged-in.png [new file with mode: 0644]
swt/org.argeo.tool.swt/icons/person.png [new file with mode: 0644]
swt/org.argeo.tool.swt/icons/query.png [new file with mode: 0644]
swt/org.argeo.tool.swt/icons/refresh.png [new file with mode: 0644]
swt/org.argeo.tool.swt/icons/remote_connected.gif [new file with mode: 0644]
swt/org.argeo.tool.swt/icons/remote_disconnected.gif [new file with mode: 0644]
swt/org.argeo.tool.swt/icons/remove.gif [new file with mode: 0644]
swt/org.argeo.tool.swt/icons/removePrivileges.gif [new file with mode: 0644]
swt/org.argeo.tool.swt/icons/rename.gif [new file with mode: 0644]
swt/org.argeo.tool.swt/icons/repositories.gif [new file with mode: 0644]
swt/org.argeo.tool.swt/icons/repository_connected.gif [new file with mode: 0644]
swt/org.argeo.tool.swt/icons/repository_disconnected.gif [new file with mode: 0644]
swt/org.argeo.tool.swt/icons/resolved.gif [new file with mode: 0644]
swt/org.argeo.tool.swt/icons/role.gif [new file with mode: 0644]
swt/org.argeo.tool.swt/icons/rollback.gif [new file with mode: 0755]
swt/org.argeo.tool.swt/icons/save-all.png [new file with mode: 0644]
swt/org.argeo.tool.swt/icons/save.gif [new file with mode: 0644]
swt/org.argeo.tool.swt/icons/save.png [new file with mode: 0644]
swt/org.argeo.tool.swt/icons/save_security.png [new file with mode: 0644]
swt/org.argeo.tool.swt/icons/save_security_disabled.png [new file with mode: 0644]
swt/org.argeo.tool.swt/icons/security.gif [new file with mode: 0644]
swt/org.argeo.tool.swt/icons/service_published.gif [new file with mode: 0644]
swt/org.argeo.tool.swt/icons/service_referenced.gif [new file with mode: 0644]
swt/org.argeo.tool.swt/icons/sort.gif [new file with mode: 0644]
swt/org.argeo.tool.swt/icons/starting.gif [new file with mode: 0644]
swt/org.argeo.tool.swt/icons/sync.gif [new file with mode: 0644]
swt/org.argeo.tool.swt/icons/user.gif [new file with mode: 0644]
swt/org.argeo.tool.swt/icons/users.gif [new file with mode: 0644]
swt/org.argeo.tool.swt/icons/workgroup.png [new file with mode: 0644]
swt/org.argeo.tool.swt/icons/workgroup.xcf [new file with mode: 0644]
swt/org.argeo.tool.swt/icons/workspace_connected.png [new file with mode: 0644]
swt/org.argeo.tool.swt/icons/workspace_disconnected.png [new file with mode: 0644]
swt/org.argeo.tool.swt/src/org/argeo/cms/ui/theme/CmsImages.java [new file with mode: 0644]
swt/org.argeo.tool.swt/src/org/argeo/cms/ui/theme/package-info.java [new file with mode: 0644]
swt/rap/org.argeo.tool.rap.cli/.classpath [new file with mode: 0644]
swt/rap/org.argeo.tool.rap.cli/.project [new file with mode: 0644]
swt/rap/org.argeo.tool.rap.cli/META-INF/native-image/jni-config.json [new file with mode: 0644]
swt/rap/org.argeo.tool.rap.cli/META-INF/native-image/predefined-classes-config.json [new file with mode: 0644]
swt/rap/org.argeo.tool.rap.cli/META-INF/native-image/proxy-config.json [new file with mode: 0644]
swt/rap/org.argeo.tool.rap.cli/META-INF/native-image/reflect-config.json [new file with mode: 0644]
swt/rap/org.argeo.tool.rap.cli/META-INF/native-image/resource-config.json [new file with mode: 0644]
swt/rap/org.argeo.tool.rap.cli/META-INF/native-image/serialization-config.json [new file with mode: 0644]
swt/rap/org.argeo.tool.rap.cli/bnd.bnd [new file with mode: 0644]
swt/rap/org.argeo.tool.rap.cli/build.properties [new file with mode: 0644]
swt/rap/org.argeo.tool.rap.cli/src/org/argeo/tool/rap/cli/CmsRapCli.java [new file with mode: 0644]
swt/rap/org.argeo.tool.rap.cli/src/org/argeo/tool/rap/cli/RapJettyServer.java [new file with mode: 0644]
swt/rap/org.argeo.tool.rap.cli/src/org/argeo/tool/rap/cli/RwtRunner.java [new file with mode: 0644]
swt/rap/org.argeo.tool.server/.classpath [new file with mode: 0644]
swt/rap/org.argeo.tool.server/.project [new file with mode: 0644]
swt/rap/org.argeo.tool.server/bnd.bnd [new file with mode: 0644]
swt/rap/org.argeo.tool.server/build.properties [new file with mode: 0644]
swt/rap/org.argeo.tool.server/src/org/argeo/tool/server/ArgeoServer.java [new file with mode: 0644]
swt/rcp/org.argeo.tool.desktop/.classpath [new file with mode: 0644]
swt/rcp/org.argeo.tool.desktop/.project [new file with mode: 0644]
swt/rcp/org.argeo.tool.desktop/bnd.bnd [new file with mode: 0644]
swt/rcp/org.argeo.tool.desktop/build.properties [new file with mode: 0644]
swt/rcp/org.argeo.tool.desktop/src/org/argeo/tool/desktop/ArgeoDesktop.java [new file with mode: 0644]
swt/rcp/org.argeo.tool.desktop/src/org/argeo/tool/desktop/MiniDesktopCli.java [new file with mode: 0644]
swt/rcp/org.argeo.tool.rcp.cli/.classpath [new file with mode: 0644]
swt/rcp/org.argeo.tool.rcp.cli/.project [new file with mode: 0644]
swt/rcp/org.argeo.tool.rcp.cli/bnd.bnd [new file with mode: 0644]
swt/rcp/org.argeo.tool.rcp.cli/build.properties [new file with mode: 0644]
swt/rcp/org.argeo.tool.rcp.cli/src/org/argeo/cms/swt/rcp/cli/CmsCli.java [new file with mode: 0644]
tp/Make.java [deleted file]
tp/org.argeo.tp.apache/apache-sshd/common.bnd [deleted file]
tp/org.argeo.tp.apache/apache-sshd/org.apache.sshd.cli.bnd [deleted file]
tp/org.argeo.tp.apache/apache-sshd/org.apache.sshd.common.bnd [deleted file]
tp/org.argeo.tp.apache/apache-sshd/org.apache.sshd.core.bnd [deleted file]
tp/org.argeo.tp.apache/apache-sshd/org.apache.sshd.git.bnd [deleted file]
tp/org.argeo.tp.apache/apache-sshd/org.apache.sshd.putty.bnd [deleted file]
tp/org.argeo.tp.apache/apache-sshd/org.apache.sshd.scp.bnd [deleted file]
tp/org.argeo.tp.apache/apache-sshd/org.apache.sshd.sftp.bnd [deleted file]
tp/org.argeo.tp.apache/org.apache.commons.cli.bnd [deleted file]
tp/org.argeo.tp.apache/org.apache.commons.codec.bnd [deleted file]
tp/org.argeo.tp.apache/org.apache.commons.compress.bnd [deleted file]
tp/org.argeo.tp.apache/org.apache.commons.exec.bnd [deleted file]
tp/org.argeo.tp.apache/org.apache.commons.fileupload.bnd [deleted file]
tp/org.argeo.tp.apache/org.apache.commons.httpclient.bnd [deleted file]
tp/org.argeo.tp.apache/org.apache.commons.io.bnd [deleted file]
tp/org.argeo.tp.apache/org.apache.commons.vfs.bnd [deleted file]
tp/org.argeo.tp.apache/org.apache.httpcomponents.httpclient.bnd [deleted file]
tp/org.argeo.tp.apache/org.apache.httpcomponents.httpcore.bnd [deleted file]
tp/org.argeo.tp.apache/org.apache.httpcomponents.httpmime.bnd [deleted file]
tp/org.argeo.tp.apache/org.apache.tika.core.bnd [deleted file]
tp/org.argeo.tp.apache/org.apache.tika.parsers.bnd [deleted file]
tp/org.argeo.tp.apache/org.apache.xalan.bnd [deleted file]
tp/org.argeo.tp.apache/org.apache.xalan.serializer.bnd [deleted file]
tp/org.argeo.tp.apache/org.apache.xerces.bnd [deleted file]
tp/org.argeo.tp.apache/org.apache.xml.resolver.bnd [deleted file]
tp/org.argeo.tp.eclipse.equinox/eclipse-equinox/common.bnd [deleted file]
tp/org.argeo.tp.eclipse.equinox/eclipse-equinox/includes.properties [deleted file]
tp/org.argeo.tp.eclipse.rap/eclipse-rap/common.bnd [deleted file]
tp/org.argeo.tp.eclipse.rap/eclipse-rap/includes.properties [deleted file]
tp/org.argeo.tp.eclipse.rcp/eclipse-rcp/common.bnd [deleted file]
tp/org.argeo.tp.eclipse.rcp/eclipse-rcp/includes.properties [deleted file]
tp/org.argeo.tp.eclipse.rcp/org.eclipse.emf.common.bnd [deleted file]
tp/org.argeo.tp.eclipse.rcp/org.eclipse.emf.ecore.bnd [deleted file]
tp/org.argeo.tp.eclipse.rcp/org.eclipse.emf.ecore.change.bnd [deleted file]
tp/org.argeo.tp.eclipse.rcp/org.eclipse.emf.ecore.xmi.bnd [deleted file]
tp/org.argeo.tp.formats/batik/common.bnd [deleted file]
tp/org.argeo.tp.formats/batik/org.apache.batik.anim.bnd [deleted file]
tp/org.argeo.tp.formats/batik/org.apache.batik.awt.util.bnd [deleted file]
tp/org.argeo.tp.formats/batik/org.apache.batik.bridge.bnd [deleted file]
tp/org.argeo.tp.formats/batik/org.apache.batik.constants.bnd [deleted file]
tp/org.argeo.tp.formats/batik/org.apache.batik.css.bnd [deleted file]
tp/org.argeo.tp.formats/batik/org.apache.batik.dom.bnd [deleted file]
tp/org.argeo.tp.formats/batik/org.apache.batik.ext.bnd [deleted file]
tp/org.argeo.tp.formats/batik/org.apache.batik.extension.bnd [deleted file]
tp/org.argeo.tp.formats/batik/org.apache.batik.gui.util.bnd [deleted file]
tp/org.argeo.tp.formats/batik/org.apache.batik.gvt.bnd [deleted file]
tp/org.argeo.tp.formats/batik/org.apache.batik.i18n.bnd [deleted file]
tp/org.argeo.tp.formats/batik/org.apache.batik.parser.bnd [deleted file]
tp/org.argeo.tp.formats/batik/org.apache.batik.resources.bnd [deleted file]
tp/org.argeo.tp.formats/batik/org.apache.batik.script.bnd [deleted file]
tp/org.argeo.tp.formats/batik/org.apache.batik.svg.dom.bnd [deleted file]
tp/org.argeo.tp.formats/batik/org.apache.batik.svggen.bnd [deleted file]
tp/org.argeo.tp.formats/batik/org.apache.batik.swing.bnd [deleted file]
tp/org.argeo.tp.formats/batik/org.apache.batik.transcoder.bnd [deleted file]
tp/org.argeo.tp.formats/batik/org.apache.batik.util.bnd [deleted file]
tp/org.argeo.tp.formats/batik/org.apache.batik.xml.bnd [deleted file]
tp/org.argeo.tp.formats/com.adobe.xmp.xmpcore.bnd [deleted file]
tp/org.argeo.tp.formats/com.drew.metadata.bnd [deleted file]
tp/org.argeo.tp.formats/com.graphbuilder.bnd [deleted file]
tp/org.argeo.tp.formats/de.rototor.pdfbox.graphics2d.bnd [deleted file]
tp/org.argeo.tp.formats/fop/common.bnd [deleted file]
tp/org.argeo.tp.formats/fop/org.apache.fop.core.bnd [deleted file]
tp/org.argeo.tp.formats/fop/org.apache.fop.events.bnd [deleted file]
tp/org.argeo.tp.formats/fop/org.apache.fop.util.bnd [deleted file]
tp/org.argeo.tp.formats/javax.activation.bnd [deleted file]
tp/org.argeo.tp.formats/javax.mail.bnd [deleted file]
tp/org.argeo.tp.formats/javax.xml.bind.bnd [deleted file]
tp/org.argeo.tp.formats/org.apache.commons.collections4.bnd [deleted file]
tp/org.argeo.tp.formats/org.apache.commons.csv.bnd [deleted file]
tp/org.argeo.tp.formats/org.apache.commons.imaging.bnd [deleted file]
tp/org.argeo.tp.formats/org.apache.commons.lang3.bnd [deleted file]
tp/org.argeo.tp.formats/org.apache.commons.math3.bnd [deleted file]
tp/org.argeo.tp.formats/org.apache.commons.text.bnd [deleted file]
tp/org.argeo.tp.formats/org.apache.pdfbox.jempbox.bnd [deleted file]
tp/org.argeo.tp.formats/org.apache.xml.security.bnd [deleted file]
tp/org.argeo.tp.formats/org.apache.xmlgraphics.bnd [deleted file]
tp/org.argeo.tp.formats/org.mozilla.javascript.bnd [deleted file]
tp/org.argeo.tp.formats/org.w3c.dom.smil.bnd [deleted file]
tp/org.argeo.tp.formats/org.w3c.dom.svg.bnd [deleted file]
tp/org.argeo.tp.formats/pdfbox/common.bnd [deleted file]
tp/org.argeo.tp.formats/pdfbox/org.apache.pdfbox.bnd [deleted file]
tp/org.argeo.tp.formats/pdfbox/org.apache.pdfbox.fontbox.bnd [deleted file]
tp/org.argeo.tp.formats/pdfbox/org.apache.pdfbox.xmpbox.bnd [deleted file]
tp/org.argeo.tp.gis/geotools/merge.bnd [deleted file]
tp/org.argeo.tp.gis/org.json.simple.bnd [deleted file]
tp/org.argeo.tp.gis/org.locationtech.jts.bnd [deleted file]
tp/org.argeo.tp.gis/units/merge.bnd [deleted file]
tp/org.argeo.tp.javax/javax.xml.bind.bnd.deactivated [deleted file]
tp/org.argeo.tp.jcr/EDU.oswego.cs.dl.util.concurrent.bnd [deleted file]
tp/org.argeo.tp.jcr/com.google.guava.bnd.retired [deleted file]
tp/org.argeo.tp.jcr/com.google.guava.failureaccess.bnd.retired [deleted file]
tp/org.argeo.tp.jcr/jackrabbit/common.bnd [deleted file]
tp/org.argeo.tp.jcr/jackrabbit/org.apache.jackrabbit.api.bnd.retired [deleted file]
tp/org.argeo.tp.jcr/jackrabbit/org.apache.jackrabbit.core.bnd [deleted file]
tp/org.argeo.tp.jcr/jackrabbit/org.apache.jackrabbit.data.bnd [deleted file]
tp/org.argeo.tp.jcr/jackrabbit/org.apache.jackrabbit.jcr.client.bnd [deleted file]
tp/org.argeo.tp.jcr/jackrabbit/org.apache.jackrabbit.jcr.commons.bnd [deleted file]
tp/org.argeo.tp.jcr/jackrabbit/org.apache.jackrabbit.jcr2dav.bnd [deleted file]
tp/org.argeo.tp.jcr/jackrabbit/org.apache.jackrabbit.jcr2spi.bnd [deleted file]
tp/org.argeo.tp.jcr/jackrabbit/org.apache.jackrabbit.server.bnd [deleted file]
tp/org.argeo.tp.jcr/jackrabbit/org.apache.jackrabbit.servlet.bnd [deleted file]
tp/org.argeo.tp.jcr/jackrabbit/org.apache.jackrabbit.spi.bnd [deleted file]
tp/org.argeo.tp.jcr/jackrabbit/org.apache.jackrabbit.spi.commons.bnd [deleted file]
tp/org.argeo.tp.jcr/jackrabbit/org.apache.jackrabbit.spi2dav.bnd [deleted file]
tp/org.argeo.tp.jcr/jackrabbit/org.apache.jackrabbit.spi2jcr.bnd [deleted file]
tp/org.argeo.tp.jcr/jackrabbit/org.apache.jackrabbit.webdav.bnd [deleted file]
tp/org.argeo.tp.jcr/javax.jcr.bnd [deleted file]
tp/org.argeo.tp.jcr/oak/common.bnd [deleted file]
tp/org.argeo.tp.jcr/oak/org.apache.jackrabbit.api.bnd [deleted file]
tp/org.argeo.tp.jcr/org.apache.commons.collections.bnd [deleted file]
tp/org.argeo.tp.jcr/org.apache.commons.dbcp.bnd [deleted file]
tp/org.argeo.tp.jcr/org.apache.commons.pool.bnd [deleted file]
tp/org.argeo.tp.jcr/org.apache.lucene.bnd [deleted file]
tp/org.argeo.tp.jetty.websocket/javax.websocket.bnd [deleted file]
tp/org.argeo.tp.jetty.websocket/jetty-websocket/common.bnd [deleted file]
tp/org.argeo.tp.jetty.websocket/jetty-websocket/org.eclipse.jetty.websocket.api.bnd [deleted file]
tp/org.argeo.tp.jetty.websocket/jetty-websocket/org.eclipse.jetty.websocket.client.bnd [deleted file]
tp/org.argeo.tp.jetty.websocket/jetty-websocket/org.eclipse.jetty.websocket.common.bnd [deleted file]
tp/org.argeo.tp.jetty.websocket/jetty-websocket/org.eclipse.jetty.websocket.core.client.bnd [deleted file]
tp/org.argeo.tp.jetty.websocket/jetty-websocket/org.eclipse.jetty.websocket.core.common.bnd [deleted file]
tp/org.argeo.tp.jetty.websocket/jetty-websocket/org.eclipse.jetty.websocket.core.server.bnd [deleted file]
tp/org.argeo.tp.jetty.websocket/jetty-websocket/org.eclipse.jetty.websocket.jakarta.websocket.client.bnd.disabled [deleted file]
tp/org.argeo.tp.jetty.websocket/jetty-websocket/org.eclipse.jetty.websocket.jakarta.websocket.common.bnd.disabled [deleted file]
tp/org.argeo.tp.jetty.websocket/jetty-websocket/org.eclipse.jetty.websocket.jakarta.websocket.server.bnd.disabled [deleted file]
tp/org.argeo.tp.jetty.websocket/jetty-websocket/org.eclipse.jetty.websocket.server.bnd [deleted file]
tp/org.argeo.tp.jetty.websocket/jetty-websocket/org.eclipse.jetty.websocket.servlet.bnd [deleted file]
tp/org.argeo.tp.jetty/jetty/common.bnd [deleted file]
tp/org.argeo.tp.jetty/jetty/org.eclipse.jetty.client.bnd.retired [deleted file]
tp/org.argeo.tp.jetty/jetty/org.eclipse.jetty.continuation.bnd.retired [deleted file]
tp/org.argeo.tp.jetty/jetty/org.eclipse.jetty.http.bnd [deleted file]
tp/org.argeo.tp.jetty/jetty/org.eclipse.jetty.io.bnd [deleted file]
tp/org.argeo.tp.jetty/jetty/org.eclipse.jetty.jmx.bnd [deleted file]
tp/org.argeo.tp.jetty/jetty/org.eclipse.jetty.security.bnd [deleted file]
tp/org.argeo.tp.jetty/jetty/org.eclipse.jetty.server.bnd [deleted file]
tp/org.argeo.tp.jetty/jetty/org.eclipse.jetty.servlet.bnd [deleted file]
tp/org.argeo.tp.jetty/jetty/org.eclipse.jetty.servlets.bnd [deleted file]
tp/org.argeo.tp.jetty/jetty/org.eclipse.jetty.util.ajax.bnd [deleted file]
tp/org.argeo.tp.jetty/jetty/org.eclipse.jetty.util.bnd [deleted file]
tp/org.argeo.tp.jetty/jetty/org.eclipse.jetty.xml.bnd [deleted file]
tp/org.argeo.tp.poi/org.apache.xmlbeans.bnd [deleted file]
tp/org.argeo.tp.poi/poi/common.bnd [deleted file]
tp/org.argeo.tp.poi/poi/org.apache.poi.bnd [deleted file]
tp/org.argeo.tp.poi/poi/org.apache.poi.ooxml.bnd [deleted file]
tp/org.argeo.tp.poi/poi/org.apache.poi.ooxml.schemas.bnd [deleted file]
tp/org.argeo.tp.poi/poi/org.apache.poi.scratchpad.bnd [deleted file]
tp/org.argeo.tp.sdk/biz.aQute.bndlib.bnd [deleted file]
tp/org.argeo.tp.sdk/org.eclipse.jdt.core.compiler.batch.bnd [deleted file]
tp/org.argeo.tp.sdk/org.eclipse.jgit.bnd [deleted file]
tp/org.argeo.tp.sdk/org.hamcrest.bnd [deleted file]
tp/org.argeo.tp.sdk/org.junit.bnd [deleted file]
tp/org.argeo.tp.sdk/org.redline-rpm.bnd [deleted file]
tp/org.argeo.tp/bouncycastle/bcpg.bnd [deleted file]
tp/org.argeo.tp/bouncycastle/bcpkix.bnd [deleted file]
tp/org.argeo.tp/bouncycastle/bcprov.bnd [deleted file]
tp/org.argeo.tp/bouncycastle/bcutil.bnd [deleted file]
tp/org.argeo.tp/bouncycastle/common.bnd [deleted file]
tp/org.argeo.tp/com.googlecode.javaewah.JavaEWAH.bnd [deleted file]
tp/org.argeo.tp/com.zaxxer.sparsebits.bnd [deleted file]
tp/org.argeo.tp/jackson/com.fasterxml.jackson.core.jackson-annotations.bnd [deleted file]
tp/org.argeo.tp/jackson/com.fasterxml.jackson.core.jackson-core.bnd [deleted file]
tp/org.argeo.tp/jackson/com.fasterxml.jackson.core.jackson-databind.bnd [deleted file]
tp/org.argeo.tp/jackson/common.bnd [deleted file]
tp/org.argeo.tp/org.h2.bnd [deleted file]
tp/org.argeo.tp/org.postgresql.jdbc42.bnd [deleted file]
tp/org.argeo.tp/org.tukaani.xz.bnd [deleted file]
tp/org.argeo.tp/slf4j/common.bnd [deleted file]
tp/org.argeo.tp/slf4j/org.slf4j.api.bnd [deleted file]
tp/org.argeo.tp/slf4j/org.slf4j.commons.logging.bnd [deleted file]
tp/org.argeo.tp/slf4j/org.slf4j.log4j.bnd.deactivated [deleted file]

diff --git a/COPYING.LESSER b/COPYING.LESSER
deleted file mode 100644 (file)
index 0a04128..0000000
+++ /dev/null
@@ -1,165 +0,0 @@
-                   GNU LESSER GENERAL PUBLIC LICENSE
-                       Version 3, 29 June 2007
-
- Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
- Everyone is permitted to copy and distribute verbatim copies
- of this license document, but changing it is not allowed.
-
-
-  This version of the GNU Lesser General Public License incorporates
-the terms and conditions of version 3 of the GNU General Public
-License, supplemented by the additional permissions listed below.
-
-  0. Additional Definitions.
-
-  As used herein, "this License" refers to version 3 of the GNU Lesser
-General Public License, and the "GNU GPL" refers to version 3 of the GNU
-General Public License.
-
-  "The Library" refers to a covered work governed by this License,
-other than an Application or a Combined Work as defined below.
-
-  An "Application" is any work that makes use of an interface provided
-by the Library, but which is not otherwise based on the Library.
-Defining a subclass of a class defined by the Library is deemed a mode
-of using an interface provided by the Library.
-
-  A "Combined Work" is a work produced by combining or linking an
-Application with the Library.  The particular version of the Library
-with which the Combined Work was made is also called the "Linked
-Version".
-
-  The "Minimal Corresponding Source" for a Combined Work means the
-Corresponding Source for the Combined Work, excluding any source code
-for portions of the Combined Work that, considered in isolation, are
-based on the Application, and not on the Linked Version.
-
-  The "Corresponding Application Code" for a Combined Work means the
-object code and/or source code for the Application, including any data
-and utility programs needed for reproducing the Combined Work from the
-Application, but excluding the System Libraries of the Combined Work.
-
-  1. Exception to Section 3 of the GNU GPL.
-
-  You may convey a covered work under sections 3 and 4 of this License
-without being bound by section 3 of the GNU GPL.
-
-  2. Conveying Modified Versions.
-
-  If you modify a copy of the Library, and, in your modifications, a
-facility refers to a function or data to be supplied by an Application
-that uses the facility (other than as an argument passed when the
-facility is invoked), then you may convey a copy of the modified
-version:
-
-   a) under this License, provided that you make a good faith effort to
-   ensure that, in the event an Application does not supply the
-   function or data, the facility still operates, and performs
-   whatever part of its purpose remains meaningful, or
-
-   b) under the GNU GPL, with none of the additional permissions of
-   this License applicable to that copy.
-
-  3. Object Code Incorporating Material from Library Header Files.
-
-  The object code form of an Application may incorporate material from
-a header file that is part of the Library.  You may convey such object
-code under terms of your choice, provided that, if the incorporated
-material is not limited to numerical parameters, data structure
-layouts and accessors, or small macros, inline functions and templates
-(ten or fewer lines in length), you do both of the following:
-
-   a) Give prominent notice with each copy of the object code that the
-   Library is used in it and that the Library and its use are
-   covered by this License.
-
-   b) Accompany the object code with a copy of the GNU GPL and this license
-   document.
-
-  4. Combined Works.
-
-  You may convey a Combined Work under terms of your choice that,
-taken together, effectively do not restrict modification of the
-portions of the Library contained in the Combined Work and reverse
-engineering for debugging such modifications, if you also do each of
-the following:
-
-   a) Give prominent notice with each copy of the Combined Work that
-   the Library is used in it and that the Library and its use are
-   covered by this License.
-
-   b) Accompany the Combined Work with a copy of the GNU GPL and this license
-   document.
-
-   c) For a Combined Work that displays copyright notices during
-   execution, include the copyright notice for the Library among
-   these notices, as well as a reference directing the user to the
-   copies of the GNU GPL and this license document.
-
-   d) Do one of the following:
-
-       0) Convey the Minimal Corresponding Source under the terms of this
-       License, and the Corresponding Application Code in a form
-       suitable for, and under terms that permit, the user to
-       recombine or relink the Application with a modified version of
-       the Linked Version to produce a modified Combined Work, in the
-       manner specified by section 6 of the GNU GPL for conveying
-       Corresponding Source.
-
-       1) Use a suitable shared library mechanism for linking with the
-       Library.  A suitable mechanism is one that (a) uses at run time
-       a copy of the Library already present on the user's computer
-       system, and (b) will operate properly with a modified version
-       of the Library that is interface-compatible with the Linked
-       Version.
-
-   e) Provide Installation Information, but only if you would otherwise
-   be required to provide such information under section 6 of the
-   GNU GPL, and only to the extent that such information is
-   necessary to install and execute a modified version of the
-   Combined Work produced by recombining or relinking the
-   Application with a modified version of the Linked Version. (If
-   you use option 4d0, the Installation Information must accompany
-   the Minimal Corresponding Source and Corresponding Application
-   Code. If you use option 4d1, you must provide the Installation
-   Information in the manner specified by section 6 of the GNU GPL
-   for conveying Corresponding Source.)
-
-  5. Combined Libraries.
-
-  You may place library facilities that are a work based on the
-Library side by side in a single library together with other library
-facilities that are not Applications and are not covered by this
-License, and convey such a combined library under terms of your
-choice, if you do both of the following:
-
-   a) Accompany the combined library with a copy of the same work based
-   on the Library, uncombined with any other library facilities,
-   conveyed under the terms of this License.
-
-   b) Give prominent notice with the combined library that part of it
-   is a work based on the Library, and explaining where to find the
-   accompanying uncombined form of the same work.
-
-  6. Revised Versions of the GNU Lesser General Public License.
-
-  The Free Software Foundation may publish revised and/or new versions
-of the GNU Lesser General Public License from time to time. Such new
-versions will be similar in spirit to the present version, but may
-differ in detail to address new problems or concerns.
-
-  Each version is given a distinguishing version number. If the
-Library as you received it specifies that a certain numbered version
-of the GNU Lesser General Public License "or any later version"
-applies to it, you have the option of following the terms and
-conditions either of that published version or of any later version
-published by the Free Software Foundation. If the Library as you
-received it does not specify a version number of the GNU Lesser
-General Public License, you may choose any version of the GNU Lesser
-General Public License ever published by the Free Software Foundation.
-
-  If the Library as you received it specifies that a proxy can decide
-whether future versions of the GNU Lesser General Public License shall
-apply, that proxy's public statement of acceptance of any version is
-permanent authorization for you to choose that version for the
-Library.
index 54bdff2f9c711057c447bfb99e00037be3bfcc53..4c8c0f9d63a8270f1b9a724b8ba1dfe139ec1cc1 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -1,41 +1,69 @@
 include sdk.mk
-.PHONY: clean all osgi
+.PHONY: clean all osgi jni
 
-all: osgi
-       $(MAKE) -f Makefile-ext.mk
+all: osgi jni
+       $(MAKE) -f Makefile-rcp.mk all
+
+jni:
+       $(MAKE) -C jni
 
 A2_CATEGORY = org.argeo.slc
 
 BUNDLES = \
 org.argeo.slc.api \
-org.argeo.slc.factory \
 org.argeo.slc.runtime \
+org.argeo.slc.cms \
+org.argeo.slc.repo \
+org.argeo.slc.rpmfactory \
+org.argeo.slc.jcr \
+lib/linux/org.argeo.slc.systemd \
+swt/org.argeo.tool.swt \
+swt/org.argeo.tool.devops.e4 \
+swt/rap/org.argeo.tool.rap.cli \
+swt/rap/org.argeo.tool.server \
 
-BOOTSTRAP_BASE=$(SDK_BUILD_BASE)/bootstrap
-
-distribution: bootstrap
-       $(JVM) -cp \
-        $(BOOTSTRAP_BASE)/bndlib.jar:$(BOOTSTRAP_BASE)/slf4j-api.jar:$(BOOTSTRAP_BASE)/org.argeo.slc.api/bin:$(BOOTSTRAP_BASE)/org.argeo.slc.factory/bin \
-        tp/Make.java $(A2_OUTPUT)
-       
-bootstrap :
-       mkdir -p $(SDK_BUILD_BASE)/bootstrap
-       wget -c -O $(BOOTSTRAP_BASE)/ecj.jar https://repo1.maven.org/maven2/org/eclipse/jdt/ecj/3.28.0/ecj-3.28.0.jar
-       wget -c -O $(BOOTSTRAP_BASE)/slf4j-api.jar https://repo1.maven.org/maven2/org/slf4j/slf4j-api/1.7.28/slf4j-api-1.7.28.jar
-       wget -c -O $(BOOTSTRAP_BASE)/bndlib.jar https://repo1.maven.org/maven2/biz/aQute/bnd/biz.aQute.bndlib/5.3.0/biz.aQute.bndlib-5.3.0.jar
-       $(JVM) -cp $(BOOTSTRAP_BASE)/ecj.jar org.eclipse.jdt.internal.compiler.batch.Main -11 -nowarn -time -cp \
-        $(BOOTSTRAP_BASE)/bndlib.jar:$(BOOTSTRAP_BASE)/slf4j.jar \
-        org.argeo.slc.api/src[-d $(BOOTSTRAP_BASE)/org.argeo.slc.api/bin] \
-        org.argeo.slc.factory/src[-d $(BOOTSTRAP_BASE)/org.argeo.slc.factory/bin] \
+DEP_CATEGORIES = \
+org.argeo.tp \
+org.argeo.tp.sdk \
+org.argeo.tp.crypto \
+org.argeo.tp.jetty \
+org.argeo.tp.sql \
+org.argeo.tp.utils \
+org.argeo.tp.jcr \
+org.argeo.tp.gis \
+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 \
+org.argeo.cms \
+org.argeo.cms.jcr \
+swt/org.argeo.cms \
+swt/org.argeo.cms.jcr \
+swt/rap/org.argeo.cms \
 
 clean:
        rm -rf $(BUILD_BASE)
-       rm -rf $(BOOTSTRAP_BASE)
-       $(MAKE) -f Makefile-ext.mk clean
+       $(MAKE) -C jni clean
+       $(MAKE) -f Makefile-rcp.mk clean
+
+GRAALVM_HOME = /opt/graalvm-ce
+A2_BUNDLES_CLASSPATH = $(subst $(space),$(pathsep),$(strip $(A2_BUNDLES)))
+
+graalvm-custom:
+       $(GRAALVM_HOME)/bin/java -jar $(ECJ_JAR) @$(SDK_SRC_BASE)/sdk/argeo-build/ecj.args -cp $(A2_CLASSPATH) \
+               graalvm/org.argeo.slc.graalvm/src[-d $(SDK_BUILD_BASE)/$(A2_CATEGORY)/graalvm/bin]
 
-A2_OUTPUT = $(SDK_BUILD_BASE)/a2
-A2_BASE = $(A2_OUTPUT)
+tool-server: osgi graalvm-custom
+       mkdir -p $(A2_OUTPUT)/libexec/$(A2_CATEGORY)
+       cd $(A2_OUTPUT)/libexec/$(A2_CATEGORY) && $(GRAALVM_HOME)/bin/native-image \
+               -cp $(A2_CLASSPATH):$(A2_BUNDLES_CLASSPATH):$(SDK_BUILD_BASE)/$(A2_CATEGORY)/graalvm/bin \
+               --enable-url-protocols=http,https \
+               -H:AdditionalSecurityProviders=sun.security.jgss.SunProvider \
+               --initialize-at-build-time=org.argeo.init.logging.ThinLogging,org.slf4j.LoggerFactory \
+               --no-fallback \
+               -Dargeo.logging.synchronous=true \
+                org.argeo.tool.server.ArgeoServer \
+                argeo
 
-DEP_CATEGORIES = org.argeo.tp org.argeo.tp.apache org.argeo.tp.sdk org.argeo.tp.jcr
 
 include  $(SDK_SRC_BASE)/sdk/argeo-build/osgi.mk
\ No newline at end of file
diff --git a/Makefile-ext.mk b/Makefile-ext.mk
deleted file mode 100644 (file)
index 11868d4..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-include sdk.mk
-.PHONY: clean all osgi
-
-all: osgi
-
-A2_CATEGORY = org.argeo.tp
-
-BUNDLES = \
-ext/org.argeo.ext.slf4j \
-
-clean:
-       rm -rf $(BUILD_BASE)
-
-A2_OUTPUT = $(SDK_BUILD_BASE)/a2
-A2_BASE = $(A2_OUTPUT)
-
-VPATH = .:ext
-DEP_CATEGORIES = org.argeo.tp
-
-include  $(SDK_SRC_BASE)/sdk/argeo-build/osgi.mk
\ No newline at end of file
diff --git a/Makefile-rcp.mk b/Makefile-rcp.mk
new file mode 100644 (file)
index 0000000..8f19b1a
--- /dev/null
@@ -0,0 +1,47 @@
+include sdk.mk
+.PHONY: clean all osgi
+
+all: osgi
+
+A2_CATEGORY = org.argeo.slc
+
+BUNDLES = \
+swt/rcp/org.argeo.tool.rcp.cli \
+swt/rcp/org.argeo.tool.desktop \
+
+DEP_CATEGORIES = \
+org.argeo.tp \
+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 \
+org.argeo.cms \
+swt/org.argeo.cms \
+swt/rcp/org.argeo.cms \
+
+clean:
+       rm -rf $(BUILD_BASE)
+
+GRAALVM_HOME = /opt/graalvm-ce
+A2_BUNDLES_CLASSPATH = $(subst $(space),$(pathsep),$(strip $(A2_BUNDLES)))
+
+graalvm-custom:
+       $(GRAALVM_HOME)/bin/java -jar $(ECJ_JAR) @$(SDK_SRC_BASE)/sdk/argeo-build/ecj.args -cp $(A2_CLASSPATH) \
+               graalvm/org.argeo.slc.graalvm/src[-d $(SDK_BUILD_BASE)/$(A2_CATEGORY)/graalvm/bin]
+
+tool-desktop: graalvm-custom
+       mkdir -p $(A2_OUTPUT)/libexec/$(A2_CATEGORY)
+       cd $(A2_OUTPUT)/libexec/$(A2_CATEGORY) && $(GRAALVM_HOME)/bin/native-image \
+               -cp $(A2_CLASSPATH):$(A2_BUNDLES_CLASSPATH):$(SDK_BUILD_BASE)/$(A2_CATEGORY)/graalvm/bin \
+               --features=org.argeo.slc.graalvm.feature.ArgeoToolFeature \
+               --enable-url-protocols=http,https \
+               -H:AdditionalSecurityProviders=sun.security.jgss.SunProvider,org.bouncycastle.jce.provider.BouncyCastleProvider,net.i2p.crypto.eddsa.EdDSASecurityProvider \
+               --initialize-at-build-time=org.argeo.init.logging.ThinLogging,org.slf4j.LoggerFactory \
+               --trace-object-instantiation=java.lang.Thread \
+               -H:+ReportExceptionStackTraces \
+               --no-fallback \
+                org.argeo.tool.desktop.ArgeoDesktop \
+                argeo-desktop
+
+include  $(SDK_SRC_BASE)/sdk/argeo-build/osgi.mk
\ No newline at end of file
index 936a67569f072bfad1eeb935aca211a0e0530e86..dbecaaa4bb30c9a334af654716d6bb4acc1dfbf0 100644 (file)
--- a/branch.mk
+++ b/branch.mk
@@ -1 +1 @@
-include $(SDK_SRC_BASE)/cnf/testing.bnd
+BRANCH=testing
\ No newline at end of file
diff --git a/cms/.gitignore b/cms/.gitignore
deleted file mode 100644 (file)
index b83d222..0000000
+++ /dev/null
@@ -1 +0,0 @@
-/target/
diff --git a/cms/org.argeo.cms.integration/.classpath b/cms/org.argeo.cms.integration/.classpath
deleted file mode 100644 (file)
index e801ebf..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-<?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>
diff --git a/cms/org.argeo.cms.integration/.gitignore b/cms/org.argeo.cms.integration/.gitignore
deleted file mode 100644 (file)
index 09e3bc9..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/bin/
-/target/
diff --git a/cms/org.argeo.cms.integration/.project b/cms/org.argeo.cms.integration/.project
deleted file mode 100644 (file)
index 36b98f0..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
-       <name>org.argeo.cms.integration</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>
diff --git a/cms/org.argeo.cms.integration/.settings/org.eclipse.jdt.core.prefs b/cms/org.argeo.cms.integration/.settings/org.eclipse.jdt.core.prefs
deleted file mode 100644 (file)
index 4187edf..0000000
+++ /dev/null
@@ -1,103 +0,0 @@
-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.annotatedTypeArgumentToUnannotated=info
-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.suppressWarningsNotFullyAnalysed=info
-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
diff --git a/cms/org.argeo.cms.integration/META-INF/.gitignore b/cms/org.argeo.cms.integration/META-INF/.gitignore
deleted file mode 100644 (file)
index 4854a41..0000000
+++ /dev/null
@@ -1 +0,0 @@
-/MANIFEST.MF
diff --git a/cms/org.argeo.cms.integration/bnd.bnd b/cms/org.argeo.cms.integration/bnd.bnd
deleted file mode 100644 (file)
index 0741af8..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-Import-Package:\
-javax.jcr.nodetype,\
-org.apache.commons.logging,\
-org.apache.jackrabbit.*;version="[1,4)",\
-javax.servlet.*;version="[3,5)",\
-*
\ No newline at end of file
diff --git a/cms/org.argeo.cms.integration/build.properties b/cms/org.argeo.cms.integration/build.properties
deleted file mode 100644 (file)
index 4e0e742..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-source.. = src/
-output.. = bin/
-bin.includes = META-INF/,\
-               .
-additional.bundles = org.apache.sshd.common,\
-org.apache.sshd.core
diff --git a/cms/org.argeo.cms.integration/src/org/argeo/cms/integration/CmsExceptionsChain.java b/cms/org.argeo.cms.integration/src/org/argeo/cms/integration/CmsExceptionsChain.java
deleted file mode 100644 (file)
index fb289c1..0000000
+++ /dev/null
@@ -1,154 +0,0 @@
-package org.argeo.cms.integration;
-
-import java.io.IOException;
-import java.io.Writer;
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.servlet.http.HttpServletResponse;
-
-import org.argeo.api.cms.CmsLog;
-
-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 {
-       public final static CmsLog log = CmsLog.getLog(CmsExceptionsChain.class);
-
-       private List<SystemException> exceptions = new ArrayList<>();
-
-       public CmsExceptionsChain() {
-               super();
-       }
-
-       public CmsExceptionsChain(Throwable exception) {
-               writeException(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);
-               }
-       }
-
-       /** 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();
-       }
-
-//     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 vjeSystemErrors = new CmsExceptionsChain(e);
-//                     ObjectMapper objectMapper = new ObjectMapper();
-//                     System.out.println(objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(vjeSystemErrors));
-//                     System.out.println(objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(e));
-//                     e.printStackTrace();
-//             }
-//     }
-//
-//     static void testDeeper() throws Exception {
-//             throw new IllegalStateException("Deep exception");
-//     }
-
-}
diff --git a/cms/org.argeo.cms.integration/src/org/argeo/cms/integration/CmsLoginServlet.java b/cms/org.argeo.cms.integration/src/org/argeo/cms/integration/CmsLoginServlet.java
deleted file mode 100644 (file)
index 29a3137..0000000
+++ /dev/null
@@ -1,112 +0,0 @@
-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;
-       }
-}
diff --git a/cms/org.argeo.cms.integration/src/org/argeo/cms/integration/CmsLogoutServlet.java b/cms/org.argeo.cms.integration/src/org/argeo/cms/integration/CmsLogoutServlet.java
deleted file mode 100644 (file)
index 0628eae..0000000
+++ /dev/null
@@ -1,79 +0,0 @@
-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.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;
-
-/** 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;
-       }
-}
diff --git a/cms/org.argeo.cms.integration/src/org/argeo/cms/integration/CmsPrivateServletContext.java b/cms/org.argeo.cms.integration/src/org/argeo/cms/integration/CmsPrivateServletContext.java
deleted file mode 100644 (file)
index cec04d2..0000000
+++ /dev/null
@@ -1,82 +0,0 @@
-package org.argeo.cms.integration;
-
-import java.io.IOException;
-import java.security.AccessControlContext;
-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.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 = new LoginContext(CmsAuth.LOGIN_CONTEXT_USER, 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;
-       }
-}
diff --git a/cms/org.argeo.cms.integration/src/org/argeo/cms/integration/CmsSessionDescriptor.java b/cms/org.argeo.cms.integration/src/org/argeo/cms/integration/CmsSessionDescriptor.java
deleted file mode 100644 (file)
index 30de616..0000000
+++ /dev/null
@@ -1,96 +0,0 @@
-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();
-       }
-
-}
diff --git a/cms/org.argeo.cms.integration/src/org/argeo/cms/integration/CmsTokenServlet.java b/cms/org.argeo.cms.integration/src/org/argeo/cms/integration/CmsTokenServlet.java
deleted file mode 100644 (file)
index 983202a..0000000
+++ /dev/null
@@ -1,117 +0,0 @@
-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.cms.CmsAuth;
-import org.argeo.cms.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.argeo.util.naming.NamingUtils;
-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;
-       }
-}
diff --git a/cms/org.argeo.cms.integration/src/org/argeo/cms/integration/JcrReadServlet.java b/cms/org.argeo.cms.integration/src/org/argeo/cms/integration/JcrReadServlet.java
deleted file mode 100644 (file)
index 0553d7f..0000000
+++ /dev/null
@@ -1,318 +0,0 @@
-package org.argeo.cms.integration;
-
-import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-import java.net.URLDecoder;
-import java.nio.charset.StandardCharsets;
-import java.security.AccessControlContext;
-import java.security.PrivilegedActionException;
-import java.security.PrivilegedExceptionAction;
-import java.util.ArrayList;
-import java.util.LinkedHashMap;
-import java.util.List;
-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.PropertyType;
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.Value;
-import javax.jcr.nodetype.NodeType;
-import javax.security.auth.Subject;
-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.apache.jackrabbit.api.JackrabbitNode;
-import org.apache.jackrabbit.api.JackrabbitValue;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.jcr.JcrUtils;
-import org.osgi.service.http.context.ServletContextHelper;
-
-import com.fasterxml.jackson.core.JsonGenerator;
-import com.fasterxml.jackson.databind.ObjectMapper;
-
-/** Access a JCR repository via web services. */
-public class JcrReadServlet extends HttpServlet {
-       private static final long serialVersionUID = 6536175260540484539L;
-       private final static CmsLog log = CmsLog.getLog(JcrReadServlet.class);
-
-       protected final static String ACCEPT_HTTP_HEADER = "Accept";
-       protected final static String CONTENT_DISPOSITION_HTTP_HEADER = "Content-Disposition";
-
-       protected final static String OCTET_STREAM_CONTENT_TYPE = "application/octet-stream";
-       protected final static String XML_CONTENT_TYPE = "application/xml";
-       protected final static String JSON_CONTENT_TYPE = "application/json";
-
-       private final static String PARAM_VERBOSE = "verbose";
-       private final static String PARAM_DEPTH = "depth";
-
-       protected final static String JCR_NODES = "jcr:nodes";
-       // cf. javax.jcr.Property
-       protected final static String JCR_PATH = "path";
-       protected final static String JCR_NAME = "name";
-
-       protected final static String _JCR = "_jcr";
-       protected final static String JCR_PREFIX = "jcr:";
-       protected final static String REP_PREFIX = "rep:";
-
-       private Repository repository;
-       private Integer maxDepth = 8;
-
-       private ObjectMapper objectMapper = new ObjectMapper();
-
-       @Override
-       protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
-               if (log.isTraceEnabled())
-                       log.trace("Data service: " + req.getPathInfo());
-
-               String dataWorkspace = getWorkspace(req);
-               String jcrPath = getJcrPath(req);
-
-               boolean verbose = req.getParameter(PARAM_VERBOSE) != null && !req.getParameter(PARAM_VERBOSE).equals("false");
-               int depth = 1;
-               if (req.getParameter(PARAM_DEPTH) != null) {
-                       depth = Integer.parseInt(req.getParameter(PARAM_DEPTH));
-                       if (depth > maxDepth)
-                               throw new RuntimeException("Depth " + depth + " is higher than maximum " + maxDepth);
-               }
-
-               Session session = null;
-               try {
-                       // authentication
-                       session = openJcrSession(req, resp, getRepository(), dataWorkspace);
-                       if (!session.itemExists(jcrPath))
-                               throw new RuntimeException("JCR node " + jcrPath + " does not exist");
-                       Node node = session.getNode(jcrPath);
-
-                       List<String> acceptHeader = readAcceptHeader(req);
-                       if (!acceptHeader.isEmpty() && node.isNodeType(NodeType.NT_FILE)) {
-                               resp.setContentType(OCTET_STREAM_CONTENT_TYPE);
-                               resp.addHeader(CONTENT_DISPOSITION_HTTP_HEADER, "attachment; filename='" + node.getName() + "'");
-                               IOUtils.copy(JcrUtils.getFileAsStream(node), resp.getOutputStream());
-                               resp.flushBuffer();
-                       } else {
-                               if (!acceptHeader.isEmpty() && acceptHeader.get(0).equals(XML_CONTENT_TYPE)) {
-                                       // TODO Use req.startAsync(); ?
-                                       resp.setContentType(XML_CONTENT_TYPE);
-                                       session.exportSystemView(node.getPath(), resp.getOutputStream(), false, depth <= 1);
-                                       return;
-                               }
-                               if (!acceptHeader.isEmpty() && !acceptHeader.contains(JSON_CONTENT_TYPE)) {
-                                       if (log.isTraceEnabled())
-                                               log.warn("Content type " + acceptHeader + " in Accept header is not supported. Supported: "
-                                                               + JSON_CONTENT_TYPE + " (default), " + XML_CONTENT_TYPE);
-                               }
-                               resp.setContentType(JSON_CONTENT_TYPE);
-                               JsonGenerator jsonGenerator = getObjectMapper().getFactory().createGenerator(resp.getWriter());
-                               jsonGenerator.writeStartObject();
-                               writeNodeChildren(node, jsonGenerator, depth, verbose);
-                               writeNodeProperties(node, jsonGenerator, verbose);
-                               jsonGenerator.writeEndObject();
-                               jsonGenerator.flush();
-                       }
-               } catch (Exception e) {
-                       new CmsExceptionsChain(e).writeAsJson(getObjectMapper(), resp);
-               } finally {
-                       JcrUtils.logoutQuietly(session);
-               }
-       }
-
-       protected Session openJcrSession(HttpServletRequest req, HttpServletResponse resp, Repository repository,
-                       String workspace) throws RepositoryException {
-               AccessControlContext acc = (AccessControlContext) req.getAttribute(ServletContextHelper.REMOTE_USER);
-               Subject subject = Subject.getSubject(acc);
-               try {
-                       return Subject.doAs(subject, new PrivilegedExceptionAction<Session>() {
-
-                               @Override
-                               public Session run() throws RepositoryException {
-                                       return repository.login(workspace);
-                               }
-
-                       });
-               } catch (PrivilegedActionException e) {
-                       if (e.getException() instanceof RepositoryException)
-                               throw (RepositoryException) e.getException();
-                       else
-                               throw new RuntimeException(e.getException());
-               }
-//             return workspace != null ? repository.login(workspace) : repository.login();
-       }
-
-       protected String getWorkspace(HttpServletRequest req) {
-               String path = req.getPathInfo();
-               try {
-                       path = URLDecoder.decode(path, StandardCharsets.UTF_8.name());
-               } catch (UnsupportedEncodingException e) {
-                       throw new IllegalArgumentException(e);
-               }
-               String[] pathTokens = path.split("/");
-               return pathTokens[1];
-       }
-
-       protected String getJcrPath(HttpServletRequest req) {
-               String path = req.getPathInfo();
-               try {
-                       path = URLDecoder.decode(path, StandardCharsets.UTF_8.name());
-               } catch (UnsupportedEncodingException e) {
-                       throw new IllegalArgumentException(e);
-               }
-               String[] pathTokens = path.split("/");
-               String domain = pathTokens[1];
-               String jcrPath = path.substring(domain.length() + 1);
-               return jcrPath;
-       }
-
-       protected List<String> readAcceptHeader(HttpServletRequest req) {
-               List<String> lst = new ArrayList<>();
-               String acceptHeader = req.getHeader(ACCEPT_HTTP_HEADER);
-               if (acceptHeader == null)
-                       return lst;
-//             Enumeration<String> acceptHeader = req.getHeaders(ACCEPT_HTTP_HEADER);
-//             while (acceptHeader.hasMoreElements()) {
-               String[] arr = acceptHeader.split("\\.");
-               for (int i = 0; i < arr.length; i++) {
-                       String str = arr[i].trim();
-                       if (!"".equals(str))
-                               lst.add(str);
-               }
-//             }
-               return lst;
-       }
-
-       protected void writeNodeProperties(Node node, JsonGenerator jsonGenerator, boolean verbose)
-                       throws RepositoryException, IOException {
-               String jcrPath = node.getPath();
-               Map<String, Map<String, Property>> namespaces = new TreeMap<>();
-
-               PropertyIterator pit = node.getProperties();
-               properties: while (pit.hasNext()) {
-                       Property property = pit.nextProperty();
-
-                       final String propertyName = property.getName();
-                       int columnIndex = propertyName.indexOf(':');
-                       if (columnIndex > 0) {
-                               // mark prefix with a '_' before the name of the object, according to JSON
-                               // conventions to indicate a special value
-                               String prefix = "_" + propertyName.substring(0, columnIndex);
-                               String unqualifiedName = propertyName.substring(columnIndex + 1);
-                               if (!namespaces.containsKey(prefix))
-                                       namespaces.put(prefix, new LinkedHashMap<String, Property>());
-                               Map<String, Property> map = namespaces.get(prefix);
-                               assert !map.containsKey(unqualifiedName);
-                               map.put(unqualifiedName, property);
-                               continue properties;
-                       }
-
-                       if (property.getType() == PropertyType.BINARY) {
-                               if (!(node instanceof JackrabbitNode)) {
-                                       continue properties;// skip
-                               }
-                       }
-
-                       writeProperty(propertyName, property, jsonGenerator);
-               }
-
-               for (String prefix : namespaces.keySet()) {
-                       Map<String, Property> map = namespaces.get(prefix);
-                       jsonGenerator.writeFieldName(prefix);
-                       jsonGenerator.writeStartObject();
-                       if (_JCR.equals(prefix)) {
-                               jsonGenerator.writeStringField(JCR_NAME, node.getName());
-                               jsonGenerator.writeStringField(JCR_PATH, jcrPath);
-                       }
-                       properties: for (String unqualifiedName : map.keySet()) {
-                               Property property = map.get(unqualifiedName);
-                               if (property.getType() == PropertyType.BINARY) {
-                                       if (!(node instanceof JackrabbitNode)) {
-                                               continue properties;// skip
-                                       }
-                               }
-                               writeProperty(unqualifiedName, property, jsonGenerator);
-                       }
-                       jsonGenerator.writeEndObject();
-               }
-       }
-
-       protected void writeProperty(String fieldName, Property property, JsonGenerator jsonGenerator)
-                       throws RepositoryException, IOException {
-               if (!property.isMultiple()) {
-                       jsonGenerator.writeFieldName(fieldName);
-                       writePropertyValue(property.getType(), property.getValue(), jsonGenerator);
-               } else {
-                       jsonGenerator.writeFieldName(fieldName);
-                       jsonGenerator.writeStartArray();
-                       Value[] values = property.getValues();
-                       for (Value value : values) {
-                               writePropertyValue(property.getType(), value, jsonGenerator);
-                       }
-                       jsonGenerator.writeEndArray();
-               }
-       }
-
-       protected void writePropertyValue(int type, Value value, JsonGenerator jsonGenerator)
-                       throws RepositoryException, IOException {
-               if (type == PropertyType.DOUBLE)
-                       jsonGenerator.writeNumber(value.getDouble());
-               else if (type == PropertyType.LONG)
-                       jsonGenerator.writeNumber(value.getLong());
-               else if (type == PropertyType.BINARY) {
-                       if (value instanceof JackrabbitValue) {
-                               String contentIdentity = ((JackrabbitValue) value).getContentIdentity();
-                               jsonGenerator.writeString("SHA256:" + contentIdentity);
-                       } else {
-                               // TODO write Base64 ?
-                               jsonGenerator.writeNull();
-                       }
-               } else
-                       jsonGenerator.writeString(value.getString());
-       }
-
-       protected void writeNodeChildren(Node node, JsonGenerator jsonGenerator, int depth, boolean verbose)
-                       throws RepositoryException, IOException {
-               if (!node.hasNodes())
-                       return;
-               if (depth <= 0)
-                       return;
-               NodeIterator nit;
-
-               nit = node.getNodes();
-               children: while (nit.hasNext()) {
-                       Node child = nit.nextNode();
-                       if (!verbose && child.getName().startsWith(REP_PREFIX)) {
-                               continue children;// skip Jackrabbit auth metadata
-                       }
-
-                       jsonGenerator.writeFieldName(child.getName());
-                       jsonGenerator.writeStartObject();
-                       writeNodeChildren(child, jsonGenerator, depth - 1, verbose);
-                       writeNodeProperties(child, jsonGenerator, verbose);
-                       jsonGenerator.writeEndObject();
-               }
-       }
-
-       public void setRepository(Repository repository) {
-               this.repository = repository;
-       }
-
-       public void setMaxDepth(Integer maxDepth) {
-               this.maxDepth = maxDepth;
-       }
-
-       protected Repository getRepository() {
-               return repository;
-       }
-
-       protected ObjectMapper getObjectMapper() {
-               return objectMapper;
-       }
-
-}
diff --git a/cms/org.argeo.cms.integration/src/org/argeo/cms/integration/JcrWriteServlet.java b/cms/org.argeo.cms.integration/src/org/argeo/cms/integration/JcrWriteServlet.java
deleted file mode 100644 (file)
index 8a3864b..0000000
+++ /dev/null
@@ -1,91 +0,0 @@
-package org.argeo.cms.integration;
-
-import java.io.IOException;
-
-import javax.jcr.ImportUUIDBehavior;
-import javax.jcr.Node;
-import javax.jcr.Session;
-import javax.jcr.nodetype.NodeType;
-import javax.servlet.ServletException;
-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.api.cms.CmsLog;
-import org.argeo.jcr.JcrUtils;
-
-/** Access a JCR repository via web services. */
-public class JcrWriteServlet extends JcrReadServlet {
-       private static final long serialVersionUID = 17272653843085492L;
-       private final static CmsLog log = CmsLog.getLog(JcrWriteServlet.class);
-
-       @Override
-       protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
-               if (log.isDebugEnabled())
-                       log.debug("Data service POST: " + req.getPathInfo());
-
-               String dataWorkspace = getWorkspace(req);
-               String jcrPath = getJcrPath(req);
-
-               Session session = null;
-               try {
-                       // authentication
-                       session = openJcrSession(req, resp, getRepository(), dataWorkspace);
-
-                       if (req.getContentType() != null && req.getContentType().equals(XML_CONTENT_TYPE)) {
-//                             resp.setContentType(XML_CONTENT_TYPE);
-                               session.getWorkspace().importXML(jcrPath, req.getInputStream(),
-                                               ImportUUIDBehavior.IMPORT_UUID_COLLISION_REMOVE_EXISTING);
-                               return;
-                       }
-
-                       if (!session.itemExists(jcrPath)) {
-                               String parentPath = FilenameUtils.getFullPathNoEndSeparator(jcrPath);
-                               String fileName = FilenameUtils.getName(jcrPath);
-                               Node folderNode = JcrUtils.mkfolders(session, parentPath);
-                               byte[] bytes = IOUtils.toByteArray(req.getInputStream());
-                               JcrUtils.copyBytesAsFile(folderNode, fileName, bytes);
-                       } else {
-                               Node node = session.getNode(jcrPath);
-                               if (!node.isNodeType(NodeType.NT_FILE))
-                                       throw new IllegalArgumentException("Node " + jcrPath + " exists but is not a file");
-                               byte[] bytes = IOUtils.toByteArray(req.getInputStream());
-                               JcrUtils.copyBytesAsFile(node.getParent(), node.getName(), bytes);
-                       }
-                       session.save();
-               } catch (Exception e) {
-                       new CmsExceptionsChain(e).writeAsJson(getObjectMapper(), resp);
-               } finally {
-                       JcrUtils.logoutQuietly(session);
-               }
-       }
-
-       @Override
-       protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
-               if (log.isDebugEnabled())
-                       log.debug("Data service DELETE: " + req.getPathInfo());
-
-               String dataWorkspace = getWorkspace(req);
-               String jcrPath = getJcrPath(req);
-
-               Session session = null;
-               try {
-                       // authentication
-                       session = openJcrSession(req, resp, getRepository(), dataWorkspace);
-                       if (!session.itemExists(jcrPath)) {
-                               // ignore
-                               return;
-                       } else {
-                               Node node = session.getNode(jcrPath);
-                               node.remove();
-                       }
-                       session.save();
-               } catch (Exception e) {
-                       new CmsExceptionsChain(e).writeAsJson(getObjectMapper(), resp);
-               } finally {
-                       JcrUtils.logoutQuietly(session);
-               }
-       }
-
-}
diff --git a/cms/org.argeo.cms.integration/src/org/argeo/cms/integration/TokenDescriptor.java b/cms/org.argeo.cms.integration/src/org/argeo/cms/integration/TokenDescriptor.java
deleted file mode 100644 (file)
index 1541b4f..0000000
+++ /dev/null
@@ -1,49 +0,0 @@
-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;
-       }
-
-}
diff --git a/cms/org.argeo.cms.integration/src/org/argeo/cms/integration/XslTemplate.java b/cms/org.argeo.cms.integration/src/org/argeo/cms/integration/XslTemplate.java
deleted file mode 100644 (file)
index c9802a2..0000000
+++ /dev/null
@@ -1,55 +0,0 @@
-package org.argeo.cms.integration;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-import javax.xml.transform.Result;
-import javax.xml.transform.Source;
-import javax.xml.transform.Transformer;
-import javax.xml.transform.TransformerConfigurationException;
-import javax.xml.transform.TransformerException;
-import javax.xml.transform.TransformerFactory;
-import javax.xml.transform.TransformerFactoryConfigurationError;
-import javax.xml.transform.stream.StreamResult;
-import javax.xml.transform.stream.StreamSource;
-
-public class XslTemplate {
-       private Transformer transformer;
-
-       public XslTemplate(InputStream in, String systemId) {
-               this(loadTransformer(in, systemId));
-       }
-
-       public XslTemplate(Transformer transformer) {
-               this.transformer = transformer;
-       }
-
-       private static Transformer loadTransformer(InputStream in, String systemId) {
-               try {
-                       TransformerFactory tFactory = TransformerFactory.newInstance();
-                       StreamSource stylesource = new StreamSource(in, systemId);
-                       return tFactory.newTransformer(stylesource);
-               } catch (TransformerConfigurationException | TransformerFactoryConfigurationError e) {
-                       throw new IllegalArgumentException("Cannot initialise stylesheet with systemId " + systemId, e);
-               }
-       }
-
-       public synchronized void apply(Node node, OutputStream out) {
-               // TODO use a pool of Transformer instead of synchronized
-               try (ByteArrayOutputStream xml = new ByteArrayOutputStream()) {
-                       node.getSession().exportDocumentView(node.getPath(), xml, true, false);
-                       try (ByteArrayInputStream xmlIn = new ByteArrayInputStream(xml.toByteArray())) {
-                               Source source = new StreamSource(xmlIn);
-                               Result results = new StreamResult(out);
-                               transformer.transform(source, results);
-                       }
-               } catch (IOException | RepositoryException | TransformerException e) {
-                       throw new RuntimeException("Cannot process XSL template on " + node, e);
-               }
-       }
-}
diff --git a/cms/org.argeo.cms.integration/src/org/argeo/cms/integration/package-info.java b/cms/org.argeo.cms.integration/src/org/argeo/cms/integration/package-info.java
deleted file mode 100644 (file)
index 1405737..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Argeo CMS integration (JSON, web services). */
-package org.argeo.cms.integration;
\ No newline at end of file
diff --git a/cms/org.argeo.cms.integration/src/org/argeo/cms/websocket/CmsWebSocketConfigurator.java b/cms/org.argeo.cms.integration/src/org/argeo/cms/websocket/CmsWebSocketConfigurator.java
deleted file mode 100644 (file)
index 747afa4..0000000
+++ /dev/null
@@ -1,86 +0,0 @@
-package org.argeo.cms.websocket;
-
-/** <strong>Disabled until third party issues are solved.</strong>. Customises the initialisation of a new web socket. */
-public class CmsWebSocketConfigurator {
-//extends Configurator {
-//     public final static String WEBSOCKET_SUBJECT = "org.argeo.cms.websocket.subject";
-//
-//     private final static CmsLog log = CmsLog.getLog(CmsWebSocketConfigurator.class);
-//     final static String HEADER_WWW_AUTHENTICATE = "WWW-Authenticate";
-//
-//     @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) {
-//
-//             RemoteAuthSession httpSession = new ServletHttpSession((javax.servlet.http.HttpSession) request.getHttpSession());
-//             if (log.isDebugEnabled() && httpSession != null)
-//                     log.debug("Web socket HTTP session id: " + httpSession.getId());
-//
-//             if (httpSession == null) {
-//                     rejectResponse(response, null);
-//             }
-//             try {
-//                     LoginContext lc = new LoginContext(CmsAuth.LOGIN_CONTEXT_USER,
-//                                     new RemoteAuthCallbackHandler(httpSession));
-//                     lc.login();
-//                     if (log.isDebugEnabled())
-//                             log.debug("Web socket logged-in as " + lc.getSubject());
-//                     Subject.doAs(lc.getSubject(), new PrivilegedAction<Void>() {
-//
-//                             @Override
-//                             public Void run() {
-//                                     sec.getUserProperties().put(ServletContextHelper.REMOTE_USER, AccessController.getContext());
-//                                     return null;
-//                             }
-//
-//                     });
-//             } catch (Exception e) {
-//                     rejectResponse(response, e);
-//             }
-//     }
-//
-//     /**
-//      * 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) {
-//             // 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");
-//     }
-}
diff --git a/cms/org.argeo.cms.integration/src/org/argeo/cms/websocket/package-info.java b/cms/org.argeo.cms.integration/src/org/argeo/cms/websocket/package-info.java
deleted file mode 100644 (file)
index 2ab9a67..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Argeo CMS websocket integration. */
-package org.argeo.cms.websocket;
\ No newline at end of file
diff --git a/cms/org.argeo.cms.integration/src/org/argeo/maintenance/backup/vfs/AbstractAtomicBackup.java b/cms/org.argeo.cms.integration/src/org/argeo/maintenance/backup/vfs/AbstractAtomicBackup.java
deleted file mode 100644 (file)
index 9ef7b0a..0000000
+++ /dev/null
@@ -1,82 +0,0 @@
-package org.argeo.maintenance.backup.vfs;
-
-import org.apache.commons.vfs2.FileObject;
-import org.apache.commons.vfs2.FileSystemManager;
-import org.apache.commons.vfs2.FileSystemOptions;
-import org.apache.commons.vfs2.provider.sftp.SftpFileSystemConfigBuilder;
-
-/**
- * Simplify atomic backups implementation, especially by managing VFS.
- */
-public abstract class AbstractAtomicBackup implements AtomicBackup {
-       private String name;
-       private String compression = "bz2";
-
-       protected abstract void writeBackup(FileObject targetFo);
-
-       public AbstractAtomicBackup() {
-       }
-
-       public AbstractAtomicBackup(String name) {
-               this.name = name;
-       }
-
-       public void init() {
-               if (name == null)
-                       throw new MaintenanceException("Atomic backup name must be set");
-       }
-
-       public void destroy() {
-
-       }
-
-       @Override
-       public String backup(FileSystemManager fileSystemManager,
-                       String backupsBase, BackupContext backupContext,
-                       FileSystemOptions opts) {
-               if (name == null)
-                       throw new MaintenanceException("Atomic backup name must be set");
-
-               FileObject targetFo = null;
-               try {
-                       if (backupsBase.startsWith("sftp:"))
-                               SftpFileSystemConfigBuilder.getInstance()
-                                               .setStrictHostKeyChecking(opts, "no");
-                       if (compression == null || compression.equals("none"))
-                               targetFo = fileSystemManager.resolveFile(backupsBase + '/'
-                                               + backupContext.getRelativeFolder() + '/' + name, opts);
-                       else if (compression.equals("bz2"))
-                               targetFo = fileSystemManager.resolveFile("bz2:" + backupsBase
-                                               + '/' + backupContext.getRelativeFolder() + '/' + name
-                                               + ".bz2" + "!" + name, opts);
-                       else if (compression.equals("gz"))
-                               targetFo = fileSystemManager.resolveFile("gz:" + backupsBase
-                                               + '/' + backupContext.getRelativeFolder() + '/' + name
-                                               + ".gz" + "!" + name, opts);
-                       else
-                               throw new MaintenanceException("Unsupported compression "
-                                               + compression);
-
-                       writeBackup(targetFo);
-
-                       return targetFo.toString();
-               } catch (Exception e) {
-                       throw new MaintenanceException("Cannot backup " + name + " to "
-                                       + targetFo, e);
-               } finally {
-                       BackupUtils.closeFOQuietly(targetFo);
-               }
-       }
-
-       public void setName(String name) {
-               this.name = name;
-       }
-
-       public String getName() {
-               return name;
-       }
-
-       public void setCompression(String compression) {
-               this.compression = compression;
-       }
-}
diff --git a/cms/org.argeo.cms.integration/src/org/argeo/maintenance/backup/vfs/AtomicBackup.java b/cms/org.argeo.cms.integration/src/org/argeo/maintenance/backup/vfs/AtomicBackup.java
deleted file mode 100644 (file)
index db437e4..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-package org.argeo.maintenance.backup.vfs;
-
-import org.apache.commons.vfs2.FileSystemManager;
-import org.apache.commons.vfs2.FileSystemOptions;
-
-/** Performs the backup of a single component, typically a database dump */
-public interface AtomicBackup {
-       /** Name identifiying this backup */
-       public String getName();
-
-       /**
-        * Retrieves the data of the component in a format that allows to restore
-        * the component
-        * 
-        * @param backupContext
-        *            the context of this backup
-        * @return the VFS URI of the generated file or directory
-        */
-       public String backup(FileSystemManager fileSystemManager,
-                       String backupsBase, BackupContext backupContext,
-                       FileSystemOptions opts);
-}
diff --git a/cms/org.argeo.cms.integration/src/org/argeo/maintenance/backup/vfs/BackupContext.java b/cms/org.argeo.cms.integration/src/org/argeo/maintenance/backup/vfs/BackupContext.java
deleted file mode 100644 (file)
index d5eefb3..0000000
+++ /dev/null
@@ -1,25 +0,0 @@
-package org.argeo.maintenance.backup.vfs;
-
-import java.text.DateFormat;
-import java.util.Date;
-
-/**
- * Transient information of a given backup, centralizing common information such
- * as timestamp and location.
- */
-public interface BackupContext {
-       /** Backup date */
-       public Date getTimestamp();
-
-       /** Formatted backup date */
-       public String getTimestampAsString();
-
-       /** System name */
-       public String getSystemName();
-
-       /** Local base */
-       public String getRelativeFolder();
-
-       /** Date format */
-       public DateFormat getDateFormat();
-}
diff --git a/cms/org.argeo.cms.integration/src/org/argeo/maintenance/backup/vfs/BackupFileSystemManager.java b/cms/org.argeo.cms.integration/src/org/argeo/maintenance/backup/vfs/BackupFileSystemManager.java
deleted file mode 100644 (file)
index 5651b07..0000000
+++ /dev/null
@@ -1,36 +0,0 @@
-package org.argeo.maintenance.backup.vfs;
-
-import org.apache.commons.vfs2.FileSystemException;
-import org.apache.commons.vfs2.impl.DefaultFileSystemManager;
-import org.apache.commons.vfs2.provider.bzip2.Bzip2FileProvider;
-import org.apache.commons.vfs2.provider.ftp.FtpFileProvider;
-import org.apache.commons.vfs2.provider.gzip.GzipFileProvider;
-import org.apache.commons.vfs2.provider.local.DefaultLocalFileProvider;
-import org.apache.commons.vfs2.provider.ram.RamFileProvider;
-import org.apache.commons.vfs2.provider.sftp.SftpFileProvider;
-import org.apache.commons.vfs2.provider.url.UrlFileProvider;
-
-/**
- * Programatically configured VFS file system manager which can be declared as a
- * bean and associated with a life cycle (methods
- * {@link DefaultFileSystemManager#init()} and
- * {@link DefaultFileSystemManager#close()}). Supports bz2, file, ram, gzip,
- * ftp, sftp
- */
-public class BackupFileSystemManager extends DefaultFileSystemManager {
-
-       public BackupFileSystemManager() {
-               super();
-               try {
-                       addProvider("file", new DefaultLocalFileProvider());
-                       addProvider("bz2", new Bzip2FileProvider());
-                       addProvider("ftp", new FtpFileProvider());
-                       addProvider("sftp", new SftpFileProvider());
-                       addProvider("gzip", new GzipFileProvider());
-                       addProvider("ram", new RamFileProvider());
-                       setDefaultProvider(new UrlFileProvider());
-               } catch (FileSystemException e) {
-                       throw new MaintenanceException("Cannot configure backup file provider", e);
-               }
-       }
-}
diff --git a/cms/org.argeo.cms.integration/src/org/argeo/maintenance/backup/vfs/BackupPurge.java b/cms/org.argeo.cms.integration/src/org/argeo/maintenance/backup/vfs/BackupPurge.java
deleted file mode 100644 (file)
index e769da2..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-package org.argeo.maintenance.backup.vfs;
-
-import java.text.DateFormat;
-
-import org.apache.commons.vfs2.FileSystemManager;
-import org.apache.commons.vfs2.FileSystemOptions;
-
-/** Purges previous backups */
-public interface BackupPurge {
-       /**
-        * Purge the backups identified by these arguments. Although these are the
-        * same fields as a {@link BackupContext} we don't pass it as argument since
-        * we want to use this interface to purge remote backups as well (that is,
-        * with a different base), or outside the scope of a running backup.
-        */
-       public void purge(FileSystemManager fileSystemManager, String base,
-                       String name, DateFormat dateFormat, FileSystemOptions opts);
-}
diff --git a/cms/org.argeo.cms.integration/src/org/argeo/maintenance/backup/vfs/BackupUtils.java b/cms/org.argeo.cms.integration/src/org/argeo/maintenance/backup/vfs/BackupUtils.java
deleted file mode 100644 (file)
index d9f7b5a..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-package org.argeo.maintenance.backup.vfs;
-
-import org.apache.commons.vfs2.FileObject;
-
-/** Backup utilities */
-public class BackupUtils {
-       /** Close a file object quietly even if it is null or throws an exception. */
-       public static void closeFOQuietly(FileObject fo) {
-               if (fo != null) {
-                       try {
-                               fo.close();
-                       } catch (Exception e) {
-                               // silent
-                       }
-               }
-       }
-       
-       /** Prevents instantiation */
-       private BackupUtils() {
-       }
-}
diff --git a/cms/org.argeo.cms.integration/src/org/argeo/maintenance/backup/vfs/MaintenanceException.java b/cms/org.argeo.cms.integration/src/org/argeo/maintenance/backup/vfs/MaintenanceException.java
deleted file mode 100644 (file)
index b0232b6..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-package org.argeo.maintenance.backup.vfs;
-
-@Deprecated
-class MaintenanceException extends RuntimeException {
-       private static final long serialVersionUID = -5770049663929537270L;
-
-       public MaintenanceException(String message, Throwable cause) {
-               super(message, cause);
-       }
-
-       public MaintenanceException(String message) {
-               super(message);
-       }
-
-}
diff --git a/cms/org.argeo.cms.integration/src/org/argeo/maintenance/backup/vfs/MySqlBackup.java b/cms/org.argeo.cms.integration/src/org/argeo/maintenance/backup/vfs/MySqlBackup.java
deleted file mode 100644 (file)
index 7d1cce4..0000000
+++ /dev/null
@@ -1,59 +0,0 @@
-package org.argeo.maintenance.backup.vfs;
-
-import org.apache.commons.vfs2.FileObject;
-
-/** Backups a MySQL database using mysqldump. */
-public class MySqlBackup extends OsCallBackup {
-       private String mysqldumpLocation = "/usr/bin/mysqldump";
-
-       private String dbUser;
-       private String dbPassword;
-       private String dbName;
-
-       public MySqlBackup() {
-       }
-
-       public MySqlBackup(String dbUser, String dbPassword, String dbName) {
-               this.dbUser = dbUser;
-               this.dbPassword = dbPassword;
-               this.dbName = dbName;
-               init();
-       }
-
-       @Override
-       public void init() {
-               if (getName() == null)
-                       setName(dbName + ".mysql");
-               super.init();
-       }
-
-       @Override
-       public void writeBackup(FileObject targetFo) {
-               if (getCommand() == null)
-                       setCommand(mysqldumpLocation
-                                       + " --lock-tables --add-locks --add-drop-table"
-                                       + " -u ${dbUser} --password=${dbPassword} --databases ${dbName}");
-               getVariables().put("dbUser", dbUser);
-               getVariables().put("dbPassword", dbPassword);
-               getVariables().put("dbName", dbName);
-
-               super.writeBackup(targetFo);
-       }
-
-       public void setDbUser(String dbUser) {
-               this.dbUser = dbUser;
-       }
-
-       public void setDbPassword(String dbPassword) {
-               this.dbPassword = dbPassword;
-       }
-
-       public void setDbName(String dbName) {
-               this.dbName = dbName;
-       }
-
-       public void setMysqldumpLocation(String mysqldumpLocation) {
-               this.mysqldumpLocation = mysqldumpLocation;
-       }
-
-}
diff --git a/cms/org.argeo.cms.integration/src/org/argeo/maintenance/backup/vfs/OpenLdapBackup.java b/cms/org.argeo.cms.integration/src/org/argeo/maintenance/backup/vfs/OpenLdapBackup.java
deleted file mode 100644 (file)
index fb3bdf1..0000000
+++ /dev/null
@@ -1,46 +0,0 @@
-package org.argeo.maintenance.backup.vfs;
-
-import org.apache.commons.vfs2.FileObject;
-
-/** Backups an OpenLDAP server using slapcat */
-public class OpenLdapBackup extends OsCallBackup {
-       private String slapcatLocation = "/usr/sbin/slapcat";
-       private String slapdConfLocation = "/etc/openldap/slapd.conf";
-       private String baseDn;
-
-       public OpenLdapBackup() {
-               super();
-       }
-
-       public OpenLdapBackup(String baseDn) {
-               super();
-               this.baseDn = baseDn;
-       }
-
-       @Override
-       public void writeBackup(FileObject targetFo) {
-               if (baseDn == null)
-                       throw new MaintenanceException("Base DN must be set");
-
-               if (getCommand() == null)
-                       setCommand(slapcatLocation
-                                       + " -f ${slapdConfLocation} -b '${baseDn}'");
-               getVariables().put("slapdConfLocation", slapdConfLocation);
-               getVariables().put("baseDn", baseDn);
-
-               super.writeBackup(targetFo);
-       }
-
-       public void setSlapcatLocation(String slapcatLocation) {
-               this.slapcatLocation = slapcatLocation;
-       }
-
-       public void setSlapdConfLocation(String slapdConfLocation) {
-               this.slapdConfLocation = slapdConfLocation;
-       }
-
-       public void setBaseDn(String baseDn) {
-               this.baseDn = baseDn;
-       }
-
-}
diff --git a/cms/org.argeo.cms.integration/src/org/argeo/maintenance/backup/vfs/OsCallBackup.java b/cms/org.argeo.cms.integration/src/org/argeo/maintenance/backup/vfs/OsCallBackup.java
deleted file mode 100644 (file)
index a72ce63..0000000
+++ /dev/null
@@ -1,115 +0,0 @@
-package org.argeo.maintenance.backup.vfs;
-
-import java.io.ByteArrayOutputStream;
-import java.util.HashMap;
-import java.util.Map;
-
-import org.apache.commons.exec.CommandLine;
-import org.apache.commons.exec.DefaultExecutor;
-import org.apache.commons.exec.ExecuteException;
-import org.apache.commons.exec.ExecuteStreamHandler;
-import org.apache.commons.exec.Executor;
-import org.apache.commons.exec.PumpStreamHandler;
-import org.apache.commons.io.IOUtils;
-import org.apache.commons.vfs2.FileContent;
-import org.apache.commons.vfs2.FileObject;
-import org.argeo.api.cms.CmsLog;
-
-/**
- * Runs an OS command and save its standard output as a file. Typically used for
- * MySQL or OpenLDAP dumps.
- */
-public class OsCallBackup extends AbstractAtomicBackup {
-       private final static CmsLog log = CmsLog.getLog(OsCallBackup.class);
-
-       private String command;
-       private Map<String, String> variables = new HashMap<String, String>();
-       private Executor executor = new DefaultExecutor();
-
-       private Map<String, String> environment = new HashMap<String, String>();
-
-       /** Name of the sudo user, root if "", not sudo if null */
-       private String sudo = null;
-
-       public OsCallBackup() {
-       }
-
-       public OsCallBackup(String name) {
-               super(name);
-       }
-
-       public OsCallBackup(String name, String command) {
-               super(name);
-               this.command = command;
-       }
-
-       @Override
-       public void writeBackup(FileObject targetFo) {
-               String commandToUse = command;
-
-               // sudo
-               if (sudo != null) {
-                       if (sudo.equals(""))
-                               commandToUse = "sudo " + commandToUse;
-                       else
-                               commandToUse = "sudo -u " + sudo + " " + commandToUse;
-               }
-
-               CommandLine commandLine = CommandLine.parse(commandToUse, variables);
-               ByteArrayOutputStream errBos = new ByteArrayOutputStream();
-               if (log.isTraceEnabled())
-                       log.trace(commandLine.toString());
-
-               try {
-                       // stdout
-                       FileContent targetContent = targetFo.getContent();
-                       // stderr
-                       ExecuteStreamHandler streamHandler = new PumpStreamHandler(targetContent.getOutputStream(), errBos);
-                       executor.setStreamHandler(streamHandler);
-                       executor.execute(commandLine, environment);
-               } catch (ExecuteException e) {
-                       byte[] err = errBos.toByteArray();
-                       String errStr = new String(err);
-                       throw new MaintenanceException("Process " + commandLine + " failed (" + e.getExitValue() + "): " + errStr, e);
-               } catch (Exception e) {
-                       byte[] err = errBos.toByteArray();
-                       String errStr = new String(err);
-                       throw new MaintenanceException("Process " + commandLine + " failed: " + errStr, e);
-               } finally {
-                       IOUtils.closeQuietly(errBos);
-               }
-       }
-
-       public void setCommand(String command) {
-               this.command = command;
-       }
-
-       protected String getCommand() {
-               return command;
-       }
-
-       /**
-        * A reference to the environment variables that will be passed to the
-        * process. Empty by default.
-        */
-       protected Map<String, String> getEnvironment() {
-               return environment;
-       }
-
-       protected Map<String, String> getVariables() {
-               return variables;
-       }
-
-       public void setVariables(Map<String, String> variables) {
-               this.variables = variables;
-       }
-
-       public void setExecutor(Executor executor) {
-               this.executor = executor;
-       }
-
-       public void setSudo(String sudo) {
-               this.sudo = sudo;
-       }
-
-}
diff --git a/cms/org.argeo.cms.integration/src/org/argeo/maintenance/backup/vfs/PostgreSqlBackup.java b/cms/org.argeo.cms.integration/src/org/argeo/maintenance/backup/vfs/PostgreSqlBackup.java
deleted file mode 100644 (file)
index 5a00c24..0000000
+++ /dev/null
@@ -1,70 +0,0 @@
-package org.argeo.maintenance.backup.vfs;
-
-import org.apache.commons.vfs2.FileObject;
-
-/** Backups a PostgreSQL database using pg_dump. */
-public class PostgreSqlBackup extends OsCallBackup {
-       /**
-        * PostgreSQL password environment variable (see
-        * http://stackoverflow.com/questions
-        * /2893954/how-to-pass-in-password-to-pg-dump)
-        */
-       protected final static String PGPASSWORD = "PGPASSWORD";
-
-       private String pgDumpLocation = "/usr/bin/pg_dump";
-
-       private String dbUser;
-       private String dbPassword;
-       private String dbName;
-
-       public PostgreSqlBackup() {
-               super();
-       }
-
-       public PostgreSqlBackup(String dbUser, String dbPassword, String dbName) {
-               this.dbUser = dbUser;
-               this.dbPassword = dbPassword;
-               this.dbName = dbName;
-               init();
-       }
-
-       @Override
-       public void init() {
-               // disable compression since pg_dump is used with -Fc option
-               setCompression(null);
-
-               if (getName() == null)
-                       setName(dbName + ".pgdump");
-               super.init();
-       }
-
-       @Override
-       public void writeBackup(FileObject targetFo) {
-               if (getCommand() == null) {
-                       getEnvironment().put(PGPASSWORD, dbPassword);
-                       setCommand(pgDumpLocation + " -Fc" + " -U ${dbUser} ${dbName}");
-               }
-               getVariables().put("dbUser", dbUser);
-               getVariables().put("dbPassword", dbPassword);
-               getVariables().put("dbName", dbName);
-
-               super.writeBackup(targetFo);
-       }
-
-       public void setDbUser(String dbUser) {
-               this.dbUser = dbUser;
-       }
-
-       public void setDbPassword(String dbPassword) {
-               this.dbPassword = dbPassword;
-       }
-
-       public void setDbName(String dbName) {
-               this.dbName = dbName;
-       }
-
-       public void setPgDumpLocation(String mysqldumpLocation) {
-               this.pgDumpLocation = mysqldumpLocation;
-       }
-
-}
diff --git a/cms/org.argeo.cms.integration/src/org/argeo/maintenance/backup/vfs/SimpleBackupContext.java b/cms/org.argeo.cms.integration/src/org/argeo/maintenance/backup/vfs/SimpleBackupContext.java
deleted file mode 100644 (file)
index 257b20f..0000000
+++ /dev/null
@@ -1,48 +0,0 @@
-package org.argeo.maintenance.backup.vfs;
-
-import java.text.DateFormat;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-
-import org.apache.commons.vfs2.FileSystemManager;
-
-/** Simple implementation of a backup context */
-public class SimpleBackupContext implements BackupContext {
-       private DateFormat dateFormat = new SimpleDateFormat("yyyyMMdd_HHmm");
-       private final Date timestamp;
-       private final String name;
-
-       private final FileSystemManager fileSystemManager;
-
-       public SimpleBackupContext(FileSystemManager fileSystemManager,
-                       String backupsBase, String name) {
-               this.name = name;
-               this.timestamp = new Date();
-               this.fileSystemManager = fileSystemManager;
-       }
-
-       public Date getTimestamp() {
-               return timestamp;
-       }
-
-       public String getTimestampAsString() {
-               return dateFormat.format(timestamp);
-       }
-
-       public String getSystemName() {
-               return name;
-       }
-
-       public String getRelativeFolder() {
-               return name + '/' + getTimestampAsString();
-       }
-
-       public DateFormat getDateFormat() {
-               return dateFormat;
-       }
-
-       public FileSystemManager getFileSystemManager() {
-               return fileSystemManager;
-       }
-
-}
diff --git a/cms/org.argeo.cms.integration/src/org/argeo/maintenance/backup/vfs/SimpleBackupPurge.java b/cms/org.argeo.cms.integration/src/org/argeo/maintenance/backup/vfs/SimpleBackupPurge.java
deleted file mode 100644 (file)
index c8baf20..0000000
+++ /dev/null
@@ -1,71 +0,0 @@
-package org.argeo.maintenance.backup.vfs;
-
-import java.text.DateFormat;
-import java.time.Period;
-import java.time.ZoneId;
-import java.time.ZonedDateTime;
-import java.util.Date;
-import java.util.SortedMap;
-import java.util.TreeMap;
-
-import org.apache.commons.vfs2.FileObject;
-import org.apache.commons.vfs2.FileSystemManager;
-import org.apache.commons.vfs2.FileSystemOptions;
-import org.apache.commons.vfs2.Selectors;
-import org.argeo.api.cms.CmsLog;
-
-/** Simple backup purge which keeps backups only for a given number of days */
-public class SimpleBackupPurge implements BackupPurge {
-       private final static CmsLog log = CmsLog.getLog(SimpleBackupPurge.class);
-
-       private Integer daysKept = 30;
-
-       @Override
-       public void purge(FileSystemManager fileSystemManager, String base, String name, DateFormat dateFormat,
-                       FileSystemOptions opts) {
-               try {
-                       ZonedDateTime nowDt = ZonedDateTime.now();
-                       FileObject baseFo = fileSystemManager.resolveFile(base + '/' + name, opts);
-
-                       SortedMap<ZonedDateTime, FileObject> toDelete = new TreeMap<ZonedDateTime, FileObject>();
-                       int backupCount = 0;
-
-                       // make sure base dir exists
-                       baseFo.createFolder();
-
-                       // scan backups and list those which should be deleted
-                       for (FileObject backupFo : baseFo.getChildren()) {
-                               String backupName = backupFo.getName().getBaseName();
-                               Date backupDate = dateFormat.parse(backupName);
-                               backupCount++;
-                               ZonedDateTime backupDt = ZonedDateTime.ofInstant(backupDate.toInstant(), ZoneId.systemDefault());
-                               Period sinceThen = Period.between(backupDt.toLocalDate(), nowDt.toLocalDate());
-                               // new Period(backupDt, nowDt);
-                               int days = sinceThen.getDays();
-                               // int days = sinceThen.getMinutes();
-                               if (days > daysKept) {
-                                       toDelete.put(backupDt, backupFo);
-                               }
-                       }
-
-                       if (toDelete.size() != 0 && toDelete.size() == backupCount) {
-                               // all backups would be deleted
-                               // but we want to keep at least one
-                               ZonedDateTime lastBackupDt = toDelete.firstKey();
-                               FileObject keptFo = toDelete.remove(lastBackupDt);
-                               log.warn("Backup " + keptFo + " kept although it is older than " + daysKept + " days.");
-                       }
-
-                       // delete old backups
-                       for (FileObject backupFo : toDelete.values()) {
-                               backupFo.delete(Selectors.SELECT_ALL);
-                               if (log.isDebugEnabled())
-                                       log.debug("Deleted backup " + backupFo);
-                       }
-               } catch (Exception e) {
-                       throw new MaintenanceException("Could not purge previous backups", e);
-               }
-
-       }
-
-}
diff --git a/cms/org.argeo.cms.integration/src/org/argeo/maintenance/backup/vfs/SvnBackup.java b/cms/org.argeo.cms.integration/src/org/argeo/maintenance/backup/vfs/SvnBackup.java
deleted file mode 100644 (file)
index 711f518..0000000
+++ /dev/null
@@ -1,55 +0,0 @@
-package org.argeo.maintenance.backup.vfs;
-
-import java.io.File;
-
-import org.apache.commons.vfs2.FileObject;
-
-/** Backups a Subversion repository using svnadmin. */
-public class SvnBackup extends OsCallBackup {
-       private String svnadminLocation = "/usr/bin/svnadmin";
-
-       private String repoLocation;
-       private String repoName;
-
-       public SvnBackup() {
-       }
-
-       public SvnBackup(String repoLocation) {
-               this.repoLocation = repoLocation;
-               init();
-       }
-
-       @Override
-       public void init() {
-               // use directory as repo name
-               if (repoName == null)
-                       repoName = new File(repoLocation).getName();
-
-               if (getName() == null)
-                       setName(repoName + ".svndump");
-               super.init();
-       }
-
-       @Override
-       public void writeBackup(FileObject targetFo) {
-               if (getCommand() == null) {
-                       setCommand(svnadminLocation + " dump " + " ${repoLocation}");
-               }
-               getVariables().put("repoLocation", repoLocation);
-
-               super.writeBackup(targetFo);
-       }
-
-       public void setRepoLocation(String repoLocation) {
-               this.repoLocation = repoLocation;
-       }
-
-       public void setRepoName(String repoName) {
-               this.repoName = repoName;
-       }
-
-       public void setSvnadminLocation(String mysqldumpLocation) {
-               this.svnadminLocation = mysqldumpLocation;
-       }
-
-}
diff --git a/cms/org.argeo.cms.integration/src/org/argeo/maintenance/backup/vfs/SystemBackup.java b/cms/org.argeo.cms.integration/src/org/argeo/maintenance/backup/vfs/SystemBackup.java
deleted file mode 100644 (file)
index 76fc668..0000000
+++ /dev/null
@@ -1,199 +0,0 @@
-package org.argeo.maintenance.backup.vfs;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import org.apache.commons.vfs2.FileObject;
-import org.apache.commons.vfs2.FileSystemException;
-import org.apache.commons.vfs2.FileSystemManager;
-import org.apache.commons.vfs2.FileSystemOptions;
-import org.apache.commons.vfs2.Selectors;
-import org.apache.commons.vfs2.UserAuthenticator;
-import org.apache.commons.vfs2.impl.DefaultFileSystemConfigBuilder;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.util.LangUtils;
-
-/**
- * Combines multiple backups and transfer them to a remote location. Purges
- * remote and local data based on certain criteria.
- */
-public class SystemBackup implements Runnable {
-       private final static CmsLog log = CmsLog.getLog(SystemBackup.class);
-
-       private FileSystemManager fileSystemManager;
-       private UserAuthenticator userAuthenticator = null;
-
-       private String backupsBase;
-       private String systemName;
-
-       private List<AtomicBackup> atomicBackups = new ArrayList<AtomicBackup>();
-       private BackupPurge backupPurge = new SimpleBackupPurge();
-
-       private Map<String, UserAuthenticator> remoteBases = new HashMap<String, UserAuthenticator>();
-
-       @Override
-       public void run() {
-               if (atomicBackups.size() == 0)
-                       throw new MaintenanceException("No atomic backup listed");
-               List<String> failures = new ArrayList<String>();
-
-               SimpleBackupContext backupContext = new SimpleBackupContext(fileSystemManager, backupsBase, systemName);
-
-               // purge older backups
-               FileSystemOptions opts = new FileSystemOptions();
-               try {
-                       DefaultFileSystemConfigBuilder.getInstance().setUserAuthenticator(opts, userAuthenticator);
-               } catch (Exception e) {
-                       throw new MaintenanceException("Cannot create authentication", e);
-               }
-
-               try {
-
-                       backupPurge.purge(fileSystemManager, backupsBase, systemName, backupContext.getDateFormat(), opts);
-               } catch (Exception e) {
-                       failures.add("Purge " + backupsBase + " failed: " + e.getMessage());
-                       log.error("Purge of " + backupsBase + " failed", e);
-               }
-
-               // perform backup
-               for (AtomicBackup atomickBackup : atomicBackups) {
-                       try {
-                               String target = atomickBackup.backup(fileSystemManager, backupsBase, backupContext, opts);
-                               if (log.isDebugEnabled())
-                                       log.debug("Performed backup " + target);
-                       } catch (Exception e) {
-                               String msg = "Atomic backup " + atomickBackup.getName() + " failed: "
-                                               + LangUtils.chainCausesMessages(e);
-                               failures.add(msg);
-                               log.error(msg);
-                               if (log.isTraceEnabled())
-                                       log.trace("Stacktrace of atomic backup " + atomickBackup.getName() + " failure.", e);
-                       }
-               }
-
-               // dispatch to remote
-               for (String remoteBase : remoteBases.keySet()) {
-                       FileObject localBaseFo = null;
-                       FileObject remoteBaseFo = null;
-                       UserAuthenticator auth = remoteBases.get(remoteBase);
-
-                       // authentication
-                       FileSystemOptions remoteOpts = new FileSystemOptions();
-                       try {
-                               DefaultFileSystemConfigBuilder.getInstance().setUserAuthenticator(remoteOpts, auth);
-                               backupPurge.purge(fileSystemManager, remoteBase, systemName, backupContext.getDateFormat(), remoteOpts);
-                       } catch (Exception e) {
-                               failures.add("Purge " + remoteBase + " failed: " + e.getMessage());
-                               log.error("Cannot purge " + remoteBase, e);
-                       }
-
-                       try {
-                               localBaseFo = fileSystemManager.resolveFile(backupsBase + '/' + backupContext.getRelativeFolder(),
-                                               opts);
-                               remoteBaseFo = fileSystemManager.resolveFile(remoteBase + '/' + backupContext.getRelativeFolder(),
-                                               remoteOpts);
-                               remoteBaseFo.copyFrom(localBaseFo, Selectors.SELECT_ALL);
-                               if (log.isDebugEnabled())
-                                       log.debug("Copied backup to " + remoteBaseFo + " from " + localBaseFo);
-                               // }
-                       } catch (Exception e) {
-                               failures.add("Dispatch to " + remoteBase + " failed: " + e.getMessage());
-                               log.error("Cannot dispatch backups from " + backupContext.getRelativeFolder() + " to " + remoteBase, e);
-                       }
-                       BackupUtils.closeFOQuietly(localBaseFo);
-                       BackupUtils.closeFOQuietly(remoteBaseFo);
-               }
-
-               int failureCount = 0;
-               if (failures.size() > 0) {
-                       StringBuffer buf = new StringBuffer();
-                       for (String failure : failures) {
-                               buf.append('\n').append(failureCount).append(" - ").append(failure);
-                               failureCount++;
-                       }
-                       throw new MaintenanceException(failureCount + " error(s) when running the backup,"
-                                       + " check the logs and the backups as soon as possible." + buf);
-               }
-       }
-
-       public void setFileSystemManager(FileSystemManager fileSystemManager) {
-               this.fileSystemManager = fileSystemManager;
-       }
-
-       public void setBackupsBase(String backupsBase) {
-               this.backupsBase = backupsBase;
-       }
-
-       public void setSystemName(String name) {
-               this.systemName = name;
-       }
-
-       public void setAtomicBackups(List<AtomicBackup> atomicBackups) {
-               this.atomicBackups = atomicBackups;
-       }
-
-       public void setBackupPurge(BackupPurge backupPurge) {
-               this.backupPurge = backupPurge;
-       }
-
-       public void setUserAuthenticator(UserAuthenticator userAuthenticator) {
-               this.userAuthenticator = userAuthenticator;
-       }
-
-       public void setRemoteBases(Map<String, UserAuthenticator> remoteBases) {
-               this.remoteBases = remoteBases;
-       }
-
-       // public static void main(String args[]) {
-       // while (true) {
-       // try {
-       // StandardFileSystemManager fsm = new StandardFileSystemManager();
-       // fsm.init();
-       //
-       // SystemBackup systemBackup = new SystemBackup();
-       // systemBackup.setSystemName("mySystem");
-       // systemBackup
-       // .setBackupsBase("/home/mbaudier/dev/src/commons/server/runtime/org.argeo.server.core/target");
-       // systemBackup.setFileSystemManager(fsm);
-       //
-       // List<AtomicBackup> atomicBackups = new ArrayList<AtomicBackup>();
-       //
-       // MySqlBackup mySqlBackup = new MySqlBackup("root", "", "test");
-       // atomicBackups.add(mySqlBackup);
-       // PostgreSqlBackup postgreSqlBackup = new PostgreSqlBackup(
-       // "argeo", "argeo", "gis_template");
-       // atomicBackups.add(postgreSqlBackup);
-       // SvnBackup svnBackup = new SvnBackup(
-       // "/home/mbaudier/tmp/testsvnrepo");
-       // atomicBackups.add(svnBackup);
-       //
-       // systemBackup.setAtomicBackups(atomicBackups);
-       //
-       // Map<String, UserAuthenticator> remoteBases = new HashMap<String,
-       // UserAuthenticator>();
-       // StaticUserAuthenticator userAuthenticator = new StaticUserAuthenticator(
-       // null, "demo", "demo");
-       // remoteBases.put("sftp://localhost/home/mbaudier/test",
-       // userAuthenticator);
-       // systemBackup.setRemoteBases(remoteBases);
-       //
-       // systemBackup.run();
-       //
-       // fsm.close();
-       // } catch (FileSystemException e) {
-       // // TODO Auto-generated catch block
-       // e.printStackTrace();
-       // System.exit(1);
-       // }
-       //
-       // // wait
-       // try {
-       // Thread.sleep(120 * 1000);
-       // } catch (InterruptedException e) {
-       // e.printStackTrace();
-       // }
-       // }
-       // }
-}
diff --git a/cms/org.argeo.cms.integration/src/org/argeo/maintenance/backup/vfs/package-info.java b/cms/org.argeo.cms.integration/src/org/argeo/maintenance/backup/vfs/package-info.java
deleted file mode 100644 (file)
index f5d7142..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Argeo Node backup utilities based on Apache Commons VFS. */
-package org.argeo.maintenance.backup.vfs;
\ No newline at end of file
diff --git a/cms/org.argeo.cms.integration/src/org/argeo/ssh/AbstractSsh.java b/cms/org.argeo.cms.integration/src/org/argeo/ssh/AbstractSsh.java
deleted file mode 100644 (file)
index 6bdb9bc..0000000
+++ /dev/null
@@ -1,188 +0,0 @@
-package org.argeo.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.client.subsystem.sftp.fs.SftpFileSystemProvider;
-import org.apache.sshd.common.util.io.NoCloseInputStream;
-import org.apache.sshd.common.util.io.NoCloseOutputStream;
-import org.argeo.api.cms.CmsLog;
-
-@SuppressWarnings("restriction")
-abstract class AbstractSsh {
-       private final static CmsLog log = CmsLog.getLog(AbstractSsh.class);
-
-       private static SshClient sshClient;
-       private static SftpFileSystemProvider sftpFileSystemProvider;
-
-       private boolean passwordSet = false;
-       private ClientSession session;
-
-       private SshKeyPair sshKeyPair;
-
-       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;
-       }
-
-       void authenticate() {
-               try {
-                       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;
-                               }
-                       }
-                       session.auth().verify(1000l);
-               } catch (IOException e) {
-                       throw new IllegalStateException(e);
-               }
-       }
-
-       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);
-               }
-       }
-
-       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(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);
-               }
-       }
-
-}
diff --git a/cms/org.argeo.cms.integration/src/org/argeo/ssh/BasicSshServer.java b/cms/org.argeo.cms.integration/src/org/argeo/ssh/BasicSshServer.java
deleted file mode 100644 (file)
index 2a4d8f4..0000000
+++ /dev/null
@@ -1,105 +0,0 @@
-package org.argeo.ssh;
-
-import java.io.IOException;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-
-import org.apache.sshd.server.SshServer;
-import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
-import org.apache.sshd.server.scp.ScpCommandFactory;
-import org.apache.sshd.server.shell.ProcessShellFactory;
-import org.argeo.util.OS;
-
-/** A simple SSH server with some defaults. Supports SCP. */
-@SuppressWarnings("restriction")
-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.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]");
-       }
-
-}
diff --git a/cms/org.argeo.cms.integration/src/org/argeo/ssh/Sftp.java b/cms/org.argeo.cms.integration/src/org/argeo/ssh/Sftp.java
deleted file mode 100644 (file)
index da10b96..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-package org.argeo.ssh;
-
-import java.io.IOException;
-import java.net.URI;
-import java.nio.file.FileSystem;
-import java.nio.file.Path;
-
-import org.apache.sshd.client.subsystem.sftp.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);
-       }
-
-}
diff --git a/cms/org.argeo.cms.integration/src/org/argeo/ssh/Ssh.java b/cms/org.argeo.cms.integration/src/org/argeo/ssh/Ssh.java
deleted file mode 100644 (file)
index 68dd912..0000000
+++ /dev/null
@@ -1,81 +0,0 @@
-package org.argeo.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);
-       }
-}
diff --git a/cms/org.argeo.cms.integration/src/org/argeo/ssh/SshKeyPair.java b/cms/org.argeo.cms.integration/src/org/argeo/ssh/SshKeyPair.java
deleted file mode 100644 (file)
index f9b3485..0000000
+++ /dev/null
@@ -1,182 +0,0 @@
-package org.argeo.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.PEMKeyPair;
-import org.bouncycastle.openssl.PEMParser;
-import org.bouncycastle.openssl.PKCS8Generator;
-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 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 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()));
-       }
-
-}
diff --git a/cms/org.argeo.cms.integration/src/org/argeo/ssh/SshSync.java b/cms/org.argeo.cms.integration/src/org/argeo/ssh/SshSync.java
deleted file mode 100644 (file)
index b9f1e91..0000000
+++ /dev/null
@@ -1,134 +0,0 @@
-package org.argeo.ssh;
-
-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.util.Map;
-import java.util.Scanner;
-
-import org.apache.commons.io.IOUtils;
-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.future.ConnectFuture;
-import org.apache.sshd.client.session.ClientSession;
-import org.apache.sshd.client.subsystem.sftp.fs.SftpFileSystem;
-import org.apache.sshd.client.subsystem.sftp.fs.SftpFileSystemProvider;
-import org.argeo.api.cms.CmsLog;
-
-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 = true;
-                       SshAgentFactory agentFactory = osAgent ? new UnixAgentFactory() : new LocalAgentFactory();
-                       // SshAgentFactory agentFactory = new LocalAgentFactory();
-                       client.setAgentFactory(agentFactory);
-                       SshAgent sshAgent = agentFactory.createClient(client);
-
-                       String login = System.getProperty("user.name");
-                       String host = "localhost";
-                       int port = 22;
-
-                       if (!osAgent) {
-                               String keyPath = "/home/" + login + "/.ssh/id_rsa";
-                               System.out.print(keyPath + ": ");
-                               Scanner s = new Scanner(System.in);
-                               String password = s.next();
-//                             KeyPair keyPair = ClientIdentityLoader.DEFAULT.loadClientIdentity(keyPath,
-//                                             FilePasswordProvider.of(password));
-//                             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)) {
-                               IOUtils.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();
-               }
-
-       }
-
-}
diff --git a/cms/org.argeo.cms.integration/src/org/argeo/ssh/package-info.java b/cms/org.argeo.cms.integration/src/org/argeo/ssh/package-info.java
deleted file mode 100644 (file)
index 8324a7a..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** SSH support. */
-package org.argeo.ssh;
\ No newline at end of file
diff --git a/cnf/build.bnd b/cnf/build.bnd
deleted file mode 100644 (file)
index a464edc..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
--include: \
-${workspace}/cnf/testing.bnd, \
-${workspace}/sdk/argeo-build/argeo.bnd, \
diff --git a/cnf/testing.bnd b/cnf/testing.bnd
deleted file mode 100644 (file)
index bae9806..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-MAJOR=2
-MINOR=1
-MICRO=20
-qualifier=.next
-
-category=org.argeo.slc
-Bundle-RequiredExecutionEnvironment=JavaSE-11
-
-argeo.rpm.stagingRepository=/srv/rpmfactory/testing/argeo-osgi-2/argeo
-argeo.rpm.suffix=
diff --git a/cnf/unstable.bnd b/cnf/unstable.bnd
deleted file mode 100644 (file)
index 79e09bd..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-MAJOR=2
-MINOR=3
-MICRO=3
-qualifier=.next
-
-category=org.argeo.slc
-Bundle-RequiredExecutionEnvironment=JavaSE-11
-
-argeo.rpm.stagingRepository=/srv/rpmfactory/unstable/argeo-osgi-2/argeo
-argeo.rpm.suffix=-unstable
old mode 100644 (file)
new mode 100755 (executable)
index 9b3e980..47f7d96
--- a/configure
+++ b/configure
@@ -1,55 +1,7 @@
 #!/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
diff --git a/ext/org.argeo.ext.equinox.jetty/.classpath b/ext/org.argeo.ext.equinox.jetty/.classpath
deleted file mode 100644 (file)
index eca7bdb..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-<?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>
diff --git a/ext/org.argeo.ext.equinox.jetty/.gitignore b/ext/org.argeo.ext.equinox.jetty/.gitignore
deleted file mode 100644 (file)
index 09e3bc9..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/bin/
-/target/
diff --git a/ext/org.argeo.ext.equinox.jetty/.project b/ext/org.argeo.ext.equinox.jetty/.project
deleted file mode 100644 (file)
index 0b9700d..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
-       <name>org.argeo.ext.equinox.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>
diff --git a/ext/org.argeo.ext.equinox.jetty/META-INF/.gitignore b/ext/org.argeo.ext.equinox.jetty/META-INF/.gitignore
deleted file mode 100644 (file)
index 4854a41..0000000
+++ /dev/null
@@ -1 +0,0 @@
-/MANIFEST.MF
diff --git a/ext/org.argeo.ext.equinox.jetty/bnd.bnd b/ext/org.argeo.ext.equinox.jetty/bnd.bnd
deleted file mode 100644 (file)
index 0f21e73..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-Fragment-Host: org.eclipse.equinox.http.jetty
-
-Import-Package: org.eclipse.jetty.websocket.jsr356,\
-org.eclipse.jetty.websocket.api,\
-org.eclipse.jetty.websocket.common,\
-org.osgi.service.http,\
-*
\ No newline at end of file
diff --git a/ext/org.argeo.ext.equinox.jetty/build.properties b/ext/org.argeo.ext.equinox.jetty/build.properties
deleted file mode 100644 (file)
index 34d2e4d..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-source.. = src/
-output.. = bin/
-bin.includes = META-INF/,\
-               .
diff --git a/ext/org.argeo.ext.equinox.jetty/src/org/argeo/equinox/jetty/CmsJettyCustomizer.java b/ext/org.argeo.ext.equinox.jetty/src/org/argeo/equinox/jetty/CmsJettyCustomizer.java
deleted file mode 100644 (file)
index 46f0280..0000000
+++ /dev/null
@@ -1,40 +0,0 @@
-package org.argeo.equinox.jetty;
-
-import java.util.Dictionary;
-
-import javax.servlet.ServletContext;
-import javax.websocket.DeploymentException;
-
-import org.eclipse.equinox.http.jetty.JettyCustomizer;
-import org.eclipse.jetty.servlet.ServletContextHandler;
-import org.eclipse.jetty.websocket.jsr356.server.ServerContainer;
-import org.eclipse.jetty.websocket.jsr356.server.deploy.WebSocketServerContainerInitializer;
-import org.eclipse.jetty.websocket.jsr356.server.deploy.WebSocketServerContainerInitializer.Configurator;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.FrameworkUtil;
-
-/** Customises the Jetty HTTP server. */
-public class CmsJettyCustomizer extends JettyCustomizer {
-       private BundleContext bc = FrameworkUtil.getBundle(CmsJettyCustomizer.class).getBundleContext();
-
-       public final static String WEBSOCKET_ENABLED = "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;
-                       WebSocketServerContainerInitializer.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);
-
-       }
-}
diff --git a/ext/org.argeo.ext.equinox.jetty/src/org/argeo/equinox/jetty/package-info.java b/ext/org.argeo.ext.equinox.jetty/src/org/argeo/equinox/jetty/package-info.java
deleted file mode 100644 (file)
index 41c8ce9..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Equinox Jetty extensions. */
-package org.argeo.equinox.jetty;
\ No newline at end of file
diff --git a/ext/org.argeo.ext.slf4j/.classpath b/ext/org.argeo.ext.slf4j/.classpath
deleted file mode 100644 (file)
index e801ebf..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-<?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>
diff --git a/ext/org.argeo.ext.slf4j/.project b/ext/org.argeo.ext.slf4j/.project
deleted file mode 100644 (file)
index 3ed363d..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
-       <name>org.argeo.ext.slf4j</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>
diff --git a/ext/org.argeo.ext.slf4j/bnd.bnd b/ext/org.argeo.ext.slf4j/bnd.bnd
deleted file mode 100644 (file)
index 37061df..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-Export-Package: org.slf4j.impl
-
-Import-Package: *
diff --git a/ext/org.argeo.ext.slf4j/build.properties b/ext/org.argeo.ext.slf4j/build.properties
deleted file mode 100644 (file)
index 34d2e4d..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-source.. = src/
-output.. = bin/
-bin.includes = META-INF/,\
-               .
diff --git a/ext/org.argeo.ext.slf4j/src/org/slf4j/impl/ArgeoLogger.java b/ext/org.argeo.ext.slf4j/src/org/slf4j/impl/ArgeoLogger.java
deleted file mode 100644 (file)
index 5c4889a..0000000
+++ /dev/null
@@ -1,180 +0,0 @@
-package org.slf4j.impl;
-
-import org.slf4j.helpers.MarkerIgnoringBase;
-
-class ArgeoLogger extends MarkerIgnoringBase {
-       private static final long serialVersionUID = -7719157836932627307L;
-       private final SystemLoggingAdapter log;
-
-       protected ArgeoLogger(String name, SystemLoggingAdapter log) {
-               this.name = name;
-               this.log = log;
-       }
-
-       @Override
-       public boolean isDebugEnabled() {
-               return log.isDebugEnabled();
-       }
-
-       @Override
-       public boolean isTraceEnabled() {
-               return log.isDebugEnabled();
-       }
-
-       @Override
-       public void trace(String msg) {
-               log.trace(msg);
-
-       }
-
-       @Override
-       public void trace(String format, Object... arguments) {
-               log.trace(format, arguments);
-
-       }
-
-       @Override
-       public void trace(String msg, Throwable t) {
-               log.trace(msg, t);
-
-       }
-
-       @Override
-       public void debug(String msg) {
-               log.debug(msg);
-
-       }
-
-       @Override
-       public void debug(String format, Object... arguments) {
-               log.debug(format, arguments);
-
-       }
-
-       @Override
-       public void debug(String msg, Throwable t) {
-               log.debug(msg, t);
-
-       }
-
-       @Override
-       public boolean isInfoEnabled() {
-               return log.isInfoEnabled();
-       }
-
-       @Override
-       public void info(String msg) {
-               log.info(msg);
-
-       }
-
-       @Override
-       public void info(String format, Object... arguments) {
-               log.info(format, arguments);
-       }
-
-       @Override
-       public void info(String msg, Throwable t) {
-               log.info(msg, t);
-
-       }
-
-       @Override
-       public boolean isWarnEnabled() {
-               return log.isWarnEnabled();
-       }
-
-       @Override
-       public void warn(String msg) {
-               log.warn(msg);
-
-       }
-
-       @Override
-       public void warn(String format, Object... arguments) {
-               log.warn(format, arguments);
-
-       }
-
-       @Override
-       public void warn(String msg, Throwable t) {
-               log.warn(msg, t);
-
-       }
-
-       @Override
-       public boolean isErrorEnabled() {
-
-               return log.isErrorEnabled();
-       }
-
-       @Override
-       public void error(String msg) {
-               log.error(msg);
-
-       }
-
-       @Override
-       public void error(String format, Object... arguments) {
-               log.error(format, arguments);
-
-       }
-
-       @Override
-       public void error(String msg, Throwable t) {
-               log.error(msg, t);
-
-       }
-
-       @Override
-       public void trace(String format, Object arg) {
-               trace(format, new Object[] { arg });
-
-       }
-
-       @Override
-       public void trace(String format, Object arg1, Object arg2) {
-               trace(format, new Object[] { arg1, arg2 });
-       }
-
-       @Override
-       public void debug(String format, Object arg) {
-               debug(format, new Object[] { arg });
-       }
-
-       @Override
-       public void debug(String format, Object arg1, Object arg2) {
-               debug(format, new Object[] { arg1, arg2 });
-       }
-
-       @Override
-       public void info(String format, Object arg) {
-               info(format, new Object[] { arg });
-       }
-
-       @Override
-       public void info(String format, Object arg1, Object arg2) {
-               info(format, new Object[] { arg1, arg2 });
-       }
-
-       @Override
-       public void warn(String format, Object arg) {
-               warn(format, new Object[] { arg });
-       }
-
-       @Override
-       public void warn(String format, Object arg1, Object arg2) {
-               warn(format, new Object[] { arg1, arg2 });
-       }
-
-       @Override
-       public void error(String format, Object arg) {
-               error(format, new Object[] { arg });
-       }
-
-       @Override
-       public void error(String format, Object arg1, Object arg2) {
-               error(format, new Object[] { arg1, arg2 });
-       }
-
-}
\ No newline at end of file
diff --git a/ext/org.argeo.ext.slf4j/src/org/slf4j/impl/StaticLoggerBinder.java b/ext/org.argeo.ext.slf4j/src/org/slf4j/impl/StaticLoggerBinder.java
deleted file mode 100644 (file)
index 8c5b3ad..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-package org.slf4j.impl;
-
-import org.slf4j.ILoggerFactory;
-import org.slf4j.spi.LoggerFactoryBinder;
-
-public class StaticLoggerBinder implements LoggerFactoryBinder {
-       public static final String REQUESTED_API_VERSION = "1.7";
-
-       private static final StaticLoggerBinder SINGLETON = new StaticLoggerBinder();
-
-       private final ILoggerFactory loggerFactory;
-
-       protected StaticLoggerBinder() {
-               loggerFactory = new SystemLoggerFactory();
-       }
-
-       @Override
-       public ILoggerFactory getLoggerFactory() {
-               return loggerFactory;
-       }
-
-       @Override
-       public String getLoggerFactoryClassStr() {
-               return SystemLoggerFactory.class.getName();
-       }
-
-       public static final StaticLoggerBinder getSingleton() {
-               return SINGLETON;
-       }
-
-       static class SystemLoggerFactory implements ILoggerFactory {
-
-               @Override
-               public org.slf4j.Logger getLogger(String name) {
-                       SystemLoggingAdapter logger = SystemLoggingAdapter.getLog(name);
-                       return new ArgeoLogger(name, logger);
-               }
-
-       }
-
-}
diff --git a/ext/org.argeo.ext.slf4j/src/org/slf4j/impl/SystemLoggingAdapter.java b/ext/org.argeo.ext.slf4j/src/org/slf4j/impl/SystemLoggingAdapter.java
deleted file mode 100644 (file)
index 7a6c865..0000000
+++ /dev/null
@@ -1,203 +0,0 @@
-package org.slf4j.impl;
-
-import java.lang.System.Logger;
-import java.lang.System.Logger.Level;
-import java.util.Objects;
-import java.util.function.Supplier;
-
-/**
- * A Commons Logging / SLF4J style logging utilities wrapping a standard Java
- * platform {@link Logger}.
- */
-public interface SystemLoggingAdapter {
-       Logger getLogger();
-
-       default boolean isDebugEnabled() {
-               return getLogger().isLoggable(Level.DEBUG);
-       }
-
-       default boolean isErrorEnabled() {
-               return getLogger().isLoggable(Level.ERROR);
-       }
-
-       default boolean isInfoEnabled() {
-               return getLogger().isLoggable(Level.INFO);
-       }
-
-       default boolean isTraceEnabled() {
-               return getLogger().isLoggable(Level.TRACE);
-       }
-
-       default boolean isWarnEnabled() {
-               return getLogger().isLoggable(Level.WARNING);
-       }
-
-       /*
-        * TRACE
-        */
-
-       default void trace(String message) {
-               getLogger().log(Level.TRACE, message);
-       }
-
-       default void trace(Supplier<String> message) {
-               getLogger().log(Level.TRACE, message);
-       }
-
-       default void trace(Object message) {
-               getLogger().log(Level.TRACE, Objects.requireNonNull(message));
-       }
-
-       default void trace(String message, Throwable t) {
-               getLogger().log(Level.TRACE, message, t);
-       }
-
-       default void trace(Object message, Throwable t) {
-               trace(Objects.requireNonNull(message).toString(), t);
-       }
-
-       default void trace(String format, Object... arguments) {
-               getLogger().log(Level.TRACE, format, arguments);
-       }
-
-       /*
-        * DEBUG
-        */
-
-       default void debug(String message) {
-               getLogger().log(Level.DEBUG, message);
-       }
-
-       default void debug(Supplier<String> message) {
-               getLogger().log(Level.DEBUG, message);
-       }
-
-       default void debug(Object message) {
-               getLogger().log(Level.DEBUG, message);
-       }
-
-       default void debug(String message, Throwable t) {
-               getLogger().log(Level.DEBUG, message, t);
-       }
-
-       default void debug(Object message, Throwable t) {
-               debug(Objects.requireNonNull(message).toString(), t);
-       }
-
-       default void debug(String format, Object... arguments) {
-               getLogger().log(Level.DEBUG, format, arguments);
-       }
-
-       /*
-        * INFO
-        */
-
-       default void info(String message) {
-               getLogger().log(Level.INFO, message);
-       }
-
-       default void info(Supplier<String> message) {
-               getLogger().log(Level.INFO, message);
-       }
-
-       default void info(Object message) {
-               getLogger().log(Level.INFO, message);
-       }
-
-       default void info(String message, Throwable t) {
-               getLogger().log(Level.INFO, message, t);
-       }
-
-       default void info(Object message, Throwable t) {
-               info(Objects.requireNonNull(message).toString(), t);
-       }
-
-       default void info(String format, Object... arguments) {
-               getLogger().log(Level.INFO, format, arguments);
-       }
-
-       /*
-        * WARN
-        */
-
-       default void warn(String message) {
-               getLogger().log(Level.WARNING, message);
-       }
-
-       default void warn(Supplier<String> message) {
-               getLogger().log(Level.WARNING, message);
-       }
-
-       default void warn(Object message) {
-               getLogger().log(Level.WARNING, message);
-       }
-
-       default void warn(String message, Throwable t) {
-               getLogger().log(Level.WARNING, message, t);
-       }
-
-       default void warn(Object message, Throwable t) {
-               warn(Objects.requireNonNull(message).toString(), t);
-       }
-
-       default void warn(String format, Object... arguments) {
-               getLogger().log(Level.WARNING, format, arguments);
-       }
-
-       /*
-        * ERROR
-        */
-
-       default void error(String message) {
-               getLogger().log(Level.ERROR, message);
-       }
-
-       default void error(Supplier<String> message) {
-               getLogger().log(Level.ERROR, message);
-       }
-
-       default void error(Object message) {
-               getLogger().log(Level.ERROR, message);
-       }
-
-       default void error(String message, Throwable t) {
-               getLogger().log(Level.ERROR, message, t);
-       }
-
-       default void error(Object message, Throwable t) {
-               error(Objects.requireNonNull(message).toString(), t);
-       }
-
-       default void error(String format, Object... arguments) {
-               getLogger().log(Level.ERROR, format, arguments);
-       }
-
-       /*
-        * STATIC UTILITIES
-        */
-
-       static SystemLoggingAdapter getLog(Class<?> clss) {
-               return getLog(Objects.requireNonNull(clss).getName());
-       }
-
-       static SystemLoggingAdapter getLog(String name) {
-               Logger logger = System.getLogger(Objects.requireNonNull(name));
-               return new LoggerWrapper(logger);
-       }
-
-       /** A trivial implementation wrapping a platform logger. */
-       static class LoggerWrapper implements SystemLoggingAdapter {
-               private final Logger logger;
-
-               LoggerWrapper(Logger logger) {
-                       this.logger = logger;
-               }
-
-               @Override
-               public Logger getLogger() {
-                       return logger;
-               }
-
-       }
-
-}
diff --git a/ext/osgi.system/.classpath b/ext/osgi.system/.classpath
new file mode 100644 (file)
index 0000000..81fe078
--- /dev/null
@@ -0,0 +1,7 @@
+<?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>
diff --git a/ext/osgi.system/.project b/ext/osgi.system/.project
new file mode 100644 (file)
index 0000000..d7568b0
--- /dev/null
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+       <name>osgi.system</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>
diff --git a/ext/osgi.system/.settings/org.eclipse.jdt.core.prefs b/ext/osgi.system/.settings/org.eclipse.jdt.core.prefs
new file mode 100644 (file)
index 0000000..62ef348
--- /dev/null
@@ -0,0 +1,9 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=17
+org.eclipse.jdt.core.compiler.compliance=17
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning
+org.eclipse.jdt.core.compiler.release=enabled
+org.eclipse.jdt.core.compiler.source=17
diff --git a/ext/osgi.system/.settings/org.eclipse.pde.core.prefs b/ext/osgi.system/.settings/org.eclipse.pde.core.prefs
new file mode 100644 (file)
index 0000000..e8ff8be
--- /dev/null
@@ -0,0 +1,4 @@
+eclipse.preferences.version=1
+pluginProject.equinox=false
+pluginProject.extensions=false
+resolve.requirebundle=false
diff --git a/ext/osgi.system/.settings/org.eclipse.pde.prefs b/ext/osgi.system/.settings/org.eclipse.pde.prefs
new file mode 100644 (file)
index 0000000..d2a4d8a
--- /dev/null
@@ -0,0 +1,35 @@
+compilers.f.unresolved-features=1
+compilers.f.unresolved-plugins=1
+compilers.incompatible-environment=1
+compilers.p.build=1
+compilers.p.build.bin.includes=1
+compilers.p.build.encodings=2
+compilers.p.build.java.compiler=2
+compilers.p.build.java.compliance=1
+compilers.p.build.missing.output=2
+compilers.p.build.output.library=1
+compilers.p.build.source.library=1
+compilers.p.build.src.includes=1
+compilers.p.deprecated=1
+compilers.p.discouraged-class=1
+compilers.p.exec-env-too-low=1
+compilers.p.internal=1
+compilers.p.missing-packages=2
+compilers.p.missing-version-export-package=2
+compilers.p.missing-version-import-package=2
+compilers.p.missing-version-require-bundle=2
+compilers.p.no-required-att=0
+compilers.p.no.automatic.module=1
+compilers.p.not-externalized-att=2
+compilers.p.service.component.without.lazyactivation=1
+compilers.p.unknown-attribute=1
+compilers.p.unknown-class=1
+compilers.p.unknown-element=1
+compilers.p.unknown-identifier=1
+compilers.p.unknown-resource=1
+compilers.p.unresolved-ex-points=0
+compilers.p.unresolved-import=1
+compilers.s.create-docs=false
+compilers.s.doc-folder=doc
+compilers.s.open-tags=1
+eclipse.preferences.version=1
diff --git a/ext/osgi.system/JavaSE-9.profile b/ext/osgi.system/JavaSE-9.profile
new file mode 100644 (file)
index 0000000..53b2728
--- /dev/null
@@ -0,0 +1,281 @@
+###############################################################################
+# Copyright (c) 2009, 2018 IBM Corporation and others.
+#
+# This program and the accompanying materials
+# are made available under the terms of the Eclipse Public License 2.0
+# which accompanies this distribution, and is available at
+# https://www.eclipse.org/legal/epl-2.0/
+#
+# SPDX-License-Identifier: EPL-2.0
+# 
+# Contributors:
+#     IBM Corporation - initial API and implementation
+###############################################################################
+
+# NOTE: The JavaSE-9 profile is not yet finalized.
+
+org.osgi.framework.system.packages = \
+ java.applet,\
+ java.awt,\
+ java.awt.color,\
+ java.awt.datatransfer,\
+ java.awt.desktop,\
+ java.awt.dnd,\
+ java.awt.event,\
+ java.awt.font,\
+ java.awt.geom,\
+ java.awt.im,\
+ java.awt.im.spi,\
+ java.awt.image,\
+ java.awt.image.renderable,\
+ java.awt.print,\
+ java.beans,\
+ java.beans.beancontext,\
+ java.io,\
+ java.lang,\
+ java.lang.annotation,\
+ java.lang.instrument,\
+ java.lang.invoke,\
+ java.lang.management,\
+ java.lang.module,\
+ java.lang.ref,\
+ java.lang.reflect,\
+ java.math,\
+ java.net,\
+ java.net.spi,\
+ java.nio,\
+ java.nio.channels,\
+ java.nio.channels.spi,\
+ java.nio.charset,\
+ java.nio.charset.spi,\
+ java.nio.file,\
+ java.nio.file.attribute,\
+ java.nio.file.spi,\
+ java.rmi,\
+ java.rmi.activation,\
+ java.rmi.dgc,\
+ java.rmi.registry,\
+ java.rmi.server,\
+ java.security,\
+ java.security.acl,\
+ java.security.cert,\
+ java.security.interfaces,\
+ java.security.spec,\
+ java.sql,\
+ java.text,\
+ java.text.spi,\
+ java.time,\
+ java.time.chrono,\
+ java.time.format,\
+ java.time.temporal,\
+ java.time.zone,\
+ java.util,\
+ java.util.concurrent,\
+ java.util.concurrent.atomic,\
+ java.util.concurrent.locks,\
+ java.util.function,\
+ java.util.jar,\
+ java.util.logging,\
+ java.util.prefs,\
+ java.util.regex,\
+ java.util.spi,\
+ java.util.stream,\
+ java.util.zip,\
+ javax.accessibility,\
+ javax.activation,\
+ javax.activity,\
+ javax.annotation,\
+ javax.annotation.processing,\
+ javax.crypto,\
+ javax.crypto.interfaces,\
+ javax.crypto.spec,\
+ javax.imageio,\
+ javax.imageio.event,\
+ javax.imageio.metadata,\
+ javax.imageio.plugins.bmp,\
+ javax.imageio.plugins.jpeg,\
+ javax.imageio.spi,\
+ javax.imageio.stream,\
+ javax.jws,\
+ javax.jws.soap,\
+ javax.lang.model,\
+ javax.lang.model.element,\
+ javax.lang.model.type,\
+ javax.lang.model.util,\
+ javax.management,\
+ javax.management.loading,\
+ javax.management.modelmbean,\
+ javax.management.monitor,\
+ javax.management.openmbean,\
+ javax.management.relation,\
+ javax.management.remote,\
+ javax.management.remote.rmi,\
+ javax.management.timer,\
+ javax.naming,\
+ javax.naming.directory,\
+ javax.naming.event,\
+ javax.naming.ldap,\
+ javax.naming.spi,\
+ javax.net,\
+ javax.net.ssl,\
+ javax.print,\
+ javax.print.attribute,\
+ javax.print.attribute.standard,\
+ javax.print.event,\
+ javax.rmi,\
+ javax.rmi.CORBA,\
+ javax.rmi.ssl,\
+ javax.script,\
+ javax.security.auth,\
+ javax.security.auth.callback,\
+ javax.security.auth.kerberos,\
+ javax.security.auth.login,\
+ javax.security.auth.spi,\
+ javax.security.auth.x500,\
+ javax.security.cert,\
+ javax.security.sasl,\
+ javax.sound.midi,\
+ javax.sound.midi.spi,\
+ javax.sound.sampled,\
+ javax.sound.sampled.spi,\
+ javax.sql,\
+ javax.sql.rowset,\
+ javax.sql.rowset.serial,\
+ javax.sql.rowset.spi,\
+ javax.swing,\
+ javax.swing.border,\
+ javax.swing.colorchooser,\
+ javax.swing.event,\
+ javax.swing.filechooser,\
+ javax.swing.plaf,\
+ javax.swing.plaf.basic,\
+ javax.swing.plaf.metal,\
+ javax.swing.plaf.multi,\
+ javax.swing.plaf.nimbus,\
+ javax.swing.plaf.synth,\
+ javax.swing.table,\
+ javax.swing.text,\
+ javax.swing.text.html,\
+ javax.swing.text.html.parser,\
+ javax.swing.text.rtf,\
+ javax.swing.tree,\
+ javax.swing.undo,\
+ javax.tools,\
+ javax.transaction,\
+ javax.transaction.xa,\
+ javax.xml,\
+ javax.xml.bind,\
+ javax.xml.bind.annotation,\
+ javax.xml.bind.annotation.adapters,\
+ javax.xml.bind.attachment,\
+ javax.xml.bind.helpers,\
+ javax.xml.bind.util,\
+ javax.xml.crypto,\
+ javax.xml.crypto.dom,\
+ javax.xml.crypto.dsig,\
+ javax.xml.crypto.dsig.dom,\
+ javax.xml.crypto.dsig.keyinfo,\
+ javax.xml.crypto.dsig.spec,\
+ javax.xml.datatype,\
+ javax.xml.namespace,\
+ javax.xml.parsers,\
+ javax.xml.soap,\
+ javax.xml.stream,\
+ javax.xml.stream.events,\
+ javax.xml.stream.util,\
+ javax.xml.transform,\
+ javax.xml.transform.dom,\
+ javax.xml.transform.sax,\
+ javax.xml.transform.stax,\
+ javax.xml.transform.stream,\
+ javax.xml.validation,\
+ javax.xml.ws,\
+ javax.xml.ws.handler,\
+ javax.xml.ws.handler.soap,\
+ javax.xml.ws.http,\
+ javax.xml.ws.soap,\
+ javax.xml.ws.spi,\
+ javax.xml.ws.spi.http,\
+ javax.xml.ws.wsaddressing,\
+ javax.xml.xpath,\
+ org.ietf.jgss,\
+ org.omg.CORBA,\
+ org.omg.CORBA_2_3,\
+ org.omg.CORBA_2_3.portable,\
+ org.omg.CORBA.DynAnyPackage,\
+ org.omg.CORBA.ORBPackage,\
+ org.omg.CORBA.portable,\
+ org.omg.CORBA.TypeCodePackage,\
+ org.omg.CosNaming,\
+ org.omg.CosNaming.NamingContextExtPackage,\
+ org.omg.CosNaming.NamingContextPackage,\
+ org.omg.Dynamic,\
+ org.omg.DynamicAny,\
+ org.omg.DynamicAny.DynAnyFactoryPackage,\
+ org.omg.DynamicAny.DynAnyPackage,\
+ org.omg.IOP,\
+ org.omg.IOP.CodecFactoryPackage,\
+ org.omg.IOP.CodecPackage,\
+ org.omg.Messaging,\
+ org.omg.PortableInterceptor,\
+ org.omg.PortableInterceptor.ORBInitInfoPackage,\
+ org.omg.PortableServer,\
+ org.omg.PortableServer.CurrentPackage,\
+ org.omg.PortableServer.POAManagerPackage,\
+ org.omg.PortableServer.POAPackage,\
+ org.omg.PortableServer.portable,\
+ org.omg.PortableServer.ServantLocatorPackage,\
+ org.omg.SendingContext,\
+ org.omg.stub.java.rmi,\
+ org.w3c.dom,\
+ org.w3c.dom.bootstrap,\
+ org.w3c.dom.css,\
+ org.w3c.dom.events,\
+ org.w3c.dom.html,\
+ org.w3c.dom.ls,\
+ org.w3c.dom.ranges,\
+ org.w3c.dom.stylesheets,\
+ org.w3c.dom.traversal,\
+ org.w3c.dom.views,\
+ org.w3c.dom.xpath,\
+ org.xml.sax,\
+ org.xml.sax.ext,\
+ org.xml.sax.helpers
+org.osgi.framework.bootdelegation = \
+ javax.*,\
+ org.ietf.jgss,\
+ org.omg.*,\
+ org.w3c.*,\
+ org.xml.*,\
+ sun.*,\
+ com.sun.*
+org.osgi.framework.executionenvironment = \
+ OSGi/Minimum-1.0,\
+ OSGi/Minimum-1.1,\
+ OSGi/Minimum-1.2,\
+ JavaSE/compact1-1.8,\
+ JavaSE/compact2-1.8,\
+ JavaSE/compact3-1.8,\
+ JRE-1.1,\
+ J2SE-1.2,\
+ J2SE-1.3,\
+ J2SE-1.4,\
+ J2SE-1.5,\
+ JavaSE-1.6,\
+ JavaSE-1.7,\
+ JavaSE-1.8,\
+ JavaSE-9
+org.osgi.framework.system.capabilities = \
+ osgi.ee; osgi.ee="OSGi/Minimum"; version:List<Version>="1.0, 1.1, 1.2",\
+ osgi.ee; osgi.ee="JRE"; version:List<Version>="1.0, 1.1",\
+ osgi.ee; osgi.ee="JavaSE"; version:List<Version>="1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 9.0",\
+ osgi.ee; osgi.ee="JavaSE/compact1"; version:List<Version>="1.8, 9.0",\
+ osgi.ee; osgi.ee="JavaSE/compact2"; version:List<Version>="1.8, 9.0",\
+ osgi.ee; osgi.ee="JavaSE/compact3"; version:List<Version>="1.8, 9.0"
+osgi.java.profile.name = JavaSE-9
+org.eclipse.jdt.core.compiler.compliance=9
+org.eclipse.jdt.core.compiler.source=9
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=9
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
diff --git a/ext/osgi.system/build.properties b/ext/osgi.system/build.properties
new file mode 100644 (file)
index 0000000..34d2e4d
--- /dev/null
@@ -0,0 +1,4 @@
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+               .
diff --git a/ext/osgi.system/profile.list b/ext/osgi.system/profile.list
new file mode 100644 (file)
index 0000000..cdaf2a3
--- /dev/null
@@ -0,0 +1,2 @@
+java.profiles = \
+ JavaSE-9.profile
diff --git a/graalvm/org.argeo.slc.graalvm/.classpath b/graalvm/org.argeo.slc.graalvm/.classpath
new file mode 100644 (file)
index 0000000..81fe078
--- /dev/null
@@ -0,0 +1,7 @@
+<?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>
diff --git a/graalvm/org.argeo.slc.graalvm/.project b/graalvm/org.argeo.slc.graalvm/.project
new file mode 100644 (file)
index 0000000..c102f62
--- /dev/null
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+       <name>org.argeo.slc.graalvm</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>
diff --git a/graalvm/org.argeo.slc.graalvm/bnd.bnd b/graalvm/org.argeo.slc.graalvm/bnd.bnd
new file mode 100644 (file)
index 0000000..c435dc9
--- /dev/null
@@ -0,0 +1,3 @@
+Import-Package: \
+!org.graalvm.nativeimage.*, \
+*
\ No newline at end of file
diff --git a/graalvm/org.argeo.slc.graalvm/build.properties b/graalvm/org.argeo.slc.graalvm/build.properties
new file mode 100644 (file)
index 0000000..34d2e4d
--- /dev/null
@@ -0,0 +1,4 @@
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+               .
diff --git a/graalvm/org.argeo.slc.graalvm/src/org/argeo/slc/graalvm/feature/ArgeoToolFeature.java b/graalvm/org.argeo.slc.graalvm/src/org/argeo/slc/graalvm/feature/ArgeoToolFeature.java
new file mode 100644 (file)
index 0000000..fa6e5a9
--- /dev/null
@@ -0,0 +1,34 @@
+package org.argeo.slc.graalvm.feature;
+
+import java.security.Security;
+
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.graalvm.nativeimage.hosted.Feature;
+import org.graalvm.nativeimage.hosted.RuntimeClassInitialization;
+
+import net.i2p.crypto.eddsa.EdDSASecurityProvider;
+
+/** Required native image customisations. */
+public class ArgeoToolFeature implements Feature {
+
+       @Override
+       public void afterRegistration(AfterRegistrationAccess access) {
+               // JCE providers need to be registered at build time.
+               // https://github.com/oracle/graal/issues/2800#issuecomment-702480444
+               // https://github.com/micronaut-projects/micronaut-oracle-cloud/pull/17/files#r498585779
+               RuntimeClassInitialization.initializeAtBuildTime("org.bouncycastle");
+               Security.addProvider(new BouncyCastleProvider());
+
+               RuntimeClassInitialization.initializeAtBuildTime("net.i2p.crypto.eddsa");
+               Security.addProvider(new EdDSASecurityProvider());
+
+               // required for Tomcat JNI DLL to load properly
+               RuntimeClassInitialization.initializeAtBuildTime("org.apache.tomcat.jni");
+
+//             RuntimeClassInitializationSupport rci = ImageSingletonsSupport.lookup(RuntimeClassInitializationSupport.class);
+//             rci.rerunInitialization("org.bouncycastle.jcajce.provider.drbg.DRBG$Default", "dependency with native random");
+//             rci.rerunInitialization("org.bouncycastle.jcajce.provider.drbg.DRBG$NonceAndIV",
+//                             "dependency with native random");
+       }
+
+}
diff --git a/jni/.cproject b/jni/.cproject
new file mode 100644 (file)
index 0000000..259cf27
--- /dev/null
@@ -0,0 +1,68 @@
+<?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
diff --git a/jni/.project b/jni/.project
new file mode 100644 (file)
index 0000000..492a807
--- /dev/null
@@ -0,0 +1,26 @@
+<?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>
diff --git a/jni/.settings/language.settings.xml b/jni/.settings/language.settings.xml
new file mode 100644 (file)
index 0000000..e30ee16
--- /dev/null
@@ -0,0 +1,11 @@
+<?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
diff --git a/jni/.settings/org.eclipse.cdt.core.prefs b/jni/.settings/org.eclipse.cdt.core.prefs
new file mode 100644 (file)
index 0000000..c8ec5df
--- /dev/null
@@ -0,0 +1,6 @@
+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
diff --git a/jni/Makefile b/jni/Makefile
new file mode 100644 (file)
index 0000000..de2b84c
--- /dev/null
@@ -0,0 +1,14 @@
+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
+
+
+
diff --git a/jni/jni.mk b/jni/jni.mk
new file mode 100644 (file)
index 0000000..40dde44
--- /dev/null
@@ -0,0 +1,58 @@
+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)))
diff --git a/jni/org_argeo_api_uuid_libuuid/.gitignore b/jni/org_argeo_api_uuid_libuuid/.gitignore
new file mode 100644 (file)
index 0000000..84c048a
--- /dev/null
@@ -0,0 +1 @@
+/build/
diff --git a/jni/org_argeo_api_uuid_libuuid/Makefile b/jni/org_argeo_api_uuid_libuuid/Makefile
new file mode 100644 (file)
index 0000000..cfeb1db
--- /dev/null
@@ -0,0 +1,8 @@
+NATIVE_PACKAGE := org_argeo_api_uuid_libuuid
+
+ADDITIONAL_INCLUDES = /usr/include/uuid
+ADDITIONAL_LIBS = -luuid
+
+include ../../sdk.mk
+include ../jni.mk
+
diff --git a/jni/org_argeo_api_uuid_libuuid/org_argeo_api_uuid_libuuid_DirectLibuuidFactory.c b/jni/org_argeo_api_uuid_libuuid/org_argeo_api_uuid_libuuid_DirectLibuuidFactory.c
new file mode 100644 (file)
index 0000000..a5aeed0
--- /dev/null
@@ -0,0 +1,8 @@
+#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));
+}
diff --git a/jni/org_argeo_api_uuid_libuuid/org_argeo_api_uuid_libuuid_DirectLibuuidFactory.h b/jni/org_argeo_api_uuid_libuuid/org_argeo_api_uuid_libuuid_DirectLibuuidFactory.h
new file mode 100644 (file)
index 0000000..5f18bf7
--- /dev/null
@@ -0,0 +1,21 @@
+/* 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
diff --git a/jni/org_argeo_api_uuid_libuuid/org_argeo_api_uuid_libuuid_LibuuidFactory.c b/jni/org_argeo_api_uuid_libuuid/org_argeo_api_uuid_libuuid_LibuuidFactory.c
new file mode 100644 (file)
index 0000000..f97b1f4
--- /dev/null
@@ -0,0 +1,91 @@
+#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);
+}
diff --git a/jni/org_argeo_api_uuid_libuuid/org_argeo_api_uuid_libuuid_LibuuidFactory.h b/jni/org_argeo_api_uuid_libuuid/org_argeo_api_uuid_libuuid_LibuuidFactory.h
new file mode 100644 (file)
index 0000000..ad0ac5e
--- /dev/null
@@ -0,0 +1,45 @@
+/* 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
diff --git a/lib/linux/org.argeo.slc.systemd/.classpath b/lib/linux/org.argeo.slc.systemd/.classpath
new file mode 100644 (file)
index 0000000..81fe078
--- /dev/null
@@ -0,0 +1,7 @@
+<?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>
diff --git a/lib/linux/org.argeo.slc.systemd/.project b/lib/linux/org.argeo.slc.systemd/.project
new file mode 100644 (file)
index 0000000..1590750
--- /dev/null
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+       <name>org.argeo.slc.systemd</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>
diff --git a/lib/linux/org.argeo.slc.systemd/OSGI-INF/systemdServiceStatistics.xml b/lib/linux/org.argeo.slc.systemd/OSGI-INF/systemdServiceStatistics.xml
new file mode 100644 (file)
index 0000000..c689af1
--- /dev/null
@@ -0,0 +1,4 @@
+<?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="org.argeo.slc.systemd.serviceStatistics">
+   <implementation class="org.argeo.slc.systemd.dbus.ServiceStatistics"/>
+</scr:component>
diff --git a/lib/linux/org.argeo.slc.systemd/bnd.bnd b/lib/linux/org.argeo.slc.systemd/bnd.bnd
new file mode 100644 (file)
index 0000000..0b58d79
--- /dev/null
@@ -0,0 +1,6 @@
+Service-Component: OSGI-INF/systemdServiceStatistics.xml
+
+
+Import-Package:\
+de.thjom.java.systemd.features,\
+*
\ No newline at end of file
diff --git a/lib/linux/org.argeo.slc.systemd/build.properties b/lib/linux/org.argeo.slc.systemd/build.properties
new file mode 100644 (file)
index 0000000..cb0d657
--- /dev/null
@@ -0,0 +1,7 @@
+bin.includes = META-INF/,\
+               .,\
+               OSGI-INF/systemdServiceStatistics.xml
+additional.bundles = org.argeo.ext.slf4j,\
+                     org.slf4j.api
+source.. = src/
+output.. = bin/
diff --git a/lib/linux/org.argeo.slc.systemd/src/org/argeo/slc/systemd/dbus/PlainDBusTest.java b/lib/linux/org.argeo.slc.systemd/src/org/argeo/slc/systemd/dbus/PlainDBusTest.java
new file mode 100644 (file)
index 0000000..b43a033
--- /dev/null
@@ -0,0 +1,41 @@
+package org.argeo.slc.systemd.dbus;
+
+import java.util.Map;
+
+import org.freedesktop.dbus.connections.impl.DBusConnection;
+import org.freedesktop.dbus.interfaces.DBusInterface;
+import org.freedesktop.dbus.interfaces.Introspectable;
+import org.freedesktop.dbus.interfaces.Properties;
+import org.freedesktop.dbus.types.Variant;
+
+public class PlainDBusTest {
+       final static String SYSTEMD_SERVICE = "org.freedesktop.systemd1.Service";
+
+       public static void main(String[] args) throws Exception {
+//             try (DBusConnection dBusConnection = DBusConnectionBuilder.forSystemBus().build()) {
+
+               try (DBusConnection dBusConnection = DBusConnection.getConnection(DBusConnection.DBusBusType.SYSTEM)) {
+
+                       String source = "org.freedesktop.systemd1";
+                       String objectPath = "/org/freedesktop/systemd1/unit/ipsec_2eservice";
+//             String objectPath = "/org/freedesktop/systemd1";
+                       DBusInterface object = dBusConnection.getExportedObject(source, objectPath);
+                       System.out.println(object);
+//
+//                     Introspectable introspectable = dBusConnection.getExportedObject(source, objectPath, Introspectable.class);
+//                     System.out.println(introspectable.Introspect());
+//
+                       Properties props = dBusConnection.getExportedObject(source, objectPath, Properties.class);
+//             System.out.println(props);
+
+                       System.out.println(props.Get(SYSTEMD_SERVICE, "CPUUsageNSec").toString());
+
+                       Map<String, Variant<?>> values = props.GetAll(SYSTEMD_SERVICE);
+                       for (String key : values.keySet()) {
+                               Variant<?> value = values.get(key);
+                               System.out.println(key + "=" + value.getValue() + " (" + value.getType() + ") " + value.getSig());
+                       }
+               }
+       }
+
+}
diff --git a/lib/linux/org.argeo.slc.systemd/src/org/argeo/slc/systemd/dbus/ServiceStatistics.java b/lib/linux/org.argeo.slc.systemd/src/org/argeo/slc/systemd/dbus/ServiceStatistics.java
new file mode 100644 (file)
index 0000000..b40ba23
--- /dev/null
@@ -0,0 +1,209 @@
+package org.argeo.slc.systemd.dbus;
+
+import static java.lang.System.Logger.Level.DEBUG;
+import static java.lang.System.Logger.Level.ERROR;
+import static java.lang.System.Logger.Level.INFO;
+import static java.lang.System.Logger.Level.WARNING;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.lang.System.Logger;
+import java.lang.System.Logger.Level;
+import java.math.BigInteger;
+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.time.Instant;
+import java.time.ZoneOffset;
+import java.time.format.DateTimeFormatter;
+
+import org.argeo.cms.util.CsvWriter;
+import org.freedesktop.dbus.exceptions.DBusException;
+
+import de.thjom.java.systemd.Manager;
+import de.thjom.java.systemd.Service;
+import de.thjom.java.systemd.Systemd;
+import de.thjom.java.systemd.types.UnitType;
+
+/** Gathers statistics about the runnign process, if it is a systemd unit. */
+public class ServiceStatistics {
+       private final static Logger logger = System.getLogger(ServiceStatistics.class.getName());
+
+       private Manager manager;
+
+       private String unitName;
+       private Service service;
+
+       private long frequency = 60 * 1000;
+
+       private long begin;
+
+       private Statistics previousStat = null;
+
+       private StatisticsThread statisticsThread;
+
+       private Path basePath;
+
+       private BigInteger maxMemory = BigInteger.ZERO;
+       private BigInteger maxTasks = BigInteger.ZERO;
+
+       public void start() {
+               begin = Instant.now().toEpochMilli();
+               final long pid = ProcessHandle.current().pid();
+               try {
+                       manager = Systemd.get().getManager();
+                       // find own service
+                       for (UnitType unitType : manager.listUnits()) {
+                               if (unitType.isService()) {
+                                       Service s = manager.getService(unitType.getUnitName());
+                                       if (s.getMainPID() == pid) {
+                                               service = s;
+                                               unitName = unitType.getUnitName();
+                                               logger.log(INFO, "Systemd service found for pid " + pid + ": " + unitName);
+                                       }
+                               }
+                       }
+
+               } catch (DBusException e) {
+                       logger.log(WARNING, "Cannot connect to systemd", e);
+               }
+
+               if (service == null) {
+                       logger.log(DEBUG, () -> "No systemd service found for pid " + pid + ", disconnecting from DBus...");
+                       manager = null;
+                       Systemd.disconnect();
+               } else {
+                       // standard systemd property
+                       String logsDirectory = System.getenv("LOGS_DIRECTORY");
+                       if (logsDirectory == null) {
+                               logsDirectory = System.getProperty("user.dir");
+                       }
+                       // MainPID,CPUUsageNSec,MemoryCurrent,IPIngressBytes,IPEgressBytes,IOReadBytes,IOWriteBytes,TasksCurrent
+                       basePath = Paths.get(logsDirectory);
+
+                       logger.log(DEBUG, () -> "Writing statistics for " + unitName + " to " + basePath);
+                       // start collecting
+                       statisticsThread = new StatisticsThread();
+                       statisticsThread.start();
+               }
+       }
+
+       public void stop() {
+               if (manager != null) {
+                       // write accounting
+                       Path accountingPath = basePath.resolve("accounting-" + unitName + ".csv");
+                       logger.log(INFO, () -> "Writing accounting for " + unitName + " to " + accountingPath);
+                       boolean writeHeader = !Files.exists(accountingPath);
+                       try (Writer writer = Files.newBufferedWriter(accountingPath, StandardCharsets.UTF_8,
+                                       StandardOpenOption.APPEND, StandardOpenOption.CREATE)) {
+                               CsvWriter csvWriter = new CsvWriter(writer);
+
+                               if (writeHeader)// header
+                                       csvWriter.writeLine("BeginTimeMillis", "EndTimeMillis", "CPUUsageNSec", "MaxMemory",
+                                                       "IPIngressBytes", "IPEgressBytes", "IOReadBytes", "IOWriteBytes", "MaxTasks");
+
+                               // TODO better synchronise with stop
+                               csvWriter.writeLine(begin, System.currentTimeMillis(), service.getCPUUsageNSec(), maxMemory,
+                                               service.getIPIngressBytes(), service.getIPEgressBytes(), service.getIOReadBytes(),
+                                               service.getIOWriteBytes(), maxTasks);
+                               writer.flush();
+                       } catch (IOException e) {
+                               logger.log(ERROR, "Cannot write accounting to " + accountingPath, e);
+                       }
+
+                       // disconnect
+                       synchronized (this) {
+                               Systemd.disconnect();
+                               manager = null;
+                               notifyAll();
+                               statisticsThread.interrupt();
+                       }
+               }
+       }
+
+       protected void collectStatistics() {
+               try {
+                       while (manager != null) {
+                               synchronized (this) {
+
+                                       // We change the prefix in order to have a file per day
+                                       // but keep the begin timestamp in order to identify restarts
+                                       String dateSuffix = Instant.now().atOffset(ZoneOffset.UTC).format(DateTimeFormatter.ISO_LOCAL_DATE)
+                                                       + "-" + begin;
+
+                                       Path statisticsPath = basePath.resolve("statistics-" + unitName + "-" + dateSuffix + ".csv");
+                                       boolean writeHeader = !Files.exists(statisticsPath);
+                                       if (!writeHeader && previousStat == null)
+                                               logger.log(ERROR,
+                                                               "File " + statisticsPath + " exists, but we don't have previous statistics in memory");
+
+                                       try (Writer writer = Files.newBufferedWriter(statisticsPath, StandardCharsets.UTF_8,
+                                                       StandardOpenOption.APPEND, StandardOpenOption.CREATE)) {
+                                               CsvWriter csvWriter = new CsvWriter(writer);
+
+                                               if (writeHeader)// header
+                                                       csvWriter.writeLine("CurrentTimeMillis", "MemoryCurrent", "CPUUsageNSec", "IPIngressBytes",
+                                                                       "IPEgressBytes", "IOReadBytes", "IOWriteBytes", "TasksCurrent");
+
+                                               Statistics s = new Statistics(Instant.now().toEpochMilli(), service.getMemoryCurrent(),
+                                                               service.getCPUUsageNSec(), service.getIPIngressBytes(), service.getIPEgressBytes(),
+                                                               service.getIOReadBytes(), service.getIOWriteBytes(), service.getTasksCurrent());
+
+                                               if (s.MemoryCurrent().compareTo(maxMemory) > 0)
+                                                       maxMemory = s.MemoryCurrent();
+                                               if (s.TasksCurrent().compareTo(maxTasks) > 0)
+                                                       maxTasks = s.TasksCurrent();
+
+                                               Statistics diff = Statistics.diff(s, previousStat);
+                                               // TODO better synchronise with stop
+                                               csvWriter.writeLine(diff.CurrentTimeMillis(), diff.MemoryCurrent(), diff.CPUUsageNSec(),
+                                                               diff.IPIngressBytes(), diff.IPEgressBytes(), diff.IOReadBytes(), diff.IOWriteBytes(),
+                                                               diff.TasksCurrent());
+                                               previousStat = s;
+                                       }
+                                       try {
+                                               this.wait(frequency);
+                                       } catch (InterruptedException e) {
+                                               logger.log(Level.TRACE, () -> "Statistics collection interrupted for " + unitName);
+                                       }
+                               }
+                       }
+               } catch (IOException e) {
+                       throw new IllegalStateException("Cannot collect statistics", e);
+               }
+       }
+
+       class StatisticsThread extends Thread {
+
+               public StatisticsThread() {
+                       super("Statistics for " + unitName);
+               }
+
+               @Override
+               public void run() {
+                       collectStatistics();
+               }
+
+       }
+
+       private record Statistics(long CurrentTimeMillis, BigInteger MemoryCurrent, BigInteger CPUUsageNSec,
+                       BigInteger IPIngressBytes, BigInteger IPEgressBytes, BigInteger IOReadBytes, BigInteger IOWriteBytes,
+                       BigInteger TasksCurrent) {
+
+               final static Statistics NULL = new Statistics(0, BigInteger.ZERO, BigInteger.ZERO, BigInteger.ZERO,
+                               BigInteger.ZERO, BigInteger.ZERO, BigInteger.ZERO, BigInteger.ZERO);
+
+               public static Statistics diff(Statistics now, Statistics previous) {
+                       if (previous == null)
+                               previous = NULL;
+                       return new Statistics(now.CurrentTimeMillis(), now.MemoryCurrent(),
+                                       now.CPUUsageNSec().subtract(previous.CPUUsageNSec()),
+                                       now.IPIngressBytes().subtract(previous.IPIngressBytes()),
+                                       now.IPEgressBytes().subtract(previous.IPEgressBytes()),
+                                       now.IOReadBytes().subtract(previous.IOReadBytes()),
+                                       now.IOWriteBytes().subtract(previous.IOWriteBytes()), now.TasksCurrent());
+               }
+       }
+}
index 424ba5dd033dddd705ff424fabbd584cd890757e..4f43cdab171ae3a9572a2689e9846f2204d2b783 100644 (file)
@@ -7,6 +7,8 @@ public enum ManifestConstants {
        BUNDLE_LICENSE("Bundle-License"), //
        EXPORT_PACKAGE("Export-Package"), //
        IMPORT_PACKAGE("Import-Package"), //
+       // JAVA
+       AUTOMATIC_MODULE_NAME("Automatic-Module-Name"), //
        // SLC
        SLC_CATEGORY("SLC-Category"), //
        SLC_ORIGIN_M2("SLC-Origin-M2"), //
diff --git a/org.argeo.slc.api/src/org/argeo/slc/WellKnownConstants.java b/org.argeo.slc.api/src/org/argeo/slc/WellKnownConstants.java
new file mode 100644 (file)
index 0000000..f0ce614
--- /dev/null
@@ -0,0 +1,17 @@
+package org.argeo.slc;
+
+/**
+ * Centralises constant values related to software systems, which are well
+ * defined and not expected to change within a major version cycle of SLC, but
+ * which are not necessarily defined in core Java.
+ */
+public interface WellKnownConstants {
+
+       /*
+        * OSGi
+        */
+       final static String OSGI_INSTANCE_AREA = "osgi.instance.area";
+       final static String OSGI_CONFIGURATION_AREA = "osgi.configuration.area";
+
+//     final static String OSGI_HTTP_PORT = "org.osgi.service.http.port";
+}
diff --git a/org.argeo.slc.cms/.classpath b/org.argeo.slc.cms/.classpath
new file mode 100644 (file)
index 0000000..81fe078
--- /dev/null
@@ -0,0 +1,7 @@
+<?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>
diff --git a/org.argeo.slc.cms/.project b/org.argeo.slc.cms/.project
new file mode 100644 (file)
index 0000000..95c8de1
--- /dev/null
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+       <name>org.argeo.slc.cms</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>
diff --git a/org.argeo.slc.cms/bnd.bnd b/org.argeo.slc.cms/bnd.bnd
new file mode 100644 (file)
index 0000000..93725b6
--- /dev/null
@@ -0,0 +1,6 @@
+Import-Package: \
+org.apache.commons.logging,\
+org.postgresql;version="[42,43)";resolution:=optional,\
+org.apache.commons.vfs2.*resolution:=optional,\
+org.osgi.*;version="0.0.0",\
+*
\ No newline at end of file
diff --git a/org.argeo.slc.cms/build.properties b/org.argeo.slc.cms/build.properties
new file mode 100644 (file)
index 0000000..5d082ea
--- /dev/null
@@ -0,0 +1,5 @@
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+               .
+additional.bundles = org.argeo.init
diff --git a/org.argeo.slc.cms/src/org/argeo/cms/sql/postgres/CheckPg.java b/org.argeo.slc.cms/src/org/argeo/cms/sql/postgres/CheckPg.java
new file mode 100644 (file)
index 0000000..bc002a6
--- /dev/null
@@ -0,0 +1,42 @@
+package org.argeo.cms.sql.postgres;
+
+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);
+       }
+
+}
diff --git a/org.argeo.slc.cms/src/org/argeo/slc/backup/vfs/AbstractAtomicBackup.java b/org.argeo.slc.cms/src/org/argeo/slc/backup/vfs/AbstractAtomicBackup.java
new file mode 100644 (file)
index 0000000..6d55374
--- /dev/null
@@ -0,0 +1,82 @@
+package org.argeo.slc.backup.vfs;
+
+import org.apache.commons.vfs2.FileObject;
+import org.apache.commons.vfs2.FileSystemManager;
+import org.apache.commons.vfs2.FileSystemOptions;
+import org.apache.commons.vfs2.provider.sftp.SftpFileSystemConfigBuilder;
+
+/**
+ * Simplify atomic backups implementation, especially by managing VFS.
+ */
+public abstract class AbstractAtomicBackup implements AtomicBackup {
+       private String name;
+       private String compression = "bz2";
+
+       protected abstract void writeBackup(FileObject targetFo);
+
+       public AbstractAtomicBackup() {
+       }
+
+       public AbstractAtomicBackup(String name) {
+               this.name = name;
+       }
+
+       public void init() {
+               if (name == null)
+                       throw new MaintenanceException("Atomic backup name must be set");
+       }
+
+       public void destroy() {
+
+       }
+
+       @Override
+       public String backup(FileSystemManager fileSystemManager,
+                       String backupsBase, BackupContext backupContext,
+                       FileSystemOptions opts) {
+               if (name == null)
+                       throw new MaintenanceException("Atomic backup name must be set");
+
+               FileObject targetFo = null;
+               try {
+                       if (backupsBase.startsWith("sftp:"))
+                               SftpFileSystemConfigBuilder.getInstance()
+                                               .setStrictHostKeyChecking(opts, "no");
+                       if (compression == null || compression.equals("none"))
+                               targetFo = fileSystemManager.resolveFile(backupsBase + '/'
+                                               + backupContext.getRelativeFolder() + '/' + name, opts);
+                       else if (compression.equals("bz2"))
+                               targetFo = fileSystemManager.resolveFile("bz2:" + backupsBase
+                                               + '/' + backupContext.getRelativeFolder() + '/' + name
+                                               + ".bz2" + "!" + name, opts);
+                       else if (compression.equals("gz"))
+                               targetFo = fileSystemManager.resolveFile("gz:" + backupsBase
+                                               + '/' + backupContext.getRelativeFolder() + '/' + name
+                                               + ".gz" + "!" + name, opts);
+                       else
+                               throw new MaintenanceException("Unsupported compression "
+                                               + compression);
+
+                       writeBackup(targetFo);
+
+                       return targetFo.toString();
+               } catch (Exception e) {
+                       throw new MaintenanceException("Cannot backup " + name + " to "
+                                       + targetFo, e);
+               } finally {
+                       BackupUtils.closeFOQuietly(targetFo);
+               }
+       }
+
+       public void setName(String name) {
+               this.name = name;
+       }
+
+       public String getName() {
+               return name;
+       }
+
+       public void setCompression(String compression) {
+               this.compression = compression;
+       }
+}
diff --git a/org.argeo.slc.cms/src/org/argeo/slc/backup/vfs/AtomicBackup.java b/org.argeo.slc.cms/src/org/argeo/slc/backup/vfs/AtomicBackup.java
new file mode 100644 (file)
index 0000000..0cfcfab
--- /dev/null
@@ -0,0 +1,22 @@
+package org.argeo.slc.backup.vfs;
+
+import org.apache.commons.vfs2.FileSystemManager;
+import org.apache.commons.vfs2.FileSystemOptions;
+
+/** Performs the backup of a single component, typically a database dump */
+public interface AtomicBackup {
+       /** Name identifiying this backup */
+       public String getName();
+
+       /**
+        * Retrieves the data of the component in a format that allows to restore
+        * the component
+        * 
+        * @param backupContext
+        *            the context of this backup
+        * @return the VFS URI of the generated file or directory
+        */
+       public String backup(FileSystemManager fileSystemManager,
+                       String backupsBase, BackupContext backupContext,
+                       FileSystemOptions opts);
+}
diff --git a/org.argeo.slc.cms/src/org/argeo/slc/backup/vfs/BackupContext.java b/org.argeo.slc.cms/src/org/argeo/slc/backup/vfs/BackupContext.java
new file mode 100644 (file)
index 0000000..9c1140b
--- /dev/null
@@ -0,0 +1,25 @@
+package org.argeo.slc.backup.vfs;
+
+import java.text.DateFormat;
+import java.util.Date;
+
+/**
+ * Transient information of a given backup, centralizing common information such
+ * as timestamp and location.
+ */
+public interface BackupContext {
+       /** Backup date */
+       public Date getTimestamp();
+
+       /** Formatted backup date */
+       public String getTimestampAsString();
+
+       /** System name */
+       public String getSystemName();
+
+       /** Local base */
+       public String getRelativeFolder();
+
+       /** Date format */
+       public DateFormat getDateFormat();
+}
diff --git a/org.argeo.slc.cms/src/org/argeo/slc/backup/vfs/BackupFileSystemManager.java b/org.argeo.slc.cms/src/org/argeo/slc/backup/vfs/BackupFileSystemManager.java
new file mode 100644 (file)
index 0000000..ad42dd3
--- /dev/null
@@ -0,0 +1,36 @@
+package org.argeo.slc.backup.vfs;
+
+import org.apache.commons.vfs2.FileSystemException;
+import org.apache.commons.vfs2.impl.DefaultFileSystemManager;
+import org.apache.commons.vfs2.provider.bzip2.Bzip2FileProvider;
+import org.apache.commons.vfs2.provider.ftp.FtpFileProvider;
+import org.apache.commons.vfs2.provider.gzip.GzipFileProvider;
+import org.apache.commons.vfs2.provider.local.DefaultLocalFileProvider;
+import org.apache.commons.vfs2.provider.ram.RamFileProvider;
+import org.apache.commons.vfs2.provider.sftp.SftpFileProvider;
+import org.apache.commons.vfs2.provider.url.UrlFileProvider;
+
+/**
+ * Programatically configured VFS file system manager which can be declared as a
+ * bean and associated with a life cycle (methods
+ * {@link DefaultFileSystemManager#init()} and
+ * {@link DefaultFileSystemManager#close()}). Supports bz2, file, ram, gzip,
+ * ftp, sftp
+ */
+public class BackupFileSystemManager extends DefaultFileSystemManager {
+
+       public BackupFileSystemManager() {
+               super();
+               try {
+                       addProvider("file", new DefaultLocalFileProvider());
+                       addProvider("bz2", new Bzip2FileProvider());
+                       addProvider("ftp", new FtpFileProvider());
+                       addProvider("sftp", new SftpFileProvider());
+                       addProvider("gzip", new GzipFileProvider());
+                       addProvider("ram", new RamFileProvider());
+                       setDefaultProvider(new UrlFileProvider());
+               } catch (FileSystemException e) {
+                       throw new MaintenanceException("Cannot configure backup file provider", e);
+               }
+       }
+}
diff --git a/org.argeo.slc.cms/src/org/argeo/slc/backup/vfs/BackupPurge.java b/org.argeo.slc.cms/src/org/argeo/slc/backup/vfs/BackupPurge.java
new file mode 100644 (file)
index 0000000..1d07bbf
--- /dev/null
@@ -0,0 +1,18 @@
+package org.argeo.slc.backup.vfs;
+
+import java.text.DateFormat;
+
+import org.apache.commons.vfs2.FileSystemManager;
+import org.apache.commons.vfs2.FileSystemOptions;
+
+/** Purges previous backups */
+public interface BackupPurge {
+       /**
+        * Purge the backups identified by these arguments. Although these are the
+        * same fields as a {@link BackupContext} we don't pass it as argument since
+        * we want to use this interface to purge remote backups as well (that is,
+        * with a different base), or outside the scope of a running backup.
+        */
+       public void purge(FileSystemManager fileSystemManager, String base,
+                       String name, DateFormat dateFormat, FileSystemOptions opts);
+}
diff --git a/org.argeo.slc.cms/src/org/argeo/slc/backup/vfs/BackupUtils.java b/org.argeo.slc.cms/src/org/argeo/slc/backup/vfs/BackupUtils.java
new file mode 100644 (file)
index 0000000..bc4d0c8
--- /dev/null
@@ -0,0 +1,21 @@
+package org.argeo.slc.backup.vfs;
+
+import org.apache.commons.vfs2.FileObject;
+
+/** Backup utilities */
+public class BackupUtils {
+       /** Close a file object quietly even if it is null or throws an exception. */
+       public static void closeFOQuietly(FileObject fo) {
+               if (fo != null) {
+                       try {
+                               fo.close();
+                       } catch (Exception e) {
+                               // silent
+                       }
+               }
+       }
+       
+       /** Prevents instantiation */
+       private BackupUtils() {
+       }
+}
diff --git a/org.argeo.slc.cms/src/org/argeo/slc/backup/vfs/MaintenanceException.java b/org.argeo.slc.cms/src/org/argeo/slc/backup/vfs/MaintenanceException.java
new file mode 100644 (file)
index 0000000..1532503
--- /dev/null
@@ -0,0 +1,15 @@
+package org.argeo.slc.backup.vfs;
+
+@Deprecated
+class MaintenanceException extends RuntimeException {
+       private static final long serialVersionUID = -5770049663929537270L;
+
+       public MaintenanceException(String message, Throwable cause) {
+               super(message, cause);
+       }
+
+       public MaintenanceException(String message) {
+               super(message);
+       }
+
+}
diff --git a/org.argeo.slc.cms/src/org/argeo/slc/backup/vfs/MySqlBackup.java b/org.argeo.slc.cms/src/org/argeo/slc/backup/vfs/MySqlBackup.java
new file mode 100644 (file)
index 0000000..3f0e670
--- /dev/null
@@ -0,0 +1,59 @@
+package org.argeo.slc.backup.vfs;
+
+import org.apache.commons.vfs2.FileObject;
+
+/** Backups a MySQL database using mysqldump. */
+public class MySqlBackup extends OsCallBackup {
+       private String mysqldumpLocation = "/usr/bin/mysqldump";
+
+       private String dbUser;
+       private String dbPassword;
+       private String dbName;
+
+       public MySqlBackup() {
+       }
+
+       public MySqlBackup(String dbUser, String dbPassword, String dbName) {
+               this.dbUser = dbUser;
+               this.dbPassword = dbPassword;
+               this.dbName = dbName;
+               init();
+       }
+
+       @Override
+       public void init() {
+               if (getName() == null)
+                       setName(dbName + ".mysql");
+               super.init();
+       }
+
+       @Override
+       public void writeBackup(FileObject targetFo) {
+               if (getCommand() == null)
+                       setCommand(mysqldumpLocation
+                                       + " --lock-tables --add-locks --add-drop-table"
+                                       + " -u ${dbUser} --password=${dbPassword} --databases ${dbName}");
+               getVariables().put("dbUser", dbUser);
+               getVariables().put("dbPassword", dbPassword);
+               getVariables().put("dbName", dbName);
+
+               super.writeBackup(targetFo);
+       }
+
+       public void setDbUser(String dbUser) {
+               this.dbUser = dbUser;
+       }
+
+       public void setDbPassword(String dbPassword) {
+               this.dbPassword = dbPassword;
+       }
+
+       public void setDbName(String dbName) {
+               this.dbName = dbName;
+       }
+
+       public void setMysqldumpLocation(String mysqldumpLocation) {
+               this.mysqldumpLocation = mysqldumpLocation;
+       }
+
+}
diff --git a/org.argeo.slc.cms/src/org/argeo/slc/backup/vfs/OpenLdapBackup.java b/org.argeo.slc.cms/src/org/argeo/slc/backup/vfs/OpenLdapBackup.java
new file mode 100644 (file)
index 0000000..31914bd
--- /dev/null
@@ -0,0 +1,46 @@
+package org.argeo.slc.backup.vfs;
+
+import org.apache.commons.vfs2.FileObject;
+
+/** Backups an OpenLDAP server using slapcat */
+public class OpenLdapBackup extends OsCallBackup {
+       private String slapcatLocation = "/usr/sbin/slapcat";
+       private String slapdConfLocation = "/etc/openldap/slapd.conf";
+       private String baseDn;
+
+       public OpenLdapBackup() {
+               super();
+       }
+
+       public OpenLdapBackup(String baseDn) {
+               super();
+               this.baseDn = baseDn;
+       }
+
+       @Override
+       public void writeBackup(FileObject targetFo) {
+               if (baseDn == null)
+                       throw new MaintenanceException("Base DN must be set");
+
+               if (getCommand() == null)
+                       setCommand(slapcatLocation
+                                       + " -f ${slapdConfLocation} -b '${baseDn}'");
+               getVariables().put("slapdConfLocation", slapdConfLocation);
+               getVariables().put("baseDn", baseDn);
+
+               super.writeBackup(targetFo);
+       }
+
+       public void setSlapcatLocation(String slapcatLocation) {
+               this.slapcatLocation = slapcatLocation;
+       }
+
+       public void setSlapdConfLocation(String slapdConfLocation) {
+               this.slapdConfLocation = slapdConfLocation;
+       }
+
+       public void setBaseDn(String baseDn) {
+               this.baseDn = baseDn;
+       }
+
+}
diff --git a/org.argeo.slc.cms/src/org/argeo/slc/backup/vfs/OsCallBackup.java b/org.argeo.slc.cms/src/org/argeo/slc/backup/vfs/OsCallBackup.java
new file mode 100644 (file)
index 0000000..380dffe
--- /dev/null
@@ -0,0 +1,115 @@
+package org.argeo.slc.backup.vfs;
+
+import java.io.ByteArrayOutputStream;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.commons.exec.CommandLine;
+import org.apache.commons.exec.DefaultExecutor;
+import org.apache.commons.exec.ExecuteException;
+import org.apache.commons.exec.ExecuteStreamHandler;
+import org.apache.commons.exec.Executor;
+import org.apache.commons.exec.PumpStreamHandler;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.vfs2.FileContent;
+import org.apache.commons.vfs2.FileObject;
+import org.argeo.api.cms.CmsLog;
+
+/**
+ * Runs an OS command and save its standard output as a file. Typically used for
+ * MySQL or OpenLDAP dumps.
+ */
+public class OsCallBackup extends AbstractAtomicBackup {
+       private final static CmsLog log = CmsLog.getLog(OsCallBackup.class);
+
+       private String command;
+       private Map<String, String> variables = new HashMap<String, String>();
+       private Executor executor = new DefaultExecutor();
+
+       private Map<String, String> environment = new HashMap<String, String>();
+
+       /** Name of the sudo user, root if "", not sudo if null */
+       private String sudo = null;
+
+       public OsCallBackup() {
+       }
+
+       public OsCallBackup(String name) {
+               super(name);
+       }
+
+       public OsCallBackup(String name, String command) {
+               super(name);
+               this.command = command;
+       }
+
+       @Override
+       public void writeBackup(FileObject targetFo) {
+               String commandToUse = command;
+
+               // sudo
+               if (sudo != null) {
+                       if (sudo.equals(""))
+                               commandToUse = "sudo " + commandToUse;
+                       else
+                               commandToUse = "sudo -u " + sudo + " " + commandToUse;
+               }
+
+               CommandLine commandLine = CommandLine.parse(commandToUse, variables);
+               ByteArrayOutputStream errBos = new ByteArrayOutputStream();
+               if (log.isTraceEnabled())
+                       log.trace(commandLine.toString());
+
+               try {
+                       // stdout
+                       FileContent targetContent = targetFo.getContent();
+                       // stderr
+                       ExecuteStreamHandler streamHandler = new PumpStreamHandler(targetContent.getOutputStream(), errBos);
+                       executor.setStreamHandler(streamHandler);
+                       executor.execute(commandLine, environment);
+               } catch (ExecuteException e) {
+                       byte[] err = errBos.toByteArray();
+                       String errStr = new String(err);
+                       throw new MaintenanceException("Process " + commandLine + " failed (" + e.getExitValue() + "): " + errStr, e);
+               } catch (Exception e) {
+                       byte[] err = errBos.toByteArray();
+                       String errStr = new String(err);
+                       throw new MaintenanceException("Process " + commandLine + " failed: " + errStr, e);
+               } finally {
+                       IOUtils.closeQuietly(errBos);
+               }
+       }
+
+       public void setCommand(String command) {
+               this.command = command;
+       }
+
+       protected String getCommand() {
+               return command;
+       }
+
+       /**
+        * A reference to the environment variables that will be passed to the
+        * process. Empty by default.
+        */
+       protected Map<String, String> getEnvironment() {
+               return environment;
+       }
+
+       protected Map<String, String> getVariables() {
+               return variables;
+       }
+
+       public void setVariables(Map<String, String> variables) {
+               this.variables = variables;
+       }
+
+       public void setExecutor(Executor executor) {
+               this.executor = executor;
+       }
+
+       public void setSudo(String sudo) {
+               this.sudo = sudo;
+       }
+
+}
diff --git a/org.argeo.slc.cms/src/org/argeo/slc/backup/vfs/PostgreSqlBackup.java b/org.argeo.slc.cms/src/org/argeo/slc/backup/vfs/PostgreSqlBackup.java
new file mode 100644 (file)
index 0000000..613fd5a
--- /dev/null
@@ -0,0 +1,70 @@
+package org.argeo.slc.backup.vfs;
+
+import org.apache.commons.vfs2.FileObject;
+
+/** Backups a PostgreSQL database using pg_dump. */
+public class PostgreSqlBackup extends OsCallBackup {
+       /**
+        * PostgreSQL password environment variable (see
+        * http://stackoverflow.com/questions
+        * /2893954/how-to-pass-in-password-to-pg-dump)
+        */
+       protected final static String PGPASSWORD = "PGPASSWORD";
+
+       private String pgDumpLocation = "/usr/bin/pg_dump";
+
+       private String dbUser;
+       private String dbPassword;
+       private String dbName;
+
+       public PostgreSqlBackup() {
+               super();
+       }
+
+       public PostgreSqlBackup(String dbUser, String dbPassword, String dbName) {
+               this.dbUser = dbUser;
+               this.dbPassword = dbPassword;
+               this.dbName = dbName;
+               init();
+       }
+
+       @Override
+       public void init() {
+               // disable compression since pg_dump is used with -Fc option
+               setCompression(null);
+
+               if (getName() == null)
+                       setName(dbName + ".pgdump");
+               super.init();
+       }
+
+       @Override
+       public void writeBackup(FileObject targetFo) {
+               if (getCommand() == null) {
+                       getEnvironment().put(PGPASSWORD, dbPassword);
+                       setCommand(pgDumpLocation + " -Fc" + " -U ${dbUser} ${dbName}");
+               }
+               getVariables().put("dbUser", dbUser);
+               getVariables().put("dbPassword", dbPassword);
+               getVariables().put("dbName", dbName);
+
+               super.writeBackup(targetFo);
+       }
+
+       public void setDbUser(String dbUser) {
+               this.dbUser = dbUser;
+       }
+
+       public void setDbPassword(String dbPassword) {
+               this.dbPassword = dbPassword;
+       }
+
+       public void setDbName(String dbName) {
+               this.dbName = dbName;
+       }
+
+       public void setPgDumpLocation(String mysqldumpLocation) {
+               this.pgDumpLocation = mysqldumpLocation;
+       }
+
+}
diff --git a/org.argeo.slc.cms/src/org/argeo/slc/backup/vfs/SimpleBackupContext.java b/org.argeo.slc.cms/src/org/argeo/slc/backup/vfs/SimpleBackupContext.java
new file mode 100644 (file)
index 0000000..9ee6871
--- /dev/null
@@ -0,0 +1,48 @@
+package org.argeo.slc.backup.vfs;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import org.apache.commons.vfs2.FileSystemManager;
+
+/** Simple implementation of a backup context */
+public class SimpleBackupContext implements BackupContext {
+       private DateFormat dateFormat = new SimpleDateFormat("yyyyMMdd_HHmm");
+       private final Date timestamp;
+       private final String name;
+
+       private final FileSystemManager fileSystemManager;
+
+       public SimpleBackupContext(FileSystemManager fileSystemManager,
+                       String backupsBase, String name) {
+               this.name = name;
+               this.timestamp = new Date();
+               this.fileSystemManager = fileSystemManager;
+       }
+
+       public Date getTimestamp() {
+               return timestamp;
+       }
+
+       public String getTimestampAsString() {
+               return dateFormat.format(timestamp);
+       }
+
+       public String getSystemName() {
+               return name;
+       }
+
+       public String getRelativeFolder() {
+               return name + '/' + getTimestampAsString();
+       }
+
+       public DateFormat getDateFormat() {
+               return dateFormat;
+       }
+
+       public FileSystemManager getFileSystemManager() {
+               return fileSystemManager;
+       }
+
+}
diff --git a/org.argeo.slc.cms/src/org/argeo/slc/backup/vfs/SimpleBackupPurge.java b/org.argeo.slc.cms/src/org/argeo/slc/backup/vfs/SimpleBackupPurge.java
new file mode 100644 (file)
index 0000000..0871109
--- /dev/null
@@ -0,0 +1,71 @@
+package org.argeo.slc.backup.vfs;
+
+import java.text.DateFormat;
+import java.time.Period;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.util.Date;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+import org.apache.commons.vfs2.FileObject;
+import org.apache.commons.vfs2.FileSystemManager;
+import org.apache.commons.vfs2.FileSystemOptions;
+import org.apache.commons.vfs2.Selectors;
+import org.argeo.api.cms.CmsLog;
+
+/** Simple backup purge which keeps backups only for a given number of days */
+public class SimpleBackupPurge implements BackupPurge {
+       private final static CmsLog log = CmsLog.getLog(SimpleBackupPurge.class);
+
+       private Integer daysKept = 30;
+
+       @Override
+       public void purge(FileSystemManager fileSystemManager, String base, String name, DateFormat dateFormat,
+                       FileSystemOptions opts) {
+               try {
+                       ZonedDateTime nowDt = ZonedDateTime.now();
+                       FileObject baseFo = fileSystemManager.resolveFile(base + '/' + name, opts);
+
+                       SortedMap<ZonedDateTime, FileObject> toDelete = new TreeMap<ZonedDateTime, FileObject>();
+                       int backupCount = 0;
+
+                       // make sure base dir exists
+                       baseFo.createFolder();
+
+                       // scan backups and list those which should be deleted
+                       for (FileObject backupFo : baseFo.getChildren()) {
+                               String backupName = backupFo.getName().getBaseName();
+                               Date backupDate = dateFormat.parse(backupName);
+                               backupCount++;
+                               ZonedDateTime backupDt = ZonedDateTime.ofInstant(backupDate.toInstant(), ZoneId.systemDefault());
+                               Period sinceThen = Period.between(backupDt.toLocalDate(), nowDt.toLocalDate());
+                               // new Period(backupDt, nowDt);
+                               int days = sinceThen.getDays();
+                               // int days = sinceThen.getMinutes();
+                               if (days > daysKept) {
+                                       toDelete.put(backupDt, backupFo);
+                               }
+                       }
+
+                       if (toDelete.size() != 0 && toDelete.size() == backupCount) {
+                               // all backups would be deleted
+                               // but we want to keep at least one
+                               ZonedDateTime lastBackupDt = toDelete.firstKey();
+                               FileObject keptFo = toDelete.remove(lastBackupDt);
+                               log.warn("Backup " + keptFo + " kept although it is older than " + daysKept + " days.");
+                       }
+
+                       // delete old backups
+                       for (FileObject backupFo : toDelete.values()) {
+                               backupFo.delete(Selectors.SELECT_ALL);
+                               if (log.isDebugEnabled())
+                                       log.debug("Deleted backup " + backupFo);
+                       }
+               } catch (Exception e) {
+                       throw new MaintenanceException("Could not purge previous backups", e);
+               }
+
+       }
+
+}
diff --git a/org.argeo.slc.cms/src/org/argeo/slc/backup/vfs/SvnBackup.java b/org.argeo.slc.cms/src/org/argeo/slc/backup/vfs/SvnBackup.java
new file mode 100644 (file)
index 0000000..ea9f844
--- /dev/null
@@ -0,0 +1,55 @@
+package org.argeo.slc.backup.vfs;
+
+import java.io.File;
+
+import org.apache.commons.vfs2.FileObject;
+
+/** Backups a Subversion repository using svnadmin. */
+public class SvnBackup extends OsCallBackup {
+       private String svnadminLocation = "/usr/bin/svnadmin";
+
+       private String repoLocation;
+       private String repoName;
+
+       public SvnBackup() {
+       }
+
+       public SvnBackup(String repoLocation) {
+               this.repoLocation = repoLocation;
+               init();
+       }
+
+       @Override
+       public void init() {
+               // use directory as repo name
+               if (repoName == null)
+                       repoName = new File(repoLocation).getName();
+
+               if (getName() == null)
+                       setName(repoName + ".svndump");
+               super.init();
+       }
+
+       @Override
+       public void writeBackup(FileObject targetFo) {
+               if (getCommand() == null) {
+                       setCommand(svnadminLocation + " dump " + " ${repoLocation}");
+               }
+               getVariables().put("repoLocation", repoLocation);
+
+               super.writeBackup(targetFo);
+       }
+
+       public void setRepoLocation(String repoLocation) {
+               this.repoLocation = repoLocation;
+       }
+
+       public void setRepoName(String repoName) {
+               this.repoName = repoName;
+       }
+
+       public void setSvnadminLocation(String mysqldumpLocation) {
+               this.svnadminLocation = mysqldumpLocation;
+       }
+
+}
diff --git a/org.argeo.slc.cms/src/org/argeo/slc/backup/vfs/SystemBackup.java b/org.argeo.slc.cms/src/org/argeo/slc/backup/vfs/SystemBackup.java
new file mode 100644 (file)
index 0000000..3b0a1ac
--- /dev/null
@@ -0,0 +1,199 @@
+package org.argeo.slc.backup.vfs;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.vfs2.FileObject;
+import org.apache.commons.vfs2.FileSystemException;
+import org.apache.commons.vfs2.FileSystemManager;
+import org.apache.commons.vfs2.FileSystemOptions;
+import org.apache.commons.vfs2.Selectors;
+import org.apache.commons.vfs2.UserAuthenticator;
+import org.apache.commons.vfs2.impl.DefaultFileSystemConfigBuilder;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.cms.util.LangUtils;
+
+/**
+ * Combines multiple backups and transfer them to a remote location. Purges
+ * remote and local data based on certain criteria.
+ */
+public class SystemBackup implements Runnable {
+       private final static CmsLog log = CmsLog.getLog(SystemBackup.class);
+
+       private FileSystemManager fileSystemManager;
+       private UserAuthenticator userAuthenticator = null;
+
+       private String backupsBase;
+       private String systemName;
+
+       private List<AtomicBackup> atomicBackups = new ArrayList<AtomicBackup>();
+       private BackupPurge backupPurge = new SimpleBackupPurge();
+
+       private Map<String, UserAuthenticator> remoteBases = new HashMap<String, UserAuthenticator>();
+
+       @Override
+       public void run() {
+               if (atomicBackups.size() == 0)
+                       throw new MaintenanceException("No atomic backup listed");
+               List<String> failures = new ArrayList<String>();
+
+               SimpleBackupContext backupContext = new SimpleBackupContext(fileSystemManager, backupsBase, systemName);
+
+               // purge older backups
+               FileSystemOptions opts = new FileSystemOptions();
+               try {
+                       DefaultFileSystemConfigBuilder.getInstance().setUserAuthenticator(opts, userAuthenticator);
+               } catch (Exception e) {
+                       throw new MaintenanceException("Cannot create authentication", e);
+               }
+
+               try {
+
+                       backupPurge.purge(fileSystemManager, backupsBase, systemName, backupContext.getDateFormat(), opts);
+               } catch (Exception e) {
+                       failures.add("Purge " + backupsBase + " failed: " + e.getMessage());
+                       log.error("Purge of " + backupsBase + " failed", e);
+               }
+
+               // perform backup
+               for (AtomicBackup atomickBackup : atomicBackups) {
+                       try {
+                               String target = atomickBackup.backup(fileSystemManager, backupsBase, backupContext, opts);
+                               if (log.isDebugEnabled())
+                                       log.debug("Performed backup " + target);
+                       } catch (Exception e) {
+                               String msg = "Atomic backup " + atomickBackup.getName() + " failed: "
+                                               + LangUtils.chainCausesMessages(e);
+                               failures.add(msg);
+                               log.error(msg);
+                               if (log.isTraceEnabled())
+                                       log.trace("Stacktrace of atomic backup " + atomickBackup.getName() + " failure.", e);
+                       }
+               }
+
+               // dispatch to remote
+               for (String remoteBase : remoteBases.keySet()) {
+                       FileObject localBaseFo = null;
+                       FileObject remoteBaseFo = null;
+                       UserAuthenticator auth = remoteBases.get(remoteBase);
+
+                       // authentication
+                       FileSystemOptions remoteOpts = new FileSystemOptions();
+                       try {
+                               DefaultFileSystemConfigBuilder.getInstance().setUserAuthenticator(remoteOpts, auth);
+                               backupPurge.purge(fileSystemManager, remoteBase, systemName, backupContext.getDateFormat(), remoteOpts);
+                       } catch (Exception e) {
+                               failures.add("Purge " + remoteBase + " failed: " + e.getMessage());
+                               log.error("Cannot purge " + remoteBase, e);
+                       }
+
+                       try {
+                               localBaseFo = fileSystemManager.resolveFile(backupsBase + '/' + backupContext.getRelativeFolder(),
+                                               opts);
+                               remoteBaseFo = fileSystemManager.resolveFile(remoteBase + '/' + backupContext.getRelativeFolder(),
+                                               remoteOpts);
+                               remoteBaseFo.copyFrom(localBaseFo, Selectors.SELECT_ALL);
+                               if (log.isDebugEnabled())
+                                       log.debug("Copied backup to " + remoteBaseFo + " from " + localBaseFo);
+                               // }
+                       } catch (Exception e) {
+                               failures.add("Dispatch to " + remoteBase + " failed: " + e.getMessage());
+                               log.error("Cannot dispatch backups from " + backupContext.getRelativeFolder() + " to " + remoteBase, e);
+                       }
+                       BackupUtils.closeFOQuietly(localBaseFo);
+                       BackupUtils.closeFOQuietly(remoteBaseFo);
+               }
+
+               int failureCount = 0;
+               if (failures.size() > 0) {
+                       StringBuffer buf = new StringBuffer();
+                       for (String failure : failures) {
+                               buf.append('\n').append(failureCount).append(" - ").append(failure);
+                               failureCount++;
+                       }
+                       throw new MaintenanceException(failureCount + " error(s) when running the backup,"
+                                       + " check the logs and the backups as soon as possible." + buf);
+               }
+       }
+
+       public void setFileSystemManager(FileSystemManager fileSystemManager) {
+               this.fileSystemManager = fileSystemManager;
+       }
+
+       public void setBackupsBase(String backupsBase) {
+               this.backupsBase = backupsBase;
+       }
+
+       public void setSystemName(String name) {
+               this.systemName = name;
+       }
+
+       public void setAtomicBackups(List<AtomicBackup> atomicBackups) {
+               this.atomicBackups = atomicBackups;
+       }
+
+       public void setBackupPurge(BackupPurge backupPurge) {
+               this.backupPurge = backupPurge;
+       }
+
+       public void setUserAuthenticator(UserAuthenticator userAuthenticator) {
+               this.userAuthenticator = userAuthenticator;
+       }
+
+       public void setRemoteBases(Map<String, UserAuthenticator> remoteBases) {
+               this.remoteBases = remoteBases;
+       }
+
+       // public static void main(String args[]) {
+       // while (true) {
+       // try {
+       // StandardFileSystemManager fsm = new StandardFileSystemManager();
+       // fsm.init();
+       //
+       // SystemBackup systemBackup = new SystemBackup();
+       // systemBackup.setSystemName("mySystem");
+       // systemBackup
+       // .setBackupsBase("/home/mbaudier/dev/src/commons/server/runtime/org.argeo.server.core/target");
+       // systemBackup.setFileSystemManager(fsm);
+       //
+       // List<AtomicBackup> atomicBackups = new ArrayList<AtomicBackup>();
+       //
+       // MySqlBackup mySqlBackup = new MySqlBackup("root", "", "test");
+       // atomicBackups.add(mySqlBackup);
+       // PostgreSqlBackup postgreSqlBackup = new PostgreSqlBackup(
+       // "argeo", "argeo", "gis_template");
+       // atomicBackups.add(postgreSqlBackup);
+       // SvnBackup svnBackup = new SvnBackup(
+       // "/home/mbaudier/tmp/testsvnrepo");
+       // atomicBackups.add(svnBackup);
+       //
+       // systemBackup.setAtomicBackups(atomicBackups);
+       //
+       // Map<String, UserAuthenticator> remoteBases = new HashMap<String,
+       // UserAuthenticator>();
+       // StaticUserAuthenticator userAuthenticator = new StaticUserAuthenticator(
+       // null, "demo", "demo");
+       // remoteBases.put("sftp://localhost/home/mbaudier/test",
+       // userAuthenticator);
+       // systemBackup.setRemoteBases(remoteBases);
+       //
+       // systemBackup.run();
+       //
+       // fsm.close();
+       // } catch (FileSystemException e) {
+       // // TODO Auto-generated catch block
+       // e.printStackTrace();
+       // System.exit(1);
+       // }
+       //
+       // // wait
+       // try {
+       // Thread.sleep(120 * 1000);
+       // } catch (InterruptedException e) {
+       // e.printStackTrace();
+       // }
+       // }
+       // }
+}
diff --git a/org.argeo.slc.cms/src/org/argeo/slc/backup/vfs/package-info.java b/org.argeo.slc.cms/src/org/argeo/slc/backup/vfs/package-info.java
new file mode 100644 (file)
index 0000000..f70499d
--- /dev/null
@@ -0,0 +1,2 @@
+/** Argeo Node backup utilities based on Apache Commons VFS. */
+package org.argeo.slc.backup.vfs;
\ No newline at end of file
diff --git a/org.argeo.slc.cms/src/org/argeo/slc/cms/deploy/CmsDeployedSystem.java b/org.argeo.slc.cms/src/org/argeo/slc/cms/deploy/CmsDeployedSystem.java
new file mode 100644 (file)
index 0000000..feec64f
--- /dev/null
@@ -0,0 +1,7 @@
+package org.argeo.slc.cms.deploy;
+
+import org.argeo.slc.deploy.DeployedSystem;
+
+public interface CmsDeployedSystem extends DeployedSystem {
+
+}
diff --git a/org.argeo.slc.cms/src/org/argeo/slc/cms/deploy/CmsDeploymentData.java b/org.argeo.slc.cms/src/org/argeo/slc/cms/deploy/CmsDeploymentData.java
new file mode 100644 (file)
index 0000000..17cecd8
--- /dev/null
@@ -0,0 +1,9 @@
+package org.argeo.slc.cms.deploy;
+
+import java.util.List;
+
+import org.argeo.slc.deploy.DeploymentData;
+
+public interface CmsDeploymentData extends DeploymentData {
+       List<String> getModulesToActivate(int startLevel);
+}
diff --git a/org.argeo.slc.cms/src/org/argeo/slc/cms/deploy/CmsTargetData.java b/org.argeo.slc.cms/src/org/argeo/slc/cms/deploy/CmsTargetData.java
new file mode 100644 (file)
index 0000000..4616b20
--- /dev/null
@@ -0,0 +1,12 @@
+package org.argeo.slc.cms.deploy;
+
+import java.nio.file.Path;
+
+import org.argeo.slc.deploy.TargetData;
+
+public interface CmsTargetData extends TargetData {
+       Path getInstanceData();
+
+       Integer getHttpPort();
+
+}
diff --git a/org.argeo.slc.cms/src/org/argeo/slc/cms/deploy/SimpleCmsDeploymentData.java b/org.argeo.slc.cms/src/org/argeo/slc/cms/deploy/SimpleCmsDeploymentData.java
new file mode 100644 (file)
index 0000000..c95f441
--- /dev/null
@@ -0,0 +1,17 @@
+package org.argeo.slc.cms.deploy;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+public class SimpleCmsDeploymentData implements CmsDeploymentData {
+       private Map<Integer, List<String>> startLevels = new TreeMap<>();
+
+       @Override
+       public List<String> getModulesToActivate(int startLevel) {
+               startLevels.putIfAbsent(startLevel, new ArrayList<>());
+               return startLevels.get(startLevel);
+       }
+
+}
diff --git a/org.argeo.slc.cms/src/org/argeo/slc/cms/deploy/SimpleCmsTargetData.java b/org.argeo.slc.cms/src/org/argeo/slc/cms/deploy/SimpleCmsTargetData.java
new file mode 100644 (file)
index 0000000..ecf17d6
--- /dev/null
@@ -0,0 +1,30 @@
+package org.argeo.slc.cms.deploy;
+
+import java.nio.file.Path;
+
+public class SimpleCmsTargetData implements CmsTargetData {
+       private Path instanceData;
+       private Integer httpPort;
+
+       public SimpleCmsTargetData(Path instanceData, Integer httpPort) {
+               this.instanceData = instanceData;
+               this.httpPort = httpPort;
+       }
+
+       public Integer getHttpPort() {
+               return httpPort;
+       }
+
+       public void setHttpPort(Integer httpPort) {
+               this.httpPort = httpPort;
+       }
+
+       public Path getInstanceData() {
+               return instanceData;
+       }
+
+       public void setInstanceData(Path instanceData) {
+               this.instanceData = instanceData;
+       }
+
+}
diff --git a/org.argeo.slc.cms/src/org/argeo/slc/cms/deploy/osgi/CmsOsgiDeployedSystem.java b/org.argeo.slc.cms/src/org/argeo/slc/cms/deploy/osgi/CmsOsgiDeployedSystem.java
new file mode 100644 (file)
index 0000000..8de4354
--- /dev/null
@@ -0,0 +1,49 @@
+package org.argeo.slc.cms.deploy.osgi;
+
+import org.argeo.slc.build.Distribution;
+import org.argeo.slc.build.ModularDistribution;
+import org.argeo.slc.cms.deploy.CmsDeployedSystem;
+import org.argeo.slc.cms.deploy.CmsDeploymentData;
+import org.argeo.slc.cms.deploy.CmsTargetData;
+import org.argeo.slc.deploy.DeploymentData;
+import org.argeo.slc.deploy.TargetData;
+import org.osgi.framework.BundleContext;
+
+public class CmsOsgiDeployedSystem implements CmsDeployedSystem {
+       private ModularDistribution distribution;
+       private CmsTargetData targetData;
+       private CmsDeploymentData deploymentData;
+
+       private BundleContext systemBundleContext;
+
+       public CmsOsgiDeployedSystem(BundleContext systemBundleContext, ModularDistribution distribution,
+                       CmsTargetData targetData, CmsDeploymentData deploymentData) {
+               this.systemBundleContext = systemBundleContext;
+
+               this.distribution = distribution;
+               this.targetData = targetData;
+               this.deploymentData = deploymentData;
+       }
+
+       @Override
+       public String getDeployedSystemId() {
+               // TODO Auto-generated method stub
+               return null;
+       }
+
+       @Override
+       public Distribution getDistribution() {
+               return distribution;
+       }
+
+       @Override
+       public DeploymentData getDeploymentData() {
+               return deploymentData;
+       }
+
+       @Override
+       public TargetData getTargetData() {
+               return targetData;
+       }
+
+}
diff --git a/org.argeo.slc.cms/src/org/argeo/slc/cms/deploy/osgi/CmsOsgiDeployment.java b/org.argeo.slc.cms/src/org/argeo/slc/cms/deploy/osgi/CmsOsgiDeployment.java
new file mode 100644 (file)
index 0000000..4c9ea20
--- /dev/null
@@ -0,0 +1,193 @@
+package org.argeo.slc.cms.deploy.osgi;
+
+import java.io.IOException;
+import java.lang.System.Logger;
+import java.lang.System.Logger.Level;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.StringJoiner;
+import java.util.TreeMap;
+
+import org.argeo.cms.CmsDeployProperty;
+import org.argeo.init.a2.A2Source;
+import org.argeo.init.a2.FsA2Source;
+import org.argeo.init.osgi.OsgiBoot;
+import org.argeo.init.osgi.OsgiRuntimeContext;
+import org.argeo.slc.WellKnownConstants;
+import org.argeo.slc.build.Distribution;
+import org.argeo.slc.cms.deploy.CmsDeployedSystem;
+import org.argeo.slc.cms.deploy.CmsDeploymentData;
+import org.argeo.slc.cms.deploy.CmsTargetData;
+import org.argeo.slc.cms.deploy.SimpleCmsDeploymentData;
+import org.argeo.slc.cms.deploy.SimpleCmsTargetData;
+import org.argeo.slc.cms.distribution.A2Distribution;
+import org.argeo.slc.deploy.DeployedSystem;
+import org.argeo.slc.deploy.Deployment;
+import org.argeo.slc.deploy.DeploymentData;
+import org.argeo.slc.deploy.TargetData;
+
+public class CmsOsgiDeployment implements Deployment {
+       private final static Logger logger = System.getLogger(CmsOsgiDeployment.class.getName());
+
+       private A2Distribution distribution;
+       private CmsTargetData targetData;
+       private CmsDeploymentData deploymentData;
+
+       private CmsDeployedSystem deployedSystem;
+
+       private OsgiRuntimeContext runtimeContext;
+
+       @Override
+       public void run() {
+               try {
+                       Map<String, String> config = new TreeMap<>();
+
+                       // sources
+                       StringJoiner sourcesProperty = new StringJoiner(",");
+                       for (A2Source a2Source : distribution.getA2Sources()) {
+                               sourcesProperty.add(a2Source.getUri().toString());
+                       }
+                       config.put(OsgiBoot.PROP_ARGEO_OSGI_SOURCES, sourcesProperty.toString());
+
+                       // target
+                       config.put(WellKnownConstants.OSGI_INSTANCE_AREA,
+                                       targetData.getInstanceData().toRealPath().toUri().toString());
+                       if (targetData.getHttpPort() != null) {
+                               config.put(CmsDeployProperty.HTTP_PORT.getProperty(), targetData.getHttpPort().toString());
+                       }
+
+                       Path configurationArea = Files.createTempDirectory("slc-cms-test");
+                       config.put(WellKnownConstants.OSGI_CONFIGURATION_AREA, configurationArea.toUri().toString());
+
+                       // modules activation
+                       for (int startLevel = 0; startLevel <= 6; startLevel++) {
+                               List<String> modules = deploymentData.getModulesToActivate(startLevel);
+                               if (modules.size() != 0) {
+                                       String startProperty = String.join(",", modules);
+                                       config.put(OsgiBoot.PROP_ARGEO_OSGI_START + "." + startLevel, startProperty);
+                               }
+                       }
+
+                       config.put("org.eclipse.equinox.http.jetty.autostart", "false");
+                       config.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");
+                       config.put("eclipse.ignoreApp", "true");
+                       config.put("osgi.noShutdown", "true");
+
+                       config.put("osgi.console", "2323");
+
+                       // initialise
+                       for (String key : config.keySet()) {
+//                             System.out.println(key + "=" + config.get(key));
+                               logger.log(Level.INFO, () -> key + "=" + config.get(key));
+                       }
+
+                       runtimeContext = new OsgiRuntimeContext(config);
+                       runtimeContext.run();
+
+                       deployedSystem = new CmsOsgiDeployedSystem(runtimeContext.getFramework().getBundleContext(), distribution,
+                                       targetData, deploymentData);
+
+               } catch (Exception e) {
+                       throw new IllegalStateException("Cannot run OSGi deployment", e);
+               }
+
+       }
+
+       @Override
+       public DeployedSystem getDeployedSystem() {
+               return deployedSystem;
+       }
+
+       @Override
+       public void setTargetData(TargetData targetData) {
+               this.targetData = (CmsTargetData) targetData;
+       }
+
+       @Override
+       public void setDeploymentData(DeploymentData deploymentData) {
+               this.deploymentData = (CmsDeploymentData) deploymentData;
+       }
+
+       @Override
+       public void setDistribution(Distribution distribution) {
+               this.distribution = (A2Distribution) distribution;
+       }
+
+       public OsgiRuntimeContext getRuntimeContext() {
+               return runtimeContext;
+       }
+
+       public static void main(String[] args) {
+               try {
+                       Path userHome = Paths.get(System.getProperty("user.home"));
+
+                       // distribution
+                       Path a2Base = userHome.resolve("dev/git/unstable/output/a2");
+                       A2Distribution distribution = new A2Distribution();
+                       Map<String, String> xOr = new HashMap<>();
+                       xOr.put("osgi", "equinox");
+                       xOr.put("swt", "rap");
+                       distribution.getA2Sources().add(new FsA2Source(a2Base, xOr, true));
+
+                       // target data
+                       Path instanceData = userHome.resolve("dev/git/unstable/argeo-slc/sdk/exec/cms-deployment/data");
+                       Files.createDirectories(instanceData);
+                       Integer httpPort = 7070;
+                       SimpleCmsTargetData targetData = new SimpleCmsTargetData(instanceData, httpPort);
+
+                       // deployment data
+                       SimpleCmsDeploymentData deploymentData = new SimpleCmsDeploymentData();
+                       deploymentData.getModulesToActivate(2).add("org.eclipse.equinox.http.servlet");
+                       deploymentData.getModulesToActivate(2).add("org.apache.felix.scr");
+                       deploymentData.getModulesToActivate(2).add("org.eclipse.rap.rwt.osgi");
+                       deploymentData.getModulesToActivate(2).add("org.eclipse.equinox.console");
+
+                       deploymentData.getModulesToActivate(3).add("org.argeo.cms");
+                       deploymentData.getModulesToActivate(3).add("org.argeo.cms.ee");
+                       deploymentData.getModulesToActivate(3).add("org.argeo.cms.lib.sshd");
+                       deploymentData.getModulesToActivate(3).add("org.argeo.cms.lib.equinox");
+                       deploymentData.getModulesToActivate(3).add("org.argeo.cms.lib.jetty");
+                       deploymentData.getModulesToActivate(3).add("org.argeo.cms.swt.rap");
+
+                       deploymentData.getModulesToActivate(4).add("org.argeo.cms.jcr");
+                       deploymentData.getModulesToActivate(4).add("org.argeo.app.profile.acr.fs");
+
+                       deploymentData.getModulesToActivate(5).add("org.argeo.app.core");
+                       deploymentData.getModulesToActivate(5).add("org.argeo.app.ui");
+                       deploymentData.getModulesToActivate(5).add("org.argeo.app.theme.default");
+
+                       CmsOsgiDeployment deployment = new CmsOsgiDeployment();
+                       deployment.setDistribution(distribution);
+                       deployment.setTargetData(targetData);
+                       deployment.setDeploymentData(deploymentData);
+                       deployment.run();
+
+                       boolean multiple = false;
+                       if (multiple) {
+
+                               Path instanceData2 = userHome.resolve("dev/git/unstable/argeo-slc/sdk/exec/cms-deployment2/data");
+                               Files.createDirectories(instanceData2);
+                               Integer httpPort2 = 7071;
+                               SimpleCmsTargetData targetData2 = new SimpleCmsTargetData(instanceData2, httpPort2);
+
+                               CmsOsgiDeployment deployment2 = new CmsOsgiDeployment();
+                               deployment2.setDistribution(distribution);
+                               deployment2.setTargetData(targetData2);
+                               deployment2.setDeploymentData(deploymentData);
+                               deployment2.run();
+                       }
+
+                       deployment.getRuntimeContext().waitForStop(0);
+
+               } catch (IOException | InterruptedException e) {
+                       e.printStackTrace();
+                       System.exit(1);
+               }
+       }
+
+}
diff --git a/org.argeo.slc.cms/src/org/argeo/slc/cms/distribution/A2Distribution.java b/org.argeo.slc.cms/src/org/argeo/slc/cms/distribution/A2Distribution.java
new file mode 100644 (file)
index 0000000..6c8bbe7
--- /dev/null
@@ -0,0 +1,75 @@
+package org.argeo.slc.cms.distribution;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.argeo.init.a2.A2Branch;
+import org.argeo.init.a2.A2Component;
+import org.argeo.init.a2.A2Contribution;
+import org.argeo.init.a2.A2Module;
+import org.argeo.init.a2.A2Source;
+import org.argeo.slc.CategoryNameVersion;
+import org.argeo.slc.DefaultCategoryNameVersion;
+import org.argeo.slc.NameVersion;
+import org.argeo.slc.build.Distribution;
+import org.argeo.slc.build.ModularDistribution;
+
+public class A2Distribution implements ModularDistribution {
+       private List<A2Source> a2Sources = new ArrayList<>();
+
+       @Override
+       public String getDistributionId() {
+               // TODO Auto-generated method stub
+               return null;
+       }
+
+       @Override
+       public String getName() {
+               // TODO Auto-generated method stub
+               return null;
+       }
+
+       @Override
+       public String getVersion() {
+               // TODO Auto-generated method stub
+               return null;
+       }
+
+       @Override
+       public Iterator<? extends NameVersion> nameVersions() {
+               List<CategoryNameVersion> nameVersions = new ArrayList<>();
+               for (A2Source a2Source : a2Sources) {
+                       for (A2Contribution a2Contribution : a2Source.listContributions(null)) {
+                               for (A2Component a2Component : a2Contribution.listComponents(null)) {
+                                       for (A2Branch a2Branch : a2Component.listBranches(null)) {
+                                               for (A2Module a2Module : a2Branch.listModules(null)) {
+                                                       CategoryNameVersion nameVersion = new DefaultCategoryNameVersion(a2Contribution.getId(),
+                                                                       a2Component.getId(), a2Module.getVersion().toString());
+                                                       nameVersions.add(nameVersion);
+                                               }
+                                       }
+                               }
+                       }
+               }
+               return nameVersions.iterator();
+       }
+
+       @Override
+       public Distribution getModuleDistribution(String moduleName, String moduleVersion) {
+               // TODO Auto-generated method stub
+               return null;
+       }
+
+       @Override
+       public Object getModulesDescriptor(String descriptorType) {
+               // TODO Auto-generated method stub
+               return null;
+       }
+
+       public List<A2Source> getA2Sources() {
+               return a2Sources;
+       }
+
+       
+}
diff --git a/org.argeo.slc.cms/src/org/argeo/slc/cms/distribution/A2ModuleDistribution.java b/org.argeo.slc.cms/src/org/argeo/slc/cms/distribution/A2ModuleDistribution.java
new file mode 100644 (file)
index 0000000..cc7e681
--- /dev/null
@@ -0,0 +1,14 @@
+package org.argeo.slc.cms.distribution;
+
+import org.argeo.init.a2.A2Module;
+import org.argeo.slc.build.Distribution;
+
+public class A2ModuleDistribution implements Distribution {
+       private A2Module a2Module;
+
+       @Override
+       public String getDistributionId() {
+               return a2Module.getCoordinates();
+       }
+
+}
diff --git a/org.argeo.slc.cms/src/org/argeo/slc/cms/test/CmsSmokeTest.java b/org.argeo.slc.cms/src/org/argeo/slc/cms/test/CmsSmokeTest.java
new file mode 100644 (file)
index 0000000..10b088c
--- /dev/null
@@ -0,0 +1,20 @@
+package org.argeo.slc.cms.test;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+public class CmsSmokeTest {
+
+       public static void main(String[] args) throws IOException {
+               Path instanceData;
+               if (args.length > 0) {
+                       instanceData = Paths.get(args[0]);
+               } else {
+                       instanceData = Files.createTempDirectory("cms-test");
+               }
+
+       }
+
+}
diff --git a/org.argeo.slc.cms/src/org/argeo/slc/cms/test/MinimalJvm.java b/org.argeo.slc.cms/src/org/argeo/slc/cms/test/MinimalJvm.java
new file mode 100644 (file)
index 0000000..87eefd9
--- /dev/null
@@ -0,0 +1,21 @@
+package org.argeo.slc.cms.test;
+
+/**
+ * A program doing nothing and loading no classes, to be used as a baseline for
+ * memory usage of the JVM.
+ */
+public class MinimalJvm {
+
+       synchronized void sleep() {
+               try {
+                       wait();
+               } catch (InterruptedException e) {
+                       e.printStackTrace();
+               }
+       }
+
+       public static void main(String[] args) {
+               new MinimalJvm().sleep();
+       }
+
+}
diff --git a/org.argeo.slc.factory/.classpath b/org.argeo.slc.factory/.classpath
deleted file mode 100644 (file)
index e801ebf..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-<?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>
diff --git a/org.argeo.slc.factory/.project b/org.argeo.slc.factory/.project
deleted file mode 100644 (file)
index d0f4daf..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
-       <name>org.argeo.slc.factory</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>
diff --git a/org.argeo.slc.factory/bnd.bnd b/org.argeo.slc.factory/bnd.bnd
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/org.argeo.slc.factory/build.properties b/org.argeo.slc.factory/build.properties
deleted file mode 100644 (file)
index 6050127..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-source.. = src/
-output.. = bin/
-bin.includes = META-INF/,\
-               .
-additional.bundles = org.argeo.init,\
-                     org.slf4j.api
\ No newline at end of file
diff --git a/org.argeo.slc.factory/src/org/argeo/slc/factory/A2Factory.java b/org.argeo.slc.factory/src/org/argeo/slc/factory/A2Factory.java
deleted file mode 100644 (file)
index b336dc6..0000000
+++ /dev/null
@@ -1,810 +0,0 @@
-package org.argeo.slc.factory;
-
-import static java.lang.System.Logger.Level.DEBUG;
-import static org.argeo.slc.ManifestConstants.BUNDLE_SYMBOLICNAME;
-import static org.argeo.slc.ManifestConstants.BUNDLE_VERSION;
-import static org.argeo.slc.ManifestConstants.EXPORT_PACKAGE;
-import static org.argeo.slc.ManifestConstants.SLC_ORIGIN_M2;
-import static org.argeo.slc.ManifestConstants.SLC_ORIGIN_M2_REPO;
-
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.lang.System.Logger;
-import java.lang.System.Logger.Level;
-import java.net.URL;
-import java.nio.file.DirectoryStream;
-import java.nio.file.FileSystem;
-import java.nio.file.FileSystems;
-import java.nio.file.FileVisitResult;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.PathMatcher;
-import java.nio.file.Paths;
-import java.nio.file.SimpleFileVisitor;
-import java.nio.file.StandardOpenOption;
-import java.nio.file.attribute.BasicFileAttributes;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Properties;
-import java.util.TreeMap;
-import java.util.jar.Attributes;
-import java.util.jar.JarEntry;
-import java.util.jar.JarInputStream;
-import java.util.jar.JarOutputStream;
-import java.util.jar.Manifest;
-
-import org.argeo.slc.DefaultCategoryNameVersion;
-import org.argeo.slc.DefaultNameVersion;
-import org.argeo.slc.ManifestConstants;
-import org.argeo.slc.NameVersion;
-import org.argeo.slc.factory.m2.DefaultArtifact;
-import org.argeo.slc.factory.m2.MavenConventionsUtils;
-
-import aQute.bnd.osgi.Analyzer;
-import aQute.bnd.osgi.Jar;
-
-/** The central class for A2 packaging. */
-public class A2Factory {
-       private final static Logger logger = System.getLogger(A2Factory.class.getName());
-
-       private final static String COMMON_BND = "common.bnd";
-       private final static String MERGE_BND = "merge.bnd";
-
-       private Path originBase;
-       private Path a2Base;
-
-       /** key is URI prefix, value list of base URLs */
-       private Map<String, List<String>> mirrors = new HashMap<String, List<String>>();
-
-       public A2Factory(Path a2Base) {
-               this.originBase = Paths.get(System.getProperty("user.home"), ".cache", "argeo/slc/origin");
-               this.a2Base = a2Base;
-
-               // TODO make it configurable
-               List<String> eclipseMirrors = new ArrayList<>();
-               eclipseMirrors.add("https://archive.eclipse.org/");
-               eclipseMirrors.add("http://ftp-stud.hs-esslingen.de/Mirrors/eclipse/");
-               eclipseMirrors.add("http://ftp.fau.de/eclipse/");
-
-               mirrors.put("http://www.eclipse.org/downloads", eclipseMirrors);
-       }
-
-       public void processCategory(Path targetCategoryBase) {
-               try {
-                       DirectoryStream<Path> bnds = Files.newDirectoryStream(targetCategoryBase,
-                                       (p) -> p.getFileName().toString().endsWith(".bnd") && !p.getFileName().toString().equals(COMMON_BND)
-                                                       && !p.getFileName().toString().equals(MERGE_BND));
-                       for (Path p : bnds) {
-                               processSingleM2ArtifactDistributionUnit(p);
-                       }
-
-                       DirectoryStream<Path> dus = Files.newDirectoryStream(targetCategoryBase, (p) -> Files.isDirectory(p));
-                       for (Path duDir : dus) {
-                               processM2BasedDistributionUnit(duDir);
-                       }
-               } catch (IOException e) {
-                       throw new RuntimeException("Cannot process category " + targetCategoryBase, e);
-               }
-       }
-
-       public void processSingleM2ArtifactDistributionUnit(Path bndFile) {
-               try {
-                       String category = bndFile.getParent().getFileName().toString();
-                       Path targetCategoryBase = a2Base.resolve(category);
-                       Properties fileProps = new Properties();
-                       try (InputStream in = Files.newInputStream(bndFile)) {
-                               fileProps.load(in);
-                       }
-                       String repoStr = fileProps.containsKey(SLC_ORIGIN_M2_REPO.toString())
-                                       ? fileProps.getProperty(SLC_ORIGIN_M2_REPO.toString())
-                                       : null;
-
-                       if (!fileProps.containsKey(BUNDLE_SYMBOLICNAME.toString())
-                                       && !fileProps.containsKey(ManifestConstants.SLC_ORIGIN_MANIFEST_NOT_MODIFIED.toString())) {
-                               // use file name as symbolic name
-                               String symbolicName = bndFile.getFileName().toString();
-                               symbolicName = symbolicName.substring(0, symbolicName.length() - ".bnd".length());
-                               fileProps.put(BUNDLE_SYMBOLICNAME.toString(), symbolicName);
-                       }
-
-                       String m2Coordinates = fileProps.getProperty(SLC_ORIGIN_M2.toString());
-                       if (m2Coordinates == null)
-                               throw new IllegalArgumentException("No M2 coordinates available for " + bndFile);
-                       DefaultArtifact artifact = new DefaultArtifact(m2Coordinates);
-                       URL url = MavenConventionsUtils.mavenRepoUrl(repoStr, artifact);
-                       Path downloaded = download(url, originBase, artifact.toM2Coordinates() + ".jar");
-
-                       Path targetBundleDir = processBndJar(downloaded, targetCategoryBase, fileProps, artifact);
-
-                       downloadAndProcessM2Sources(repoStr, artifact, targetBundleDir);
-
-                       createJar(targetBundleDir);
-               } catch (Exception e) {
-                       throw new RuntimeException("Cannot process " + bndFile, e);
-               }
-       }
-
-       public void processM2BasedDistributionUnit(Path duDir) {
-               try {
-                       String category = duDir.getParent().getFileName().toString();
-                       Path targetCategoryBase = a2Base.resolve(category);
-
-                       // merge
-                       Path mergeBnd = duDir.resolve(MERGE_BND);
-                       if (Files.exists(mergeBnd)) {
-                               mergeM2Artifacts(mergeBnd);
-                               return;
-                       }
-
-                       Path commonBnd = duDir.resolve(COMMON_BND);
-                       Properties commonProps = new Properties();
-                       try (InputStream in = Files.newInputStream(commonBnd)) {
-                               commonProps.load(in);
-                       }
-
-                       String m2Version = commonProps.getProperty(SLC_ORIGIN_M2.toString());
-                       if (m2Version == null) {
-                               logger.log(Level.WARNING, "Ignoring " + duDir + " as it is not an M2-based distribution unit");
-                               return;// ignore, this is probably an Eclipse archive
-                       }
-                       if (!m2Version.startsWith(":")) {
-                               throw new IllegalStateException("Only the M2 version can be specified: " + m2Version);
-                       }
-                       m2Version = m2Version.substring(1);
-
-                       DirectoryStream<Path> ds = Files.newDirectoryStream(duDir,
-                                       (p) -> p.getFileName().toString().endsWith(".bnd")
-                                                       && !p.getFileName().toString().equals(COMMON_BND));
-                       for (Path p : ds) {
-                               Properties fileProps = new Properties();
-                               try (InputStream in = Files.newInputStream(p)) {
-                                       fileProps.load(in);
-                               }
-                               String m2Coordinates = fileProps.getProperty(SLC_ORIGIN_M2.toString());
-                               DefaultArtifact artifact = new DefaultArtifact(m2Coordinates);
-
-                               // temporary rewrite, for migration
-//                             String localLicense = fileProps.getProperty(BUNDLE_LICENSE.toString());
-//                             if (localLicense != null || artifact.getVersion() != null) {
-//                                     fileProps.remove(BUNDLE_LICENSE.toString());
-//                                     fileProps.put(SLC_ORIGIN_M2.toString(), artifact.getGroupId() + ":" + artifact.getArtifactId());
-//                                     try (Writer writer = Files.newBufferedWriter(p)) {
-//                                             for (Object key : fileProps.keySet()) {
-//                                                     String value = fileProps.getProperty(key.toString());
-//                                                     writer.write(key + ": " + value + '\n');
-//                                             }
-//                                             logger.log(DEBUG, () -> "Migrated  " + p);
-//                                     }
-//                             }
-
-                               artifact.setVersion(m2Version);
-
-                               // prepare manifest entries
-                               Properties mergeProps = new Properties();
-                               mergeProps.putAll(commonProps);
-
-                               fileEntries: for (Object key : fileProps.keySet()) {
-                                       if (ManifestConstants.SLC_ORIGIN_M2.toString().equals(key))
-                                               continue fileEntries;
-                                       String value = fileProps.getProperty(key.toString());
-                                       Object previousValue = mergeProps.put(key.toString(), value);
-                                       if (previousValue != null) {
-                                               logger.log(Level.WARNING,
-                                                               commonBnd + ": " + key + " was " + previousValue + ", overridden with " + value);
-                                       }
-                               }
-                               mergeProps.put(ManifestConstants.SLC_ORIGIN_M2.toString(), artifact.toM2Coordinates());
-                               if (!mergeProps.containsKey(BUNDLE_SYMBOLICNAME.toString())
-                                               && !mergeProps.containsKey(ManifestConstants.SLC_ORIGIN_MANIFEST_NOT_MODIFIED.toString())) {
-                                       // use file name as symbolic name
-                                       String symbolicName = p.getFileName().toString();
-                                       symbolicName = symbolicName.substring(0, symbolicName.length() - ".bnd".length());
-                                       mergeProps.put(BUNDLE_SYMBOLICNAME.toString(), symbolicName);
-                               }
-
-                               String repoStr = mergeProps.containsKey(SLC_ORIGIN_M2_REPO.toString())
-                                               ? mergeProps.getProperty(SLC_ORIGIN_M2_REPO.toString())
-                                               : null;
-
-                               // download
-                               URL url = MavenConventionsUtils.mavenRepoUrl(repoStr, artifact);
-                               Path downloaded = download(url, originBase, artifact.toM2Coordinates() + ".jar");
-
-                               Path targetBundleDir = processBndJar(downloaded, targetCategoryBase, mergeProps, artifact);
-//                             logger.log(Level.DEBUG, () -> "Processed " + downloaded);
-
-                               // sources
-                               downloadAndProcessM2Sources(repoStr, artifact, targetBundleDir);
-
-                               createJar(targetBundleDir);
-                       }
-               } catch (IOException e) {
-                       throw new RuntimeException("Cannot process " + duDir, e);
-               }
-
-       }
-
-       protected void mergeM2Artifacts(Path mergeBnd) throws IOException {
-               Path duDir = mergeBnd.getParent();
-               String category = duDir.getParent().getFileName().toString();
-               Path targetCategoryBase = a2Base.resolve(category);
-
-               Properties mergeProps = new Properties();
-               try (InputStream in = Files.newInputStream(mergeBnd)) {
-                       mergeProps.load(in);
-               }
-               String m2Version = mergeProps.getProperty(SLC_ORIGIN_M2.toString());
-               if (m2Version == null) {
-                       logger.log(Level.WARNING, "Ignoring " + duDir + " as it is not an M2-based distribution unit");
-                       return;// ignore, this is probably an Eclipse archive
-               }
-               if (!m2Version.startsWith(":")) {
-                       throw new IllegalStateException("Only the M2 version can be specified: " + m2Version);
-               }
-               m2Version = m2Version.substring(1);
-
-               String artifactsStr = mergeProps.getProperty(ManifestConstants.SLC_ORIGIN_M2_MERGE.toString());
-               String repoStr = mergeProps.containsKey(SLC_ORIGIN_M2_REPO.toString())
-                               ? mergeProps.getProperty(SLC_ORIGIN_M2_REPO.toString())
-                               : null;
-
-               String bundleSymbolicName = mergeProps.getProperty(ManifestConstants.BUNDLE_SYMBOLICNAME.toString());
-               DefaultCategoryNameVersion nameVersion = new DefaultArtifact(
-                               category + ":" + bundleSymbolicName + ":" + m2Version);
-               Path targetBundleDir = targetCategoryBase.resolve(bundleSymbolicName + "." + nameVersion.getBranch());
-
-               String[] artifacts = artifactsStr.split(",");
-               artifacts: for (String str : artifacts) {
-                       String m2Coordinates = str.trim();
-                       if ("".equals(m2Coordinates))
-                               continue artifacts;
-                       DefaultArtifact artifact = new DefaultArtifact(m2Coordinates.trim());
-                       if (artifact.getVersion() == null)
-                               artifact.setVersion(m2Version);
-                       URL url = MavenConventionsUtils.mavenRepoUrl(repoStr, artifact);
-                       Path downloaded = download(url, originBase, artifact.toM2Coordinates() + ".jar");
-                       JarEntry entry;
-                       try (JarInputStream jarIn = new JarInputStream(Files.newInputStream(downloaded), false)) {
-                               entries: while ((entry = jarIn.getNextJarEntry()) != null) {
-                                       if (entry.isDirectory())
-                                               continue entries;
-                                       if (entry.getName().endsWith(".RSA") || entry.getName().endsWith(".SF"))
-                                               continue entries;
-                                       if (entry.getName().startsWith("META-INF/versions/"))
-                                               continue entries;
-                                       if (entry.getName().equals("module-info.class"))
-                                               continue entries;
-                                       Path target = targetBundleDir.resolve(entry.getName());
-                                       Files.createDirectories(target.getParent());
-                                       if (!Files.exists(target)) {
-                                               Files.copy(jarIn, target);
-                                       } else {
-                                               if (entry.getName().startsWith("META-INF/services/")) {
-                                                       try (OutputStream out = Files.newOutputStream(target, StandardOpenOption.APPEND)) {
-                                                               out.write("\n".getBytes());
-                                                               jarIn.transferTo(out);
-                                                               if (logger.isLoggable(DEBUG))
-                                                                       logger.log(DEBUG, "Appended " + entry.getName());
-                                                       }
-                                               } else {
-                                                       throw new IllegalStateException("File " + target + " already exists");
-                                               }
-                                       }
-                                       logger.log(Level.TRACE, () -> "Copied " + target);
-                               }
-
-                       }
-                       downloadAndProcessM2Sources(repoStr, artifact, targetBundleDir);
-               }
-
-               Map<String, String> entries = new TreeMap<>();
-               try (Analyzer bndAnalyzer = new Analyzer()) {
-                       bndAnalyzer.setProperties(mergeProps);
-                       Jar jar = new Jar(targetBundleDir.toFile());
-                       bndAnalyzer.setJar(jar);
-                       Manifest manifest = bndAnalyzer.calcManifest();
-
-                       keys: for (Object key : manifest.getMainAttributes().keySet()) {
-                               Object value = manifest.getMainAttributes().get(key);
-
-                               switch (key.toString()) {
-                               case "Tool":
-                               case "Bnd-LastModified":
-                               case "Created-By":
-                                       continue keys;
-                               }
-                               if ("Require-Capability".equals(key.toString())
-                                               && value.toString().equals("osgi.ee;filter:=\"(&(osgi.ee=JavaSE)(version=1.1))\""))
-                                       continue keys;// hack for very old classes
-                               entries.put(key.toString(), value.toString());
-                               logger.log(DEBUG, () -> key + "=" + value);
-
-                       }
-               } catch (Exception e) {
-                       throw new RuntimeException("Cannot process " + mergeBnd, e);
-               }
-
-               Manifest manifest = new Manifest();
-               Path manifestPath = targetBundleDir.resolve("META-INF/MANIFEST.MF");
-               Files.createDirectories(manifestPath.getParent());
-               for (String key : entries.keySet()) {
-                       String value = entries.get(key);
-                       manifest.getMainAttributes().putValue(key, value);
-               }
-               try (OutputStream out = Files.newOutputStream(manifestPath)) {
-                       manifest.write(out);
-               }
-
-               createJar(targetBundleDir);
-
-       }
-
-       protected void downloadAndProcessM2Sources(String repoStr, DefaultArtifact artifact, Path targetBundleDir)
-                       throws IOException {
-               DefaultArtifact sourcesArtifact = new DefaultArtifact(artifact.toM2Coordinates(), "sources");
-               URL sourcesUrl = MavenConventionsUtils.mavenRepoUrl(repoStr, sourcesArtifact);
-               Path sourcesDownloaded = download(sourcesUrl, originBase, artifact.toM2Coordinates() + ".sources.jar");
-               processM2SourceJar(sourcesDownloaded, targetBundleDir);
-               logger.log(Level.DEBUG, () -> "Processed source " + sourcesDownloaded);
-
-       }
-
-       protected Path processBndJar(Path downloaded, Path targetCategoryBase, Properties fileProps,
-                       DefaultArtifact artifact) {
-
-               try {
-                       Map<String, String> additionalEntries = new TreeMap<>();
-                       boolean doNotModify = Boolean.parseBoolean(fileProps
-                                       .getOrDefault(ManifestConstants.SLC_ORIGIN_MANIFEST_NOT_MODIFIED.toString(), "false").toString());
-
-                       // we always force the symbolic name
-
-                       if (doNotModify) {
-                               fileEntries: for (Object key : fileProps.keySet()) {
-                                       if (ManifestConstants.SLC_ORIGIN_M2.toString().equals(key))
-                                               continue fileEntries;
-                                       String value = fileProps.getProperty(key.toString());
-                                       additionalEntries.put(key.toString(), value);
-                               }
-                       } else {
-                               if (artifact != null) {
-                                       if (!fileProps.containsKey(BUNDLE_SYMBOLICNAME.toString())) {
-                                               fileProps.put(BUNDLE_SYMBOLICNAME.toString(), artifact.getName());
-                                       }
-                                       if (!fileProps.containsKey(BUNDLE_VERSION.toString())) {
-                                               fileProps.put(BUNDLE_VERSION.toString(), artifact.getVersion());
-                                       }
-                               }
-
-                               if (!fileProps.containsKey(EXPORT_PACKAGE.toString())) {
-                                       fileProps.put(EXPORT_PACKAGE.toString(),
-                                                       "*;version=\"" + fileProps.getProperty(BUNDLE_VERSION.toString()) + "\"");
-                               }
-//                             if (!fileProps.contains(IMPORT_PACKAGE.toString())) {
-//                                     fileProps.put(IMPORT_PACKAGE.toString(), "*");
-//                             }
-
-                               try (Analyzer bndAnalyzer = new Analyzer()) {
-                                       bndAnalyzer.setProperties(fileProps);
-                                       Jar jar = new Jar(downloaded.toFile());
-                                       bndAnalyzer.setJar(jar);
-                                       Manifest manifest = bndAnalyzer.calcManifest();
-
-                                       keys: for (Object key : manifest.getMainAttributes().keySet()) {
-                                               Object value = manifest.getMainAttributes().get(key);
-
-                                               switch (key.toString()) {
-                                               case "Tool":
-                                               case "Bnd-LastModified":
-                                               case "Created-By":
-                                                       continue keys;
-                                               }
-                                               if ("Require-Capability".equals(key.toString())
-                                                               && value.toString().equals("osgi.ee;filter:=\"(&(osgi.ee=JavaSE)(version=1.1))\""))
-                                                       continue keys;// hack for very old classes
-                                               additionalEntries.put(key.toString(), value.toString());
-                                               logger.log(DEBUG, () -> key + "=" + value);
-
-                                       }
-                               }
-
-//                             try (Builder bndBuilder = new Builder()) {
-//                                     Jar jar = new Jar(downloaded.toFile());
-//                                     bndBuilder.addClasspath(jar);
-//                                     Path targetBundleDir = targetCategoryBase.resolve(artifact.getName() + "." + artifact.getBranch());
-//
-//                                     Jar target = new Jar(targetBundleDir.toFile());
-//                                     bndBuilder.setJar(target);
-//                                     return targetBundleDir;
-//                             }
-                       }
-                       Path targetBundleDir = processBundleJar(downloaded, targetCategoryBase, additionalEntries);
-                       logger.log(Level.DEBUG, () -> "Processed " + downloaded);
-                       return targetBundleDir;
-               } catch (Exception e) {
-                       throw new RuntimeException("Cannot BND process " + downloaded, e);
-               }
-
-       }
-
-       protected void processM2SourceJar(Path file, Path targetBundleDir) throws IOException {
-               try (JarInputStream jarIn = new JarInputStream(Files.newInputStream(file), false)) {
-                       Path targetSourceDir = targetBundleDir.resolve("OSGI-OPT/src");
-
-                       // TODO make it less dangerous?
-                       if (Files.exists(targetSourceDir)) {
-//                             deleteDirectory(targetSourceDir);
-                       } else {
-                               Files.createDirectories(targetSourceDir);
-                       }
-
-                       // copy entries
-                       JarEntry entry;
-                       entries: while ((entry = jarIn.getNextJarEntry()) != null) {
-                               if (entry.isDirectory())
-                                       continue entries;
-                               if (entry.getName().startsWith("META-INF"))// skip META-INF entries
-                                       continue entries;
-                               if (entry.getName().startsWith("module-info.java"))// skip META-INF entries
-                                       continue entries;
-                               Path target = targetSourceDir.resolve(entry.getName());
-                               Files.createDirectories(target.getParent());
-                               if (!Files.exists(target)) {
-                                       Files.copy(jarIn, target);
-                                       logger.log(Level.TRACE, () -> "Copied source " + target);
-                               } else {
-                                       logger.log(Level.WARNING, () -> target + " already exists, skipping...");
-                               }
-                       }
-               }
-
-       }
-
-       public void processEclipseArchive(Path duDir) {
-               try {
-                       String category = duDir.getParent().getFileName().toString();
-                       Path targetCategoryBase = a2Base.resolve(category);
-                       Files.createDirectories(targetCategoryBase);
-                       // first delete all directories from previous builds
-                       for (Path dir : Files.newDirectoryStream(targetCategoryBase, (p) -> Files.isDirectory(p))) {
-                               deleteDirectory(dir);
-                       }
-
-                       Files.createDirectories(originBase);
-
-                       Path commonBnd = duDir.resolve(COMMON_BND);
-                       Properties commonProps = new Properties();
-                       try (InputStream in = Files.newInputStream(commonBnd)) {
-                               commonProps.load(in);
-                       }
-                       Properties includes = new Properties();
-                       try (InputStream in = Files.newInputStream(duDir.resolve("includes.properties"))) {
-                               includes.load(in);
-                       }
-                       String url = commonProps.getProperty(ManifestConstants.SLC_ORIGIN_URI.toString());
-                       Path downloaded = tryDownload(url, originBase);
-
-                       FileSystem zipFs = FileSystems.newFileSystem(downloaded, (ClassLoader) null);
-
-                       List<PathMatcher> pathMatchers = new ArrayList<>();
-                       for (Object pattern : includes.keySet()) {
-                               PathMatcher pathMatcher = zipFs.getPathMatcher("glob:/" + pattern);
-                               pathMatchers.add(pathMatcher);
-                       }
-
-                       Files.walkFileTree(zipFs.getRootDirectories().iterator().next(), new SimpleFileVisitor<Path>() {
-
-                               @Override
-                               public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
-                                       pathMatchers: for (PathMatcher pathMatcher : pathMatchers) {
-                                               if (pathMatcher.matches(file)) {
-                                                       if (file.getFileName().toString().contains(".source_")) {
-                                                               processEclipseSourceJar(file, targetCategoryBase);
-                                                               logger.log(Level.DEBUG, () -> "Processed source " + file);
-
-                                                       } else {
-                                                               processBundleJar(file, targetCategoryBase, new HashMap<>());
-                                                               logger.log(Level.DEBUG, () -> "Processed " + file);
-                                                       }
-                                                       break pathMatchers;
-                                               }
-                                       }
-                                       return FileVisitResult.CONTINUE;
-                               }
-                       });
-
-                       DirectoryStream<Path> dirs = Files.newDirectoryStream(targetCategoryBase, (p) -> Files.isDirectory(p));
-                       for (Path dir : dirs) {
-                               createJar(dir);
-                       }
-               } catch (IOException e) {
-                       throw new RuntimeException("Cannot process " + duDir, e);
-               }
-
-       }
-
-       protected Path processBundleJar(Path file, Path targetBase, Map<String, String> entries) throws IOException {
-               DefaultNameVersion nameVersion;
-               Path targetBundleDir;
-               try (JarInputStream jarIn = new JarInputStream(Files.newInputStream(file), false)) {
-                       Manifest manifest = new Manifest(jarIn.getManifest());
-
-                       // remove problematic entries in MANIFEST
-                       manifest.getEntries().clear();
-//                     Set<String> entriesToDelete = new HashSet<>();
-//                     for (String key : manifest.getEntries().keySet()) {
-////                           logger.log(DEBUG, "## " + key);
-//                             Attributes attrs = manifest.getAttributes(key);
-//                             for (Object attrName : attrs.keySet()) {
-////                                   logger.log(DEBUG, attrName + "=" + attrs.get(attrName));
-//                                     if ("Specification-Version".equals(attrName.toString())
-//                                                     || "Implementation-Version".equals(attrName.toString())) {
-//                                             entriesToDelete.add(key);
-//
-//                                     }
-//                             }
-//                     }
-//                     for (String key : entriesToDelete) {
-//                             manifest.getEntries().remove(key);
-//                     }
-
-                       String symbolicNameFromEntries = entries.get(BUNDLE_SYMBOLICNAME.toString());
-                       String versionFromEntries = entries.get(BUNDLE_VERSION.toString());
-
-                       if (symbolicNameFromEntries != null && versionFromEntries != null) {
-                               nameVersion = new DefaultNameVersion(symbolicNameFromEntries, versionFromEntries);
-                       } else {
-                               nameVersion = nameVersionFromManifest(manifest);
-                               if (versionFromEntries != null && !nameVersion.getVersion().equals(versionFromEntries)) {
-                                       logger.log(Level.WARNING, "Original version is " + nameVersion.getVersion()
-                                                       + " while new version is " + versionFromEntries);
-                               }
-                               if (symbolicNameFromEntries != null) {
-                                       // we always force our symbolic name
-                                       nameVersion.setName(symbolicNameFromEntries);
-                               }
-                       }
-                       targetBundleDir = targetBase.resolve(nameVersion.getName() + "." + nameVersion.getBranch());
-
-                       // TODO make it less dangerous?
-//                     if (Files.exists(targetBundleDir)) {
-//                             deleteDirectory(targetBundleDir);
-//                     }
-
-                       // copy entries
-                       JarEntry entry;
-                       entries: while ((entry = jarIn.getNextJarEntry()) != null) {
-                               if (entry.isDirectory())
-                                       continue entries;
-                               if (entry.getName().endsWith(".RSA") || entry.getName().endsWith(".SF"))
-                                       continue entries;
-                               Path target = targetBundleDir.resolve(entry.getName());
-                               Files.createDirectories(target.getParent());
-                               Files.copy(jarIn, target);
-                               logger.log(Level.TRACE, () -> "Copied " + target);
-                       }
-
-                       // copy MANIFEST
-                       Path manifestPath = targetBundleDir.resolve("META-INF/MANIFEST.MF");
-                       Files.createDirectories(manifestPath.getParent());
-                       for (String key : entries.keySet()) {
-                               String value = entries.get(key);
-                               Object previousValue = manifest.getMainAttributes().putValue(key, value);
-                               if (previousValue != null && !previousValue.equals(value)) {
-                                       if (ManifestConstants.IMPORT_PACKAGE.toString().equals(key)
-                                                       || ManifestConstants.EXPORT_PACKAGE.toString().equals(key))
-                                               logger.log(Level.WARNING, file.getFileName() + ": " + key + " was modified");
-
-                                       else
-                                               logger.log(Level.WARNING, file.getFileName() + ": " + key + " was " + previousValue
-                                                               + ", overridden with " + value);
-                               }
-                       }
-                       try (OutputStream out = Files.newOutputStream(manifestPath)) {
-                               manifest.write(out);
-                       }
-               }
-               return targetBundleDir;
-       }
-
-       protected void processEclipseSourceJar(Path file, Path targetBase) throws IOException {
-               try {
-                       Path targetBundleDir;
-                       try (JarInputStream jarIn = new JarInputStream(Files.newInputStream(file), false)) {
-                               Manifest manifest = jarIn.getManifest();
-
-                               String[] relatedBundle = manifest.getMainAttributes().getValue("Eclipse-SourceBundle").split(";");
-                               String version = relatedBundle[1].substring("version=\"".length());
-                               version = version.substring(0, version.length() - 1);
-                               NameVersion nameVersion = new DefaultNameVersion(relatedBundle[0], version);
-                               targetBundleDir = targetBase.resolve(nameVersion.getName() + "." + nameVersion.getBranch());
-
-                               Path targetSourceDir = targetBundleDir.resolve("OSGI-OPT/src");
-
-                               // TODO make it less dangerous?
-                               if (Files.exists(targetSourceDir)) {
-//                             deleteDirectory(targetSourceDir);
-                               } else {
-                                       Files.createDirectories(targetSourceDir);
-                               }
-
-                               // copy entries
-                               JarEntry entry;
-                               entries: while ((entry = jarIn.getNextJarEntry()) != null) {
-                                       if (entry.isDirectory())
-                                               continue entries;
-                                       if (entry.getName().startsWith("META-INF"))// skip META-INF entries
-                                               continue entries;
-                                       Path target = targetSourceDir.resolve(entry.getName());
-                                       Files.createDirectories(target.getParent());
-                                       Files.copy(jarIn, target);
-                                       logger.log(Level.TRACE, () -> "Copied source " + target);
-                               }
-
-                               // copy MANIFEST
-//                     Path manifestPath = targetBundleDir.resolve("META-INF/MANIFEST.MF");
-//                     Files.createDirectories(manifestPath.getParent());
-//                     try (OutputStream out = Files.newOutputStream(manifestPath)) {
-//                             manifest.write(out);
-//                     }
-                       }
-               } catch (IOException e) {
-                       throw new IllegalStateException("Cannot process " + file, e);
-               }
-
-       }
-
-       static void deleteDirectory(Path path) throws IOException {
-               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;
-                       }
-               });
-       }
-
-       protected DefaultNameVersion nameVersionFromManifest(Manifest manifest) {
-               Attributes attrs = manifest.getMainAttributes();
-               // symbolic name
-               String symbolicName = attrs.getValue(ManifestConstants.BUNDLE_SYMBOLICNAME.toString());
-               if (symbolicName == null)
-                       return null;
-               // make sure there is no directive
-               symbolicName = symbolicName.split(";")[0];
-
-               String version = attrs.getValue(ManifestConstants.BUNDLE_VERSION.toString());
-               return new DefaultNameVersion(symbolicName, version);
-       }
-
-       protected Path tryDownload(String uri, Path dir) throws IOException {
-               // find mirror
-               List<String> urlBases = null;
-               String uriPrefix = null;
-               uriPrefixes: for (String uriPref : mirrors.keySet()) {
-                       if (uri.startsWith(uriPref)) {
-                               if (mirrors.get(uriPref).size() > 0) {
-                                       urlBases = mirrors.get(uriPref);
-                                       uriPrefix = uriPref;
-                                       break uriPrefixes;
-                               }
-                       }
-               }
-               if (urlBases == null)
-                       try {
-                               return download(new URL(uri), dir, null);
-                       } catch (FileNotFoundException e) {
-                               throw new FileNotFoundException("Cannot find " + uri);
-                       }
-
-               // try to download
-               for (String urlBase : urlBases) {
-                       String relativePath = uri.substring(uriPrefix.length());
-                       URL url = new URL(urlBase + relativePath);
-                       try {
-                               return download(url, dir, null);
-                       } catch (FileNotFoundException e) {
-                               logger.log(Level.WARNING, "Cannot download " + url + ", trying another mirror");
-                       }
-               }
-               throw new FileNotFoundException("Cannot find " + uri);
-       }
-
-//     protected String simplifyName(URL u) {
-//     String  name = u.getPath().substring(u.getPath().lastIndexOf('/') + 1);
-//             
-//     }
-
-       protected Path download(URL url, Path dir, String name) throws IOException {
-
-               Path dest;
-               if (name == null) {
-                       name = url.getPath().substring(url.getPath().lastIndexOf('/') + 1);
-               }
-
-               dest = dir.resolve(name);
-               if (Files.exists(dest)) {
-                       logger.log(Level.TRACE, () -> "File " + dest + " already exists for " + url + ", not downloading again");
-                       return dest;
-               }
-
-               try (InputStream in = url.openStream()) {
-                       Files.copy(in, dest);
-                       logger.log(Level.DEBUG, () -> "Downloaded " + dest + " from " + url);
-               }
-               return dest;
-       }
-
-       protected Path createJar(Path bundleDir) throws IOException {
-               Path jarPath = bundleDir.getParent().resolve(bundleDir.getFileName() + ".jar");
-               Path manifestPath = bundleDir.resolve("META-INF/MANIFEST.MF");
-               Manifest manifest;
-               try (InputStream in = Files.newInputStream(manifestPath)) {
-                       manifest = new Manifest(in);
-               }
-               try (JarOutputStream jarOut = new JarOutputStream(Files.newOutputStream(jarPath), manifest)) {
-                       Files.walkFileTree(bundleDir, new SimpleFileVisitor<Path>() {
-
-                               @Override
-                               public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
-                                       if (file.getFileName().toString().equals("MANIFEST.MF"))
-                                               return super.visitFile(file, attrs);
-                                       JarEntry entry = new JarEntry(bundleDir.relativize(file).toString());
-                                       jarOut.putNextEntry(entry);
-                                       Files.copy(file, jarOut);
-                                       return super.visitFile(file, attrs);
-                               }
-
-                       });
-               }
-               deleteDirectory(bundleDir);
-               return jarPath;
-       }
-
-       public static void main(String[] args) {
-               Path factoryBase = Paths.get("../../output/a2").toAbsolutePath().normalize();
-               A2Factory factory = new A2Factory(factoryBase);
-
-               Path descriptorsBase = Paths.get("../tp").toAbsolutePath().normalize();
-
-//             factory.processSingleM2ArtifactDistributionUnit(descriptorsBase.resolve("org.argeo.tp.apache").resolve("org.apache.xml.resolver.bnd"));
-//             factory.processM2BasedDistributionUnit(descriptorsBase.resolve("org.argeo.tp.apache/apache-sshd"));
-//             factory.processM2BasedDistributionUnit(descriptorsBase.resolve("org.argeo.tp.jetty/jetty"));
-//             factory.processM2BasedDistributionUnit(descriptorsBase.resolve("org.argeo.tp.jetty/jetty-websocket"));
-//             factory.processCategory(descriptorsBase.resolve("org.argeo.tp.eclipse.rcp"));
-//             factory.processCategory(descriptorsBase.resolve("org.argeo.tp"));
-//             factory.processCategory(descriptorsBase.resolve("org.argeo.tp.apache"));
-//             factory.processCategory(descriptorsBase.resolve("org.argeo.tp.formats"));
-               factory.processCategory(descriptorsBase.resolve("org.argeo.tp.gis"));
-               System.exit(0);
-
-               // Eclipse
-               factory.processEclipseArchive(
-                               descriptorsBase.resolve("org.argeo.tp.eclipse.equinox").resolve("eclipse-equinox"));
-               factory.processEclipseArchive(descriptorsBase.resolve("org.argeo.tp.eclipse.rap").resolve("eclipse-rap"));
-               factory.processEclipseArchive(descriptorsBase.resolve("org.argeo.tp.eclipse.rcp").resolve("eclipse-rcp"));
-
-               System.exit(0);
-
-               // Maven
-               factory.processCategory(descriptorsBase.resolve("org.argeo.tp.sdk"));
-               factory.processCategory(descriptorsBase.resolve("org.argeo.tp"));
-               factory.processCategory(descriptorsBase.resolve("org.argeo.tp.apache"));
-               factory.processCategory(descriptorsBase.resolve("org.argeo.tp.jetty"));
-               factory.processCategory(descriptorsBase.resolve("org.argeo.tp.jcr"));
-       }
-}
diff --git a/org.argeo.slc.factory/src/org/argeo/slc/factory/m2/Artifact.java b/org.argeo.slc.factory/src/org/argeo/slc/factory/m2/Artifact.java
deleted file mode 100644 (file)
index 9189f08..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-package org.argeo.slc.factory.m2;
-
-public interface Artifact {
-       String getGroupId();
-
-       String getArtifactId();
-
-       String getVersion();
-
-       default String getBaseVersion() {
-               return getVersion();
-       }
-
-//     boolean isSnapshot();
-
-       default String getClassifier() {
-               return "";
-       }
-
-       default String getExtension() {
-               return "jar";
-       }
-
-}
diff --git a/org.argeo.slc.factory/src/org/argeo/slc/factory/m2/DefaultArtifact.java b/org.argeo.slc.factory/src/org/argeo/slc/factory/m2/DefaultArtifact.java
deleted file mode 100644 (file)
index 0c3b0c1..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-package org.argeo.slc.factory.m2;
-
-import org.argeo.slc.DefaultCategoryNameVersion;
-
-/**
- * Simple representation of an M2 artifact, not taking into account classifiers,
- * types, etc.
- */
-public class DefaultArtifact extends DefaultCategoryNameVersion implements Artifact {
-       private String classifier;
-
-       public DefaultArtifact(String m2coordinates) {
-               this(m2coordinates, null);
-       }
-
-       public DefaultArtifact(String m2coordinates, String classifier) {
-               String[] parts = m2coordinates.split(":");
-               setCategory(parts[0]);
-               setName(parts[1]);
-               if (parts.length > 2) {
-                       setVersion(parts[2]);
-               }
-               this.classifier = classifier;
-       }
-
-       @Override
-       public String getGroupId() {
-               return getCategory();
-       }
-
-       @Override
-       public String getArtifactId() {
-               return getName();
-       }
-
-       public String toM2Coordinates() {
-               return getCategory() + ":" + getName() + (getVersion() != null ? ":" + getVersion() : "");
-       }
-
-       public String getClassifier() {
-               return classifier != null ? classifier : "";
-       }
-
-}
diff --git a/org.argeo.slc.factory/src/org/argeo/slc/factory/m2/MavenConventionsUtils.java b/org.argeo.slc.factory/src/org/argeo/slc/factory/m2/MavenConventionsUtils.java
deleted file mode 100644 (file)
index b6cc34a..0000000
+++ /dev/null
@@ -1,155 +0,0 @@
-package org.argeo.slc.factory.m2;
-
-import java.io.File;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.util.Set;
-
-/**
- * Static utilities around Maven which are NOT using the Maven APIs (conventions
- * based).
- */
-public class MavenConventionsUtils {
-       public final static String MAVEN_CENTRAL_BASE_URL = "https://repo1.maven.org/maven2/";
-
-       /**
-        * Path to the file identified by this artifact <b>without</b> using Maven APIs
-        * (convention based). Default location of repository (~/.m2/repository) is used
-        * here.
-        * 
-        * @see MavenConventionsUtils#artifactToFile(String, Artifact)
-        */
-       public static File artifactToFile(Artifact artifact) {
-               return artifactToFile(System.getProperty("user.home") + File.separator + ".m2" + File.separator + "repository",
-                               artifact);
-       }
-
-       /**
-        * Path to the file identified by this artifact <b>without</b> using Maven APIs
-        * (convention based).
-        * 
-        * @param repositoryPath path to the related local repository location
-        * @param artifact       the artifact
-        */
-       public static File artifactToFile(String repositoryPath, Artifact artifact) {
-               return new File(repositoryPath + File.separator + artifact.getGroupId().replace('.', File.separatorChar)
-                               + File.separator + artifact.getArtifactId() + File.separator + artifact.getVersion() + File.separator
-                               + artifactFileName(artifact)).getAbsoluteFile();
-       }
-
-       /** The file name of this artifact when stored */
-       public static String artifactFileName(Artifact artifact) {
-               return artifact.getArtifactId() + '-' + artifact.getVersion()
-                               + (artifact.getClassifier().equals("") ? "" : '-' + artifact.getClassifier()) + '.'
-                               + artifact.getExtension();
-       }
-
-       /** Absolute path to the file */
-       public static String artifactPath(String artifactBasePath, Artifact artifact) {
-               return artifactParentPath(artifactBasePath, artifact) + '/' + artifactFileName(artifact);
-       }
-
-       /** Absolute path to the file */
-       public static String artifactUrl(String repoUrl, Artifact artifact) {
-               if (repoUrl.endsWith("/"))
-                       return repoUrl + artifactPath("/", artifact).substring(1);
-               else
-                       return repoUrl + artifactPath("/", artifact);
-       }
-
-       /** Absolute path to the file */
-//     public static URL mavenCentralUrl(Artifact artifact) {
-//             return mavenRepoUrl(MAVEN_CENTRAL_BASE_URL, artifact);
-//     }
-
-       /** Absolute path to the file */
-       public static URL mavenRepoUrl(String repoBase, Artifact artifact) {
-               String url = artifactUrl(repoBase == null ? MAVEN_CENTRAL_BASE_URL : repoBase, artifact);
-               try {
-                       return new URL(url);
-               } catch (MalformedURLException e) {
-                       // it should not happen
-                       throw new IllegalStateException(e);
-               }
-       }
-
-       /** Absolute path to the directories where the files will be stored */
-       public static String artifactParentPath(String artifactBasePath, Artifact artifact) {
-               return artifactBasePath + (artifactBasePath.endsWith("/") ? "" : "/") + artifactParentPath(artifact);
-       }
-
-       /** Absolute path to the directory of this group */
-       public static String groupPath(String artifactBasePath, String groupId) {
-               return artifactBasePath + (artifactBasePath.endsWith("/") ? "" : "/") + groupId.replace('.', '/');
-       }
-
-       /** Relative path to the directories where the files will be stored */
-       public static String artifactParentPath(Artifact artifact) {
-               return artifact.getGroupId().replace('.', '/') + '/' + artifact.getArtifactId() + '/'
-                               + artifact.getBaseVersion();
-       }
-
-       public static String artifactsAsDependencyPom(Artifact pomArtifact, Set<Artifact> artifacts, Artifact parent) {
-               StringBuffer p = new StringBuffer();
-
-               // XML header
-               p.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
-               p.append(
-                               "<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd\">\n");
-               p.append("<modelVersion>4.0.0</modelVersion>\n");
-
-               // Artifact
-               if (parent != null) {
-                       p.append("<parent>\n");
-                       p.append("<groupId>").append(parent.getGroupId()).append("</groupId>\n");
-                       p.append("<artifactId>").append(parent.getArtifactId()).append("</artifactId>\n");
-                       p.append("<version>").append(parent.getVersion()).append("</version>\n");
-                       p.append("</parent>\n");
-               }
-               p.append("<groupId>").append(pomArtifact.getGroupId()).append("</groupId>\n");
-               p.append("<artifactId>").append(pomArtifact.getArtifactId()).append("</artifactId>\n");
-               p.append("<version>").append(pomArtifact.getVersion()).append("</version>\n");
-               p.append("<packaging>pom</packaging>\n");
-
-               // Dependencies
-               p.append("<dependencies>\n");
-               for (Artifact a : artifacts) {
-                       p.append("\t<dependency>");
-                       p.append("<artifactId>").append(a.getArtifactId()).append("</artifactId>");
-                       p.append("<groupId>").append(a.getGroupId()).append("</groupId>");
-                       if (!a.getExtension().equals("jar"))
-                               p.append("<type>").append(a.getExtension()).append("</type>");
-                       p.append("</dependency>\n");
-               }
-               p.append("</dependencies>\n");
-
-               // Dependency management
-               p.append("<dependencyManagement>\n");
-               p.append("<dependencies>\n");
-               for (Artifact a : artifacts) {
-                       p.append("\t<dependency>");
-                       p.append("<artifactId>").append(a.getArtifactId()).append("</artifactId>");
-                       p.append("<version>").append(a.getVersion()).append("</version>");
-                       p.append("<groupId>").append(a.getGroupId()).append("</groupId>");
-                       if (a.getExtension().equals("pom")) {
-                               p.append("<type>").append(a.getExtension()).append("</type>");
-                               p.append("<scope>import</scope>");
-                       }
-                       p.append("</dependency>\n");
-               }
-               p.append("</dependencies>\n");
-               p.append("</dependencyManagement>\n");
-
-               // Repositories
-               // p.append("<repositories>\n");
-               // p.append("<repository><id>argeo</id><url>http://maven.argeo.org/argeo</url></repository>\n");
-               // p.append("</repositories>\n");
-
-               p.append("</project>\n");
-               return p.toString();
-       }
-
-       /** Singleton */
-       private MavenConventionsUtils() {
-       }
-}
index b0d77e98b636a6b3f9f1873df43484b6e578e8ff..33d08b8d1980765df8d9f572579e11a184338b6e 100644 (file)
@@ -3,7 +3,6 @@ output.. = bin/
 bin.includes = META-INF/,\
                .
 additional.bundles = org.junit,\
-                     org.hamcrest,\
                      org.apache.jackrabbit.core,\
                      javax.jcr,\
                      org.apache.jackrabbit.api,\
@@ -12,13 +11,12 @@ additional.bundles = org.junit,\
                      org.apache.jackrabbit.spi,\
                      org.apache.jackrabbit.spi.commons,\
                      org.slf4j.api,\
-                      org.apache.commons.collections,\
+                     org.apache.commons.collections,\
                      EDU.oswego.cs.dl.util.concurrent,\
                      org.apache.lucene,\
-                     org.apache.tika.core,\
+                     org.apache.tika,\
                      org.apache.commons.dbcp,\
                      org.apache.jackrabbit.jcr2spi,\
                      org.apache.jackrabbit.spi2dav,\
                      org.apache.httpcomponents.httpclient,\
-                     org.apache.httpcomponents.httpcore,\
-                     org.apache.tika.parsers
+                     org.apache.httpcomponents.httpcore
\ No newline at end of file
index 5dd9955226156cb12a5031f02152a163862936f9..fd5b5d763488868edd6ba57bbab65e38ece2eb47 100644 (file)
@@ -1,6 +1,6 @@
 package org.argeo.cli.jcr;
 
-import org.argeo.slc.cli.CommandsCli;
+import org.argeo.api.cli.CommandsCli;
 
 /** File utilities. */
 public class JcrCommands extends CommandsCli {
index 5fe02eb9b1fe8c75bba5225d6014d3adddc67c47..ed1a5f869ce8172caccd616d027487049d59a1b9 100644 (file)
@@ -20,12 +20,12 @@ import org.apache.commons.cli.Option;
 import org.apache.commons.cli.Options;
 import org.apache.jackrabbit.core.RepositoryImpl;
 import org.apache.jackrabbit.core.config.RepositoryConfig;
+import org.argeo.api.cli.CommandArgsException;
+import org.argeo.api.cli.CommandRuntimeException;
+import org.argeo.api.cli.DescribedCommand;
+import org.argeo.cms.file.SyncResult;
 import org.argeo.jackrabbit.client.ClientDavexRepositoryFactory;
 import org.argeo.jcr.JcrUtils;
-import org.argeo.slc.cli.CommandArgsException;
-import org.argeo.slc.cli.CommandRuntimeException;
-import org.argeo.slc.cli.DescribedCommand;
-import org.argeo.slc.sync.SyncResult;
 
 public class JcrSync implements DescribedCommand<SyncResult<Node>> {
        public final static String DEFAULT_LOCALFS_CONFIG = "repository-localfs.xml";
index 89f416819d4a13d154e224b92e74d21db248095f..219309f33bbac4363391b619499b8d83edd3d407 100644 (file)
@@ -40,7 +40,6 @@ public class SlcJcrResultUtils {
         * it throws an exception.
         * 
         * @param session
-        * @return
         */
        public static Node getSlcResultsParentNode(Session session) {
                try {
@@ -90,7 +89,6 @@ public class SlcJcrResultUtils {
         * that node if it has the correct type and throws an exception otherwise.
         * 
         * @param session
-        * @return
         */
        public static Node getMyResultParentNode(Session session) {
                try {
@@ -123,7 +121,6 @@ public class SlcJcrResultUtils {
         * 
         * @param session
         * @param absPath
-        * @return
         */
        public static synchronized Node createResultFolderNode(Session session,
                        String absPath) {
index 3daa8079b5926141182423a382608889e0c68e61..22fe6b05e528f3973d450ab22002b255722a554d 100644 (file)
@@ -35,10 +35,10 @@ import javax.jcr.nodetype.NodeType;
 import org.apache.commons.io.FilenameUtils;
 import org.apache.commons.io.IOUtils;
 import org.argeo.api.cms.CmsLog;
+import org.argeo.api.cms.keyring.Keyring;
 import org.argeo.cms.ArgeoNames;
 import org.argeo.cms.ArgeoTypes;
 import org.argeo.cms.jcr.CmsJcrUtils;
-import org.argeo.cms.security.Keyring;
 import org.argeo.jcr.JcrMonitor;
 import org.argeo.jcr.JcrUtils;
 import org.argeo.slc.DefaultNameVersion;
index fcd6ba99b83b60e2f9995e9d822c604a79f9b6f1..6e2b7aa6d64dece1a7ebc7998181ffcbc5e65df2 100644 (file)
@@ -4,7 +4,7 @@ import javax.jcr.Repository;
 import javax.jcr.RepositoryFactory;
 import javax.jcr.Session;
 
-import org.argeo.cms.security.Keyring;
+import org.argeo.api.cms.keyring.Keyring;
 import org.argeo.slc.repo.RepoService;
 import org.argeo.slc.repo.RepoUtils;
 
index 20cad808ae9056dcfc70d1dfeb9a41c2633be20a..4a00becd81df57ed094ab0d2958c974510456391 100644 (file)
@@ -7,6 +7,5 @@
        </classpathentry>
        <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
        <classpathentry kind="src" path="src"/>
-       <classpathentry kind="src" path="ext/test"/>
        <classpathentry kind="output" path="bin"/>
 </classpath>
index 9384c2ac5075c053f51b94c2ad5bdaa5ac6b6b88..ecb301cbe843738365efee99fc43a8076ef96883 100644 (file)
@@ -1,2 +1,4 @@
-Import-Package: org.argeo.slc.deploy,\
+Import-Package: \
+org.argeo.slc.deploy,\
+org.apache.commons.exec.*resolution:=optional,\
 *
\ No newline at end of file
index 34d2e4d2dad529ceaeb953bfcdb63c51d69ffed2..7786bd6ec5dd97d2dd5a76b2fc32f750dcdb9e85 100644 (file)
@@ -2,3 +2,8 @@ source.. = src/
 output.. = bin/
 bin.includes = META-INF/,\
                .
+additional.bundles = org.w3c.dom.svg,\
+                     org.w3c.dom.smil,\
+                     org.w3c.css.sac,\
+                     org.apache.xmlgraphics,\
+                     org.argeo.init
diff --git a/org.argeo.slc.runtime/ext/test/org/argeo/fs/FsUtilsTest.java b/org.argeo.slc.runtime/ext/test/org/argeo/fs/FsUtilsTest.java
deleted file mode 100644 (file)
index 5d31f4a..0000000
+++ /dev/null
@@ -1,51 +0,0 @@
-package org.argeo.fs;
-
-import java.io.File;
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-
-import org.argeo.slc.sync.FsSyncUtils;
-
-/** {@link FsUtils} tests. */
-public class FsUtilsTest {
-       final static String FILE00 = "file00";
-       final static String FILE01 = "file01";
-       final static String SUB_DIR = "subDir";
-
-       public void testDelete() throws IOException {
-               Path dir = createDir00();
-               assert Files.exists(dir);
-               FsSyncUtils.delete(dir);
-               assert !Files.exists(dir);
-       }
-
-       public void testSync() throws IOException {
-               Path source = createDir00();
-               Path target = Files.createTempDirectory(getClass().getName());
-               FsSyncUtils.sync(source, target);
-               assert Files.exists(target.resolve(FILE00));
-               assert Files.exists(target.resolve(SUB_DIR));
-               assert Files.exists(target.resolve(SUB_DIR + File.separator + FILE01));
-               FsSyncUtils.delete(source.resolve(SUB_DIR));
-               FsSyncUtils.sync(source, target, true);
-               assert Files.exists(target.resolve(FILE00));
-               assert !Files.exists(target.resolve(SUB_DIR));
-               assert !Files.exists(target.resolve(SUB_DIR + File.separator + FILE01));
-
-               // clean up
-               FsSyncUtils.delete(source);
-               FsSyncUtils.delete(target);
-
-       }
-
-       Path createDir00() throws IOException {
-               Path base = Files.createTempDirectory(getClass().getName());
-               base.toFile().deleteOnExit();
-               Files.createFile(base.resolve(FILE00)).toFile().deleteOnExit();
-               Path subDir = Files.createDirectories(base.resolve(SUB_DIR));
-               subDir.toFile().deleteOnExit();
-               Files.createFile(subDir.resolve(FILE01)).toFile().deleteOnExit();
-               return base;
-       }
-}
diff --git a/org.argeo.slc.runtime/src/org/argeo/slc/cli/CommandArgsException.java b/org.argeo.slc.runtime/src/org/argeo/slc/cli/CommandArgsException.java
deleted file mode 100644 (file)
index 4d4d759..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-package org.argeo.slc.cli;
-
-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;
-       }
-
-}
diff --git a/org.argeo.slc.runtime/src/org/argeo/slc/cli/CommandRuntimeException.java b/org.argeo.slc.runtime/src/org/argeo/slc/cli/CommandRuntimeException.java
deleted file mode 100644 (file)
index 9072544..0000000
+++ /dev/null
@@ -1,35 +0,0 @@
-package org.argeo.slc.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;
-       }
-
-}
diff --git a/org.argeo.slc.runtime/src/org/argeo/slc/cli/CommandsCli.java b/org.argeo.slc.runtime/src/org/argeo/slc/cli/CommandsCli.java
deleted file mode 100644 (file)
index 5146fb7..0000000
+++ /dev/null
@@ -1,131 +0,0 @@
-package org.argeo.slc.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.Options;
-import org.apache.commons.cli.ParseException;
-
-/** Base class for a CLI managing sub commands. */
-public abstract class CommandsCli implements DescribedCommand<Object> {
-       public final static String HELP = "help";
-
-       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<>();
-               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.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();
-               if (function == null)
-                       throw new IllegalArgumentException("Uknown command " + cmd);
-               try {
-                       return function.apply(newArgs).toString();
-               } 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(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(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(CommandsCli.HELP, new HelpCommand(null, cli));
-                       Object output = cli.apply(Arrays.asList(args));
-                       System.out.println(output);
-                       System.exit(0);
-               } catch (CommandArgsException e) {
-                       System.err.println("Wrong arguments " + Arrays.toString(args) + ": " + e.getMessage());
-                       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);
-               }
-       }
-}
diff --git a/org.argeo.slc.runtime/src/org/argeo/slc/cli/DescribedCommand.java b/org.argeo.slc.runtime/src/org/argeo/slc/cli/DescribedCommand.java
deleted file mode 100644 (file)
index fc1b139..0000000
+++ /dev/null
@@ -1,55 +0,0 @@
-package org.argeo.slc.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 (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);
-               }
-       }
-
-}
diff --git a/org.argeo.slc.runtime/src/org/argeo/slc/cli/HelpCommand.java b/org.argeo.slc.runtime/src/org/argeo/slc/cli/HelpCommand.java
deleted file mode 100644 (file)
index 8400d05..0000000
+++ /dev/null
@@ -1,143 +0,0 @@
-package org.argeo.slc.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.Options;
-
-/** A special command that can describe {@link DescribedCommand}. */
-public class HelpCommand implements DescribedCommand<String> {
-       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 {
-                       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();
-               formatter.printHelp(new PrintWriter(out), helpWidth, usage, command.getDescription(), command.getOptions(),
-                               helpLeftPad, helpDescPad, command.getExamples(), false);
-
-       }
-
-       public static void printHelp(CommandsCli commandsCli, String commandName, StringWriter out) {
-               DescribedCommand<?> command = (DescribedCommand<?>) commandsCli.getCommand(commandName);
-               String usage = commandsCli.getHelpCommand().getCommandUsage(commandName, command);
-               HelpFormatter formatter = new HelpFormatter();
-               formatter.printHelp(new PrintWriter(out), helpWidth, usage, command.getDescription(), command.getOptions(),
-                               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();
-               }
-       }
-}
diff --git a/org.argeo.slc.runtime/src/org/argeo/slc/cli/fs/FileSync.java b/org.argeo.slc.runtime/src/org/argeo/slc/cli/fs/FileSync.java
deleted file mode 100644 (file)
index dfff27c..0000000
+++ /dev/null
@@ -1,102 +0,0 @@
-package org.argeo.slc.cli.fs;
-
-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.slc.cli.CommandArgsException;
-import org.argeo.slc.cli.DescribedCommand;
-import org.argeo.slc.sync.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";
-       }
-
-}
diff --git a/org.argeo.slc.runtime/src/org/argeo/slc/cli/fs/FsCommands.java b/org.argeo.slc.runtime/src/org/argeo/slc/cli/fs/FsCommands.java
deleted file mode 100644 (file)
index cab3c1e..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-package org.argeo.slc.cli.fs;
-
-import org.argeo.slc.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";
-       }
-
-}
diff --git a/org.argeo.slc.runtime/src/org/argeo/slc/cli/fs/PathSync.java b/org.argeo.slc.runtime/src/org/argeo/slc/cli/fs/PathSync.java
deleted file mode 100644 (file)
index 844af01..0000000
+++ /dev/null
@@ -1,61 +0,0 @@
-package org.argeo.slc.cli.fs;
-
-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;
-
-import org.argeo.slc.sync.SyncResult;
-
-/** 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;
-       }
-}
diff --git a/org.argeo.slc.runtime/src/org/argeo/slc/cli/fs/SyncFileVisitor.java b/org.argeo.slc.runtime/src/org/argeo/slc/cli/fs/SyncFileVisitor.java
deleted file mode 100644 (file)
index 43af111..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-package org.argeo.slc.cli.fs;
-
-import java.lang.System.Logger;
-import java.lang.System.Logger.Level;
-import java.nio.file.Path;
-import java.util.Objects;
-
-import org.argeo.slc.sync.BasicSyncFileVisitor;
-
-/** Synchronises two directory structures. */
-public class SyncFileVisitor extends BasicSyncFileVisitor {
-       private final static Logger logger = System.getLogger(SyncFileVisitor.class.getName());
-
-       public SyncFileVisitor(Path sourceBasePath, Path targetBasePath, boolean delete, boolean recursive) {
-               super(sourceBasePath, targetBasePath, delete, recursive);
-       }
-
-       @Override
-       protected void error(Object obj, Throwable e) {
-               logger.log(Level.ERROR, Objects.toString(obj), e);
-       }
-
-       @Override
-       protected boolean isTraceEnabled() {
-               return logger.isLoggable(Level.TRACE);
-       }
-
-       @Override
-       protected void trace(Object obj) {
-               logger.log(Level.TRACE, Objects.toString(obj));
-       }
-}
diff --git a/org.argeo.slc.runtime/src/org/argeo/slc/cli/fs/package-info.java b/org.argeo.slc.runtime/src/org/argeo/slc/cli/fs/package-info.java
deleted file mode 100644 (file)
index 8c61d93..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** File system CLI commands. */
-package org.argeo.slc.cli.fs;
\ No newline at end of file
diff --git a/org.argeo.slc.runtime/src/org/argeo/slc/cli/package-info.java b/org.argeo.slc.runtime/src/org/argeo/slc/cli/package-info.java
deleted file mode 100644 (file)
index 0216a28..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Command line API. */
-package org.argeo.slc.cli;
\ No newline at end of file
diff --git a/org.argeo.slc.runtime/src/org/argeo/slc/cli/posix/Echo.java b/org.argeo.slc.runtime/src/org/argeo/slc/cli/posix/Echo.java
deleted file mode 100644 (file)
index a479e96..0000000
+++ /dev/null
@@ -1,46 +0,0 @@
-package org.argeo.slc.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.slc.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();
-       }
-
-}
diff --git a/org.argeo.slc.runtime/src/org/argeo/slc/cli/posix/PosixCommands.java b/org.argeo.slc.runtime/src/org/argeo/slc/cli/posix/PosixCommands.java
deleted file mode 100644 (file)
index 6754410..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-package org.argeo.slc.cli.posix;
-
-import org.argeo.slc.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);
-       }
-}
diff --git a/org.argeo.slc.runtime/src/org/argeo/slc/cli/posix/package-info.java b/org.argeo.slc.runtime/src/org/argeo/slc/cli/posix/package-info.java
deleted file mode 100644 (file)
index f6da265..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Posix CLI commands. */
-package org.argeo.slc.cli.posix;
\ No newline at end of file
diff --git a/org.argeo.slc.runtime/src/org/argeo/slc/runtime/ArgeoCli.java b/org.argeo.slc.runtime/src/org/argeo/slc/runtime/ArgeoCli.java
deleted file mode 100644 (file)
index 40470fe..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-package org.argeo.slc.runtime;
-
-import org.apache.commons.cli.Option;
-import org.argeo.slc.cli.CommandsCli;
-import org.argeo.slc.cli.fs.FsCommands;
-import org.argeo.slc.cli.posix.PosixCommands;
-
-/** 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());
-
-               addCommandsCli(new PosixCommands("posix"));
-               addCommandsCli(new FsCommands("fs"));
-//             addCommandsCli(new JcrCommands("jcr"));
-       }
-
-       @Override
-       public String getDescription() {
-               return "Argeo command line utilities";
-       }
-
-       public static void main(String[] args) {
-               mainImpl(new ArgeoCli("argeo"), args);
-       }
-
-}
index 33ea9f07ae6f0ddfe26b29127df8986e9dbe4828..1bbf9d12857a3bb554491aff8625125aa8e8f008 100644 (file)
@@ -148,7 +148,6 @@ public class ProcessThread extends Thread {
                }
        }
 
-       /** @return the (distinct) thread used for this execution */
        protected final void execute(RealizedFlow realizedFlow, Boolean synchronous) throws InterruptedException {
                if (killed)
                        return;
diff --git a/org.argeo.slc.runtime/src/org/argeo/slc/sync/BasicSyncFileVisitor.java b/org.argeo.slc.runtime/src/org/argeo/slc/sync/BasicSyncFileVisitor.java
deleted file mode 100644 (file)
index 066812d..0000000
+++ /dev/null
@@ -1,162 +0,0 @@
-package org.argeo.slc.sync;
-
-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;
-       }
-
-}
diff --git a/org.argeo.slc.runtime/src/org/argeo/slc/sync/FsSyncUtils.java b/org.argeo.slc.runtime/src/org/argeo/slc/sync/FsSyncUtils.java
deleted file mode 100644 (file)
index 4321778..0000000
+++ /dev/null
@@ -1,62 +0,0 @@
-package org.argeo.slc.sync;
-
-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() {
-
-       }
-}
diff --git a/org.argeo.slc.runtime/src/org/argeo/slc/sync/SyncResult.java b/org.argeo.slc.runtime/src/org/argeo/slc/sync/SyncResult.java
deleted file mode 100644 (file)
index 22a2f56..0000000
+++ /dev/null
@@ -1,101 +0,0 @@
-package org.argeo.slc.sync;
-
-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();
-               }
-
-       }
-}
diff --git a/sdk/argeo-tp-rap.target b/sdk/argeo-tp-rap.target
deleted file mode 100644 (file)
index 81c9e81..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<?pde version="3.8"?>
-<target name="argeo-tp-rap">
-       <locations>
-               <location path="/usr/share/a2/org.argeo.tp" type="Directory"/>
-               <location path="/usr/share/a2/org.argeo.tp.apache" type="Directory"/>
-               <location path="/usr/share/a2/org.argeo.tp.eclipse.equinox" type="Directory"/>
-               <location path="/usr/share/a2/org.argeo.tp.eclipse.rap" type="Directory"/>
-               <location path="/usr/share/a2/org.argeo.tp.jetty" type="Directory"/>
-               <location path="/usr/share/a2/org.argeo.tp.sdk" type="Directory"/>
-               <location path="/usr/share/a2/org.argeo.tp.jcr" type="Directory"/>
-       </locations>
-</target>
\ No newline at end of file
diff --git a/sdk/branches/testing.bnd b/sdk/branches/testing.bnd
new file mode 100644 (file)
index 0000000..8bf306a
--- /dev/null
@@ -0,0 +1,4 @@
+major=2
+minor=1
+micro=20
+qualifier=.next
diff --git a/sdk/branches/unstable.bnd b/sdk/branches/unstable.bnd
new file mode 100644 (file)
index 0000000..fcae8a6
--- /dev/null
@@ -0,0 +1,4 @@
+major=2
+minor=3
+micro=8
+qualifier=.next
\ No newline at end of file
diff --git a/sdk/init/node/.gitignore b/sdk/init/node/.gitignore
deleted file mode 100644 (file)
index f619744..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-/krb5.keytab
-/krb5.keytab.old
-/*.p12
-/*.jks
\ No newline at end of file
diff --git a/sdk/init/node/ou=roles,ou=node.ldif b/sdk/init/node/ou=roles,ou=node.ldif
deleted file mode 100644 (file)
index 3a65d49..0000000
+++ /dev/null
@@ -1,19 +0,0 @@
-dn: cn=admin,ou=roles,ou=node
-objectClass: groupOfNames
-objectClass: top
-cn: admin
-member: uid=root,ou=People,dc=example,dc=com
-
-dn: cn=org.argeo.slc.user,ou=roles,ou=node
-objectClass: groupOfNames
-objectClass: top
-cn: org.argeo.slc.user
-member: uid=root,ou=People,dc=example,dc=com
-member: uid=demo,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
-
diff --git a/sdk/init/private/.gitignore b/sdk/init/private/.gitignore
new file mode 100644 (file)
index 0000000..f619744
--- /dev/null
@@ -0,0 +1,4 @@
+/krb5.keytab
+/krb5.keytab.old
+/*.p12
+/*.jks
\ No newline at end of file
diff --git a/sdk/init/private/ou=roles,ou=node.ldif b/sdk/init/private/ou=roles,ou=node.ldif
new file mode 100644 (file)
index 0000000..3a65d49
--- /dev/null
@@ -0,0 +1,19 @@
+dn: cn=admin,ou=roles,ou=node
+objectClass: groupOfNames
+objectClass: top
+cn: admin
+member: uid=root,ou=People,dc=example,dc=com
+
+dn: cn=org.argeo.slc.user,ou=roles,ou=node
+objectClass: groupOfNames
+objectClass: top
+cn: org.argeo.slc.user
+member: uid=root,ou=People,dc=example,dc=com
+member: uid=demo,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
+
diff --git a/sdk/output-argeo-tp-rap.target b/sdk/output-argeo-tp-rap.target
deleted file mode 100644 (file)
index 938ed69..0000000
+++ /dev/null
@@ -1,16 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<?pde version="3.8"?>
-<target name="(output) argeo-tp-rap">
-       <locations>
-               <location path="${project_loc:argeo-slc}/../output/a2/org.argeo.tp.apache" type="Directory"/>
-               <location path="${project_loc:argeo-slc}/../output/a2/org.argeo.tp.eclipse.equinox" type="Directory"/>
-               <location path="${project_loc:argeo-slc}/../output/a2/org.argeo.tp.eclipse.rap" type="Directory"/>
-               <location path="${project_loc:argeo-slc}/../output/a2/org.argeo.tp.jetty" type="Directory"/>
-               <location path="${project_loc:argeo-slc}/../output/a2/org.argeo.tp.sdk" type="Directory"/>
-               <location path="${project_loc:argeo-slc}/../output/a2/org.argeo.tp.jcr" type="Directory"/>
-               <location path="${project_loc:argeo-slc}/../output/a2/org.argeo.tp" type="Directory"/>
-               <location path="${project_loc:argeo-slc}/../output/a2/org.argeo.tp.poi" type="Directory"/>
-               <location path="${project_loc:argeo-slc}/../output/a2/org.argeo.tp.gis" type="Directory"/>
-               <location path="${project_loc:argeo-slc}/../output/a2/org.argeo.tp" type="Directory"/>
-       </locations>
-</target>
\ No newline at end of file
diff --git a/sdk/output-argeo-tp-rcp.target b/sdk/output-argeo-tp-rcp.target
deleted file mode 100644 (file)
index 1b88949..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<?pde version="3.8"?>
-<target name="(output) argeo-tp-rcp">
-       <locations>
-               <location path="${project_loc:argeo-slc}/../output/a2/org.argeo.tp.apache" type="Directory"/>
-               <location path="${project_loc:argeo-slc}/../output/a2/org.argeo.tp.eclipse.equinox" type="Directory"/>
-               <location path="${project_loc:argeo-slc}/../output/a2/org.argeo.tp.eclipse.rcp" type="Directory"/>
-               <location path="${project_loc:argeo-slc}/../output/a2/org.argeo.tp.jetty" type="Directory"/>
-               <location path="${project_loc:argeo-slc}/../output/a2/org.argeo.tp.sdk" type="Directory"/>
-               <location path="${project_loc:argeo-slc}/../output/a2/org.argeo.tp.jcr" type="Directory"/>
-               <location path="${project_loc:argeo-slc}/../output/a2/org.argeo.tp" type="Directory"/>
-       </locations>
-</target>
\ No newline at end of file
diff --git a/swt/org.argeo.tool.devops.e4/.classpath b/swt/org.argeo.tool.devops.e4/.classpath
new file mode 100644 (file)
index 0000000..81fe078
--- /dev/null
@@ -0,0 +1,7 @@
+<?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>
diff --git a/swt/org.argeo.tool.devops.e4/.project b/swt/org.argeo.tool.devops.e4/.project
new file mode 100644 (file)
index 0000000..6a3ee57
--- /dev/null
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+       <name>org.argeo.tool.devops.e4</name>
+       <comment></comment>
+       <projects>
+       </projects>
+       <buildSpec>
+               <buildCommand>
+                       <name>org.eclipse.jdt.core.javabuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.ManifestBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.SchemaBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.ds.core.builder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+       </buildSpec>
+       <natures>
+               <nature>org.eclipse.pde.PluginNature</nature>
+               <nature>org.eclipse.jdt.core.javanature</nature>
+       </natures>
+</projectDescription>
diff --git a/swt/org.argeo.tool.devops.e4/OSGI-INF/cmsAdminRap.xml b/swt/org.argeo.tool.devops.e4/OSGI-INF/cmsAdminRap.xml
new file mode 100644 (file)
index 0000000..70f4ae9
--- /dev/null
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" configuration-policy="optional" name="CMS Admin RAP">
+   <implementation class="org.argeo.cms.jcr.e4.rap.CmsE4AdminApp"/>
+   <service>
+      <provide interface="org.eclipse.rap.rwt.application.ApplicationConfiguration"/>
+      <property name="contextName" type="String" value="slc/tool"/>
+   </service>
+</scr:component>
diff --git a/swt/org.argeo.tool.devops.e4/OSGI-INF/homeRepository.xml b/swt/org.argeo.tool.devops.e4/OSGI-INF/homeRepository.xml
new file mode 100644 (file)
index 0000000..2722aab
--- /dev/null
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" immediate="true" name="Home Repository">
+   <implementation class="org.argeo.cms.e4.OsgiFilterContextFunction"/>
+   <property name="service.context.key" type="String" value="(cn=ego)"/>
+   <service>
+      <provide interface="org.eclipse.e4.core.contexts.IContextFunction"/>
+   </service>
+</scr:component>
diff --git a/swt/org.argeo.tool.devops.e4/OSGI-INF/userAdminWrapper.xml b/swt/org.argeo.tool.devops.e4/OSGI-INF/userAdminWrapper.xml
new file mode 100644 (file)
index 0000000..22e6956
--- /dev/null
@@ -0,0 +1,10 @@
+<?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.api.cms.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.cms.osgi.useradmin.UserDirectory" name="UserDirectory" policy="static" unbind="removeUserDirectory"/>
+</scr:component>
diff --git a/swt/org.argeo.tool.devops.e4/bnd.bnd b/swt/org.argeo.tool.devops.e4/bnd.bnd
new file mode 100644 (file)
index 0000000..36231d3
--- /dev/null
@@ -0,0 +1,22 @@
+Bundle-ActivationPolicy: lazy
+
+Import-Package: \
+org.eclipse.swt,\
+org.eclipse.swt.widgets;version="0.0.0",\
+org.eclipse.jface.window,\
+org.eclipse.core.commands.common,\
+org.eclipse.e4.ui.model.application.ui;resolution:=optional,\
+org.eclipse.e4.ui.model.application;resolution:=optional,\
+javax.jcr.nodetype,\
+org.apache.jackrabbit.*;version="[2,3)",\
+org.argeo.cms,\
+org.argeo.jcr,\
+org.argeo.api.acr,\
+org.argeo.api.cms.directory,\
+org.argeo.cms.e4.rap;resolution:=optional,\
+org.eclipse.*;resolution:=optional,\
+*
+
+Service-Component: OSGI-INF/homeRepository.xml,\
+OSGI-INF/userAdminWrapper.xml,\
+OSGI-INF/cmsAdminRap.xml,\
diff --git a/swt/org.argeo.tool.devops.e4/build.properties b/swt/org.argeo.tool.devops.e4/build.properties
new file mode 100644 (file)
index 0000000..34d2e4d
--- /dev/null
@@ -0,0 +1,4 @@
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+               .
diff --git a/swt/org.argeo.tool.devops.e4/e4xmi/devops.e4xmi b/swt/org.argeo.tool.devops.e4/e4xmi/devops.e4xmi
new file mode 100644 (file)
index 0000000..d9361b8
--- /dev/null
@@ -0,0 +1,129 @@
+<?xml version="1.0" encoding="ASCII"?>
+<application:Application xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:advanced="http://www.eclipse.org/ui/2010/UIModel/application/ui/advanced" xmlns:application="http://www.eclipse.org/ui/2010/UIModel/application" xmlns:basic="http://www.eclipse.org/ui/2010/UIModel/application/ui/basic" xmlns:menu="http://www.eclipse.org/ui/2010/UIModel/application/ui/menu" xmi:id="_XqkCQKknEeObFrG_clJBYA" elementId="">
+  <children xsi:type="basic:TrimmedWindow" xmi:id="_Zdy6cKknEeObFrG_clJBYA" elementId="org.argeo.cms.e4.apps.admin.trimmedwindow.0" label="" x="10" y="10" width="500" height="500">
+    <persistedState key="styleOverride" value="8"/>
+    <tags>shellMaximized</tags>
+    <tags>auth.cn=admin,ou=roles,ou=node</tags>
+    <children xsi:type="advanced:PerspectiveStack" xmi:id="_jXVqsCk4Eein5vuhpK-Dew" elementId="org.argeo.cms.e4.perspectivestack.0" selectedElement="_xOVlsDvOEeiF1foPJZSZkw">
+      <children xsi:type="advanced:Perspective" xmi:id="_xOVlsDvOEeiF1foPJZSZkw" elementId="org.argeo.cms.e4.perspective.users" label="Users" iconURI="platform:/plugin/org.argeo.tool.swt/icons/group.png">
+        <tags>auth.cn=admin,ou=roles,ou=node</tags>
+        <children xsi:type="basic:PartSashContainer" xmi:id="_1tQoEDvOEeiF1foPJZSZkw" elementId="org.argeo.cms.e4.partsashcontainer.2" horizontal="true">
+          <children xsi:type="basic:PartStack" xmi:id="_vtbKkDvkEeiF1foPJZSZkw" elementId="org.argeo.cms.e4.partstack.4" containerData="4000" selectedElement="_9gukYDvOEeiF1foPJZSZkw">
+            <children xsi:type="basic:Part" xmi:id="_9gukYDvOEeiF1foPJZSZkw" elementId="org.argeo.cms.e4.part.users" containerData="" contributionURI="bundleclass://org.argeo.tool.devops.e4/org.argeo.cms.e4.users.UsersView" label="Users" iconURI="platform:/plugin/org.argeo.tool.swt/icons/person.png">
+              <handlers xmi:id="_0mN68DvjEeiF1foPJZSZkw" elementId="org.argeo.cms.e4.handler.4" contributionURI="bundleclass://org.argeo.tool.devops.e4/org.argeo.cms.e4.users.handlers.NewUser" command="_uL5i4DvjEeiF1foPJZSZkw"/>
+              <handlers xmi:id="_ODLdgDvkEeiF1foPJZSZkw" elementId="org.argeo.cms.e4.handler.5" contributionURI="bundleclass://org.argeo.tool.devops.e4/org.argeo.cms.e4.users.handlers.DeleteUsers" command="_xkcMADvjEeiF1foPJZSZkw"/>
+              <toolbar xmi:id="_jLWmkDvjEeiF1foPJZSZkw" elementId="org.argeo.cms.e4.toolbar.1">
+                <children xsi:type="menu:HandledToolItem" xmi:id="_jy_OUDvjEeiF1foPJZSZkw" elementId="org.argeo.cms.e4.handledtoolitem.new" label="New" iconURI="platform:/plugin/org.argeo.tool.swt/icons/add.png" command="_uL5i4DvjEeiF1foPJZSZkw"/>
+                <children xsi:type="menu:HandledToolItem" xmi:id="_9qszMDvjEeiF1foPJZSZkw" elementId="org.argeo.cms.e4.handledtoolitem.delete" label="Delete" iconURI="platform:/plugin/org.argeo.tool.swt/icons/delete.png" command="_xkcMADvjEeiF1foPJZSZkw"/>
+              </toolbar>
+            </children>
+          </children>
+          <children xsi:type="basic:PartStack" xmi:id="__g1a8DvOEeiF1foPJZSZkw" elementId="org.argeo.cms.e4.partstack.3" containerData="4000">
+            <tags>usersEditorArea</tags>
+          </children>
+          <children xsi:type="basic:PartStack" xmi:id="_-mFn8DvkEeiF1foPJZSZkw" elementId="org.argeo.cms.e4.partstack.5" containerData="2000">
+            <children xsi:type="basic:Part" xmi:id="_6etk4DvOEeiF1foPJZSZkw" elementId="org.argeo.cms.e4.part.groups" containerData="" contributionURI="bundleclass://org.argeo.tool.devops.e4/org.argeo.cms.e4.users.GroupsView" label="Groups" iconURI="platform:/plugin/org.argeo.tool.swt/icons/group.png">
+              <handlers xmi:id="_cmShoDvkEeiF1foPJZSZkw" elementId="org.argeo.cms.e4.handler.6" contributionURI="bundleclass://org.argeo.tool.devops.e4/org.argeo.cms.e4.users.handlers.NewGroup" command="_uL5i4DvjEeiF1foPJZSZkw"/>
+              <handlers xmi:id="_fbYfcDvkEeiF1foPJZSZkw" elementId="org.argeo.cms.e4.handler.7" contributionURI="bundleclass://org.argeo.tool.devops.e4/org.argeo.cms.e4.users.handlers.DeleteGroups" command="_xkcMADvjEeiF1foPJZSZkw"/>
+              <toolbar xmi:id="_Us0rADvkEeiF1foPJZSZkw" elementId="org.argeo.cms.e4.toolbar.2">
+                <children xsi:type="menu:HandledToolItem" xmi:id="_VQTLgDvkEeiF1foPJZSZkw" elementId="org.argeo.cms.e4.handledtoolitem.new" label="New" iconURI="platform:/plugin/org.argeo.tool.swt/icons/add.png" command="_uL5i4DvjEeiF1foPJZSZkw"/>
+                <children xsi:type="menu:HandledToolItem" xmi:id="_XfME8DvkEeiF1foPJZSZkw" elementId="org.argeo.cms.e4.handledtoolitem.delete" label="Delete" iconURI="platform:/plugin/org.argeo.tool.swt/icons/delete.png" command="_xkcMADvjEeiF1foPJZSZkw"/>
+              </toolbar>
+            </children>
+          </children>
+        </children>
+      </children>
+      <children xsi:type="advanced:Perspective" xmi:id="_jvjWYCk4Eein5vuhpK-Dew" elementId="org.argeo.cms.e4.perspective.data" label="Data" iconURI="platform:/plugin/org.argeo.tool.swt/icons/nodes.gif">
+        <children xsi:type="basic:PartSashContainer" xmi:id="_h3tvMCkxEein5vuhpK-Dew" elementId="org.argeo.cms.e4.partsashcontainer.0" selectedElement="_0B9SECkxEein5vuhpK-Dew" horizontal="true">
+          <children xsi:type="basic:PartStack" xmi:id="_0B9SECkxEein5vuhpK-Dew" elementId="org.argeo.cms.e4.partstack.0" containerData="4000" selectedElement="_WAjPkCkTEein5vuhpK-Dew">
+            <children xsi:type="basic:Part" xmi:id="_WAjPkCkTEein5vuhpK-Dew" elementId="org.argeo.cms.e4.jcrbrowser" containerData="" contributionURI="bundleclass://org.argeo.tool.devops.e4/org.argeo.cms.e4.jcr.JcrBrowserView" label="JCR" iconURI="platform:/plugin/org.argeo.tool.swt/icons/browser.gif">
+              <menus xsi:type="menu:PopupMenu" xmi:id="_eXiUECqREeidr6NYQH6GbQ" elementId="org.argeo.cms.e4.popupmenu.nodeViewer">
+                <children xsi:type="menu:HandledMenuItem" xmi:id="_GVeO8CqhEeidr6NYQH6GbQ" elementId="org.argeo.cms.e4.handledmenuitem.refresh" label="Refresh" iconURI="platform:/plugin/org.argeo.tool.swt/icons/refresh.png" command="_TOKHsCqYEeidr6NYQH6GbQ"/>
+                <children xsi:type="menu:HandledMenuItem" xmi:id="_fU238CqREeidr6NYQH6GbQ" elementId="org.argeo.cms.e4.handledmenuitem.addfoldernode" label="Add folder" iconURI="platform:/plugin/org.argeo.tool.swt/icons/addFolder.gif" command="_RgE5cCqREeidr6NYQH6GbQ"/>
+                <children xsi:type="menu:HandledMenuItem" xmi:id="_U4o9cCqhEeidr6NYQH6GbQ" elementId="org.argeo.cms.e4.handledmenuitem.rename" label="Rename" iconURI="platform:/plugin/org.argeo.tool.swt/icons/rename.gif" command="_ZrcUMCqYEeidr6NYQH6GbQ"/>
+                <children xsi:type="menu:HandledMenuItem" xmi:id="_Ncxo0CqhEeidr6NYQH6GbQ" elementId="org.argeo.cms.e4.handledmenuitem.remove" label="Remove" iconURI="platform:/plugin/org.argeo.tool.swt/icons/remove.gif" command="_ChJ-4CqYEeidr6NYQH6GbQ"/>
+              </menus>
+              <menus xmi:id="_oRg_ACqTEeidr6NYQH6GbQ" elementId="org.argeo.cms.e4.menu.0">
+                <tags>ViewMenu</tags>
+                <children xsi:type="menu:HandledMenuItem" xmi:id="_yJR8ECqYEeidr6NYQH6GbQ" elementId="org.argeo.cms.e4.handledmenuitem.refresh" label="Refresh" iconURI="platform:/plugin/org.argeo.tool.swt/icons/refresh.png" command="_TOKHsCqYEeidr6NYQH6GbQ"/>
+                <children xsi:type="menu:HandledMenuItem" xmi:id="_o6HQECqTEeidr6NYQH6GbQ" elementId="org.argeo.cms.e4.handledmenuitem.addfoldernode" label="Add folder" iconURI="platform:/plugin/org.argeo.tool.swt/icons/addFolder.gif" command="_RgE5cCqREeidr6NYQH6GbQ"/>
+                <children xsi:type="menu:HandledMenuItem" xmi:id="_5D7aACqYEeidr6NYQH6GbQ" elementId="org.argeo.cms.e4.handledmenuitem.rename" label="Rename" iconURI="platform:/plugin/org.argeo.tool.swt/icons/rename.gif" command="_ZrcUMCqYEeidr6NYQH6GbQ"/>
+                <children xsi:type="menu:HandledMenuItem" xmi:id="_7rR2wCqYEeidr6NYQH6GbQ" elementId="org.argeo.cms.e4.handledmenuitem.delete" label="Delete" iconURI="platform:/plugin/org.argeo.tool.swt/icons/remove.gif" command="_ChJ-4CqYEeidr6NYQH6GbQ"/>
+                <children xsi:type="menu:HandledMenuItem" xmi:id="_XsHLgFgQEeiknZQLx-vtnA" elementId="org.argeo.cms.e4.handledmenuitem.0" iconURI="platform:/plugin/org.argeo.tool.swt/icons/addRepo.gif" command="_ZWpasFgQEeiknZQLx-vtnA"/>
+              </menus>
+            </children>
+          </children>
+          <children xsi:type="basic:PartStack" xmi:id="_mHrEUCk4Eein5vuhpK-Dew" elementId="org.argeo.cms.e4.partstack.1" containerData="6000">
+            <tags>dataExplorer</tags>
+          </children>
+        </children>
+      </children>
+      <children xsi:type="advanced:Perspective" xmi:id="_u5ZakFhJEeiknZQLx-vtnA" elementId="org.argeo.cms.e4.perspective.monitoring" label="Monitoring" iconURI="platform:/plugin/org.argeo.tool.swt/icons/bundles.gif">
+        <children xsi:type="basic:PartStack" xmi:id="_7i7t8FhJEeiknZQLx-vtnA" elementId="org.argeo.cms.e4.partstack.6">
+          <children xsi:type="basic:Part" xmi:id="_Z-3cMFhbEeiknZQLx-vtnA" elementId="org.argeo.cms.e4.part.osgiConfigurations" contributionURI="bundleclass://org.argeo.tool.devops.e4/org.argeo.cms.e4.monitoring.OsgiConfigurationsView" label="OSGi Configurations" iconURI="platform:/plugin/org.argeo.tool.swt/icons/node.gif"/>
+          <children xsi:type="basic:Part" xmi:id="_8dM90FhJEeiknZQLx-vtnA" elementId="org.argeo.cms.e4.part.cmsSessions" contributionURI="bundleclass://org.argeo.tool.devops.e4/org.argeo.cms.e4.monitoring.CmsSessionsView" label="CMS Sessions" iconURI="platform:/plugin/org.argeo.tool.swt/icons/person-logged-in.png"/>
+          <children xsi:type="basic:Part" xmi:id="_KqRZIFhNEeiknZQLx-vtnA" elementId="org.argeo.cms.e4.part.modules" contributionURI="bundleclass://org.argeo.tool.devops.e4/org.argeo.cms.e4.monitoring.ModulesView" label="Modules" iconURI="platform:/plugin/org.argeo.tool.swt/icons/bundles.gif"/>
+          <children xsi:type="basic:Part" xmi:id="_dXtIoFhNEeiknZQLx-vtnA" elementId="org.argeo.cms.e4.part.bundles" contributionURI="bundleclass://org.argeo.tool.devops.e4/org.argeo.cms.e4.monitoring.BundlesView" label="Bundles" iconURI="platform:/plugin/org.argeo.tool.swt/icons/bundles.gif"/>
+        </children>
+      </children>
+      <children xsi:type="advanced:Perspective" xmi:id="_ABK2ADsNEeiUntFYWh-hFg" elementId="org.argeo.cms.e4.perspective.files" label="Files" iconURI="platform:/plugin/org.argeo.tool.swt/icons/file.gif">
+        <children xsi:type="basic:PartSashContainer" xmi:id="_FPimEDsSEeiUntFYWh-hFg" elementId="org.argeo.cms.e4.partsashcontainer.1" horizontal="true">
+          <children xsi:type="basic:PartStack" xmi:id="_H93NgDsSEeiUntFYWh-hFg" elementId="org.argeo.cms.e4.partstack.2" containerData="4000">
+            <children xsi:type="basic:Part" xmi:id="_Izxh0DsSEeiUntFYWh-hFg" elementId="org.argeo.cms.e4.part.files" contributionURI="bundleclass://org.argeo.tool.devops.e4/org.argeo.cms.e4.files.NodeFsBrowserView" label="Files" iconURI="platform:/plugin/org.argeo.tool.swt/icons/file.gif"/>
+          </children>
+          <children xsi:type="basic:Part" xmi:id="_TMqBMDsSEeiUntFYWh-hFg" elementId="org.argeo.cms.e4.part.0" containerData="6000"/>
+        </children>
+      </children>
+    </children>
+    <handlers xmi:id="_Vwax0DvrEeiF1foPJZSZkw" elementId="org.argeo.cms.e4.handler.8" contributionURI="bundleclass://org.argeo.cms.e4/org.argeo.cms.e4.handlers.OpenPerspective" command="_AF1UsDvrEeiF1foPJZSZkw"/>
+    <trimBars xmi:id="_euVxMCk2Eein5vuhpK-Dew" elementId="org.argeo.cms.e4.trimbar.0" side="Left">
+      <children xsi:type="menu:ToolBar" xmi:id="_fotHsCk2Eein5vuhpK-Dew" elementId="org.argeo.cms.e4.toolbar.0">
+        <children xsi:type="menu:HandledToolItem" xmi:id="_jCSQgDvrEeiF1foPJZSZkw" elementId="org.argeo.cms.e4.handledtoolitem.users" label="Users" iconURI="platform:/plugin/org.argeo.tool.swt/icons/group.png" command="_AF1UsDvrEeiF1foPJZSZkw">
+          <tags>auth.cn=admin,ou=roles,ou=node</tags>
+          <parameters xmi:id="_lu_uYDvrEeiF1foPJZSZkw" elementId="org.argeo.cms.e4.parameter.2" name="perspectiveId" value="org.argeo.cms.e4.perspective.users"/>
+        </children>
+        <children xsi:type="menu:HandledToolItem" xmi:id="_jfUM4Ck2Eein5vuhpK-Dew" elementId="org.argeo.cms.e4.handledtoolitem.test" label="Data" iconURI="platform:/plugin/org.argeo.tool.swt/icons/nodes.gif" command="_AF1UsDvrEeiF1foPJZSZkw">
+          <parameters xmi:id="_KDlXQDvrEeiF1foPJZSZkw" elementId="org.argeo.cms.e4.parameter.0" name="perspectiveId" value="org.argeo.cms.e4.perspective.data"/>
+        </children>
+        <children xsi:type="menu:HandledToolItem" xmi:id="_dhv80FhKEeiknZQLx-vtnA" elementId="org.argeo.cms.e4.handledtoolitem.monitoring" label="Monitoring" iconURI="platform:/plugin/org.argeo.tool.swt/icons/bundles.gif" command="_AF1UsDvrEeiF1foPJZSZkw">
+          <parameters xmi:id="_kjN0cFhKEeiknZQLx-vtnA" elementId="org.argeo.cms.e4.parameter.3" name="perspectiveId" value="org.argeo.cms.e4.perspective.monitoring"/>
+        </children>
+        <children xsi:type="menu:HandledToolItem" xmi:id="_b0OHUDvrEeiF1foPJZSZkw" elementId="org.argeo.cms.e4.handledtoolitem.files" label="Files" iconURI="platform:/plugin/org.argeo.tool.swt/icons/file.gif" command="_AF1UsDvrEeiF1foPJZSZkw">
+          <parameters xmi:id="_fXvRYDvrEeiF1foPJZSZkw" elementId="org.argeo.cms.e4.parameter.1" name="perspectiveId" value="org.argeo.cms.e4.perspective.files"/>
+        </children>
+        <children xsi:type="menu:ToolBarSeparator" xmi:id="_wuoL8FhLEeiknZQLx-vtnA" elementId="org.argeo.cms.e4.toolbarseparator.0"/>
+        <children xsi:type="menu:HandledToolItem" xmi:id="_2v8DkFhKEeiknZQLx-vtnA" elementId="org.argeo.cms.e4.handledtoolitem.logout" label="Log out" iconURI="platform:/plugin/org.argeo.tool.swt/icons/logout.png" command="_PsWd0FhLEeiknZQLx-vtnA"/>
+      </children>
+    </trimBars>
+  </children>
+  <handlers xmi:id="_Xp-P4CqREeidr6NYQH6GbQ" elementId="org.argeo.cms.e4.handler.0" contributionURI="bundleclass://org.argeo.tool.devops.e4/org.argeo.cms.e4.jcr.handlers.AddFolderNode" command="_RgE5cCqREeidr6NYQH6GbQ"/>
+  <handlers xmi:id="_jbnNwCqYEeidr6NYQH6GbQ" elementId="org.argeo.cms.e4.handler.1" contributionURI="bundleclass://org.argeo.tool.devops.e4/org.argeo.cms.e4.jcr.handlers.DeleteNodes" command="_ChJ-4CqYEeidr6NYQH6GbQ"/>
+  <handlers xmi:id="_loxB0CqYEeidr6NYQH6GbQ" elementId="org.argeo.cms.e4.handler.2" contributionURI="bundleclass://org.argeo.tool.devops.e4/org.argeo.cms.e4.jcr.handlers.Refresh" command="_TOKHsCqYEeidr6NYQH6GbQ"/>
+  <handlers xmi:id="_omPfkCqYEeidr6NYQH6GbQ" elementId="org.argeo.cms.e4.handler.3" contributionURI="bundleclass://org.argeo.tool.devops.e4/org.argeo.cms.e4.jcr.handlers.RenameNode" command="_ZrcUMCqYEeidr6NYQH6GbQ"/>
+  <handlers xmi:id="_dUg-cFgQEeiknZQLx-vtnA" elementId="org.argeo.cms.e4.handler.9" contributionURI="bundleclass://org.argeo.tool.devops.e4/org.argeo.cms.e4.jcr.handlers.AddRemoteRepository" command="_ZWpasFgQEeiknZQLx-vtnA"/>
+  <handlers xmi:id="_RQyFAFhLEeiknZQLx-vtnA" elementId="org.argeo.cms.e4.handler.10" contributionURI="bundleclass://org.argeo.cms.e4/org.argeo.cms.e4.handlers.CloseWorkbench" command="_PsWd0FhLEeiknZQLx-vtnA"/>
+  <descriptors xmi:id="_XzfoMCqlEeidr6NYQH6GbQ" elementId="org.argeo.cms.e4.partdescriptor.nodeEditor" label="Node Editor" iconURI="platform:/plugin/org.argeo.tool.swt/icons/node.gif" allowMultiple="true" category="dataExplorer" closeable="true" contributionURI="bundleclass://org.argeo.tool.devops.e4/org.argeo.cms.e4.jcr.JcrNodeEditor"/>
+  <descriptors xmi:id="_sAdNwDvdEeiF1foPJZSZkw" elementId="org.argeo.cms.e4.partdescriptor.userEditor" label="User Editor" iconURI="platform:/plugin/org.argeo.tool.swt/icons/person.png" allowMultiple="true" category="usersEditorArea" closeable="true" dirtyable="true" contributionURI="bundleclass://org.argeo.tool.devops.e4/org.argeo.cms.e4.users.UserEditor"/>
+  <descriptors xmi:id="_5nK7EDvdEeiF1foPJZSZkw" elementId="org.argeo.cms.e4.partdescriptor.groupEditor" label="Group Editor" iconURI="platform:/plugin/org.argeo.tool.swt/icons/group.png" allowMultiple="true" category="usersEditorArea" closeable="true" dirtyable="true" contributionURI="bundleclass://org.argeo.tool.devops.e4/org.argeo.cms.e4.users.GroupEditor"/>
+  <commands xmi:id="_RgE5cCqREeidr6NYQH6GbQ" elementId="org.argeo.cms.e4.command.addFolderNode" commandName="Add folder node" category="_MDkwUCqYEeidr6NYQH6GbQ"/>
+  <commands xmi:id="_ChJ-4CqYEeidr6NYQH6GbQ" elementId="org.argeo.cms.e4.command.deleteNodes" commandName="Delete nodes" category="_MDkwUCqYEeidr6NYQH6GbQ"/>
+  <commands xmi:id="_TOKHsCqYEeidr6NYQH6GbQ" elementId="org.argeo.cms.e4.command.refreshNodes" commandName="Refresh nodes" category="_MDkwUCqYEeidr6NYQH6GbQ"/>
+  <commands xmi:id="_ZrcUMCqYEeidr6NYQH6GbQ" elementId="org.argeo.cms.e4.command.renameNode" commandName="Rename node" category="_MDkwUCqYEeidr6NYQH6GbQ"/>
+  <commands xmi:id="_uL5i4DvjEeiF1foPJZSZkw" elementId="org.argeo.cms.e4.command.add" commandName="Add"/>
+  <commands xmi:id="_xkcMADvjEeiF1foPJZSZkw" elementId="org.argeo.cms.e4.command.delete" commandName="Delete"/>
+  <commands xmi:id="_AF1UsDvrEeiF1foPJZSZkw" elementId="org.argeo.cms.e4.command.openPerspective" commandName="Open Perspective">
+    <parameters xmi:id="_F3WAUDvrEeiF1foPJZSZkw" elementId="perspectiveId" name="Perspective Id" optional="false"/>
+  </commands>
+  <commands xmi:id="_ZWpasFgQEeiknZQLx-vtnA" elementId="org.argeo.cms.e4.command.addRemoteRepository" commandName="Add Remote Repository"/>
+  <commands xmi:id="_PsWd0FhLEeiknZQLx-vtnA" elementId="org.argeo.cms.e4.command.logout" commandName="Log out"/>
+  <addons xmi:id="_XqkCQaknEeObFrG_clJBYA" elementId="org.eclipse.e4.core.commands.service" contributionURI="bundleclass://org.eclipse.e4.core.commands/org.eclipse.e4.core.commands.CommandServiceAddon"/>
+  <addons xmi:id="_XqkCQqknEeObFrG_clJBYA" elementId="org.eclipse.e4.ui.contexts.service" contributionURI="bundleclass://org.eclipse.e4.ui.services/org.eclipse.e4.ui.services.ContextServiceAddon"/>
+  <addons xmi:id="_XqkCQ6knEeObFrG_clJBYA" elementId="org.eclipse.e4.ui.bindings.service" contributionURI="bundleclass://org.eclipse.e4.ui.bindings/org.eclipse.e4.ui.bindings.BindingServiceAddon"/>
+  <addons xmi:id="_XqkCRKknEeObFrG_clJBYA" elementId="org.eclipse.e4.ui.workbench.commands.model" contributionURI="bundleclass://org.eclipse.e4.ui.workbench/org.eclipse.e4.ui.internal.workbench.addons.CommandProcessingAddon"/>
+  <addons xmi:id="_XqkCRaknEeObFrG_clJBYA" elementId="org.eclipse.e4.ui.workbench.contexts.model" contributionURI="bundleclass://org.eclipse.e4.ui.workbench/org.eclipse.e4.ui.internal.workbench.addons.ContextProcessingAddon"/>
+  <addons xmi:id="_XqkCRqknEeObFrG_clJBYA" elementId="org.eclipse.e4.ui.workbench.bindings.model" contributionURI="bundleclass://org.eclipse.e4.ui.workbench.swt/org.eclipse.e4.ui.workbench.swt.util.BindingProcessingAddon"/>
+  <addons xmi:id="_XqkCR6knEeObFrG_clJBYA" elementId="org.eclipse.e4.ui.workbench.handler.model" contributionURI="bundleclass://org.eclipse.e4.ui.workbench/org.eclipse.e4.ui.internal.workbench.addons.HandlerProcessingAddon"/>
+  <addons xmi:id="_8VnK8OdKEeijEOqYKRSeoQ" elementId="org.argeo.cms.e4.addon.locale" contributionURI="bundleclass://org.argeo.cms.e4/org.argeo.cms.e4.addons.LocaleAddon"/>
+  <addons xmi:id="_-xeJYOdKEeijEOqYKRSeoQ" elementId="org.argeo.cms.e4.addon.auth" contributionURI="bundleclass://org.argeo.cms.e4/org.argeo.cms.e4.addons.AuthAddon"/>
+  <categories xmi:id="_MDkwUCqYEeidr6NYQH6GbQ" elementId="org.argeo.cms.e4.category.jcrBrowser" name="JCR Browser"/>
+</application:Application>
\ No newline at end of file
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/files/NodeFsBrowserView.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/files/NodeFsBrowserView.java
new file mode 100644 (file)
index 0000000..aabfbf5
--- /dev/null
@@ -0,0 +1,47 @@
+package org.argeo.cms.e4.files;
+
+import java.net.URI;
+import java.nio.file.FileSystem;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.spi.FileSystemProvider;
+
+import javax.annotation.PostConstruct;
+import javax.inject.Inject;
+
+import org.argeo.eclipse.ui.fs.SimpleFsBrowser;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.Composite;
+
+/** Browse the node file system. */
+public class NodeFsBrowserView {
+       // public final static String ID = WorkbenchUiPlugin.PLUGIN_ID +
+       // ".nodeFsBrowserView";
+
+       @Inject
+       FileSystemProvider nodeFileSystemProvider;
+
+       @PostConstruct
+       public void createPartControl(Composite parent) {
+               try {
+                       // URI uri = new URI("node://root:demo@localhost:7070/");
+                       URI uri = new URI("node:///");
+                       FileSystem fileSystem = nodeFileSystemProvider.getFileSystem(uri);
+                       if (fileSystem == null)
+                               fileSystem = nodeFileSystemProvider.newFileSystem(uri, null);
+                       Path nodePath = fileSystem.getPath("/");
+
+                       Path localPath = Paths.get(System.getProperty("user.home"));
+
+                       SimpleFsBrowser browser = new SimpleFsBrowser(parent, SWT.NO_FOCUS);
+                       browser.setInput(nodePath, localPath);
+//                     AdvancedFsBrowser browser = new AdvancedFsBrowser();
+//                     browser.createUi(parent, localPath);
+               } catch (Exception e) {
+                       throw new RuntimeException("Cannot open file system browser", e);
+               }
+       }
+
+       public void setFocus() {
+       }
+}
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/files/package-info.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/files/package-info.java
new file mode 100644 (file)
index 0000000..b481dd4
--- /dev/null
@@ -0,0 +1,2 @@
+/** Files browser perspective. */
+package org.argeo.cms.e4.files;
\ No newline at end of file
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/jcr/EclipseJcrMonitor.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/jcr/EclipseJcrMonitor.java
new file mode 100644 (file)
index 0000000..e10738e
--- /dev/null
@@ -0,0 +1,44 @@
+package org.argeo.cms.e4.jcr;
+
+import org.argeo.jcr.JcrMonitor;
+import org.eclipse.core.runtime.IProgressMonitor;
+
+/**
+ * Wraps an Eclipse {@link IProgressMonitor} so that it can be passed to
+ * framework agnostic Argeo routines.
+ */
+public class EclipseJcrMonitor implements JcrMonitor {
+       private final IProgressMonitor progressMonitor;
+
+       public EclipseJcrMonitor(IProgressMonitor progressMonitor) {
+               this.progressMonitor = progressMonitor;
+       }
+
+       public void beginTask(String name, int totalWork) {
+               progressMonitor.beginTask(name, totalWork);
+       }
+
+       public void done() {
+               progressMonitor.done();
+       }
+
+       public boolean isCanceled() {
+               return progressMonitor.isCanceled();
+       }
+
+       public void setCanceled(boolean value) {
+               progressMonitor.setCanceled(value);
+       }
+
+       public void setTaskName(String name) {
+               progressMonitor.setTaskName(name);
+       }
+
+       public void subTask(String name) {
+               progressMonitor.subTask(name);
+       }
+
+       public void worked(int work) {
+               progressMonitor.worked(work);
+       }
+}
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/jcr/GenericPropertyPage.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/jcr/GenericPropertyPage.java
new file mode 100644 (file)
index 0000000..e17f17b
--- /dev/null
@@ -0,0 +1,141 @@
+package org.argeo.cms.e4.jcr;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.PropertyIterator;
+import javax.jcr.RepositoryException;
+
+import org.argeo.cms.ui.jcr.PropertyLabelProvider;
+import org.argeo.eclipse.ui.EclipseUiException;
+import org.eclipse.jface.layout.TreeColumnLayout;
+import org.eclipse.jface.viewers.ColumnWeightData;
+import org.eclipse.jface.viewers.IBaseLabelProvider;
+import org.eclipse.jface.viewers.ITreeContentProvider;
+import org.eclipse.jface.viewers.TreeViewer;
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.ScrolledComposite;
+import org.eclipse.swt.layout.FillLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Tree;
+import org.eclipse.swt.widgets.TreeColumn;
+
+/**
+ * Generic editor property page. Lists all properties of current node as a
+ * complex tree. TODO: enable editing
+ */
+public class GenericPropertyPage {
+
+       // Main business Objects
+       private Node currentNode;
+
+       public GenericPropertyPage(Node currentNode) {
+               this.currentNode = currentNode;
+       }
+
+       protected void createFormContent(Composite parent) {
+               Composite innerBox = new Composite(parent, SWT.NONE);
+               // Composite innerBox = new Composite(body, SWT.NO_FOCUS);
+               FillLayout layout = new FillLayout();
+               layout.marginHeight = 5;
+               layout.marginWidth = 5;
+               innerBox.setLayout(layout);
+               createComplexTree(innerBox);
+               // TODO TreeColumnLayout triggers a scroll issue with the form:
+               // The inside body is always to big and a scroll bar is shown
+               // Composite tableCmp = new Composite(body, SWT.NO_FOCUS);
+               // createComplexTree(tableCmp);
+       }
+
+       private TreeViewer createComplexTree(Composite parent) {
+               int style = SWT.BORDER | SWT.MULTI | SWT.FULL_SELECTION;
+               Tree tree = new Tree(parent, style);
+               TreeColumnLayout tableColumnLayout = new TreeColumnLayout();
+
+               createColumn(tree, tableColumnLayout, "Property", SWT.LEFT, 200, 30);
+               createColumn(tree, tableColumnLayout, "Value(s)", SWT.LEFT, 300, 60);
+               createColumn(tree, tableColumnLayout, "Type", SWT.LEFT, 75, 10);
+               createColumn(tree, tableColumnLayout, "Attributes", SWT.LEFT, 75, 0);
+               // Do not apply the treeColumnLayout it does not work yet
+               // parent.setLayout(tableColumnLayout);
+
+               tree.setLinesVisible(true);
+               tree.setHeaderVisible(true);
+
+               TreeViewer treeViewer = new TreeViewer(tree);
+               treeViewer.setContentProvider(new TreeContentProvider());
+               treeViewer.setLabelProvider((IBaseLabelProvider) new PropertyLabelProvider());
+               treeViewer.setInput(currentNode);
+               treeViewer.expandAll();
+               return treeViewer;
+       }
+
+       private static TreeColumn createColumn(Tree parent, TreeColumnLayout tableColumnLayout, String name, int style,
+                       int width, int weight) {
+               TreeColumn column = new TreeColumn(parent, style);
+               column.setText(name);
+               column.setWidth(width);
+               column.setMoveable(true);
+               column.setResizable(true);
+               tableColumnLayout.setColumnData(column, new ColumnWeightData(weight, width, true));
+               return column;
+       }
+
+       private class TreeContentProvider implements ITreeContentProvider {
+               private static final long serialVersionUID = -6162736530019406214L;
+
+               public Object[] getElements(Object parent) {
+                       Object[] props = null;
+                       try {
+
+                               if (parent instanceof Node) {
+                                       Node node = (Node) parent;
+                                       PropertyIterator pi;
+                                       pi = node.getProperties();
+                                       List<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() {
+               }
+       }
+}
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/jcr/JcrBrowserView.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/jcr/JcrBrowserView.java
new file mode 100644 (file)
index 0000000..dc0ba94
--- /dev/null
@@ -0,0 +1,349 @@
+package org.argeo.cms.e4.jcr;
+
+import java.util.List;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
+import javax.inject.Inject;
+import javax.jcr.Property;
+import javax.jcr.PropertyType;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.RepositoryFactory;
+import javax.jcr.Session;
+import javax.jcr.Value;
+import javax.jcr.observation.Event;
+import javax.jcr.observation.EventListener;
+import javax.jcr.observation.ObservationManager;
+
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.api.cms.keyring.CryptoKeyring;
+import org.argeo.api.cms.keyring.Keyring;
+import org.argeo.cms.swt.CmsException;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.ui.jcr.JcrBrowserUtils;
+import org.argeo.cms.ui.jcr.NodeContentProvider;
+import org.argeo.cms.ui.jcr.NodeLabelProvider;
+import org.argeo.cms.ui.jcr.OsgiRepositoryRegister;
+import org.argeo.cms.ui.jcr.PropertiesContentProvider;
+import org.argeo.cms.ui.jcr.model.SingleJcrNodeElem;
+import org.argeo.cms.ux.widgets.TreeParent;
+import org.argeo.eclipse.ui.EclipseUiException;
+import org.argeo.eclipse.ui.jcr.AsyncUiEventListener;
+import org.argeo.eclipse.ui.jcr.util.NodeViewerComparer;
+import org.argeo.jcr.JcrUtils;
+import org.eclipse.e4.core.contexts.IEclipseContext;
+import org.eclipse.e4.core.di.annotations.Optional;
+import org.eclipse.e4.ui.services.EMenuService;
+import org.eclipse.e4.ui.workbench.modeling.EPartService;
+import org.eclipse.e4.ui.workbench.modeling.ESelectionService;
+import org.eclipse.jface.viewers.ColumnLabelProvider;
+import org.eclipse.jface.viewers.IBaseLabelProvider;
+import org.eclipse.jface.viewers.ISelectionChangedListener;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.viewers.ITreeContentProvider;
+import org.eclipse.jface.viewers.SelectionChangedEvent;
+import org.eclipse.jface.viewers.StructuredSelection;
+import org.eclipse.jface.viewers.TableViewer;
+import org.eclipse.jface.viewers.TableViewerColumn;
+import org.eclipse.jface.viewers.TreeViewer;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.SashForm;
+import org.eclipse.swt.layout.FillLayout;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Display;
+
+/**
+ * Basic View to display a sash form to browse a JCR compliant multiple
+ * repository environment
+ */
+public class JcrBrowserView {
+       final static String ID = "org.argeo.cms.e4.jcrbrowser";
+       final static String NODE_VIEWER_POPUP_MENU_ID = "org.argeo.cms.e4.popupmenu.nodeViewer";
+
+       private boolean sortChildNodes = true;
+
+       /* DEPENDENCY INJECTION */
+       @Inject
+       @Optional
+       private Keyring keyring;
+       @Inject
+       private RepositoryFactory repositoryFactory;
+       @Inject
+       private Repository nodeRepository;
+
+       // Current user session on the home repository default workspace
+       private Session userSession;
+
+       private OsgiRepositoryRegister repositoryRegister = new OsgiRepositoryRegister();
+
+       // This page widgets
+       private TreeViewer nodesViewer;
+       private NodeContentProvider nodeContentProvider;
+       private TableViewer propertiesViewer;
+       private EventListener resultsObserver;
+
+       @PostConstruct
+       public void createPartControl(Composite parent, IEclipseContext context, EPartService partService,
+                       ESelectionService selectionService, EMenuService menuService) {
+               repositoryRegister.init();
+
+               parent.setLayout(new FillLayout());
+               SashForm sashForm = new SashForm(parent, SWT.VERTICAL);
+               // sashForm.setSashWidth(4);
+               // sashForm.setLayout(new FillLayout());
+
+               // Create the tree on top of the view
+               Composite top = new Composite(sashForm, SWT.NONE);
+               // GridLayout gl = new GridLayout(1, false);
+               top.setLayout(CmsSwtUtils.noSpaceGridLayout());
+
+               try {
+                       this.userSession = this.nodeRepository.login(CmsConstants.HOME_WORKSPACE);
+               } catch (RepositoryException e) {
+                       throw new CmsException("Cannot open user session", e);
+               }
+
+               nodeContentProvider = new NodeContentProvider(userSession, keyring, repositoryRegister, repositoryFactory,
+                               sortChildNodes);
+
+               // nodes viewer
+               nodesViewer = createNodeViewer(top, nodeContentProvider);
+
+               // context menu : it is completely defined in the plugin.xml file.
+               // MenuManager menuManager = new MenuManager();
+               // Menu menu = menuManager.createContextMenu(nodesViewer.getTree());
+
+               // nodesViewer.getTree().setMenu(menu);
+
+               nodesViewer.setInput("");
+
+               // Create the property viewer on the bottom
+               Composite bottom = new Composite(sashForm, SWT.NONE);
+               bottom.setLayout(CmsSwtUtils.noSpaceGridLayout());
+               propertiesViewer = createPropertiesViewer(bottom);
+
+               sashForm.setWeights(getWeights());
+               nodesViewer.setComparer(new NodeViewerComparer());
+               nodesViewer.addSelectionChangedListener(new ISelectionChangedListener() {
+                       public void selectionChanged(SelectionChangedEvent event) {
+                               IStructuredSelection selection = (IStructuredSelection) event.getSelection();
+                               selectionService.setSelection(selection.toList());
+                       }
+               });
+               nodesViewer.addDoubleClickListener(new JcrE4DClickListener(nodesViewer, partService));
+               menuService.registerContextMenu(nodesViewer.getControl(), NODE_VIEWER_POPUP_MENU_ID);
+               // getSite().registerContextMenu(menuManager, nodesViewer);
+               // getSite().setSelectionProvider(nodesViewer);
+       }
+
+       @PreDestroy
+       public void dispose() {
+               JcrUtils.logoutQuietly(userSession);
+               repositoryRegister.destroy();
+       }
+
+       public void refresh(Object obj) {
+               // Enable full refresh from a command when no element of the tree is
+               // selected
+               if (obj == null) {
+                       Object[] elements = nodeContentProvider.getElements(null);
+                       for (Object el : elements) {
+                               if (el instanceof TreeParent)
+                                       JcrBrowserUtils.forceRefreshIfNeeded((TreeParent) el);
+                               getNodeViewer().refresh(el);
+                       }
+               } else
+                       getNodeViewer().refresh(obj);
+       }
+
+       /**
+        * To be overridden to adapt size of form and result frames.
+        */
+       protected int[] getWeights() {
+               return new int[] { 70, 30 };
+       }
+
+       protected TreeViewer createNodeViewer(Composite parent, final ITreeContentProvider nodeContentProvider) {
+
+               final TreeViewer tmpNodeViewer = new TreeViewer(parent, SWT.MULTI);
+
+               tmpNodeViewer.getTree().setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
+
+               tmpNodeViewer.setContentProvider(nodeContentProvider);
+               tmpNodeViewer.setLabelProvider((IBaseLabelProvider) new NodeLabelProvider());
+               tmpNodeViewer.addSelectionChangedListener(new ISelectionChangedListener() {
+                       public void selectionChanged(SelectionChangedEvent event) {
+                               if (!event.getSelection().isEmpty()) {
+                                       IStructuredSelection sel = (IStructuredSelection) event.getSelection();
+                                       Object firstItem = sel.getFirstElement();
+                                       if (firstItem instanceof SingleJcrNodeElem)
+                                               propertiesViewer.setInput(((SingleJcrNodeElem) firstItem).getNode());
+                               } else {
+                                       propertiesViewer.setInput("");
+                               }
+                       }
+               });
+
+               resultsObserver = new TreeObserver(tmpNodeViewer.getTree().getDisplay());
+               if (keyring != null)
+                       try {
+                               ObservationManager observationManager = userSession.getWorkspace().getObservationManager();
+                               observationManager.addEventListener(resultsObserver, Event.PROPERTY_ADDED | Event.PROPERTY_CHANGED, "/",
+                                               true, null, null, false);
+                       } catch (RepositoryException e) {
+                               throw new EclipseUiException("Cannot register listeners", e);
+                       }
+
+               // tmpNodeViewer.addDoubleClickListener(new JcrDClickListener(tmpNodeViewer));
+               return tmpNodeViewer;
+       }
+
+       protected TableViewer createPropertiesViewer(Composite parent) {
+               propertiesViewer = new TableViewer(parent, SWT.NONE);
+               propertiesViewer.getTable().setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
+               propertiesViewer.getTable().setHeaderVisible(true);
+               propertiesViewer.setContentProvider(new PropertiesContentProvider());
+               TableViewerColumn col = new TableViewerColumn(propertiesViewer, SWT.NONE);
+               col.getColumn().setText("Name");
+               col.getColumn().setWidth(200);
+               col.setLabelProvider(new ColumnLabelProvider() {
+                       private static final long serialVersionUID = -6684361063107478595L;
+
+                       public String getText(Object element) {
+                               try {
+                                       return ((Property) element).getName();
+                               } catch (RepositoryException e) {
+                                       throw new EclipseUiException("Unexpected exception in label provider", e);
+                               }
+                       }
+               });
+               col = new TableViewerColumn(propertiesViewer, SWT.NONE);
+               col.getColumn().setText("Value");
+               col.getColumn().setWidth(400);
+               col.setLabelProvider(new ColumnLabelProvider() {
+                       private static final long serialVersionUID = -8201994187693336657L;
+
+                       public String getText(Object element) {
+                               try {
+                                       Property property = (Property) element;
+                                       if (property.getType() == PropertyType.BINARY)
+                                               return "<binary>";
+                                       else if (property.isMultiple()) {
+                                               StringBuffer buf = new StringBuffer("[");
+                                               Value[] values = property.getValues();
+                                               for (int i = 0; i < values.length; i++) {
+                                                       if (i != 0)
+                                                               buf.append(", ");
+                                                       buf.append(values[i].getString());
+                                               }
+                                               buf.append(']');
+                                               return buf.toString();
+                                       } else
+                                               return property.getValue().getString();
+                               } catch (RepositoryException e) {
+                                       throw new EclipseUiException("Unexpected exception in label provider", e);
+                               }
+                       }
+               });
+               col = new TableViewerColumn(propertiesViewer, SWT.NONE);
+               col.getColumn().setText("Type");
+               col.getColumn().setWidth(200);
+               col.setLabelProvider(new ColumnLabelProvider() {
+                       private static final long serialVersionUID = -6009599998150286070L;
+
+                       public String getText(Object element) {
+                               return JcrBrowserUtils.getPropertyTypeAsString((Property) element);
+                       }
+               });
+               propertiesViewer.setInput("");
+               return propertiesViewer;
+       }
+
+       protected TreeViewer getNodeViewer() {
+               return nodesViewer;
+       }
+
+       /**
+        * Resets the tree content provider
+        * 
+        * @param sortChildNodes if true the content provider will use a comparer to
+        *                       sort nodes that might slow down the display
+        */
+       public void setSortChildNodes(boolean sortChildNodes) {
+               this.sortChildNodes = sortChildNodes;
+               ((NodeContentProvider) nodesViewer.getContentProvider()).setSortChildren(sortChildNodes);
+               nodesViewer.setInput("");
+       }
+
+       /** Notifies the current view that a node has been added */
+       public void nodeAdded(TreeParent parentNode) {
+               // insure that Ui objects have been correctly created:
+               JcrBrowserUtils.forceRefreshIfNeeded(parentNode);
+               getNodeViewer().refresh(parentNode);
+               getNodeViewer().expandToLevel(parentNode, 1);
+       }
+
+       /** Notifies the current view that a node has been removed */
+       public void nodeRemoved(TreeParent parentNode) {
+               IStructuredSelection newSel = new StructuredSelection(parentNode);
+               getNodeViewer().setSelection(newSel, true);
+               // Force refresh
+               IStructuredSelection tmpSel = (IStructuredSelection) getNodeViewer().getSelection();
+               getNodeViewer().refresh(tmpSel.getFirstElement());
+       }
+
+       class TreeObserver extends AsyncUiEventListener {
+
+               public TreeObserver(Display display) {
+                       super(display);
+               }
+
+               @Override
+               protected Boolean willProcessInUiThread(List<Event> events) throws RepositoryException {
+                       for (Event event : events) {
+                               if (getLog().isTraceEnabled())
+                                       getLog().debug("Received event " + event);
+                               String path = event.getPath();
+                               int index = path.lastIndexOf('/');
+                               String propertyName = path.substring(index + 1);
+                               if (getLog().isTraceEnabled())
+                                       getLog().debug("Concerned property " + propertyName);
+                       }
+                       return false;
+               }
+
+               protected void onEventInUiThread(List<Event> events) throws RepositoryException {
+                       if (getLog().isTraceEnabled())
+                               getLog().trace("Refresh result list");
+                       nodesViewer.refresh();
+               }
+
+       }
+
+       public boolean getSortChildNodes() {
+               return sortChildNodes;
+       }
+
+       public void setFocus() {
+               getNodeViewer().getTree().setFocus();
+       }
+
+       /* DEPENDENCY INJECTION */
+       // public void setRepositoryRegister(RepositoryRegister repositoryRegister) {
+       // this.repositoryRegister = repositoryRegister;
+       // }
+
+       public void setKeyring(CryptoKeyring keyring) {
+               this.keyring = keyring;
+       }
+
+       public void setRepositoryFactory(RepositoryFactory repositoryFactory) {
+               this.repositoryFactory = repositoryFactory;
+       }
+
+       public void setNodeRepository(Repository nodeRepository) {
+               this.nodeRepository = nodeRepository;
+       }
+}
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/jcr/JcrE4DClickListener.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/jcr/JcrE4DClickListener.java
new file mode 100644 (file)
index 0000000..f4ee2e8
--- /dev/null
@@ -0,0 +1,36 @@
+package org.argeo.cms.e4.jcr;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+
+import org.argeo.cms.swt.CmsException;
+import org.argeo.cms.ui.jcr.JcrDClickListener;
+import org.eclipse.e4.ui.model.application.ui.basic.MPart;
+import org.eclipse.e4.ui.workbench.modeling.EPartService;
+import org.eclipse.e4.ui.workbench.modeling.EPartService.PartState;
+import org.eclipse.jface.viewers.TreeViewer;
+
+public class JcrE4DClickListener extends JcrDClickListener {
+       EPartService partService;
+
+       public JcrE4DClickListener(TreeViewer nodeViewer, EPartService partService) {
+               super(nodeViewer);
+               this.partService = partService;
+       }
+
+       @Override
+       protected void openNode(Node node) {
+               MPart part = partService.createPart(JcrNodeEditor.DESCRIPTOR_ID);
+               try {
+                       part.setLabel(node.getName());
+                       part.getPersistedState().put("nodeWorkspace", node.getSession().getWorkspace().getName());
+                       part.getPersistedState().put("nodePath", node.getPath());
+               } catch (RepositoryException e) {
+                       throw new CmsException("Cannot open " + node, e);
+               }
+
+               // the provided part is be shown
+               partService.showPart(part, PartState.ACTIVATE);
+       }
+
+}
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/jcr/JcrNodeEditor.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/jcr/JcrNodeEditor.java
new file mode 100644 (file)
index 0000000..ae2b325
--- /dev/null
@@ -0,0 +1,26 @@
+package org.argeo.cms.e4.jcr;
+
+import java.util.List;
+
+import javax.annotation.PostConstruct;
+import javax.jcr.Node;
+
+import org.argeo.cms.ui.jcr.model.SingleJcrNodeElem;
+import org.eclipse.e4.ui.model.application.ui.basic.MPart;
+import org.eclipse.e4.ui.workbench.modeling.ESelectionService;
+import org.eclipse.swt.layout.FillLayout;
+import org.eclipse.swt.widgets.Composite;
+
+public class JcrNodeEditor {
+       final static String DESCRIPTOR_ID = "org.argeo.cms.e4.partdescriptor.nodeEditor";
+
+       @PostConstruct
+       public void createUi(Composite parent, MPart part, ESelectionService selectionService) {
+               parent.setLayout(new FillLayout());
+               List<?> selection = (List<?>) selectionService.getSelection();
+               Node node = ((SingleJcrNodeElem) selection.get(0)).getNode();
+               GenericPropertyPage propertyPage = new GenericPropertyPage(node);
+               propertyPage.createFormContent(parent);
+       }
+
+}
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/jcr/SimplePart.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/jcr/SimplePart.java
new file mode 100644 (file)
index 0000000..17d8d2a
--- /dev/null
@@ -0,0 +1,19 @@
+package org.argeo.cms.e4.jcr;
+
+import javax.annotation.PostConstruct;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Label;
+
+public class SimplePart {
+
+       @PostConstruct
+       void init(Composite parent) {
+               parent.setLayout(new GridLayout());
+               Label label = new Label(parent, SWT.NONE);
+               label.setText("Hello e4 World");
+       }
+
+}
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/jcr/handlers/AddFolderNode.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/jcr/handlers/AddFolderNode.java
new file mode 100644 (file)
index 0000000..09fa760
--- /dev/null
@@ -0,0 +1,67 @@
+package org.argeo.cms.e4.jcr.handlers;
+
+import java.util.List;
+
+import javax.inject.Named;
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.nodetype.NodeType;
+
+import org.argeo.cms.e4.jcr.JcrBrowserView;
+import org.argeo.cms.ui.jcr.model.SingleJcrNodeElem;
+import org.argeo.cms.ui.jcr.model.WorkspaceElem;
+import org.argeo.cms.ux.widgets.TreeParent;
+import org.argeo.eclipse.ui.dialogs.ErrorFeedback;
+import org.argeo.eclipse.ui.dialogs.SingleValue;
+import org.eclipse.e4.core.di.annotations.Execute;
+import org.eclipse.e4.ui.model.application.ui.basic.MPart;
+import org.eclipse.e4.ui.services.IServiceConstants;
+import org.eclipse.e4.ui.workbench.modeling.ESelectionService;
+
+/**
+ * Adds a node of type nt:folder, only on {@link SingleJcrNodeElem} and
+ * {@link WorkspaceElem} TreeObject types.
+ * 
+ * This handler assumes that a selection provider is available and picks only
+ * first selected item. It is UI's job to enable the command only when the
+ * selection contains one and only one element. Thus no parameter is passed
+ * through the command.
+ */
+public class AddFolderNode {
+       @Execute
+       public void execute(@Named(IServiceConstants.ACTIVE_PART) MPart part, ESelectionService selectionService) {
+               List<?> selection = (List<?>) selectionService.getSelection();
+               JcrBrowserView view = (JcrBrowserView) part.getObject();
+
+               if (selection != null && selection.size() == 1) {
+                       TreeParent treeParentNode = null;
+                       Node jcrParentNode = null;
+                       Object obj = selection.get(0);
+
+                       if (obj instanceof SingleJcrNodeElem) {
+                               treeParentNode = (TreeParent) obj;
+                               jcrParentNode = ((SingleJcrNodeElem) treeParentNode).getNode();
+                       } else if (obj instanceof WorkspaceElem) {
+                               treeParentNode = (TreeParent) obj;
+                               jcrParentNode = ((WorkspaceElem) treeParentNode).getRootNode();
+                       } else
+                               return;
+
+                       String folderName = SingleValue.ask("Folder name", "Enter folder name");
+                       if (folderName != null) {
+                               try {
+                                       jcrParentNode.addNode(folderName, NodeType.NT_FOLDER);
+                                       jcrParentNode.getSession().save();
+                                       view.nodeAdded(treeParentNode);
+                               } catch (RepositoryException e) {
+                                       ErrorFeedback.show("Cannot create folder " + folderName + " under " + treeParentNode, e);
+                               }
+                       }
+               } else {
+                       // ErrorFeedback.show(WorkbenchUiPlugin
+                       // .getMessage("errorUnvalidNtFolderNodeType"));
+                       ErrorFeedback.show("Invalid NT folder node type");
+               }
+       }
+
+}
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/jcr/handlers/AddRemoteRepository.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/jcr/handlers/AddRemoteRepository.java
new file mode 100644 (file)
index 0000000..8b8d8b7
--- /dev/null
@@ -0,0 +1,210 @@
+package org.argeo.cms.e4.jcr.handlers;
+
+import java.net.URI;
+import java.util.Hashtable;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.jcr.Node;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryFactory;
+import javax.jcr.Session;
+import javax.jcr.SimpleCredentials;
+
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.api.cms.keyring.Keyring;
+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.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;
+               }
+       }
+}
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/jcr/handlers/DeleteNodes.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/jcr/handlers/DeleteNodes.java
new file mode 100644 (file)
index 0000000..b8de06b
--- /dev/null
@@ -0,0 +1,95 @@
+package org.argeo.cms.e4.jcr.handlers;
+
+import java.util.List;
+
+import javax.inject.Named;
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+
+import org.argeo.cms.e4.jcr.JcrBrowserView;
+import org.argeo.cms.ui.jcr.model.SingleJcrNodeElem;
+import org.argeo.cms.ui.jcr.model.WorkspaceElem;
+import org.argeo.cms.ux.widgets.TreeParent;
+import org.argeo.eclipse.ui.EclipseUiException;
+import org.argeo.eclipse.ui.dialogs.ErrorFeedback;
+import org.eclipse.e4.core.di.annotations.Execute;
+import org.eclipse.e4.ui.model.application.ui.basic.MPart;
+import org.eclipse.e4.ui.services.IServiceConstants;
+import org.eclipse.e4.ui.workbench.modeling.ESelectionService;
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.swt.widgets.Display;
+
+/**
+ * Delete the selected nodes: both in the JCR repository and in the UI view.
+ * Warning no check is done, except implementation dependent native checks,
+ * handle with care.
+ * 
+ * This handler is still 'hard linked' to a GenericJcrBrowser view to enable
+ * correct tree refresh when a node is added. This must be corrected in future
+ * versions.
+ */
+public class DeleteNodes {
+       @Execute
+       public void execute(@Named(IServiceConstants.ACTIVE_PART) MPart part, ESelectionService selectionService) {
+               List<?> selection = (List<?>) selectionService.getSelection();
+               if (selection == null)
+                       return;
+
+               JcrBrowserView view = (JcrBrowserView) part.getObject();
+
+               // confirmation
+               StringBuffer buf = new StringBuffer("");
+               for (Object o : selection) {
+                       SingleJcrNodeElem sjn = (SingleJcrNodeElem) o;
+                       buf.append(sjn.getName()).append(' ');
+               }
+               Boolean doRemove = MessageDialog.openConfirm(Display.getCurrent().getActiveShell(), "Confirm deletion",
+                               "Do you want to delete " + buf + "?");
+
+               // operation
+               if (doRemove) {
+                       SingleJcrNodeElem ancestor = null;
+                       WorkspaceElem rootAncestor = null;
+                       try {
+                               for (Object obj : selection) {
+                                       if (obj instanceof SingleJcrNodeElem) {
+                                               // Cache objects
+                                               SingleJcrNodeElem sjn = (SingleJcrNodeElem) obj;
+                                               TreeParent tp = (TreeParent) sjn.getParent();
+                                               Node node = sjn.getNode();
+
+                                               // Jcr Remove
+                                               node.remove();
+                                               node.getSession().save();
+                                               // UI remove
+                                               tp.removeChild(sjn);
+
+                                               // Check if the parent is the root node
+                                               if (tp instanceof WorkspaceElem)
+                                                       rootAncestor = (WorkspaceElem) tp;
+                                               else
+                                                       ancestor = getOlder(ancestor, (SingleJcrNodeElem) tp);
+                                       }
+                               }
+                               if (rootAncestor != null)
+                                       view.nodeRemoved(rootAncestor);
+                               else if (ancestor != null)
+                                       view.nodeRemoved(ancestor);
+                       } catch (Exception e) {
+                               ErrorFeedback.show("Cannot delete selected node ", e);
+                       }
+               }
+       }
+
+       private SingleJcrNodeElem getOlder(SingleJcrNodeElem A, SingleJcrNodeElem B) {
+               try {
+                       if (A == null)
+                               return B == null ? null : B;
+                       // Todo enhanced this method
+                       else
+                               return A.getNode().getDepth() <= B.getNode().getDepth() ? A : B;
+               } catch (RepositoryException re) {
+                       throw new EclipseUiException("Cannot find ancestor", re);
+               }
+       }
+}
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/jcr/handlers/Refresh.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/jcr/handlers/Refresh.java
new file mode 100644 (file)
index 0000000..036e70a
--- /dev/null
@@ -0,0 +1,43 @@
+package org.argeo.cms.e4.jcr.handlers;
+
+import java.util.List;
+
+import javax.inject.Named;
+
+import org.argeo.cms.e4.jcr.JcrBrowserView;
+import org.argeo.cms.ui.jcr.JcrBrowserUtils;
+import org.argeo.cms.ux.widgets.TreeParent;
+import org.eclipse.e4.core.di.annotations.Execute;
+import org.eclipse.e4.ui.model.application.ui.basic.MPart;
+import org.eclipse.e4.ui.services.IServiceConstants;
+import org.eclipse.e4.ui.workbench.modeling.EPartService;
+import org.eclipse.e4.ui.workbench.modeling.ESelectionService;
+
+/**
+ * Force the selected objects of the active view to be refreshed doing the
+ * following:
+ * <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
+       }
+}
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/jcr/handlers/RenameNode.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/jcr/handlers/RenameNode.java
new file mode 100644 (file)
index 0000000..97674ab
--- /dev/null
@@ -0,0 +1,59 @@
+package org.argeo.cms.e4.jcr.handlers;
+
+import java.util.List;
+
+import javax.inject.Named;
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.argeo.cms.e4.jcr.JcrBrowserView;
+import org.argeo.cms.ui.jcr.model.SingleJcrNodeElem;
+import org.argeo.eclipse.ui.EclipseUiException;
+import org.argeo.eclipse.ui.dialogs.SingleValue;
+import org.argeo.jcr.JcrUtils;
+import org.eclipse.e4.core.di.annotations.Execute;
+import org.eclipse.e4.ui.model.application.ui.basic.MPart;
+import org.eclipse.e4.ui.services.IServiceConstants;
+import org.eclipse.e4.ui.workbench.modeling.EPartService;
+import org.eclipse.e4.ui.workbench.modeling.ESelectionService;
+
+/**
+ * Canonically call JCR Session#move(String, String) on the first element
+ * returned by HandlerUtil#getActiveWorkbenchWindow()
+ * (...getActivePage().getSelection()), if it is a {@link SingleJcrNodeElem}.
+ * The user must then fill a new name in and confirm
+ */
+public class RenameNode {
+       @Execute
+       public void execute(@Named(IServiceConstants.ACTIVE_PART) MPart part, EPartService partService,
+                       ESelectionService selectionService) {
+               List<?> selection = (List<?>) selectionService.getSelection();
+               if (selection == null || selection.size() != 1)
+                       return;
+               JcrBrowserView view = (JcrBrowserView) part.getObject();
+
+               Object element = selection.get(0);
+               if (element instanceof SingleJcrNodeElem) {
+                       SingleJcrNodeElem sjn = (SingleJcrNodeElem) element;
+                       Node node = sjn.getNode();
+                       Session session = null;
+                       String newName = null;
+                       String oldPath = null;
+                       try {
+                               newName = SingleValue.ask("New node name", "Please provide a new name for [" + node.getName() + "]");
+                               // TODO sanity check and user feedback
+                               newName = JcrUtils.replaceInvalidChars(newName);
+                               oldPath = node.getPath();
+                               session = node.getSession();
+                               session.move(oldPath, JcrUtils.parentPath(oldPath) + "/" + newName);
+                               session.save();
+
+                               // Manually refresh the browser view. Must be enhanced
+                               view.refresh(sjn);
+                       } catch (RepositoryException e) {
+                               throw new EclipseUiException("Unable to rename " + node + " to " + newName, e);
+                       }
+               }
+       }
+}
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/jcr/handlers/package-info.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/jcr/handlers/package-info.java
new file mode 100644 (file)
index 0000000..4e075e2
--- /dev/null
@@ -0,0 +1,2 @@
+/** JCR browser handlers. */
+package org.argeo.cms.e4.jcr.handlers;
\ No newline at end of file
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/jcr/package-info.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/jcr/package-info.java
new file mode 100644 (file)
index 0000000..3e92fb0
--- /dev/null
@@ -0,0 +1,2 @@
+/** JCR browser perspective. */
+package org.argeo.cms.e4.jcr;
\ No newline at end of file
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/maintenance/AbstractOsgiComposite.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/maintenance/AbstractOsgiComposite.java
new file mode 100644 (file)
index 0000000..4fd1d68
--- /dev/null
@@ -0,0 +1,41 @@
+package org.argeo.cms.e4.maintenance;
+
+import java.util.Collection;
+
+import org.argeo.api.cms.CmsLog;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.widgets.Composite;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceReference;
+
+abstract class AbstractOsgiComposite extends Composite {
+       private static final long serialVersionUID = -4097415973477517137L;
+       protected final BundleContext bc = FrameworkUtil.getBundle(getClass()).getBundleContext();
+       protected final CmsLog log = CmsLog.getLog(getClass());
+
+       public AbstractOsgiComposite(Composite parent, int style) {
+               super(parent, style);
+               parent.setLayout(CmsSwtUtils.noSpaceGridLayout());
+               setLayout(CmsSwtUtils.noSpaceGridLayout());
+               setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
+               initUi(style);
+       }
+
+       protected abstract void initUi(int style);
+
+       protected <T> 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);
+               }
+       }
+}
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/maintenance/Browse.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/maintenance/Browse.java
new file mode 100644 (file)
index 0000000..a536da0
--- /dev/null
@@ -0,0 +1,576 @@
+package org.argeo.cms.e4.maintenance;
+
+import static org.eclipse.swt.SWT.RIGHT;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.LinkedHashMap;
+
+import javax.jcr.ItemNotFoundException;
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.Property;
+import javax.jcr.PropertyIterator;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import javax.jcr.Value;
+
+import org.argeo.api.cms.ux.Cms2DSize;
+import org.argeo.cms.swt.CmsException;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.ui.CmsUiProvider;
+import org.argeo.cms.ui.util.CmsLink;
+import org.argeo.cms.ui.widgets.EditableImage;
+import org.argeo.cms.ui.widgets.Img;
+import org.argeo.jcr.JcrUtils;
+import org.eclipse.jface.viewers.ColumnLabelProvider;
+import org.eclipse.jface.viewers.ILazyContentProvider;
+import org.eclipse.jface.viewers.ISelectionChangedListener;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.viewers.SelectionChangedEvent;
+import org.eclipse.jface.viewers.StructuredSelection;
+import org.eclipse.jface.viewers.TableViewer;
+import org.eclipse.jface.viewers.TableViewerColumn;
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.ScrolledComposite;
+import org.eclipse.swt.events.ControlAdapter;
+import org.eclipse.swt.events.ControlEvent;
+import org.eclipse.swt.events.KeyEvent;
+import org.eclipse.swt.events.KeyListener;
+import org.eclipse.swt.events.ModifyEvent;
+import org.eclipse.swt.events.ModifyListener;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Table;
+import org.eclipse.swt.widgets.TableColumn;
+import org.eclipse.swt.widgets.Text;
+
+public class Browse implements CmsUiProvider {
+
+       // Some local constants to experiment. should be cleaned
+       private final static String BROWSE_PREFIX = "browse#";
+       private final static int THUMBNAIL_WIDTH = 400;
+       private final static int COLUMN_WIDTH = 160;
+       private DateFormat timeFormatter = new SimpleDateFormat("dd-MM-yyyy', 'HH:mm");
+
+       // keep a cache of the opened nodes
+       // Key is the path
+       private LinkedHashMap<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
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/maintenance/ConnectivityDeploymentUi.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/maintenance/ConnectivityDeploymentUi.java
new file mode 100644 (file)
index 0000000..97f3e67
--- /dev/null
@@ -0,0 +1,48 @@
+package org.argeo.cms.e4.maintenance;
+
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Label;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.http.HttpService;
+import org.osgi.service.useradmin.UserAdmin;
+
+class ConnectivityDeploymentUi extends AbstractOsgiComposite {
+       private static final long serialVersionUID = 590221539553514693L;
+
+       public ConnectivityDeploymentUi(Composite parent, int style) {
+               super(parent, style);
+       }
+
+       @Override
+       protected void initUi(int style) {
+               StringBuffer text = new StringBuffer();
+               text.append("<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;
+       }
+}
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/maintenance/DataDeploymentUi.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/maintenance/DataDeploymentUi.java
new file mode 100644 (file)
index 0000000..ef95bde
--- /dev/null
@@ -0,0 +1,139 @@
+package org.argeo.cms.e4.maintenance;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.FileStore;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Collection;
+
+import org.apache.jackrabbit.core.RepositoryContext;
+import org.apache.jackrabbit.core.config.RepositoryConfig;
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Label;
+import org.osgi.framework.ServiceReference;
+
+class DataDeploymentUi extends AbstractOsgiComposite {
+       private static final long serialVersionUID = 590221539553514693L;
+
+       public DataDeploymentUi(Composite parent, int style) {
+               super(parent, style);
+       }
+
+       @Override
+       protected void initUi(int style) {
+               if (isDeployed()) {
+                       initCurrentUi(this);
+               } else {
+                       initNewUi(this);
+               }
+       }
+
+       private void initNewUi(Composite parent) {
+//             try {
+//                     ConfigurationAdmin confAdmin = bc.getService(bc.getServiceReference(ConfigurationAdmin.class));
+//                     Configuration[] confs = confAdmin.listConfigurations(
+//                                     "(" + ConfigurationAdmin.SERVICE_FACTORYPID + "=" + NodeConstants.NODE_REPOS_FACTORY_PID + ")");
+//                     if (confs == null || confs.length == 0) {
+//                             Group buttonGroup = new Group(parent, SWT.NONE);
+//                             buttonGroup.setText("Repository Type");
+//                             buttonGroup.setLayout(new GridLayout(2, true));
+//                             buttonGroup.setLayoutData(new GridData(GridData.FILL_VERTICAL));
+//
+//                             SelectionListener selectionListener = new SelectionAdapter() {
+//                                     private static final long serialVersionUID = 6247064348421088092L;
+//
+//                                     public void widgetSelected(SelectionEvent event) {
+//                                             Button radio = (Button) event.widget;
+//                                             if (!radio.getSelection())
+//                                                     return;
+//                                             log.debug(event);
+//                                             JackrabbitType nodeType = (JackrabbitType) radio.getData();
+//                                             if (log.isDebugEnabled())
+//                                                     log.debug(" selected = " + nodeType.name());
+//                                     };
+//                             };
+//
+//                             for (JackrabbitType nodeType : JackrabbitType.values()) {
+//                                     Button radio = new Button(buttonGroup, SWT.RADIO);
+//                                     radio.setText(nodeType.name());
+//                                     radio.setData(nodeType);
+//                                     if (nodeType.equals(JackrabbitType.localfs))
+//                                             radio.setSelection(true);
+//                                     radio.addSelectionListener(selectionListener);
+//                             }
+//
+//                     } else if (confs.length == 1) {
+//
+//                     } else {
+//                             throw new CmsException("Multiple repos not yet supported");
+//                     }
+//             } catch (Exception e) {
+//                     throw new CmsException("Cannot initialize UI", e);
+//             }
+
+       }
+
+       private void initCurrentUi(Composite parent) {
+               parent.setLayout(new GridLayout());
+               Collection<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;
+       }
+
+}
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/maintenance/DeploymentEntryPoint.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/maintenance/DeploymentEntryPoint.java
new file mode 100644 (file)
index 0000000..0a28dc5
--- /dev/null
@@ -0,0 +1,95 @@
+package org.argeo.cms.e4.maintenance;
+
+import java.util.GregorianCalendar;
+import java.util.TimeZone;
+
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.api.cms.CmsContext;
+import org.argeo.api.cms.CmsDeployment;
+import org.argeo.api.cms.CmsState;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.FillLayout;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Group;
+import org.eclipse.swt.widgets.Label;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.ServiceReference;
+
+class DeploymentEntryPoint {
+       private final BundleContext bc = FrameworkUtil.getBundle(getClass()).getBundleContext();
+
+       protected void createContents(Composite parent) {
+               // FIXME manage authentication if needed
+               // if (!CurrentUser.roles().contains(AuthConstants.ROLE_ADMIN))
+               // return;
+
+               // parent.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
+               if (isDesktop()) {
+                       parent.setLayout(new GridLayout(2, true));
+               } else {
+                       // TODO add scrolling
+                       parent.setLayout(new GridLayout(1, true));
+               }
+
+               initHighLevelSummary(parent);
+
+               Group securityGroup = createHighLevelGroup(parent, "Security");
+               securityGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
+               new SecurityDeploymentUi(securityGroup, SWT.NONE);
+
+               Group dataGroup = createHighLevelGroup(parent, "Data");
+               dataGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
+               new DataDeploymentUi(dataGroup, SWT.NONE);
+
+               Group logGroup = createHighLevelGroup(parent, "Notifications");
+               logGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, true));
+               new LogDeploymentUi(logGroup, SWT.NONE);
+
+               Group connectivityGroup = createHighLevelGroup(parent, "Connectivity");
+               new ConnectivityDeploymentUi(connectivityGroup, SWT.NONE);
+               connectivityGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, true));
+
+       }
+
+       private void initHighLevelSummary(Composite parent) {
+               Composite composite = new Composite(parent, SWT.NONE);
+               GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, false);
+               if (isDesktop())
+                       gridData.horizontalSpan = 3;
+               composite.setLayoutData(gridData);
+               composite.setLayout(new FillLayout());
+
+               ServiceReference<CmsState> nodeStateRef = bc.getServiceReference(CmsState.class);
+               if (nodeStateRef == null)
+                       throw new IllegalStateException("No CMS state available");
+               CmsState nodeState = bc.getService(nodeStateRef);
+               ServiceReference<CmsContext> nodeDeploymentRef = bc.getServiceReference(CmsContext.class);
+               Label label = new Label(composite, SWT.WRAP);
+               CmsSwtUtils.markup(label);
+               if (nodeDeploymentRef == null) {
+                       label.setText("Not yet deployed on, please configure below.");
+               } else {
+                       Object stateUuid = nodeStateRef.getProperty(CmsConstants.CN);
+                       CmsContext nodeDeployment = bc.getService(nodeDeploymentRef);
+                       GregorianCalendar calendar = new GregorianCalendar();
+                       calendar.setTimeInMillis(nodeDeployment.getAvailableSince());
+                       calendar.setTimeZone(TimeZone.getDefault());
+                       label.setText("Deployment state " + stateUuid + ", available since <b>" + calendar.getTime() + "</b>");
+               }
+       }
+
+       private static Group createHighLevelGroup(Composite parent, String text) {
+               Group group = new Group(parent, SWT.NONE);
+               group.setText(text);
+               CmsSwtUtils.markup(group);
+               return group;
+       }
+
+       private boolean isDesktop() {
+               return true;
+       }
+}
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/maintenance/LogDeploymentUi.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/maintenance/LogDeploymentUi.java
new file mode 100644 (file)
index 0000000..fa5d3da
--- /dev/null
@@ -0,0 +1,73 @@
+package org.argeo.cms.e4.maintenance;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Enumeration;
+import java.util.GregorianCalendar;
+import java.util.TimeZone;
+
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Text;
+import org.osgi.service.log.LogEntry;
+import org.osgi.service.log.LogListener;
+import org.osgi.service.log.LogReaderService;
+
+class LogDeploymentUi extends AbstractOsgiComposite implements LogListener {
+       private static final long serialVersionUID = 590221539553514693L;
+
+       private DateFormat dateFormat = new SimpleDateFormat("MMdd HH:mm");
+
+       private Display display;
+       private Text logDisplay;
+
+       public LogDeploymentUi(Composite parent, int style) {
+               super(parent, style);
+       }
+
+       @Override
+       protected void initUi(int style) {
+               LogReaderService logReader = getService(LogReaderService.class);
+               // FIXME use server push
+               // logReader.addLogListener(this);
+               this.display = getDisplay();
+               this.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
+               logDisplay = new Text(this, SWT.WRAP | SWT.MULTI | SWT.READ_ONLY);
+               logDisplay.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
+               CmsSwtUtils.markup(logDisplay);
+               Enumeration<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);
+       // }
+}
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/maintenance/MaintenanceStyles.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/maintenance/MaintenanceStyles.java
new file mode 100644 (file)
index 0000000..df1be51
--- /dev/null
@@ -0,0 +1,10 @@
+package org.argeo.cms.e4.maintenance;
+
+/** Specific styles used by the various maintenance pages . */
+public interface MaintenanceStyles {
+       // General
+       public final static String PREFIX = "maintenance_";
+
+       // Browser
+       public final static String BROWSER_COLUMN = "browser_column";
+       }
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/maintenance/NonAdminPage.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/maintenance/NonAdminPage.java
new file mode 100644 (file)
index 0000000..cb38ce8
--- /dev/null
@@ -0,0 +1,30 @@
+package org.argeo.cms.e4.maintenance;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.ui.CmsUiProvider;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+
+public class NonAdminPage implements CmsUiProvider{
+
+       @Override
+       public Control createUi(Composite parent, Node context)
+                       throws RepositoryException {
+               Composite body = new Composite(parent, SWT.NO_FOCUS);
+               body.setLayoutData(CmsSwtUtils.fillAll());
+               body.setLayout(new GridLayout());
+               Label label = new Label(body, SWT.NONE);
+               label.setText("You should be an admin to perform maintenance operations. "
+                               + "Are you sure you are logged in?");
+               label.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, true, true));
+               return null;
+       }
+       
+}
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/maintenance/SecurityDeploymentUi.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/maintenance/SecurityDeploymentUi.java
new file mode 100644 (file)
index 0000000..3492c54
--- /dev/null
@@ -0,0 +1,85 @@
+package org.argeo.cms.e4.maintenance;
+
+import java.net.URI;
+
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Label;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.useradmin.Role;
+import org.osgi.service.useradmin.UserAdmin;
+
+class SecurityDeploymentUi extends AbstractOsgiComposite {
+       private static final long serialVersionUID = 590221539553514693L;
+
+       public SecurityDeploymentUi(Composite parent, int style) {
+               super(parent, style);
+       }
+
+       @Override
+       protected void initUi(int style) {
+               if (isDeployed()) {
+                       initCurrentUi(this);
+               } else {
+                       initNewUi(this);
+               }
+       }
+
+       private void initNewUi(Composite parent) {
+               new Label(parent, SWT.NONE).setText("Security is not configured");
+       }
+
+       private void initCurrentUi(Composite parent) {
+               ServiceReference<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;
+       }
+}
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/maintenance/package-info.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/maintenance/package-info.java
new file mode 100644 (file)
index 0000000..e4d2ad4
--- /dev/null
@@ -0,0 +1,2 @@
+/** Maintenance perspective. */
+package org.argeo.cms.e4.maintenance;
\ No newline at end of file
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/monitoring/BundleNode.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/monitoring/BundleNode.java
new file mode 100644 (file)
index 0000000..e953683
--- /dev/null
@@ -0,0 +1,46 @@
+package org.argeo.cms.e4.monitoring;
+
+import org.argeo.cms.ux.widgets.TreeParent;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.ServiceReference;
+
+/** A tree element representing a {@link Bundle} */
+class BundleNode extends TreeParent {
+       private final Bundle bundle;
+
+       public BundleNode(Bundle bundle) {
+               this(bundle, false);
+       }
+
+       @SuppressWarnings("rawtypes")
+       public BundleNode(Bundle bundle, boolean hasChildren) {
+               super(bundle.getSymbolicName());
+               this.bundle = bundle;
+
+               if (hasChildren) {
+                       // REFERENCES
+                       ServiceReference[] usedServices = bundle.getServicesInUse();
+                       if (usedServices != null) {
+                               for (ServiceReference sr : usedServices) {
+                                       if (sr != null)
+                                               addChild(new ServiceReferenceNode(sr, false));
+                               }
+                       }
+
+                       // SERVICES
+                       ServiceReference[] registeredServices = bundle
+                                       .getRegisteredServices();
+                       if (registeredServices != null) {
+                               for (ServiceReference sr : registeredServices) {
+                                       if (sr != null)
+                                               addChild(new ServiceReferenceNode(sr, true));
+                               }
+                       }
+               }
+
+       }
+
+       Bundle getBundle() {
+               return bundle;
+       }
+}
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/monitoring/BundlesView.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/monitoring/BundlesView.java
new file mode 100644 (file)
index 0000000..c639255
--- /dev/null
@@ -0,0 +1,114 @@
+//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) {
+               }
+       }
+}
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/monitoring/CmsSessionsView.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/monitoring/CmsSessionsView.java
new file mode 100644 (file)
index 0000000..0f98f58
--- /dev/null
@@ -0,0 +1,173 @@
+//package org.argeo.eclipse.ui.workbench.osgi;
+//public class BundlesView {}
+
+package org.argeo.cms.e4.monitoring;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import javax.annotation.PostConstruct;
+
+import org.argeo.api.cms.CmsSession;
+import org.argeo.cms.RoleNameUtils;
+import org.argeo.cms.util.LangUtils;
+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.BundleContext;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceReference;
+
+/**
+ * Overview of the active CMS sessions.
+ */
+public class CmsSessionsView {
+       private final static BundleContext bc = FrameworkUtil.getBundle(CmsSessionsView.class).getBundleContext();
+
+       private TableViewer viewer;
+
+       @PostConstruct
+       public void createPartControl(Composite parent) {
+               viewer = new TableViewer(parent);
+               viewer.setContentProvider(new CmsSessionContentProvider());
+               viewer.getTable().setHeaderVisible(true);
+
+               EclipseUiSpecificUtils.enableToolTipSupport(viewer);
+
+               int longColWidth = 150;
+               int smallColWidth = 100;
+
+               // Display name
+               TableViewerColumn column = new TableViewerColumn(viewer, SWT.NONE);
+               column.getColumn().setWidth(longColWidth);
+               column.getColumn().setText("User");
+               column.setLabelProvider(new ColumnLabelProvider() {
+                       private static final long serialVersionUID = -5234573509093747505L;
+
+                       public String getText(Object element) {
+                               return ((CmsSession) element).getDisplayName();
+                       }
+
+                       public String getToolTipText(Object element) {
+                               return ((CmsSession) element).getUserDn().toString();
+                       }
+               });
+               new ColumnViewerComparator(column);
+
+               // Creation time
+               column = new TableViewerColumn(viewer, SWT.NONE);
+               column.getColumn().setWidth(smallColWidth);
+               column.getColumn().setText("Since");
+               column.setLabelProvider(new ColumnLabelProvider() {
+                       private static final long serialVersionUID = -5234573509093747505L;
+
+                       public String getText(Object element) {
+                               return LangUtils.since(((CmsSession) element).getCreationTime());
+                       }
+
+                       public String getToolTipText(Object element) {
+                               return ((CmsSession) element).getCreationTime().toString();
+                       }
+               });
+               new ColumnViewerComparator(column);
+
+               // Username
+               column = new TableViewerColumn(viewer, SWT.NONE);
+               column.getColumn().setWidth(smallColWidth);
+               column.getColumn().setText("Username");
+               column.setLabelProvider(new ColumnLabelProvider() {
+                       private static final long serialVersionUID = -5234573509093747505L;
+
+                       public String getText(Object element) {
+                               String userDn = ((CmsSession) element).getUserDn();
+                               return RoleNameUtils.getLastRdnValue(userDn);
+                       }
+
+                       public String getToolTipText(Object element) {
+                               return ((CmsSession) element).getUserDn().toString();
+                       }
+               });
+               new ColumnViewerComparator(column);
+
+               // UUID
+               column = new TableViewerColumn(viewer, SWT.NONE);
+               column.getColumn().setWidth(smallColWidth);
+               column.getColumn().setText("UUID");
+               column.setLabelProvider(new ColumnLabelProvider() {
+                       private static final long serialVersionUID = -5234573509093747505L;
+
+                       public String getText(Object element) {
+                               return ((CmsSession) element).getUuid().toString();
+                       }
+
+                       public String getToolTipText(Object element) {
+                               return getText(element);
+                       }
+               });
+               new ColumnViewerComparator(column);
+
+               // Local ID
+               column = new TableViewerColumn(viewer, SWT.NONE);
+               column.getColumn().setWidth(smallColWidth);
+               column.getColumn().setText("Local ID");
+               column.setLabelProvider(new ColumnLabelProvider() {
+                       private static final long serialVersionUID = -5234573509093747505L;
+
+                       public String getText(Object element) {
+                               return ((CmsSession) element).getLocalId();
+                       }
+
+                       public String getToolTipText(Object element) {
+                               return getText(element);
+                       }
+               });
+               new ColumnViewerComparator(column);
+
+               viewer.setInput(bc);
+
+       }
+
+       @Focus
+       public void setFocus() {
+               if (viewer != null)
+                       viewer.getControl().setFocus();
+       }
+
+       /** Content provider managing the array of bundles */
+       private static class CmsSessionContentProvider implements IStructuredContentProvider {
+               private static final long serialVersionUID = -8533792785725875977L;
+
+               public Object[] getElements(Object inputElement) {
+                       if (inputElement instanceof BundleContext) {
+                               BundleContext bc = (BundleContext) inputElement;
+                               Collection<ServiceReference<CmsSession>> srs;
+                               try {
+                                       srs = bc.getServiceReferences(CmsSession.class, null);
+                               } catch (InvalidSyntaxException e) {
+                                       throw new IllegalArgumentException("Cannot retrieve CMS sessions", e);
+                               }
+                               List<CmsSession> res = new ArrayList<>();
+                               for (ServiceReference<CmsSession> sr : srs) {
+                                       res.add(bc.getService(sr));
+                               }
+                               return res.toArray();
+                       }
+                       return null;
+               }
+
+               public void dispose() {
+               }
+
+               public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
+               }
+       }
+}
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/monitoring/ModulesView.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/monitoring/ModulesView.java
new file mode 100644 (file)
index 0000000..6317882
--- /dev/null
@@ -0,0 +1,91 @@
+package org.argeo.cms.e4.monitoring;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.annotation.PostConstruct;
+
+import org.argeo.cms.ux.widgets.TreeParent;
+import org.eclipse.e4.ui.di.Focus;
+import org.eclipse.jface.viewers.ITreeContentProvider;
+import org.eclipse.jface.viewers.TreeViewer;
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.Composite;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
+
+/** The OSGi runtime from a module perspective. */
+public class ModulesView {
+       private final static BundleContext bc = FrameworkUtil.getBundle(ModulesView.class).getBundleContext();
+       private TreeViewer viewer;
+
+       @PostConstruct
+       public void createPartControl(Composite parent) {
+               viewer = new TreeViewer(parent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL);
+               viewer.setContentProvider(new ModulesContentProvider());
+               viewer.setLabelProvider(new ModulesLabelProvider());
+               viewer.setInput(bc);
+       }
+
+       @Focus
+       public void setFocus() {
+               viewer.getTree().setFocus();
+       }
+
+       private class ModulesContentProvider implements ITreeContentProvider {
+               private static final long serialVersionUID = 3819934804640641721L;
+
+               public Object[] getElements(Object inputElement) {
+                       return getChildren(inputElement);
+               }
+
+               public Object[] getChildren(Object parentElement) {
+                       if (parentElement instanceof BundleContext) {
+                               BundleContext bundleContext = (BundleContext) parentElement;
+                               Bundle[] bundles = bundleContext.getBundles();
+
+                               List<BundleNode> modules = new ArrayList<BundleNode>();
+                               for (Bundle bundle : bundles) {
+                                       if (bundle.getState() == Bundle.ACTIVE)
+                                               modules.add(new BundleNode(bundle, true));
+                               }
+                               return modules.toArray();
+                       } else if (parentElement instanceof TreeParent) {
+                               return ((TreeParent) parentElement).getChildren();
+                       } else {
+                               return null;
+                       }
+               }
+
+               public Object getParent(Object element) {
+                       // TODO Auto-generated method stub
+                       return null;
+               }
+
+               public boolean hasChildren(Object element) {
+                       if (element instanceof TreeParent) {
+                               return ((TreeParent) element).hasChildren();
+                       }
+                       return false;
+               }
+
+               public void dispose() {
+               }
+
+               public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
+               }
+       }
+
+       private class ModulesLabelProvider extends StateLabelProvider {
+               private static final long serialVersionUID = 5290046145534824722L;
+
+               @Override
+               public String getText(Object element) {
+                       if (element instanceof BundleNode)
+                               return element.toString() + " [" + ((BundleNode) element).getBundle().getBundleId() + "]";
+                       return element.toString();
+               }
+       }
+}
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/monitoring/OsgiConfigurationsView.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/monitoring/OsgiConfigurationsView.java
new file mode 100644 (file)
index 0000000..53e8033
--- /dev/null
@@ -0,0 +1,163 @@
+package org.argeo.cms.e4.monitoring;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.Dictionary;
+import java.util.List;
+
+import javax.annotation.PostConstruct;
+
+import org.argeo.cms.swt.CmsException;
+import org.argeo.cms.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());
+               }
+
+       }
+}
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/monitoring/OsgiExplorerImages.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/monitoring/OsgiExplorerImages.java
new file mode 100644 (file)
index 0000000..7217fe6
--- /dev/null
@@ -0,0 +1,15 @@
+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");
+}
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/monitoring/ServiceReferenceNode.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/monitoring/ServiceReferenceNode.java
new file mode 100644 (file)
index 0000000..1c60811
--- /dev/null
@@ -0,0 +1,46 @@
+package org.argeo.cms.e4.monitoring;
+
+import org.argeo.cms.ux.widgets.TreeParent;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.ServiceReference;
+
+/** A tree element representing a {@link ServiceReference} */
+@SuppressWarnings({ "rawtypes" })
+class ServiceReferenceNode extends TreeParent {
+       private final ServiceReference serviceReference;
+       private final boolean published;
+
+       public ServiceReferenceNode(ServiceReference serviceReference,
+                       boolean published) {
+               super(serviceReference.toString());
+               this.serviceReference = serviceReference;
+               this.published = published;
+
+               if (isPublished()) {
+                       Bundle[] usedBundles = serviceReference.getUsingBundles();
+                       if (usedBundles != null) {
+                               for (Bundle b : usedBundles) {
+                                       if (b != null)
+                                               addChild(new BundleNode(b));
+                               }
+                       }
+               } else {
+                       Bundle provider = serviceReference.getBundle();
+                       addChild(new BundleNode(provider));
+               }
+
+               for (String key : serviceReference.getPropertyKeys()) {
+                       addChild(new TreeParent(key + "="
+                                       + serviceReference.getProperty(key)));
+               }
+
+       }
+
+       public ServiceReference getServiceReference() {
+               return serviceReference;
+       }
+
+       public boolean isPublished() {
+               return published;
+       }
+}
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/monitoring/StateLabelProvider.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/monitoring/StateLabelProvider.java
new file mode 100644 (file)
index 0000000..5cb5b65
--- /dev/null
@@ -0,0 +1,82 @@
+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;
+               }
+       }
+}
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/monitoring/package-info.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/monitoring/package-info.java
new file mode 100644 (file)
index 0000000..873bf31
--- /dev/null
@@ -0,0 +1,2 @@
+/** Monitoring perspective. */
+package org.argeo.cms.e4.monitoring;
\ No newline at end of file
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/parts/EgoDashboard.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/parts/EgoDashboard.java
new file mode 100644 (file)
index 0000000..c27c13e
--- /dev/null
@@ -0,0 +1,41 @@
+package org.argeo.cms.e4.parts;
+
+import java.time.ZonedDateTime;
+
+import javax.annotation.PostConstruct;
+
+import org.argeo.api.cms.CmsSession;
+import org.argeo.cms.CurrentUser;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+
+/** A canonical view of the logged in user. */
+public class EgoDashboard {
+//     private BundleContext bc = FrameworkUtil.getBundle(EgoDashboard.class).getBundleContext();
+
+       @PostConstruct
+       public void createPartControl(Composite p) {
+               p.setLayout(new GridLayout());
+               String username = CurrentUser.getUsername();
+
+               CmsSwtUtils.lbl(p, "<strong>" + CurrentUser.getDisplayName() + "</strong>");
+               CmsSwtUtils.txt(p, username);
+               CmsSwtUtils.lbl(p, "Roles:");
+               roles: for (String role : CurrentUser.roles()) {
+                       if (username.equals(role))
+                               continue roles;
+                       CmsSwtUtils.txt(p, role);
+               }
+
+//             Subject subject = Subject.getSubject(AccessController.getContext());
+//             if (subject != null) {
+               CmsSession cmsSession = CurrentUser.getCmsSession();
+               ZonedDateTime loggedIndSince = cmsSession.getCreationTime();
+               CmsSwtUtils.lbl(p, "Session:");
+               CmsSwtUtils.txt(p, cmsSession.getUuid().toString());
+               CmsSwtUtils.lbl(p, "Logged in since:");
+               CmsSwtUtils.txt(p, loggedIndSince.toString());
+//             }
+       }
+}
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/users/AbstractRoleEditor.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/users/AbstractRoleEditor.java
new file mode 100644 (file)
index 0000000..e3ec913
--- /dev/null
@@ -0,0 +1,287 @@
+package org.argeo.cms.e4.users;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
+import javax.inject.Inject;
+
+import org.argeo.api.acr.ldap.LdapAttr;
+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.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(LdapAttr.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, LdapAttr.cn.name());
+                       title = "".equals(commonName) ? user.getName() : commonName;
+               }
+               setPartName(title);
+       }
+
+       protected void setPartName(String name) {
+               mPart.setLabel(name);
+       }
+
+       // protected void addPages() {
+       // try {
+       // if (user.getType() == Role.GROUP)
+       // addPage(new GroupMainPage(this, userAdminWrapper, repository, nodeInstance));
+       // else
+       // addPage(new UserMainPage(this, userAdminWrapper));
+       // } catch (Exception e) {
+       // throw new CmsException("Cannot add pages", e);
+       // }
+       // }
+
+       @Persist
+       public void doSave(IProgressMonitor monitor) {
+               userAdminWrapper.beginTransactionIfNeeded();
+               commitPages(true);
+               userAdminWrapper.commitOrNotifyTransactionStateChange();
+               // firePropertyChange(PROP_DIRTY);
+               userAdminWrapper.notifyListeners(new UserAdminEvent(null, UserAdminEvent.ROLE_REMOVED, user));
+       }
+
+       protected void commitPages(boolean b) {
+               managedForm.commit(b);
+       }
+
+       @PreDestroy
+       public void dispose() {
+               userAdminWrapper.removeListener(listener);
+               managedForm.dispose();
+       }
+
+       // CONTROLERS FOR THIS EDITOR AND ITS PAGES
+
+       class NameChangeListener extends UiUserAdminListener {
+               public NameChangeListener(Display display) {
+                       super(display);
+               }
+
+               @Override
+               public void roleChangedToUiThread(UserAdminEvent event) {
+                       Role changedRole = event.getRole();
+                       if (changedRole == null || changedRole.equals(user)) {
+                               updateEditorTitle(null);
+                               User reloadedUser = (User) userAdminWrapper.getUserAdmin().getRole(user.getName());
+                               setDisplayedUser(reloadedUser);
+                       }
+               }
+       }
+
+       class MainInfoListener extends UiUserAdminListener {
+               private final AbstractFormPart part;
+
+               public MainInfoListener(Display display, AbstractFormPart part) {
+                       super(display);
+                       this.part = part;
+               }
+
+               @Override
+               public void roleChangedToUiThread(UserAdminEvent event) {
+                       // Rollback
+                       if (event.getRole() == null)
+                               part.markStale();
+               }
+       }
+
+       class GroupChangeListener extends UiUserAdminListener {
+               private final AbstractFormPart part;
+
+               public GroupChangeListener(Display display, AbstractFormPart part) {
+                       super(display);
+                       this.part = part;
+               }
+
+               @Override
+               public void roleChangedToUiThread(UserAdminEvent event) {
+                       // always mark as stale
+                       part.markStale();
+               }
+       }
+
+       /** Registers a listener that will notify this part */
+       class FormPartML implements ModifyListener {
+               private static final long serialVersionUID = 6299808129505381333L;
+               private AbstractFormPart formPart;
+
+               public FormPartML(AbstractFormPart generalPart) {
+                       this.formPart = generalPart;
+               }
+
+               public void modifyText(ModifyEvent e) {
+                       // Discard event when the control does not have the focus, typically
+                       // to avoid all editors being marked as dirty during a Rollback
+                       if (((Control) e.widget).isFocusControl())
+                               formPart.markDirty();
+               }
+       }
+
+       /* DEPENDENCY INJECTION */
+       public void setUserAdminWrapper(UserAdminWrapper userAdminWrapper) {
+               this.userAdminWrapper = userAdminWrapper;
+       }
+
+       /** Creates label and multiline text. */
+       Text createLMT(Composite parent, String label, String value) {
+               Label lbl = new Label(parent, SWT.NONE);
+               lbl.setText(label);
+               lbl.setLayoutData(new GridData(SWT.LEAD, SWT.CENTER, false, false));
+               Text text = new Text(parent, SWT.NONE);
+               text.setText(value);
+               text.setLayoutData(new GridData(SWT.LEAD, SWT.FILL, true, true));
+               return text;
+       }
+
+       /** Creates label and password. */
+       Text createLP(Composite parent, String label, String value) {
+               Label lbl = new Label(parent, SWT.NONE);
+               lbl.setText(label);
+               lbl.setLayoutData(new GridData(SWT.LEAD, SWT.CENTER, false, false));
+               Text text = new Text(parent, SWT.PASSWORD | SWT.BORDER);
+               text.setText(value);
+               text.setLayoutData(new GridData(SWT.LEAD, SWT.FILL, true, false));
+               return text;
+       }
+
+       /** Creates label and text. */
+       Text createLT(Composite parent, String label, String value) {
+               Label lbl = new Label(parent, SWT.NONE);
+               lbl.setText(label);
+               lbl.setLayoutData(new GridData(SWT.LEAD, SWT.CENTER, false, false));
+               lbl.setFont(EclipseUiUtils.getBoldFont(parent));
+               Text text = new Text(parent, SWT.BORDER);
+               text.setText(value);
+               text.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
+               // CmsUiUtils.style(text, CmsWorkbenchStyles.WORKBENCH_FORM_TEXT);
+               return text;
+       }
+
+       Text createReadOnlyLT(Composite parent, String label, String value) {
+               Label lbl = new Label(parent, SWT.NONE);
+               lbl.setText(label);
+               lbl.setLayoutData(new GridData(SWT.LEAD, SWT.CENTER, false, false));
+               lbl.setFont(EclipseUiUtils.getBoldFont(parent));
+               Text text = new Text(parent, SWT.NONE);
+               text.setText(value);
+               text.setLayoutData(new GridData(SWT.LEAD, SWT.FILL, true, false));
+               text.setEditable(false);
+               // CmsUiUtils.style(text, CmsWorkbenchStyles.WORKBENCH_FORM_TEXT);
+               return text;
+       }
+
+}
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/users/CmsWorkbenchStyles.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/users/CmsWorkbenchStyles.java
new file mode 100644 (file)
index 0000000..07df312
--- /dev/null
@@ -0,0 +1,8 @@
+package org.argeo.cms.e4.users;
+
+/** Centralize the declaration of Workbench specific CSS Styles */
+interface CmsWorkbenchStyles {
+
+       // Specific People layouting
+       String WORKBENCH_FORM_TEXT = "workbench_form_text";
+}
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/users/GroupEditor.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/users/GroupEditor.java
new file mode 100644 (file)
index 0000000..8ec1621
--- /dev/null
@@ -0,0 +1,566 @@
+package org.argeo.cms.e4.users;
+
+import static org.argeo.api.acr.ldap.LdapAttr.businessCategory;
+import static org.argeo.api.acr.ldap.LdapAttr.description;
+import static org.argeo.api.cms.CmsContext.WORKGROUP;
+import static org.argeo.cms.auth.UserAdminUtils.setProperty;
+
+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.acr.ldap.LdapAttr;
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.api.cms.CmsContext;
+import org.argeo.api.cms.transaction.WorkTransaction;
+import org.argeo.cms.auth.UserAdminUtils;
+import org.argeo.cms.e4.users.providers.CommonNameLP;
+import org.argeo.cms.e4.users.providers.MailLP;
+import org.argeo.cms.e4.users.providers.RoleIconLP;
+import org.argeo.cms.e4.users.providers.UserFilter;
+import org.argeo.cms.jcr.CmsJcrUtils;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.swt.useradmin.LdifUsersTable;
+import org.argeo.cms.ui.eclipse.forms.AbstractFormPart;
+import org.argeo.cms.ui.eclipse.forms.IManagedForm;
+import org.argeo.eclipse.ui.ColumnDefinition;
+import org.argeo.eclipse.ui.EclipseUiUtils;
+import org.argeo.jcr.JcrException;
+import org.argeo.jcr.JcrUtils;
+import org.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, LdapAttr.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, LdapAttr.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;
+       // }
+
+}
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/users/GroupsView.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/users/GroupsView.java
new file mode 100644 (file)
index 0000000..d941159
--- /dev/null
@@ -0,0 +1,251 @@
+package org.argeo.cms.e4.users;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
+import javax.inject.Inject;
+
+import org.argeo.api.acr.ldap.LdapAttr;
+import org.argeo.api.acr.ldap.LdapObj;
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.cms.CurrentUser;
+import org.argeo.cms.e4.users.providers.CommonNameLP;
+import org.argeo.cms.e4.users.providers.DomainNameLP;
+import org.argeo.cms.e4.users.providers.RoleIconLP;
+import org.argeo.cms.e4.users.providers.UserDragListener;
+import org.argeo.cms.swt.CmsException;
+import org.argeo.cms.swt.useradmin.LdifUsersTable;
+//import org.argeo.cms.ui.workbench.WorkbenchUiPlugin;
+//import org.argeo.cms.ui.workbench.internal.useradmin.UiUserAdminListener;
+//import org.argeo.cms.ui.workbench.internal.useradmin.UserAdminWrapper;
+//import org.argeo.cms.ui.workbench.internal.useradmin.providers.CommonNameLP;
+//import org.argeo.cms.ui.workbench.internal.useradmin.providers.DomainNameLP;
+//import org.argeo.cms.ui.workbench.internal.useradmin.providers.RoleIconLP;
+//import org.argeo.cms.ui.workbench.internal.useradmin.providers.UserDragListener;
+//import org.argeo.cms.ui.workbench.internal.useradmin.providers.UserTableDefaultDClickListener;
+import org.argeo.eclipse.ui.ColumnDefinition;
+import org.argeo.eclipse.ui.EclipseUiUtils;
+import org.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 = { LdapAttr.uid.name(), LdapAttr.cn.name(), LdapAttr.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(LdapAttr.objectClass.name()).append("=")
+                                                       .append(LdapObj.groupOfNames.name()).append(")");
+                                       // hide tokens
+                                       builder.append("(!(").append(LdapAttr.DN).append("=*").append(CmsConstants.TOKENS_BASEDN)
+                                                       .append("))");
+
+                                       if (!showSystemRoles)
+                                               builder.append("(!(").append(LdapAttr.DN).append("=*").append(CmsConstants.SYSTEM_ROLES_BASEDN)
+                                                               .append("))");
+                                       builder.append("(|");
+                                       builder.append(tmpBuilder.toString());
+                                       builder.append("))");
+                               } else {
+                                       if (!showSystemRoles)
+                                               builder.append("(&(").append(LdapAttr.objectClass.name()).append("=")
+                                                               .append(LdapObj.groupOfNames.name()).append(")(!(").append(LdapAttr.DN).append("=*")
+                                                               .append(CmsConstants.SYSTEM_ROLES_BASEDN).append("))(!(").append(LdapAttr.DN).append("=*")
+                                                               .append(CmsConstants.TOKENS_BASEDN).append(")))");
+                                       else
+                                               builder.append("(&(").append(LdapAttr.objectClass.name()).append("=")
+                                                               .append(LdapObj.groupOfNames.name()).append(")(!(").append(LdapAttr.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;
+       }
+}
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/users/SecurityAdminImages.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/users/SecurityAdminImages.java
new file mode 100644 (file)
index 0000000..7bbe3c7
--- /dev/null
@@ -0,0 +1,19 @@
+package org.argeo.cms.e4.users;
+
+import org.argeo.cms.ui.theme.CmsImages;
+import org.eclipse.jface.resource.ImageDescriptor;
+import org.eclipse.swt.graphics.Image;
+
+/** Shared icons that must be declared programmatically . */
+public class SecurityAdminImages extends CmsImages {
+       private final static String PREFIX = "icons/";
+
+       public final static ImageDescriptor ICON_REMOVE_DESC = createDesc(PREFIX + "delete.png");
+       public final static ImageDescriptor ICON_USER_DESC = createDesc(PREFIX + "person.png");
+
+       public final static Image ICON_USER = ICON_USER_DESC.createImage();
+       public final static Image ICON_GROUP = createImg(PREFIX + "group.png");
+       public final static Image ICON_WORKGROUP = createImg(PREFIX + "workgroup.png");
+       public final static Image ICON_ROLE = createImg(PREFIX + "role.gif");
+
+}
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/users/UiAdminUtils.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/users/UiAdminUtils.java
new file mode 100644 (file)
index 0000000..bc9fd83
--- /dev/null
@@ -0,0 +1,34 @@
+package org.argeo.cms.e4.users;
+
+import org.argeo.api.cms.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,})$";
+}
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/users/UiUserAdminListener.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/users/UiUserAdminListener.java
new file mode 100644 (file)
index 0000000..eb64aba
--- /dev/null
@@ -0,0 +1,27 @@
+package org.argeo.cms.e4.users;
+
+import org.eclipse.swt.widgets.Display;
+import org.osgi.service.useradmin.UserAdminEvent;
+import org.osgi.service.useradmin.UserAdminListener;
+
+/** Convenience class to insure the call to refresh is done in the UI thread */
+public abstract class UiUserAdminListener implements UserAdminListener {
+
+       private final Display display;
+
+       public UiUserAdminListener(Display display) {
+               this.display = display;
+       }
+
+       @Override
+       public void roleChanged(final UserAdminEvent event) {
+               display.asyncExec(new Runnable() {
+                       @Override
+                       public void run() {
+                               roleChangedToUiThread(event);
+                       }
+               });
+       }
+
+       public abstract void roleChangedToUiThread(UserAdminEvent event);
+}
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/users/UserAdminWrapper.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/users/UserAdminWrapper.java
new file mode 100644 (file)
index 0000000..9f333a7
--- /dev/null
@@ -0,0 +1,153 @@
+package org.argeo.cms.e4.users;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.api.cms.directory.UserDirectory;
+import org.argeo.api.cms.transaction.WorkTransaction;
+import org.argeo.cms.runtime.DirectoryConf;
+import org.argeo.cms.swt.CmsException;
+import org.osgi.service.useradmin.UserAdmin;
+import org.osgi.service.useradmin.UserAdminEvent;
+import org.osgi.service.useradmin.UserAdminListener;
+
+/** Centralise interaction with the UserAdmin in this bundle */
+public class UserAdminWrapper {
+
+       private UserAdmin userAdmin;
+       // private ServiceReference<UserAdmin> userAdminServiceReference;
+//     private Set<String> uris;
+       private Map<UserDirectory, Hashtable<String, String>> userDirectories = Collections
+                       .synchronizedMap(new LinkedHashMap<>());
+       private WorkTransaction userTransaction;
+
+       // First effort to simplify UX while managing users and groups
+       public final static boolean COMMIT_ON_SAVE = true;
+
+       // Registered listeners
+       List<UserAdminListener> listeners = new ArrayList<UserAdminListener>();
+
+       /**
+        * Starts a transaction if necessary. Should always been called together with
+        * {@link UserAdminWrapper#commitOrNotifyTransactionStateChange()} once the
+        * security model changes have been performed.
+        */
+       public WorkTransaction beginTransactionIfNeeded() {
+               try {
+                       // UserTransaction userTransaction = getUserTransaction();
+                       if (userTransaction.isNoTransactionStatus()) {
+                               userTransaction.begin();
+                               // UiAdminUtils.notifyTransactionStateChange(userTransaction);
+                       }
+                       return userTransaction;
+               } catch (Exception e) {
+                       throw new CmsException("Unable to begin transaction", e);
+               }
+       }
+
+       /**
+        * Depending on the current application configuration, it will either commit the
+        * current transaction or throw a notification that the transaction state has
+        * changed (In the later case, it must be called from the UI thread).
+        */
+       public void commitOrNotifyTransactionStateChange() {
+               try {
+                       // UserTransaction userTransaction = getUserTransaction();
+                       if (userTransaction.isNoTransactionStatus())
+                               return;
+
+                       if (UserAdminWrapper.COMMIT_ON_SAVE)
+                               userTransaction.commit();
+                       else
+                               UiAdminUtils.notifyTransactionStateChange(userTransaction);
+               } catch (Exception e) {
+                       throw new CmsException("Unable to clean transaction", e);
+               }
+       }
+
+       // TODO implement safer mechanism
+       public void addListener(UserAdminListener userAdminListener) {
+               if (!listeners.contains(userAdminListener))
+                       listeners.add(userAdminListener);
+       }
+
+       public void removeListener(UserAdminListener userAdminListener) {
+               if (listeners.contains(userAdminListener))
+                       listeners.remove(userAdminListener);
+       }
+
+       public void notifyListeners(UserAdminEvent event) {
+               for (UserAdminListener listener : listeners)
+                       listener.roleChanged(event);
+       }
+
+       public Map<String, String> getKnownBaseDns(boolean onlyWritable) {
+               Map<String, String> dns = new HashMap<String, String>();
+               for (UserDirectory userDirectory : userDirectories.keySet()) {
+                       Boolean readOnly = userDirectory.isReadOnly();
+                       String baseDn = userDirectory.getBase();
+
+                       if (onlyWritable && readOnly)
+                               continue;
+                       if (baseDn.equalsIgnoreCase(CmsConstants.SYSTEM_ROLES_BASEDN))
+                               continue;
+                       if (baseDn.equalsIgnoreCase(CmsConstants.TOKENS_BASEDN))
+                               continue;
+                       dns.put(baseDn, DirectoryConf.propertiesAsUri(userDirectories.get(userDirectory)).toString());
+
+               }
+//             for (String uri : uris) {
+//                     if (!uri.startsWith("/"))
+//                             continue;
+//                     Dictionary<String, ?> props = UserAdminConf.uriAsProperties(uri);
+//                     String readOnly = UserAdminConf.readOnly.getValue(props);
+//                     String baseDn = UserAdminConf.baseDn.getValue(props);
+//
+//                     if (onlyWritable && "true".equals(readOnly))
+//                             continue;
+//                     if (baseDn.equalsIgnoreCase(NodeConstants.ROLES_BASEDN))
+//                             continue;
+//                     if (baseDn.equalsIgnoreCase(NodeConstants.TOKENS_BASEDN))
+//                             continue;
+//                     dns.put(baseDn, uri);
+//             }
+               return dns;
+       }
+
+       public UserAdmin getUserAdmin() {
+               return userAdmin;
+       }
+
+       public WorkTransaction getUserTransaction() {
+               return userTransaction;
+       }
+
+       /* DEPENDENCY INJECTION */
+       public void setUserAdmin(UserAdmin userAdmin, Map<String, String> properties) {
+               this.userAdmin = userAdmin;
+//             this.uris = Collections.unmodifiableSortedSet(new TreeSet<>(properties.keySet()));
+       }
+
+       public void setUserTransaction(WorkTransaction userTransaction) {
+               this.userTransaction = userTransaction;
+       }
+
+       public void addUserDirectory(UserDirectory userDirectory, Map<String, String> properties) {
+               userDirectories.put(userDirectory, new Hashtable<>(properties));
+       }
+
+       public void removeUserDirectory(UserDirectory userDirectory, Map<String, String> properties) {
+               userDirectories.remove(userDirectory);
+       }
+
+       // public void setUserAdminServiceReference(
+       // ServiceReference<UserAdmin> userAdminServiceReference) {
+       // this.userAdminServiceReference = userAdminServiceReference;
+       // }
+}
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/users/UserBatchUpdateWizard.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/users/UserBatchUpdateWizard.java
new file mode 100644 (file)
index 0000000..07efcbb
--- /dev/null
@@ -0,0 +1,606 @@
+package org.argeo.cms.e4.users;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.argeo.api.acr.ldap.LdapAttr;
+import org.argeo.api.acr.ldap.LdapObj;
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.api.cms.transaction.WorkTransaction;
+import org.argeo.cms.CurrentUser;
+import org.argeo.cms.auth.UserAdminUtils;
+import org.argeo.cms.e4.users.providers.CommonNameLP;
+import org.argeo.cms.e4.users.providers.DomainNameLP;
+import org.argeo.cms.e4.users.providers.MailLP;
+import org.argeo.cms.e4.users.providers.UserNameLP;
+import org.argeo.cms.swt.CmsException;
+import org.argeo.cms.swt.useradmin.LdifUsersTable;
+import org.argeo.eclipse.ui.ColumnDefinition;
+import org.argeo.eclipse.ui.EclipseUiUtils;
+import org.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(LdapAttr.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 = { LdapAttr.uid.name(), LdapAttr.DN, LdapAttr.cn.name(),
+                                       LdapAttr.givenName.name(), LdapAttr.sn.name(), LdapAttr.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(LdapAttr.objectClass.name()).append("=")
+                                                               .append(LdapObj.inetOrgPerson.name()).append(")(|");
+                                               builder.append(tmpBuilder.toString());
+                                               builder.append("))");
+                                       } else
+                                               builder.append("(").append(LdapAttr.objectClass.name()).append("=")
+                                                               .append(LdapObj.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();
+                       }
+               }
+       }
+}
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/users/UserEditor.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/users/UserEditor.java
new file mode 100644 (file)
index 0000000..34892b5
--- /dev/null
@@ -0,0 +1,535 @@
+package org.argeo.cms.e4.users;
+
+import static org.argeo.api.acr.ldap.LdapAttr.cn;
+import static org.argeo.api.acr.ldap.LdapAttr.givenName;
+import static org.argeo.api.acr.ldap.LdapAttr.mail;
+import static org.argeo.api.acr.ldap.LdapAttr.sn;
+import static org.argeo.api.acr.ldap.LdapAttr.uid;
+import static org.argeo.cms.auth.UserAdminUtils.getProperty;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.inject.Inject;
+
+import org.argeo.api.acr.ldap.LdapAttr;
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.cms.CurrentUser;
+import org.argeo.cms.auth.UserAdminUtils;
+import org.argeo.cms.e4.users.providers.CommonNameLP;
+import org.argeo.cms.e4.users.providers.DomainNameLP;
+import org.argeo.cms.e4.users.providers.RoleIconLP;
+import org.argeo.cms.e4.users.providers.UserFilter;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.swt.useradmin.LdifUsersTable;
+import org.argeo.cms.ui.eclipse.forms.AbstractFormPart;
+//import org.argeo.cms.ui.eclipse.forms.FormToolkit;
+import org.argeo.cms.ui.eclipse.forms.IManagedForm;
+import org.argeo.eclipse.ui.ColumnDefinition;
+import org.argeo.eclipse.ui.EclipseUiUtils;
+import org.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(LdapAttr.givenName.name(), firstName.getText());
+                               user.getProperties().put(LdapAttr.sn.name(), lastName.getText());
+                               user.getProperties().put(LdapAttr.cn.name(), commonName.getText());
+                               user.getProperties().put(LdapAttr.mail.name(), email.getText());
+                               super.commit(onSave);
+                       }
+
+                       @Override
+                       public void refresh() {
+                               distinguishedName.setText(UserAdminUtils.getProperty(user, LdapAttr.uid.name()));
+                               commonName.setText(UserAdminUtils.getProperty(user, LdapAttr.cn.name()));
+                               firstName.setText(UserAdminUtils.getProperty(user, LdapAttr.givenName.name()));
+                               lastName.setText(UserAdminUtils.getProperty(user, LdapAttr.sn.name()));
+                               email.setText(UserAdminUtils.getProperty(user, LdapAttr.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;
+       // }
+
+}
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/users/UserTableDefaultDClickListener.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/users/UserTableDefaultDClickListener.java
new file mode 100644 (file)
index 0000000..c3e6b0c
--- /dev/null
@@ -0,0 +1,39 @@
+package org.argeo.cms.e4.users;
+
+import org.argeo.api.acr.ldap.LdapAttr;
+import org.argeo.cms.e4.CmsE4Utils;
+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, LdapAttr.uid.name(), user.getName());
+       }
+
+       protected String getEditorId(User user) {
+               if (user instanceof Group)
+                       return "org.argeo.cms.e4.partdescriptor.groupEditor";
+               else
+                       return "org.argeo.cms.e4.partdescriptor.userEditor";
+       }
+}
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/users/UsersView.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/users/UsersView.java
new file mode 100644 (file)
index 0000000..238ae4d
--- /dev/null
@@ -0,0 +1,182 @@
+package org.argeo.cms.e4.users;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
+import javax.inject.Inject;
+
+import org.argeo.api.acr.ldap.LdapAttr;
+import org.argeo.api.acr.ldap.LdapObj;
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.cms.CurrentUser;
+import org.argeo.cms.e4.users.providers.CommonNameLP;
+import org.argeo.cms.e4.users.providers.DomainNameLP;
+import org.argeo.cms.e4.users.providers.MailLP;
+import org.argeo.cms.e4.users.providers.UserDragListener;
+import org.argeo.cms.e4.users.providers.UserNameLP;
+import org.argeo.cms.swt.CmsException;
+import org.argeo.cms.swt.useradmin.LdifUsersTable;
+import org.argeo.eclipse.ui.ColumnDefinition;
+import org.argeo.eclipse.ui.EclipseUiUtils;
+import org.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 = { LdapAttr.DN, LdapAttr.uid.name(), LdapAttr.cn.name(),
+                               LdapAttr.givenName.name(), LdapAttr.sn.name(), LdapAttr.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(LdapAttr.objectClass.name()).append("=")
+                                                       .append(LdapObj.inetOrgPerson.name()).append(")(|");
+                                       builder.append(tmpBuilder.toString());
+                                       builder.append("))");
+                               } else
+                                       builder.append("(").append(LdapAttr.objectClass.name()).append("=")
+                                                       .append(LdapObj.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;
+       }
+}
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/users/handlers/DeleteGroups.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/users/handlers/DeleteGroups.java
new file mode 100644 (file)
index 0000000..742bc3f
--- /dev/null
@@ -0,0 +1,95 @@
+package org.argeo.cms.e4.users.handlers;
+
+import java.util.List;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+import org.argeo.cms.auth.UserAdminUtils;
+import org.argeo.cms.e4.users.GroupsView;
+import org.argeo.cms.e4.users.UserAdminWrapper;
+import org.eclipse.e4.core.di.annotations.CanExecute;
+import org.eclipse.e4.core.di.annotations.Execute;
+import org.eclipse.e4.ui.model.application.ui.basic.MPart;
+import org.eclipse.e4.ui.services.IServiceConstants;
+import org.eclipse.e4.ui.workbench.modeling.ESelectionService;
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.swt.widgets.Display;
+import org.osgi.service.useradmin.Group;
+import org.osgi.service.useradmin.UserAdmin;
+import org.osgi.service.useradmin.UserAdminEvent;
+
+/** Delete the selected groups */
+public class DeleteGroups {
+       // public final static String ID = WorkbenchUiPlugin.PLUGIN_ID +
+       // ".deleteGroups";
+
+       /* DEPENDENCY INJECTION */
+       @Inject
+       private UserAdminWrapper userAdminWrapper;
+
+       @Inject
+       ESelectionService selectionService;
+
+       @SuppressWarnings("unchecked")
+       @Execute
+       public void execute(@Named(IServiceConstants.ACTIVE_PART) MPart part, ESelectionService selectionService) {
+               // ISelection selection = null;// HandlerUtil.getCurrentSelection(event);
+               // if (selection.isEmpty())
+               // return null;
+               //
+               // List<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;
+       // }
+}
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/users/handlers/DeleteUsers.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/users/handlers/DeleteUsers.java
new file mode 100644 (file)
index 0000000..d1afd22
--- /dev/null
@@ -0,0 +1,88 @@
+package org.argeo.cms.e4.users.handlers;
+
+import java.util.List;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+import org.argeo.cms.auth.UserAdminUtils;
+import org.argeo.cms.e4.users.UserAdminWrapper;
+import org.argeo.cms.e4.users.UsersView;
+import org.eclipse.e4.core.di.annotations.CanExecute;
+import org.eclipse.e4.core.di.annotations.Execute;
+import org.eclipse.e4.ui.model.application.ui.basic.MPart;
+import org.eclipse.e4.ui.services.IServiceConstants;
+import org.eclipse.e4.ui.workbench.modeling.ESelectionService;
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.swt.widgets.Display;
+import org.osgi.service.useradmin.User;
+import org.osgi.service.useradmin.UserAdmin;
+import org.osgi.service.useradmin.UserAdminEvent;
+
+/** Delete the selected users */
+public class DeleteUsers {
+       // public final static String ID = WorkbenchUiPlugin.PLUGIN_ID + ".deleteUsers";
+
+       /* DEPENDENCY INJECTION */
+       @Inject
+       private UserAdminWrapper userAdminWrapper;
+
+       @SuppressWarnings("unchecked")
+       @Execute
+       public void execute(@Named(IServiceConstants.ACTIVE_PART) MPart part, ESelectionService selectionService) {
+               // ISelection selection = null;// HandlerUtil.getCurrentSelection(event);
+               // if (selection.isEmpty())
+               // return null;
+               List<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;
+       }
+}
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/users/handlers/NewGroup.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/users/handlers/NewGroup.java
new file mode 100644 (file)
index 0000000..5b41eae
--- /dev/null
@@ -0,0 +1,212 @@
+package org.argeo.cms.e4.users.handlers;
+
+import java.util.Dictionary;
+import java.util.Map;
+
+import javax.inject.Inject;
+
+import org.argeo.api.acr.ldap.LdapAttr;
+import org.argeo.cms.e4.users.UserAdminWrapper;
+import org.argeo.cms.runtime.DirectoryConf;
+import org.argeo.cms.swt.CmsException;
+import org.argeo.eclipse.ui.EclipseUiUtils;
+import org.argeo.eclipse.ui.dialogs.ErrorFeedback;
+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(LdapAttr.description.name(), descStr);
+                               userAdminWrapper.commitOrNotifyTransactionStateChange();
+                               userAdminWrapper.notifyListeners(new UserAdminEvent(null, UserAdminEvent.ROLE_CREATED, group));
+                               return true;
+                       } catch (Exception e) {
+                               ErrorFeedback.show("Cannot create new group " + commonName, e);
+                               return false;
+                       }
+               }
+
+               private class MainGroupInfoWizardPage extends WizardPage implements FocusListener {
+                       private static final long serialVersionUID = -3150193365151601807L;
+
+                       public MainGroupInfoWizardPage() {
+                               super("Main");
+                               setTitle("General information");
+                               setMessage("Please choose a domain, provide a common name " + "and a free description");
+                       }
+
+                       @Override
+                       public void createControl(Composite parent) {
+                               Composite bodyCmp = new Composite(parent, SWT.NONE);
+                               setControl(bodyCmp);
+                               bodyCmp.setLayout(new GridLayout(2, false));
+
+                               dNameTxt = EclipseUiUtils.createGridLT(bodyCmp, "Distinguished name");
+                               dNameTxt.setEnabled(false);
+
+                               baseDnCmb = createGridLC(bodyCmp, "Base DN");
+                               // Initialise before adding the listener to avoid NPE
+                               initialiseDnCmb(baseDnCmb);
+                               baseDnCmb.addFocusListener(this);
+
+                               commonNameTxt = EclipseUiUtils.createGridLT(bodyCmp, "Common name");
+                               commonNameTxt.addFocusListener(this);
+
+                               Label descLbl = new Label(bodyCmp, SWT.LEAD);
+                               descLbl.setText("Description");
+                               descLbl.setLayoutData(new GridData(SWT.RIGHT, SWT.TOP, false, false));
+                               descriptionTxt = new Text(bodyCmp, SWT.LEAD | SWT.MULTI | SWT.WRAP | SWT.BORDER);
+                               descriptionTxt.setLayoutData(EclipseUiUtils.fillAll());
+                               descriptionTxt.addFocusListener(this);
+
+                               // Initialize buttons
+                               setPageComplete(false);
+                               getContainer().updateButtons();
+                       }
+
+                       @Override
+                       public void focusLost(FocusEvent event) {
+                               String name = commonNameTxt.getText();
+                               if (EclipseUiUtils.isEmpty(name))
+                                       dNameTxt.setText("");
+                               else
+                                       dNameTxt.setText(getDn(name));
+
+                               String message = checkComplete();
+                               if (message != null) {
+                                       setMessage(message, WizardPage.ERROR);
+                                       setPageComplete(false);
+                               } else {
+                                       setMessage("Complete", WizardPage.INFORMATION);
+                                       setPageComplete(true);
+                               }
+                               getContainer().updateButtons();
+                       }
+
+                       @Override
+                       public void focusGained(FocusEvent event) {
+                       }
+
+                       /** @return the error message or null if complete */
+                       protected String checkComplete() {
+                               String name = commonNameTxt.getText();
+
+                               if (name.trim().equals(""))
+                                       return "Common name must not be empty";
+                               Role role = userAdminWrapper.getUserAdmin().getRole(getDn(name));
+                               if (role != null)
+                                       return "Group " + name + " already exists";
+                               return null;
+                       }
+
+                       @Override
+                       public void setVisible(boolean visible) {
+                               super.setVisible(visible);
+                               if (visible)
+                                       if (baseDnCmb.getSelectionIndex() == -1)
+                                               baseDnCmb.setFocus();
+                                       else
+                                               commonNameTxt.setFocus();
+                       }
+               }
+
+               private Map<String, String> getDns() {
+                       return userAdminWrapper.getKnownBaseDns(true);
+               }
+
+               private String getDn(String cn) {
+                       Map<String, String> dns = getDns();
+                       String bdn = baseDnCmb.getText();
+                       if (EclipseUiUtils.notEmpty(bdn)) {
+                               Dictionary<String, ?> props = DirectoryConf.uriAsProperties(dns.get(bdn));
+                               String dn = LdapAttr.cn.name() + "=" + cn + "," + DirectoryConf.groupBase.getValue(props) + "," + bdn;
+                               return dn;
+                       }
+                       return null;
+               }
+
+               private void initialiseDnCmb(Combo combo) {
+                       Map<String, String> dns = userAdminWrapper.getKnownBaseDns(true);
+                       if (dns.isEmpty())
+                               throw new CmsException("No writable base dn found. Cannot create group");
+                       combo.setItems(dns.keySet().toArray(new String[0]));
+                       if (dns.size() == 1)
+                               combo.select(0);
+               }
+       }
+
+       private Combo createGridLC(Composite parent, String label) {
+               Label lbl = new Label(parent, SWT.LEAD);
+               lbl.setText(label);
+               lbl.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false));
+               Combo combo = new Combo(parent, SWT.LEAD | SWT.BORDER | SWT.READ_ONLY);
+               combo.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
+               return combo;
+       }
+
+       /* DEPENDENCY INJECTION */
+       public void setUserAdminWrapper(UserAdminWrapper userAdminWrapper) {
+               this.userAdminWrapper = userAdminWrapper;
+       }
+}
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/users/handlers/NewUser.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/users/handlers/NewUser.java
new file mode 100644 (file)
index 0000000..d8fb0fe
--- /dev/null
@@ -0,0 +1,287 @@
+package org.argeo.cms.e4.users.handlers;
+
+import java.util.Dictionary;
+import java.util.List;
+import java.util.Map;
+
+import javax.inject.Inject;
+import javax.naming.InvalidNameException;
+import javax.naming.ldap.LdapName;
+import javax.naming.ldap.Rdn;
+
+import org.argeo.api.acr.ldap.LdapAttr;
+import org.argeo.cms.auth.UserAdminUtils;
+import org.argeo.cms.e4.users.UiAdminUtils;
+import org.argeo.cms.e4.users.UserAdminWrapper;
+import org.argeo.cms.runtime.DirectoryConf;
+import org.argeo.cms.swt.CmsException;
+import org.argeo.eclipse.ui.EclipseUiUtils;
+import org.argeo.eclipse.ui.dialogs.ErrorFeedback;
+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(LdapAttr.sn.name(), lastNameStr);
+
+                               String firstNameStr = firstNameTxt.getText();
+                               if (EclipseUiUtils.notEmpty(firstNameStr))
+                                       props.put(LdapAttr.givenName.name(), firstNameStr);
+
+                               String cn = UserAdminUtils.buildDefaultCn(firstNameStr, lastNameStr);
+                               if (EclipseUiUtils.notEmpty(cn))
+                                       props.put(LdapAttr.cn.name(), cn);
+
+                               String mailStr = primaryMailTxt.getText();
+                               if (EclipseUiUtils.notEmpty(mailStr))
+                                       props.put(LdapAttr.mail.name(), mailStr);
+
+                               char[] password = mainUserInfo.getPassword();
+                               user.getCredentials().put(null, password);
+                               userAdminWrapper.commitOrNotifyTransactionStateChange();
+                               userAdminWrapper.notifyListeners(new UserAdminEvent(null, UserAdminEvent.ROLE_CREATED, user));
+                               return true;
+                       } catch (Exception e) {
+                               ErrorFeedback.show("Cannot create new user " + username, e);
+                               return false;
+                       }
+               }
+
+               private class MainUserInfoWizardPage extends WizardPage implements ModifyListener {
+                       private static final long serialVersionUID = -3150193365151601807L;
+
+                       public MainUserInfoWizardPage() {
+                               super("Main");
+                               setTitle("Required Information");
+                       }
+
+                       @Override
+                       public void createControl(Composite parent) {
+                               Composite composite = new Composite(parent, SWT.NONE);
+                               composite.setLayout(new GridLayout(2, false));
+                               dNameTxt = EclipseUiUtils.createGridLT(composite, "Distinguished name", this);
+                               dNameTxt.setEnabled(false);
+
+                               baseDnCmb = createGridLC(composite, "Base DN");
+                               initialiseDnCmb(baseDnCmb);
+                               baseDnCmb.addModifyListener(this);
+                               baseDnCmb.addModifyListener(new ModifyListener() {
+                                       private static final long serialVersionUID = -1435351236582736843L;
+
+                                       @Override
+                                       public void modifyText(ModifyEvent event) {
+                                               String name = usernameTxt.getText();
+                                               dNameTxt.setText(getDn(name));
+                                       }
+                               });
+
+                               usernameTxt = EclipseUiUtils.createGridLT(composite, "Local ID", this);
+                               usernameTxt.addModifyListener(new ModifyListener() {
+                                       private static final long serialVersionUID = -1435351236582736843L;
+
+                                       @Override
+                                       public void modifyText(ModifyEvent event) {
+                                               String name = usernameTxt.getText();
+                                               if (name.trim().equals("")) {
+                                                       dNameTxt.setText("");
+                                                       lastNameTxt.setText("");
+                                                       primaryMailTxt.setText("");
+                                                       pwd1Txt.setText("");
+                                                       pwd2Txt.setText("");
+                                               } else {
+                                                       dNameTxt.setText(getDn(name));
+                                                       lastNameTxt.setText(name.toUpperCase());
+                                                       primaryMailTxt.setText(getMail(name));
+                                                       pwd1Txt.setText("demo");
+                                                       pwd2Txt.setText("demo");
+                                               }
+                                       }
+                               });
+
+                               primaryMailTxt = EclipseUiUtils.createGridLT(composite, "Email", this);
+                               firstNameTxt = EclipseUiUtils.createGridLT(composite, "First name", this);
+                               lastNameTxt = EclipseUiUtils.createGridLT(composite, "Last name", this);
+                               pwd1Txt = EclipseUiUtils.createGridLP(composite, "Password", this);
+                               pwd2Txt = EclipseUiUtils.createGridLP(composite, "Repeat password", this);
+                               setControl(composite);
+
+                               // Initialize buttons
+                               setPageComplete(false);
+                               getContainer().updateButtons();
+                       }
+
+                       @Override
+                       public void modifyText(ModifyEvent event) {
+                               String message = checkComplete();
+                               if (message != null) {
+                                       setMessage(message, WizardPage.ERROR);
+                                       setPageComplete(false);
+                               } else {
+                                       setMessage("Complete", WizardPage.INFORMATION);
+                                       setPageComplete(true);
+                               }
+                               getContainer().updateButtons();
+                       }
+
+                       /** @return error message or null if complete */
+                       protected String checkComplete() {
+                               String name = usernameTxt.getText();
+
+                               if (name.trim().equals(""))
+                                       return "User name must not be empty";
+                               Role role = userAdminWrapper.getUserAdmin().getRole(getDn(name));
+                               if (role != null)
+                                       return "User " + name + " already exists";
+                               if (!primaryMailTxt.getText().matches(UiAdminUtils.EMAIL_PATTERN))
+                                       return "Not a valid email address";
+                               if (lastNameTxt.getText().trim().equals(""))
+                                       return "Specify a last name";
+                               if (pwd1Txt.getText().trim().equals(""))
+                                       return "Specify a password";
+                               if (pwd2Txt.getText().trim().equals(""))
+                                       return "Repeat the password";
+                               if (!pwd2Txt.getText().equals(pwd1Txt.getText()))
+                                       return "Passwords are different";
+                               return null;
+                       }
+
+                       @Override
+                       public void setVisible(boolean visible) {
+                               super.setVisible(visible);
+                               if (visible)
+                                       if (baseDnCmb.getSelectionIndex() == -1)
+                                               baseDnCmb.setFocus();
+                                       else
+                                               usernameTxt.setFocus();
+                       }
+
+                       public String getUsername() {
+                               return usernameTxt.getText();
+                       }
+
+                       public char[] getPassword() {
+                               return pwd1Txt.getTextChars();
+                       }
+
+               }
+
+               private Map<String, String> getDns() {
+                       return userAdminWrapper.getKnownBaseDns(true);
+               }
+
+               private String getDn(String uid) {
+                       Map<String, String> dns = getDns();
+                       String bdn = baseDnCmb.getText();
+                       if (EclipseUiUtils.notEmpty(bdn)) {
+                               Dictionary<String, ?> props = DirectoryConf.uriAsProperties(dns.get(bdn));
+                               String dn = LdapAttr.uid.name() + "=" + uid + "," + DirectoryConf.userBase.getValue(props) + "," + bdn;
+                               return dn;
+                       }
+                       return null;
+               }
+
+               private void initialiseDnCmb(Combo combo) {
+                       Map<String, String> dns = userAdminWrapper.getKnownBaseDns(true);
+                       if (dns.isEmpty())
+                               throw new CmsException("No writable base dn found. Cannot create user");
+                       combo.setItems(dns.keySet().toArray(new String[0]));
+                       if (dns.size() == 1)
+                               combo.select(0);
+               }
+
+               private String getMail(String username) {
+                       if (baseDnCmb.getSelectionIndex() == -1)
+                               return null;
+                       String baseDn = baseDnCmb.getText();
+                       try {
+                               LdapName name = new LdapName(baseDn);
+                               List<Rdn> rdns = name.getRdns();
+                               return username + "@" + (String) rdns.get(1).getValue() + '.' + (String) rdns.get(0).getValue();
+                       } catch (InvalidNameException e) {
+                               throw new CmsException("Unable to generate mail for " + username + " with base dn " + baseDn, e);
+                       }
+               }
+       }
+
+       private Combo createGridLC(Composite parent, String label) {
+               Label lbl = new Label(parent, SWT.LEAD);
+               lbl.setText(label);
+               lbl.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false));
+               Combo combo = new Combo(parent, SWT.LEAD | SWT.BORDER | SWT.READ_ONLY);
+               combo.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
+               return combo;
+       }
+
+       /* DEPENDENCY INJECTION */
+       public void setUserAdminWrapper(UserAdminWrapper userAdminWrapper) {
+               this.userAdminWrapper = userAdminWrapper;
+       }
+}
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/users/handlers/package-info.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/users/handlers/package-info.java
new file mode 100644 (file)
index 0000000..cf3db1d
--- /dev/null
@@ -0,0 +1,2 @@
+/** Users management handlers. */
+package org.argeo.cms.e4.users.handlers;
\ No newline at end of file
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/users/package-info.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/users/package-info.java
new file mode 100644 (file)
index 0000000..c6f14b0
--- /dev/null
@@ -0,0 +1,2 @@
+/** Users management perspective. */
+package org.argeo.cms.e4.users;
\ No newline at end of file
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/users/providers/CommonNameLP.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/users/providers/CommonNameLP.java
new file mode 100644 (file)
index 0000000..fb76f71
--- /dev/null
@@ -0,0 +1,21 @@
+package org.argeo.cms.e4.users.providers;
+
+import org.argeo.api.acr.ldap.LdapAttr;
+import org.argeo.cms.auth.UserAdminUtils;
+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, LdapAttr.cn.name());
+       }
+
+       @Override
+       public String getToolTipText(Object element) {
+               return UserAdminUtils.getProperty((User) element, LdapAttr.DN);
+       }
+
+}
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/users/providers/DomainNameLP.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/users/providers/DomainNameLP.java
new file mode 100644 (file)
index 0000000..e23729d
--- /dev/null
@@ -0,0 +1,14 @@
+package org.argeo.cms.e4.users.providers;
+
+import org.argeo.cms.auth.UserAdminUtils;
+import org.osgi.service.useradmin.User;
+
+/** The human friendly domain name for the corresponding user. */
+public class DomainNameLP extends UserAdminAbstractLP {
+       private static final long serialVersionUID = 5256703081044911941L;
+
+       @Override
+       public String getText(User user) {
+               return UserAdminUtils.getDomainName(user);
+       }
+}
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/users/providers/MailLP.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/users/providers/MailLP.java
new file mode 100644 (file)
index 0000000..fe17a09
--- /dev/null
@@ -0,0 +1,15 @@
+package org.argeo.cms.e4.users.providers;
+
+import org.argeo.api.acr.ldap.LdapAttr;
+import org.argeo.cms.auth.UserAdminUtils;
+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, LdapAttr.mail.name());
+       }
+}
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/users/providers/RoleIconLP.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/users/providers/RoleIconLP.java
new file mode 100644 (file)
index 0000000..d3c000e
--- /dev/null
@@ -0,0 +1,35 @@
+package org.argeo.cms.e4.users.providers;
+
+import org.argeo.api.cms.CmsContext;
+import org.argeo.api.acr.ldap.LdapAttr;
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.cms.auth.UserAdminUtils;
+import org.argeo.cms.e4.users.SecurityAdminImages;
+import org.eclipse.swt.graphics.Image;
+import org.osgi.service.useradmin.Role;
+import org.osgi.service.useradmin.User;
+
+/** Provide a bundle specific image depending on the current user type */
+public class RoleIconLP extends UserAdminAbstractLP {
+       private static final long serialVersionUID = 6550449442061090388L;
+
+       @Override
+       public String getText(User user) {
+               return "";
+       }
+
+       @Override
+       public Image getImage(Object element) {
+               User user = (User) element;
+               String dn = user.getName();
+               if (dn.endsWith(CmsConstants.SYSTEM_ROLES_BASEDN))
+                       return SecurityAdminImages.ICON_ROLE;
+               else if (user.getType() == Role.GROUP) {
+                       String businessCategory = UserAdminUtils.getProperty(user, LdapAttr.businessCategory);
+                       if (businessCategory != null && businessCategory.equals(CmsContext.WORKGROUP))
+                               return SecurityAdminImages.ICON_WORKGROUP;
+                       return SecurityAdminImages.ICON_GROUP;
+               } else
+                       return SecurityAdminImages.ICON_USER;
+       }
+}
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/users/providers/UserAdminAbstractLP.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/users/providers/UserAdminAbstractLP.java
new file mode 100644 (file)
index 0000000..29873db
--- /dev/null
@@ -0,0 +1,66 @@
+package org.argeo.cms.e4.users.providers;
+
+import javax.naming.InvalidNameException;
+import javax.naming.ldap.LdapName;
+
+import org.argeo.cms.auth.UserAdminUtils;
+import org.argeo.cms.swt.CmsException;
+import org.eclipse.jface.resource.JFaceResources;
+import org.eclipse.jface.viewers.ColumnLabelProvider;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Font;
+import org.eclipse.swt.widgets.Display;
+import org.osgi.service.useradmin.User;
+
+/**
+ * Utility class that add font modifications to a column label provider
+ * depending on the given user properties
+ */
+public abstract class UserAdminAbstractLP extends ColumnLabelProvider {
+       private static final long serialVersionUID = 137336765024922368L;
+
+       // private Font italic;
+       private Font bold;
+
+       @Override
+       public Font getFont(Object element) {
+               // Self as bold
+               try {
+                       LdapName selfUserName = UserAdminUtils.getCurrentUserLdapName();
+                       String userName = ((User) element).getName();
+                       LdapName userLdapName = new LdapName(userName);
+                       if (userLdapName.equals(selfUserName)) {
+                               if (bold == null)
+                                       bold = JFaceResources.getFontRegistry()
+                                                       .defaultFontDescriptor().setStyle(SWT.BOLD)
+                                                       .createFont(Display.getCurrent());
+                               return bold;
+                       }
+               } catch (InvalidNameException e) {
+                       throw new CmsException("cannot parse dn for " + element, e);
+               }
+
+               // Disabled as Italic
+               // Node userProfile = (Node) elem;
+               // if (!userProfile.getProperty(ARGEO_ENABLED).getBoolean())
+               // return italic;
+
+               return null;
+               // return super.getFont(element);
+       }
+
+       @Override
+       public String getText(Object element) {
+               User user = (User) element;
+               return getText(user);
+       }
+
+       public void setDisplay(Display display) {
+               // italic = JFaceResources.getFontRegistry().defaultFontDescriptor()
+               // .setStyle(SWT.ITALIC).createFont(display);
+               bold = JFaceResources.getFontRegistry().defaultFontDescriptor()
+                               .setStyle(SWT.BOLD).createFont(Display.getCurrent());
+       }
+
+       public abstract String getText(User user);
+}
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/users/providers/UserDragListener.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/users/providers/UserDragListener.java
new file mode 100644 (file)
index 0000000..56a2624
--- /dev/null
@@ -0,0 +1,40 @@
+package org.argeo.cms.e4.users.providers;
+
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.swt.dnd.DragSourceEvent;
+import org.eclipse.swt.dnd.DragSourceListener;
+import org.osgi.service.useradmin.User;
+
+/** Default drag listener to modify group and users via the UI */
+public class UserDragListener implements DragSourceListener {
+       private static final long serialVersionUID = -2074337775033781454L;
+       private final Viewer viewer;
+
+       public UserDragListener(Viewer viewer) {
+               this.viewer = viewer;
+       }
+
+       public void dragStart(DragSourceEvent event) {
+               // TODO implement finer checks
+               IStructuredSelection selection = (IStructuredSelection) viewer
+                               .getSelection();
+               if (selection.isEmpty() || selection.size() > 1)
+                       event.doit = false;
+               else
+                       event.doit = true;
+       }
+
+       public void dragSetData(DragSourceEvent event) {
+               // TODO Support multiple selection
+               Object obj = ((IStructuredSelection) viewer.getSelection())
+                               .getFirstElement();
+               if (obj != null) {
+                       User user = (User) obj;
+                       event.data = user.getName();
+               }
+       }
+
+       public void dragFinished(DragSourceEvent event) {
+       }
+}
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/users/providers/UserFilter.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/users/providers/UserFilter.java
new file mode 100644 (file)
index 0000000..2cfc10b
--- /dev/null
@@ -0,0 +1,58 @@
+package org.argeo.cms.e4.users.providers;
+
+import static org.argeo.eclipse.ui.EclipseUiUtils.notEmpty;
+
+import org.argeo.api.acr.ldap.LdapAttr;
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.cms.auth.UserAdminUtils;
+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 = { LdapAttr.DN, LdapAttr.cn.name(), LdapAttr.givenName.name(),
+                       LdapAttr.sn.name(), LdapAttr.uid.name(), LdapAttr.description.name(), LdapAttr.mail.name() };
+
+       public void setSearchText(String s) {
+               // ensure that the value can be used for matching
+               if (notEmpty(s))
+                       searchString = ".*" + s.toLowerCase() + ".*";
+               else
+                       searchString = ".*";
+       }
+
+       public void setShowSystemRole(boolean showSystemRole) {
+               this.showSystemRole = showSystemRole;
+       }
+
+       @Override
+       public boolean select(Viewer viewer, Object parentElement, Object element) {
+               User user = (User) element;
+               if (!showSystemRole && user.getName().matches(".*(" + CmsConstants.SYSTEM_ROLES_BASEDN + ")"))
+                       // UserAdminUtils.getProperty(user, LdifName.dn.name())
+                       // .toLowerCase().endsWith(AuthConstants.ROLES_BASEDN))
+                       return false;
+
+               if (searchString == null || searchString.length() == 0)
+                       return true;
+
+               if (user.getName().matches(searchString))
+                       return true;
+
+               for (String key : knownProps) {
+                       String currVal = UserAdminUtils.getProperty(user, key);
+                       if (notEmpty(currVal) && currVal.toLowerCase().matches(searchString))
+                               return true;
+               }
+               return false;
+       }
+}
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/users/providers/UserNameLP.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/users/providers/UserNameLP.java
new file mode 100644 (file)
index 0000000..3cd00eb
--- /dev/null
@@ -0,0 +1,13 @@
+package org.argeo.cms.e4.users.providers;
+
+import org.osgi.service.useradmin.User;
+
+/** Simply declare a label provider that returns the username of a user */
+public class UserNameLP extends UserAdminAbstractLP {
+       private static final long serialVersionUID = 6550449442061090388L;
+
+       @Override
+       public String getText(User user) {
+               return user.getName();
+       }
+}
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/users/providers/package-info.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/e4/users/providers/package-info.java
new file mode 100644 (file)
index 0000000..33bef8d
--- /dev/null
@@ -0,0 +1,2 @@
+/** Users management content providers. */
+package org.argeo.cms.e4.users.providers;
\ No newline at end of file
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/jcr/e4/rap/CmsE4AdminApp.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/jcr/e4/rap/CmsE4AdminApp.java
new file mode 100644 (file)
index 0000000..3af4472
--- /dev/null
@@ -0,0 +1,17 @@
+package org.argeo.cms.jcr.e4.rap;
+
+import org.argeo.cms.e4.rap.AbstractRapE4App;
+import org.eclipse.rap.rwt.application.Application;
+
+/**
+ * Access to canonical views of the core CMS concepts, useful for devleopers and
+ * operators.
+ */
+public class CmsE4AdminApp extends AbstractRapE4App {
+       @Override
+       protected void addEntryPoints(Application application) {
+               addE4EntryPoint(application, "/devops", "org.argeo.tool.devops.e4/e4xmi/devops.e4xmi",
+                               customise("Argeo Tool DevOps"));
+       }
+
+}
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/swt/useradmin/LdifUsersTable.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/swt/useradmin/LdifUsersTable.java
new file mode 100644 (file)
index 0000000..a30d2f7
--- /dev/null
@@ -0,0 +1,401 @@
+package org.argeo.cms.swt.useradmin;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.argeo.eclipse.ui.ColumnDefinition;
+import org.argeo.eclipse.ui.EclipseUiException;
+import org.argeo.eclipse.ui.EclipseUiUtils;
+import org.eclipse.jface.layout.TableColumnLayout;
+import org.eclipse.jface.viewers.CheckboxTableViewer;
+import org.eclipse.jface.viewers.ColumnLabelProvider;
+import org.eclipse.jface.viewers.ColumnWeightData;
+import org.eclipse.jface.viewers.IStructuredContentProvider;
+import org.eclipse.jface.viewers.TableViewer;
+import org.eclipse.jface.viewers.TableViewerColumn;
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.ModifyEvent;
+import org.eclipse.swt.events.ModifyListener;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Link;
+import org.eclipse.swt.widgets.Table;
+import org.eclipse.swt.widgets.TableColumn;
+import org.eclipse.swt.widgets.Text;
+import org.osgi.service.useradmin.User;
+
+/**
+ * Generic composite that display a filter and a table viewer to display users
+ * (can also be groups)
+ * 
+ * Warning: this class does not extends <code>TableViewer</code>. Use the
+ * getTableViewer method to access it.
+ * 
+ */
+public abstract class LdifUsersTable extends Composite {
+       private static final long serialVersionUID = -7385959046279360420L;
+
+       // Context
+       // private UserAdmin userAdmin;
+
+       // Configuration
+       private List<ColumnDefinition> columnDefs = new ArrayList<ColumnDefinition>();
+       private boolean hasFilter;
+       private boolean preventTableLayout = false;
+       private boolean hasSelectionColumn;
+       private int tableStyle;
+
+       // Local UI Objects
+       private TableViewer usersViewer;
+       private Text filterTxt;
+
+       /* EXPOSED METHODS */
+
+       /**
+        * @param parent
+        * @param style
+        */
+       public LdifUsersTable(Composite parent, int style) {
+               super(parent, SWT.NO_FOCUS);
+               this.tableStyle = style;
+       }
+
+       // TODO workaround the bug of the table layout in the Form
+       public LdifUsersTable(Composite parent, int style, boolean preventTableLayout) {
+               super(parent, SWT.NO_FOCUS);
+               this.tableStyle = style;
+               this.preventTableLayout = preventTableLayout;
+       }
+
+       /** This must be called before the call to populate method */
+       public void setColumnDefinitions(List<ColumnDefinition> columnDefinitions) {
+               this.columnDefs = columnDefinitions;
+       }
+
+       /**
+        * 
+        * @param addFilter
+        *            choose to add a field to filter results or not
+        * @param addSelection
+        *            choose to add a column to select some of the displayed results or
+        *            not
+        */
+       public void populate(boolean addFilter, boolean addSelection) {
+               // initialization
+               Composite parent = this;
+               hasFilter = addFilter;
+               hasSelectionColumn = addSelection;
+
+               // Main Layout
+               GridLayout layout = EclipseUiUtils.noSpaceGridLayout();
+               layout.verticalSpacing = 5;
+               this.setLayout(layout);
+               if (hasFilter)
+                       createFilterPart(parent);
+
+               Composite tableComp = new Composite(parent, SWT.NO_FOCUS);
+               tableComp.setLayoutData(EclipseUiUtils.fillAll());
+               usersViewer = createTableViewer(tableComp);
+               usersViewer.setContentProvider(new UsersContentProvider());
+       }
+
+       /**
+        * 
+        * @param showMore
+        *            display static filters on creation
+        * @param addSelection
+        *            choose to add a column to select some of the displayed results or
+        *            not
+        */
+       public void populateWithStaticFilters(boolean showMore, boolean addSelection) {
+               // initialization
+               Composite parent = this;
+               hasFilter = true;
+               hasSelectionColumn = addSelection;
+
+               // Main Layout
+               GridLayout layout = EclipseUiUtils.noSpaceGridLayout();
+               layout.verticalSpacing = 5;
+               this.setLayout(layout);
+               createStaticFilterPart(parent, showMore);
+
+               Composite tableComp = new Composite(parent, SWT.NO_FOCUS);
+               tableComp.setLayoutData(EclipseUiUtils.fillAll());
+               usersViewer = createTableViewer(tableComp);
+               usersViewer.setContentProvider(new UsersContentProvider());
+       }
+
+       /** Enable access to the selected users or groups */
+       public List<User> getSelectedUsers() {
+               if (hasSelectionColumn) {
+                       Object[] elements = ((CheckboxTableViewer) usersViewer).getCheckedElements();
+
+                       List<User> result = new ArrayList<User>();
+                       for (Object obj : elements) {
+                               result.add((User) obj);
+                       }
+                       return result;
+               } else
+                       throw new EclipseUiException(
+                                       "Unvalid request: no selection column " + "has been created for the current table");
+       }
+
+       /** Returns the User table viewer, typically to add doubleclick listener */
+       public TableViewer getTableViewer() {
+               return usersViewer;
+       }
+
+       /**
+        * Force the refresh of the underlying table using the current filter string if
+        * relevant
+        */
+       public void refresh() {
+               String filter = hasFilter ? filterTxt.getText().trim() : null;
+               if ("".equals(filter))
+                       filter = null;
+               refreshFilteredList(filter);
+       }
+
+       /** Effective repository request: caller must implement this method */
+       abstract protected List<User> listFilteredElements(String filter);
+
+       // protected List<User> listFilteredElements(String filter) {
+       // List<User> users = new ArrayList<User>();
+       // try {
+       // Role[] roles = userAdmin.getRoles(filter);
+       // // Display all users and groups
+       // for (Role role : roles)
+       // users.add((User) role);
+       // } catch (InvalidSyntaxException e) {
+       // throw new EclipseUiException("Unable to get roles with filter: "
+       // + filter, e);
+       // }
+       // return users;
+       // }
+
+       /* GENERIC COMPOSITE METHODS */
+       @Override
+       public boolean setFocus() {
+               if (hasFilter)
+                       return filterTxt.setFocus();
+               else
+                       return usersViewer.getTable().setFocus();
+       }
+
+       @Override
+       public void dispose() {
+               super.dispose();
+       }
+
+       /* LOCAL CLASSES AND METHODS */
+       // Will be usefull to rather use a virtual table viewer
+       private void refreshFilteredList(String filter) {
+               List<User> users = listFilteredElements(filter);
+               usersViewer.setInput(users.toArray());
+       }
+
+       private class UsersContentProvider implements IStructuredContentProvider {
+               private static final long serialVersionUID = 1L;
+
+               public Object[] getElements(Object inputElement) {
+                       return (Object[]) inputElement;
+               }
+
+               public void dispose() {
+               }
+
+               public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
+               }
+       }
+
+       /* MANAGE FILTER */
+       private void createFilterPart(Composite parent) {
+               // Text Area for the filter
+               filterTxt = new Text(parent, SWT.BORDER | SWT.SEARCH | SWT.ICON_SEARCH | SWT.ICON_CANCEL);
+               filterTxt.setLayoutData(new GridData(GridData.FILL, GridData.FILL, true, false));
+               filterTxt.addModifyListener(new ModifyListener() {
+                       private static final long serialVersionUID = 1L;
+
+                       public void modifyText(ModifyEvent event) {
+                               refreshFilteredList(filterTxt.getText());
+                       }
+               });
+       }
+
+       private void createStaticFilterPart(Composite parent, boolean showMore) {
+               Composite filterComp = new Composite(parent, SWT.NO_FOCUS);
+               filterComp.setLayout(new GridLayout(2, false));
+               filterComp.setLayoutData(EclipseUiUtils.fillWidth());
+               // generic search
+               filterTxt = new Text(filterComp, SWT.BORDER | SWT.SEARCH | SWT.ICON_SEARCH | SWT.ICON_CANCEL);
+               filterTxt.setLayoutData(new GridData(GridData.FILL, GridData.FILL, true, false));
+               // filterTxt.setLayoutData(new GridData(GridData.GRAB_HORIZONTAL |
+               // GridData.HORIZONTAL_ALIGN_FILL));
+               filterTxt.addModifyListener(new ModifyListener() {
+                       private static final long serialVersionUID = 1L;
+
+                       public void modifyText(ModifyEvent event) {
+                               refreshFilteredList(filterTxt.getText());
+                       }
+               });
+
+               // add static filter abilities
+               Link moreLk = new Link(filterComp, SWT.NONE);
+               Composite staticFilterCmp = new Composite(filterComp, SWT.NO_FOCUS);
+               staticFilterCmp.setLayoutData(EclipseUiUtils.fillWidth(2));
+               populateStaticFilters(staticFilterCmp);
+
+               MoreLinkListener listener = new MoreLinkListener(moreLk, staticFilterCmp, showMore);
+               // initialise the layout
+               listener.refresh();
+               moreLk.addSelectionListener(listener);
+       }
+
+       /** Overwrite to add static filters */
+       protected void populateStaticFilters(Composite staticFilterCmp) {
+       }
+
+       // private void addMoreSL(final Link more) {
+       // more.addSelectionListener( }
+
+       private class MoreLinkListener extends SelectionAdapter {
+               private static final long serialVersionUID = -524987616510893463L;
+               private boolean isShown;
+               private final Composite staticFilterCmp;
+               private final Link moreLk;
+
+               public MoreLinkListener(Link moreLk, Composite staticFilterCmp, boolean isShown) {
+                       this.moreLk = moreLk;
+                       this.staticFilterCmp = staticFilterCmp;
+                       this.isShown = isShown;
+               }
+
+               @Override
+               public void widgetSelected(SelectionEvent e) {
+                       isShown = !isShown;
+                       refresh();
+               }
+
+               public void refresh() {
+                       GridData gd = (GridData) staticFilterCmp.getLayoutData();
+                       if (isShown) {
+                               moreLk.setText("<a> Less... </a>");
+                               gd.heightHint = SWT.DEFAULT;
+                       } else {
+                               moreLk.setText("<a> More... </a>");
+                               gd.heightHint = 0;
+                       }
+                       forceLayout();
+               }
+       }
+
+       private void forceLayout() {
+               LdifUsersTable.this.getParent().layout(true, true);
+       }
+
+       private TableViewer createTableViewer(final Composite parent) {
+
+               int style = tableStyle | SWT.H_SCROLL | SWT.V_SCROLL;
+               if (hasSelectionColumn)
+                       style = style | SWT.CHECK;
+               Table table = new Table(parent, style);
+               TableColumnLayout layout = new TableColumnLayout();
+
+               // TODO the table layout does not works with the scrolled form
+
+               if (preventTableLayout) {
+                       parent.setLayout(EclipseUiUtils.noSpaceGridLayout());
+                       table.setLayoutData(EclipseUiUtils.fillAll());
+               } else
+                       parent.setLayout(layout);
+
+               TableViewer viewer;
+               if (hasSelectionColumn)
+                       viewer = new CheckboxTableViewer(table);
+               else
+                       viewer = new TableViewer(table);
+               table.setLinesVisible(true);
+               table.setHeaderVisible(true);
+
+               TableViewerColumn column;
+               // int offset = 0;
+               if (hasSelectionColumn) {
+                       // offset = 1;
+                       column = ViewerUtils.createTableViewerColumn(viewer, "", SWT.NONE, 25);
+                       column.setLabelProvider(new ColumnLabelProvider() {
+                               private static final long serialVersionUID = 1L;
+
+                               @Override
+                               public String getText(Object element) {
+                                       return null;
+                               }
+                       });
+                       layout.setColumnData(column.getColumn(), new ColumnWeightData(25, 25, false));
+
+                       SelectionAdapter selectionAdapter = new SelectionAdapter() {
+                               private static final long serialVersionUID = 1L;
+
+                               boolean allSelected = false;
+
+                               @Override
+                               public void widgetSelected(SelectionEvent e) {
+                                       allSelected = !allSelected;
+                                       ((CheckboxTableViewer) usersViewer).setAllChecked(allSelected);
+                               }
+                       };
+                       column.getColumn().addSelectionListener(selectionAdapter);
+               }
+
+               // NodeViewerComparator comparator = new NodeViewerComparator();
+               // TODO enable the sort by click on the header
+               // int i = offset;
+               for (ColumnDefinition colDef : columnDefs)
+                       createTableColumn(viewer, layout, colDef);
+
+               // column = ViewerUtils.createTableViewerColumn(viewer,
+               // colDef.getHeaderLabel(), SWT.NONE, colDef.getColumnSize());
+               // column.setLabelProvider(new CLProvider(colDef.getPropertyName()));
+               // column.getColumn().addSelectionListener(
+               // JcrUiUtils.getNodeSelectionAdapter(i,
+               // colDef.getPropertyType(), colDef.getPropertyName(),
+               // comparator, viewer));
+               // i++;
+               // }
+
+               // IMPORTANT: initialize comparator before setting it
+               // JcrColumnDefinition firstCol = colDefs.get(0);
+               // comparator.setColumn(firstCol.getPropertyType(),
+               // firstCol.getPropertyName());
+               // viewer.setComparator(comparator);
+
+               return viewer;
+       }
+
+       /** Default creation of a column for a user table */
+       private TableViewerColumn createTableColumn(TableViewer tableViewer, TableColumnLayout layout,
+                       ColumnDefinition columnDef) {
+
+               boolean resizable = true;
+               TableViewerColumn tvc = new TableViewerColumn(tableViewer, SWT.NONE);
+               TableColumn column = tvc.getColumn();
+
+               column.setText(columnDef.getLabel());
+               column.setWidth(columnDef.getMinWidth());
+               column.setResizable(resizable);
+
+               ColumnLabelProvider lp = columnDef.getLabelProvider();
+               // add a reference to the display to enable font management
+               // if (lp instanceof UserAdminAbstractLP)
+               // ((UserAdminAbstractLP) lp).setDisplay(tableViewer.getTable()
+               // .getDisplay());
+               tvc.setLabelProvider(lp);
+
+               layout.setColumnData(column, new ColumnWeightData(columnDef.getWeight(), columnDef.getMinWidth(), resizable));
+
+               return tvc;
+       }
+}
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/swt/useradmin/PickUpUserDialog.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/swt/useradmin/PickUpUserDialog.java
new file mode 100644 (file)
index 0000000..3e70ca3
--- /dev/null
@@ -0,0 +1,245 @@
+package org.argeo.cms.swt.useradmin;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.argeo.api.acr.ldap.LdapAttr;
+import org.argeo.api.acr.ldap.LdapObj;
+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.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 = { LdapAttr.uid.name(),
+                               LdapAttr.cn.name(), LdapAttr.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 = "(" + LdapAttr.objectClass.name() + "="
+                                               + LdapObj.groupOfNames.name() + ")";
+                               if ((showUserBtn.getSelection()))
+                                       typeStr = "(|(" + LdapAttr.objectClass.name() + "="
+                                                       + LdapObj.inetOrgPerson.name() + ")" + typeStr
+                                                       + ")";
+
+                               if (!showSystemRoleBtn.getSelection())
+                                       typeStr = "(& " + typeStr + "(!(" + LdapAttr.DN + "=*"
+                                                       + CmsConstants.SYSTEM_ROLES_BASEDN + ")))";
+
+                               if (filterBuilder.length() > 1) {
+                                       builder.append("(&" + typeStr);
+                                       builder.append("(|");
+                                       builder.append(filterBuilder.toString());
+                                       builder.append("))");
+                               } else {
+                                       builder.append(typeStr);
+                               }
+                               roles = userAdmin.getRoles(builder.toString());
+                       } catch (InvalidSyntaxException e) {
+                               throw new EclipseUiException(
+                                               "Unable to get roles with filter: " + filter, e);
+                       }
+                       List<User> users = new ArrayList<User>();
+                       for (Role role : roles)
+                               if (!users.contains(role))
+                                       users.add((User) role);
+                       return users;
+               }
+       }
+
+       private boolean notNull(String string) {
+               if (string == null)
+                       return false;
+               else
+                       return !"".equals(string.trim());
+       }
+}
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/swt/useradmin/UserLP.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/swt/useradmin/UserLP.java
new file mode 100644 (file)
index 0000000..b3ab40e
--- /dev/null
@@ -0,0 +1,76 @@
+package org.argeo.cms.swt.useradmin;
+
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.cms.auth.UserAdminUtils;
+import org.eclipse.jface.resource.JFaceResources;
+import org.eclipse.jface.viewers.ColumnLabelProvider;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Font;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.widgets.Display;
+import org.osgi.service.useradmin.Role;
+import org.osgi.service.useradmin.User;
+
+/** Centralize label providers for the group table */
+class UserLP extends ColumnLabelProvider {
+       private static final long serialVersionUID = -4645930210988368571L;
+
+       final static String COL_ICON = "colID.icon";
+       final static String COL_DN = "colID.dn";
+       final static String COL_DISPLAY_NAME = "colID.displayName";
+       final static String COL_DOMAIN = "colID.domain";
+
+       final String currType;
+
+       // private Font italic;
+       private Font bold;
+
+       UserLP(String colId) {
+               this.currType = colId;
+       }
+
+       @Override
+       public Font getFont(Object element) {
+               // Current user as bold
+               if (UserAdminUtils.isCurrentUser(((User) element))) {
+                       if (bold == null)
+                               bold = JFaceResources.getFontRegistry().defaultFontDescriptor().setStyle(SWT.BOLD)
+                                               .createFont(Display.getCurrent());
+                       return bold;
+               }
+               return null;
+       }
+
+       @Override
+       public Image getImage(Object element) {
+               if (COL_ICON.equals(currType)) {
+                       User user = (User) element;
+                       String dn = user.getName();
+                       if (dn.endsWith(CmsConstants.SYSTEM_ROLES_BASEDN))
+                               return UsersImages.ICON_ROLE;
+                       else if (user.getType() == Role.GROUP)
+                               return UsersImages.ICON_GROUP;
+                       else
+                               return UsersImages.ICON_USER;
+               } else
+                       return null;
+       }
+
+       @Override
+       public String getText(Object element) {
+               User user = (User) element;
+               return getText(user);
+
+       }
+
+       public String getText(User user) {
+               if (COL_DN.equals(currType))
+                       return user.getName();
+               else if (COL_DISPLAY_NAME.equals(currType))
+                       return UserAdminUtils.getCommonName(user);
+               else if (COL_DOMAIN.equals(currType))
+                       return UserAdminUtils.getDomainName(user);
+               else
+                       return "";
+       }
+}
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/swt/useradmin/UsersImages.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/swt/useradmin/UsersImages.java
new file mode 100644 (file)
index 0000000..21fc5af
--- /dev/null
@@ -0,0 +1,14 @@
+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");
+}
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/swt/useradmin/ViewerUtils.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/swt/useradmin/ViewerUtils.java
new file mode 100644 (file)
index 0000000..d186783
--- /dev/null
@@ -0,0 +1,58 @@
+package org.argeo.cms.swt.useradmin;
+
+import org.eclipse.jface.viewers.TableViewer;
+import org.eclipse.jface.viewers.TableViewerColumn;
+import org.eclipse.jface.viewers.TreeViewer;
+import org.eclipse.jface.viewers.TreeViewerColumn;
+import org.eclipse.swt.widgets.Table;
+import org.eclipse.swt.widgets.TableColumn;
+import org.eclipse.swt.widgets.TreeColumn;
+
+/**
+ * Centralise useful methods to manage JFace Table, Tree and TreeColumn viewers.
+ */
+public class ViewerUtils {
+
+       /**
+        * Creates a basic column for the given table. For the time being, we do not
+        * support movable columns.
+        */
+       public static TableColumn createColumn(Table parent, String name, int style, int width) {
+               TableColumn result = new TableColumn(parent, style);
+               result.setText(name);
+               result.setWidth(width);
+               result.setResizable(true);
+               return result;
+       }
+
+       /**
+        * Creates a TableViewerColumn for the given viewer. For the time being, we do
+        * not support movable columns.
+        */
+       public static TableViewerColumn createTableViewerColumn(TableViewer parent, String name, int style, int width) {
+               TableViewerColumn tvc = new TableViewerColumn(parent, style);
+               TableColumn column = tvc.getColumn();
+               column.setText(name);
+               column.setWidth(width);
+               column.setResizable(true);
+               return tvc;
+       }
+
+       // public static TableViewerColumn createTableViewerColumn(TableViewer parent,
+       // Localized name, int style, int width) {
+       // return createTableViewerColumn(parent, name.lead(), style, width);
+       // }
+
+       /**
+        * Creates a TreeViewerColumn for the given viewer. For the time being, we do
+        * not support movable columns.
+        */
+       public static TreeViewerColumn createTreeViewerColumn(TreeViewer parent, String name, int style, int width) {
+               TreeViewerColumn tvc = new TreeViewerColumn(parent, style);
+               TreeColumn column = tvc.getColumn();
+               column.setText(name);
+               column.setWidth(width);
+               column.setResizable(true);
+               return tvc;
+       }
+}
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/swt/useradmin/package-info.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/swt/useradmin/package-info.java
new file mode 100644 (file)
index 0000000..3597bfc
--- /dev/null
@@ -0,0 +1,2 @@
+/** SWT/JFace users management components. */
+package org.argeo.cms.swt.useradmin;
\ No newline at end of file
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/eclipse/forms/AbstractFormPart.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/eclipse/forms/AbstractFormPart.java
new file mode 100644 (file)
index 0000000..4ce4688
--- /dev/null
@@ -0,0 +1,108 @@
+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();
+       }
+}
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/eclipse/forms/FormColors.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/eclipse/forms/FormColors.java
new file mode 100644 (file)
index 0000000..32b031b
--- /dev/null
@@ -0,0 +1,730 @@
+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;
+       }
+}
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/eclipse/forms/FormFonts.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/eclipse/forms/FormFonts.java
new file mode 100644 (file)
index 0000000..9e931ba
--- /dev/null
@@ -0,0 +1,122 @@
+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;
+       }
+}
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/eclipse/forms/FormToolkit.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/eclipse/forms/FormToolkit.java
new file mode 100644 (file)
index 0000000..9927104
--- /dev/null
@@ -0,0 +1,913 @@
+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;
+       }
+}
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/eclipse/forms/FormUtil.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/eclipse/forms/FormUtil.java
new file mode 100644 (file)
index 0000000..76e3f11
--- /dev/null
@@ -0,0 +1,522 @@
+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);
+//     }
+}
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/eclipse/forms/IFormColors.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/eclipse/forms/IFormColors.java
new file mode 100644 (file)
index 0000000..cf0e5d3
--- /dev/null
@@ -0,0 +1,102 @@
+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
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/eclipse/forms/IFormPart.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/eclipse/forms/IFormPart.java
new file mode 100644 (file)
index 0000000..954cc03
--- /dev/null
@@ -0,0 +1,108 @@
+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();
+}
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/eclipse/forms/IManagedForm.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/eclipse/forms/IManagedForm.java
new file mode 100644 (file)
index 0000000..490d3a3
--- /dev/null
@@ -0,0 +1,175 @@
+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();
+}
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/eclipse/forms/IPartSelectionListener.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/eclipse/forms/IPartSelectionListener.java
new file mode 100644 (file)
index 0000000..0f557d4
--- /dev/null
@@ -0,0 +1,23 @@
+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);
+}
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/eclipse/forms/ManagedForm.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/eclipse/forms/ManagedForm.java
new file mode 100644 (file)
index 0000000..4140465
--- /dev/null
@@ -0,0 +1,323 @@
+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();
+//     }
+}
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/eclipse/forms/editor/FormEditor.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/eclipse/forms/editor/FormEditor.java
new file mode 100644 (file)
index 0000000..484dae8
--- /dev/null
@@ -0,0 +1,85 @@
+package org.argeo.cms.ui.eclipse.forms.editor;
+
+import org.argeo.cms.ui.eclipse.forms.FormToolkit;
+import org.eclipse.jface.dialogs.IPageChangeProvider;
+
+/**
+ * This class forms a base of multi-page form editors that typically use one or
+ * more pages with forms and one page for raw source of the editor input.
+ * <p>
+ * Pages are added 'lazily' i.e. adding a page reserves a tab for it but does
+ * not cause the page control to be created. Page control is created when an
+ * attempt is made to select the page in question. This allows editors with
+ * several tabs and complex pages to open quickly.
+ * <p>
+ * Subclasses should extend this class and implement <code>addPages</code>
+ * method. One of the two <code>addPage</code> methods should be called to
+ * contribute pages to the editor. One adds complete (standalone) editors as
+ * nested tabs. These editors will be created right away and will be hooked so
+ * that key bindings, selection service etc. is compatible with the one for the
+ * standalone case. The other method adds classes that implement
+ * <code>IFormPage</code> interface. These pages will be created lazily and
+ * they will share the common key binding and selection service. Since 3.1,
+ * FormEditor is a page change provider. It allows listeners to attach to it and
+ * get notified when pages are changed. This new API in JFace allows dynamic
+ * help to update on page changes.
+ * 
+ * @since 1.0
+ */
+// RAP [if] As RAP is still using workbench 3.4, the implementation of
+// IPageChangeProvider is missing from MultiPageEditorPart. Remove this code
+// with the adoption of workbench > 3.5
+//public abstract class FormEditor extends MultiPageEditorPart  {
+public abstract class FormEditor  implements
+        IPageChangeProvider {
+       private FormToolkit formToolkit;
+       
+       
+public FormToolkit getToolkit() {
+               return formToolkit;
+       }
+
+public void editorDirtyStateChanged() {
+       
+}
+
+public FormPage getActivePageInstance() {
+       return null;
+}
+
+       // RAP [if] As RAP is still using workbench 3.4, the implementation of
+// IPageChangeProvider is missing from MultiPageEditorPart. Remove this code
+// with the adoption of workbench > 3.5
+//     private ListenerList pageListeners = new ListenerList();
+//     
+//    /*
+//     * (non-Javadoc)
+//     * 
+//     * @see org.eclipse.jface.dialogs.IPageChangeProvider#addPageChangedListener(org.eclipse.jface.dialogs.IPageChangedListener)
+//     */
+//    public void addPageChangedListener(IPageChangedListener listener) {
+//        pageListeners.add(listener);
+//    }
+//
+//    /*
+//     * (non-Javadoc)
+//     * 
+//     * @see org.eclipse.jface.dialogs.IPageChangeProvider#removePageChangedListener(org.eclipse.jface.dialogs.IPageChangedListener)
+//     */
+//    public void removePageChangedListener(IPageChangedListener listener) {
+//        pageListeners.remove(listener);
+//    }
+//    
+//     private void firePageChanged(final PageChangedEvent event) {
+//        Object[] listeners = pageListeners.getListeners();
+//        for (int i = 0; i < listeners.length; ++i) {
+//            final IPageChangedListener l = (IPageChangedListener) listeners[i];
+//            SafeRunnable.run(new SafeRunnable() {
+//                public void run() {
+//                    l.pageChanged(event);
+//                }
+//            });
+//        }
+//    }
+// RAPEND [if]
+}
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/eclipse/forms/editor/FormPage.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/eclipse/forms/editor/FormPage.java
new file mode 100644 (file)
index 0000000..a788412
--- /dev/null
@@ -0,0 +1,276 @@
+package org.argeo.cms.ui.eclipse.forms.editor;
+import org.argeo.cms.ui.eclipse.forms.IManagedForm;
+import org.argeo.cms.ui.eclipse.forms.ManagedForm;
+import org.eclipse.swt.custom.BusyIndicator;
+import org.eclipse.swt.custom.ScrolledComposite;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+/**
+ * A base class that all pages that should be added to FormEditor must subclass.
+ * Form page has an instance of PageForm that extends managed form. Subclasses
+ * should override method 'createFormContent(ManagedForm)' to fill the form with
+ * content. Note that page itself can be loaded lazily (on first open).
+ * Consequently, the call to create the form content can come after the editor
+ * has been opened for a while (in fact, it is possible to open and close the
+ * editor and never create the form because no attempt has been made to show the
+ * page).
+ * 
+ * @since 1.0
+ */
+public class FormPage implements IFormPage {
+       private FormEditor editor;
+       private PageForm mform;
+       private int index;
+       private String id;
+       
+       private String partName;
+       
+       
+       
+       public void setPartName(String partName) {
+               this.partName = partName;
+       }
+       private static class PageForm extends ManagedForm {
+               public PageForm(FormPage page, ScrolledComposite form) {
+                       super(page.getEditor().getToolkit(), form);
+                       setContainer(page);
+               }
+               
+               public FormPage getPage() {
+                       return (FormPage)getContainer();
+               }
+               public void dirtyStateChanged() {
+                       getPage().getEditor().editorDirtyStateChanged();
+               }
+               public void staleStateChanged() {
+                       if (getPage().isActive())
+                               refresh();
+               }
+       }
+       /**
+        * A constructor that creates the page and initializes it with the editor.
+        * 
+        * @param editor
+        *            the parent editor
+        * @param id
+        *            the unique identifier
+        * @param title
+        *            the page title
+        */
+       public FormPage(FormEditor editor, String id, String title) {
+               this(id, title);
+               initialize(editor);
+       }
+       /**
+        * The constructor. The parent editor need to be passed in the
+        * <code>initialize</code> method if this constructor is used.
+        * 
+        * @param id
+        *            a unique page identifier
+        * @param title
+        *            a user-friendly page title
+        */
+       public FormPage(String id, String title) {
+               this.id = id;
+               setPartName(title);
+       }
+       /**
+        * Initializes the form page.
+        * 
+        * @see IEditorPart#init
+        */
+//     public void init(IEditorSite site, IEditorInput input) {
+//             setSite(site);
+//             setInput(input);
+//     }
+       /**
+        * Primes the form page with the parent editor instance.
+        * 
+        * @param editor
+        *            the parent editor
+        */
+       public void initialize(FormEditor editor) {
+               this.editor = editor;
+       }
+       /**
+        * Returns the parent editor.
+        * 
+        * @return parent editor instance
+        */
+       public FormEditor getEditor() {
+               return editor;
+       }
+       /**
+        * Returns the managed form owned by this page.
+        * 
+        * @return the managed form
+        */
+       public IManagedForm getManagedForm() {
+               return mform;
+       }
+       /**
+        * Implements the required method by refreshing the form when set active.
+        * Subclasses must call super when overriding this method.
+        */
+       public void setActive(boolean active) {
+               if (active) {
+                       // We are switching to this page - refresh it
+                       // if needed.
+                       if (mform != null)
+                               mform.refresh();
+               }
+       }
+       /**
+        * Tests if the page is active by asking the parent editor if this page is
+        * the currently active page.
+        * 
+        * @return <code>true</code> if the page is currently active,
+        *         <code>false</code> otherwise.
+        */
+       public boolean isActive() {
+               return this.equals(editor.getActivePageInstance());
+       }
+       /**
+        * Creates the part control by creating the managed form using the parent
+        * editor's toolkit. Subclasses should override
+        * <code>createFormContent(IManagedForm)</code> to populate the form with
+        * content.
+        * 
+        * @param parent
+        *            the page parent composite
+        */
+       public void createPartControl(Composite parent) {
+               ScrolledComposite form = editor.getToolkit().createScrolledForm(parent);
+               mform = new PageForm(this, form);
+               BusyIndicator.showWhile(parent.getDisplay(), new Runnable() {
+                       public void run() {
+                               createFormContent(mform);
+                       }
+               });
+       }
+       /**
+        * Subclasses should override this method to create content in the form
+        * hosted in this page.
+        * 
+        * @param managedForm
+        *            the form hosted in this page.
+        */
+       protected void createFormContent(IManagedForm managedForm) {
+       }
+       /**
+        * Returns the form page control.
+        * 
+        * @return managed form's control
+        */
+       public Control getPartControl() {
+               return mform != null ? mform.getForm() : null;
+       }
+       /**
+        * Disposes the managed form.
+        */
+       public void dispose() {
+               if (mform != null)
+                       mform.dispose();
+       }
+       /**
+        * Returns the unique identifier that can be used to reference this page.
+        * 
+        * @return the unique page identifier
+        */
+       public String getId() {
+               return id;
+       }
+       /**
+        * Returns <code>null</code>- form page has no title image. Subclasses
+        * may override.
+        * 
+        * @return <code>null</code>
+        */
+       public Image getTitleImage() {
+               return null;
+       }
+       /**
+        * Sets the focus by delegating to the managed form.
+        */
+       public void setFocus() {
+               if (mform != null)
+                       mform.setFocus();
+       }
+       /**
+        * @see org.eclipse.ui.ISaveablePart#doSave(org.eclipse.core.runtime.IProgressMonitor)
+        */
+//     public void doSave(IProgressMonitor monitor) {
+//             if (mform != null)
+//                     mform.commit(true);
+//     }
+       /**
+        * @see org.eclipse.ui.ISaveablePart#doSaveAs()
+        */
+       public void doSaveAs() {
+       }
+       /**
+        * @see org.eclipse.ui.ISaveablePart#isSaveAsAllowed()
+        */
+       public boolean isSaveAsAllowed() {
+               return false;
+       }
+       /**
+        * Implemented by testing if the managed form is dirty.
+        * 
+        * @return <code>true</code> if the managed form is dirty,
+        *         <code>false</code> otherwise.
+        * 
+        * @see org.eclipse.ui.ISaveablePart#isDirty()
+        */
+       public boolean isDirty() {
+               return mform != null ? mform.isDirty() : false;
+       }
+       /**
+        * Preserves the page index.
+        * 
+        * @param index
+        *            the assigned page index
+        */
+       public void setIndex(int index) {
+               this.index = index;
+       }
+       /**
+        * Returns the saved page index.
+        * 
+        * @return the page index
+        */
+       public int getIndex() {
+               return index;
+       }
+       /**
+        * Form pages are not editors.
+        * 
+        * @return <code>false</code>
+        */
+       public boolean isEditor() {
+               return false;
+       }
+       /**
+        * Attempts to select and reveal the given object by passing the request to
+        * the managed form.
+        * 
+        * @param object
+        *            the object to select and reveal in the page if possible.
+        * @return <code>true</code> if the page has been successfully selected
+        *         and revealed by one of the managed form parts, <code>false</code>
+        *         otherwise.
+        */
+       public boolean selectReveal(Object object) {
+               if (mform != null)
+                       return mform.setInput(object);
+               return false;
+       }
+       /**
+        * By default, editor will be allowed to flip the page.
+        * @return <code>true</code>
+        */
+       public boolean canLeaveThePage() {
+               return true;
+       }
+}
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/eclipse/forms/editor/IFormPage.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/eclipse/forms/editor/IFormPage.java
new file mode 100644 (file)
index 0000000..eb08cb5
--- /dev/null
@@ -0,0 +1,119 @@
+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);
+}
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/jcr/DefaultRepositoryRegister.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/jcr/DefaultRepositoryRegister.java
new file mode 100644 (file)
index 0000000..3806341
--- /dev/null
@@ -0,0 +1,75 @@
+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);
+       }
+}
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/jcr/FullVersioningTreeContentProvider.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/jcr/FullVersioningTreeContentProvider.java
new file mode 100644 (file)
index 0000000..0f7ee77
--- /dev/null
@@ -0,0 +1,98 @@
+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) {
+       }
+
+}
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/jcr/JcrBrowserUtils.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/jcr/JcrBrowserUtils.java
new file mode 100644 (file)
index 0000000..b36acc3
--- /dev/null
@@ -0,0 +1,68 @@
+package org.argeo.cms.ui.jcr;
+
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+
+import org.argeo.cms.ui.jcr.model.RepositoriesElem;
+import org.argeo.cms.ui.jcr.model.RepositoryElem;
+import org.argeo.cms.ui.jcr.model.SingleJcrNodeElem;
+import org.argeo.cms.ui.jcr.model.WorkspaceElem;
+import org.argeo.cms.ux.widgets.TreeParent;
+import org.argeo.eclipse.ui.EclipseUiException;
+
+/** Useful methods to manage the JCR Browser */
+public class JcrBrowserUtils {
+
+       public static String getPropertyTypeAsString(Property prop) {
+               try {
+                       return PropertyType.nameFromValue(prop.getType());
+               } catch (RepositoryException e) {
+                       throw new EclipseUiException("Cannot check type for " + prop, e);
+               }
+       }
+
+       /** Insure that the UI component is not stale, refresh if needed */
+       public static void forceRefreshIfNeeded(TreeParent element) {
+               Node curNode = null;
+
+               boolean doRefresh = false;
+
+               try {
+                       if (element instanceof SingleJcrNodeElem) {
+                               curNode = ((SingleJcrNodeElem) element).getNode();
+                       } else if (element instanceof WorkspaceElem) {
+                               curNode = ((WorkspaceElem) element).getRootNode();
+                       }
+
+                       if (curNode != null && element.getChildren().length != curNode.getNodes().getSize())
+                               doRefresh = true;
+                       else if (element instanceof RepositoryElem) {
+                               RepositoryElem rn = (RepositoryElem) element;
+                               if (rn.isConnected()) {
+                                       String[] wkpNames = rn.getAccessibleWorkspaceNames();
+                                       if (element.getChildren().length != wkpNames.length)
+                                               doRefresh = true;
+                               }
+                       } else if (element instanceof RepositoriesElem) {
+                               doRefresh = true;
+                               // Always force refresh for RepositoriesElem : the condition
+                               // below does not take remote repository into account and it is
+                               // not trivial to do so.
+
+                               // RepositoriesElem rn = (RepositoriesElem) element;
+                               // if (element.getChildren().length !=
+                               // rn.getRepositoryRegister()
+                               // .getRepositories().size())
+                               // doRefresh = true;
+                       }
+                       if (doRefresh) {
+                               element.clearChildren();
+                               element.getChildren();
+                       }
+               } catch (RepositoryException re) {
+                       throw new EclipseUiException("Unexpected error while synchronising the UI with the JCR repository", re);
+               }
+       }
+}
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/jcr/JcrDClickListener.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/jcr/JcrDClickListener.java
new file mode 100644 (file)
index 0000000..1707681
--- /dev/null
@@ -0,0 +1,60 @@
+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
+       }
+}
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/jcr/JcrImages.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/jcr/JcrImages.java
new file mode 100644 (file)
index 0000000..d1d1e31
--- /dev/null
@@ -0,0 +1,24 @@
+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");
+
+}
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/jcr/JcrTreeContentProvider.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/jcr/JcrTreeContentProvider.java
new file mode 100644 (file)
index 0000000..cc8479f
--- /dev/null
@@ -0,0 +1,82 @@
+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) {
+       }
+}
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/jcr/NodeContentProvider.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/jcr/NodeContentProvider.java
new file mode 100644 (file)
index 0000000..0c1221a
--- /dev/null
@@ -0,0 +1,175 @@
+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.api.cms.keyring.Keyring;
+import org.argeo.cms.jcr.CmsJcrUtils;
+import org.argeo.cms.ui.jcr.model.RepositoriesElem;
+import org.argeo.cms.ui.jcr.model.SingleJcrNodeElem;
+import org.argeo.cms.ux.widgets.TreeParent;
+import org.eclipse.jface.viewers.ITreeContentProvider;
+import org.eclipse.jface.viewers.Viewer;
+
+/**
+ * Implementation of the {@code ITreeContentProvider} to display multiple
+ * repository environment in a tree like structure
+ */
+public class NodeContentProvider implements ITreeContentProvider {
+       private static final long serialVersionUID = -4083809398848374403L;
+       final private RepositoryRegister repositoryRegister;
+       final private RepositoryFactory repositoryFactory;
+
+       // Current user session on the default workspace of the argeo Node
+       final private Session userSession;
+       final private Keyring keyring;
+       private boolean sortChildren;
+
+       // Reference for cleaning
+       private SingleJcrNodeElem homeNode = null;
+       private RepositoriesElem repositoriesNode = null;
+
+       // Utils
+       private TreeBrowserComparator itemComparator = new TreeBrowserComparator();
+
+       public NodeContentProvider(Session userSession, Keyring keyring,
+                       RepositoryRegister repositoryRegister,
+                       RepositoryFactory repositoryFactory, Boolean sortChildren) {
+               this.userSession = userSession;
+               this.keyring = keyring;
+               this.repositoryRegister = repositoryRegister;
+               this.repositoryFactory = repositoryFactory;
+               this.sortChildren = sortChildren;
+       }
+
+       public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
+               if (newInput == null)// dispose
+                       return;
+
+               if (userSession != null) {
+                       Node userHome = CmsJcrUtils.getUserHome(userSession);
+                       if (userHome != null) {
+                               // TODO : find a way to dynamically get alias for the node
+                               if (homeNode != null)
+                                       homeNode.dispose();
+                               homeNode = new SingleJcrNodeElem(null, userHome,
+                                               userSession.getUserID(), CmsConstants.EGO_REPOSITORY);
+                       }
+               }
+               if (repositoryRegister != null) {
+                       if (repositoriesNode != null)
+                               repositoriesNode.dispose();
+                       repositoriesNode = new RepositoriesElem("Repositories",
+                                       repositoryRegister, repositoryFactory, null, userSession,
+                                       keyring);
+               }
+       }
+
+       /**
+        * Sends back the first level of the Tree. Independent from inputElement
+        * that can be null
+        */
+       public Object[] getElements(Object inputElement) {
+               List<Object> objs = new ArrayList<Object>();
+               if (homeNode != null)
+                       objs.add(homeNode);
+               if (repositoriesNode != null)
+                       objs.add(repositoriesNode);
+               return objs.toArray();
+       }
+
+       public Object[] getChildren(Object parentElement) {
+               if (parentElement instanceof TreeParent) {
+                       if (sortChildren) {
+                               Object[] tmpArr = ((TreeParent) parentElement).getChildren();
+                               if (tmpArr == null)
+                                       return new Object[0];
+                               TreeParent[] arr = new TreeParent[tmpArr.length];
+                               for (int i = 0; i < tmpArr.length; i++)
+                                       arr[i] = (TreeParent) tmpArr[i];
+                               Arrays.sort(arr, itemComparator);
+                               return arr;
+                       } else
+                               return ((TreeParent) parentElement).getChildren();
+               } else
+                       return new Object[0];
+       }
+
+       /**
+        * Sets whether the content provider should order the children nodes or not.
+        * It is user duty to call a full refresh of the tree after changing this
+        * parameter.
+        */
+       public void setSortChildren(boolean sortChildren) {
+               this.sortChildren = sortChildren;
+       }
+
+       public Object getParent(Object element) {
+               if (element instanceof TreeParent) {
+                       return ((TreeParent) element).getParent();
+               } else
+                       return null;
+       }
+
+       public boolean hasChildren(Object element) {
+               if (element instanceof RepositoriesElem) {
+                       RepositoryRegister rr = ((RepositoriesElem) element)
+                                       .getRepositoryRegister();
+                       return rr.getRepositories().size() > 0;
+               } else if (element instanceof TreeParent) {
+                       TreeParent tp = (TreeParent) element;
+                       return tp.hasChildren();
+               }
+               return false;
+       }
+
+       public void dispose() {
+               if (homeNode != null)
+                       homeNode.dispose();
+               if (repositoriesNode != null) {
+                       // logs out open sessions
+                       // see https://bugzilla.argeo.org/show_bug.cgi?id=23
+                       repositoriesNode.dispose();
+               }
+       }
+
+       /**
+        * Specific comparator for this view. See specification here:
+        * https://www.argeo.org/bugzilla/show_bug.cgi?id=139
+        */
+       private class TreeBrowserComparator implements Comparator<TreeParent> {
+
+               public int category(TreeParent element) {
+                       if (element instanceof SingleJcrNodeElem) {
+                               Node node = ((SingleJcrNodeElem) element).getNode();
+                               try {
+                                       if (node.isNodeType(NodeType.NT_FOLDER))
+                                               return 5;
+                               } catch (RepositoryException e) {
+                                       // TODO Auto-generated catch block
+                                       e.printStackTrace();
+                               }
+                       }
+                       return 10;
+               }
+
+               public int compare(TreeParent o1, TreeParent o2) {
+                       int cat1 = category(o1);
+                       int cat2 = category(o2);
+
+                       if (cat1 != cat2) {
+                               return cat1 - cat2;
+                       }
+                       return o1.getName().compareTo(o2.getName());
+               }
+       }
+}
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/jcr/NodeLabelProvider.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/jcr/NodeLabelProvider.java
new file mode 100644 (file)
index 0000000..a5751c0
--- /dev/null
@@ -0,0 +1,113 @@
+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;
+               }
+       }
+}
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/jcr/OsgiRepositoryRegister.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/jcr/OsgiRepositoryRegister.java
new file mode 100644 (file)
index 0000000..444350a
--- /dev/null
@@ -0,0 +1,52 @@
+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();
+       }
+}
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/jcr/PropertiesContentProvider.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/jcr/PropertiesContentProvider.java
new file mode 100644 (file)
index 0000000..fd544bb
--- /dev/null
@@ -0,0 +1,42 @@
+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);
+               }
+       }
+}
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/jcr/PropertyLabelProvider.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/jcr/PropertyLabelProvider.java
new file mode 100644 (file)
index 0000000..58d6031
--- /dev/null
@@ -0,0 +1,102 @@
+package org.argeo.cms.ui.jcr;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+
+import javax.jcr.Property;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import javax.jcr.Value;
+
+import org.argeo.eclipse.ui.EclipseUiException;
+import org.argeo.jcr.JcrUtils;
+import org.eclipse.jface.viewers.ColumnLabelProvider;
+import org.eclipse.jface.viewers.ViewerCell;
+
+/** Default basic label provider for a given JCR Node's properties */
+public class PropertyLabelProvider extends ColumnLabelProvider {
+       private static final long serialVersionUID = -5405794508731390147L;
+
+       // To be able to change column order easily
+       public static final int COLUMN_PROPERTY = 0;
+       public static final int COLUMN_VALUE = 1;
+       public static final int COLUMN_TYPE = 2;
+       public static final int COLUMN_ATTRIBUTES = 3;
+
+       private final static String DATE_TIME_FORMAT = "dd/MM/yyyy, HH:mm";
+
+       // Utils
+       protected DateFormat timeFormatter = new SimpleDateFormat(DATE_TIME_FORMAT);
+
+       public void update(ViewerCell cell) {
+               Object element = cell.getElement();
+               cell.setText(getColumnText(element, cell.getColumnIndex()));
+       }
+
+       public String getColumnText(Object element, int columnIndex) {
+               try {
+                       if (element instanceof Property) {
+                               Property prop = (Property) element;
+                               if (prop.isMultiple()) {
+                                       switch (columnIndex) {
+                                       case COLUMN_PROPERTY:
+                                               return prop.getName();
+                                       case COLUMN_VALUE:
+                                               // Corresponding values are listed on children
+                                               return "";
+                                       case COLUMN_TYPE:
+                                               return JcrBrowserUtils.getPropertyTypeAsString(prop);
+                                       case COLUMN_ATTRIBUTES:
+                                               return JcrUtils.getPropertyDefinitionAsString(prop);
+                                       }
+                               } else {
+                                       switch (columnIndex) {
+                                       case COLUMN_PROPERTY:
+                                               return prop.getName();
+                                       case COLUMN_VALUE:
+                                               return formatValueAsString(prop.getValue());
+                                       case COLUMN_TYPE:
+                                               return JcrBrowserUtils.getPropertyTypeAsString(prop);
+                                       case COLUMN_ATTRIBUTES:
+                                               return JcrUtils.getPropertyDefinitionAsString(prop);
+                                       }
+                               }
+                       } else if (element instanceof Value) {
+                               Value val = (Value) element;
+                               switch (columnIndex) {
+                               case COLUMN_PROPERTY:
+                                       // Nothing to show
+                                       return "";
+                               case COLUMN_VALUE:
+                                       return formatValueAsString(val);
+                               case COLUMN_TYPE:
+                                       // listed on the parent
+                                       return "";
+                               case COLUMN_ATTRIBUTES:
+                                       // Corresponding attributes are listed on the parent
+                                       return "";
+                               }
+                       }
+               } catch (RepositoryException re) {
+                       throw new EclipseUiException("Cannot retrieve prop value on " + element, re);
+               }
+               return null;
+       }
+
+       private String formatValueAsString(Value value) {
+               // TODO enhance this method
+               try {
+                       String strValue;
+
+                       if (value.getType() == PropertyType.BINARY)
+                               strValue = "<binary>";
+                       else if (value.getType() == PropertyType.DATE)
+                               strValue = timeFormatter.format(value.getDate().getTime());
+                       else
+                               strValue = value.getString();
+                       return strValue;
+               } catch (RepositoryException e) {
+                       throw new EclipseUiException("unexpected error while formatting value", e);
+               }
+       }
+}
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/jcr/RepositoryRegister.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/jcr/RepositoryRegister.java
new file mode 100644 (file)
index 0000000..802c756
--- /dev/null
@@ -0,0 +1,16 @@
+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();
+}
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/jcr/VersionLabelProvider.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/jcr/VersionLabelProvider.java
new file mode 100644 (file)
index 0000000..37dfe2b
--- /dev/null
@@ -0,0 +1,33 @@
+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);
+       }
+}
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/jcr/model/MaintainedRepositoryElem.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/jcr/model/MaintainedRepositoryElem.java
new file mode 100644 (file)
index 0000000..d33b33f
--- /dev/null
@@ -0,0 +1,21 @@
+package org.argeo.cms.ui.jcr.model;
+
+import javax.jcr.Repository;
+
+import org.argeo.cms.ux.widgets.TreeParent;
+
+/** Wrap a MaintainedRepository */
+public class MaintainedRepositoryElem extends RepositoryElem {
+
+       public MaintainedRepositoryElem(String alias, Repository repository, TreeParent parent) {
+               super(alias, repository, parent);
+               // if (!(repository instanceof MaintainedRepository)) {
+               // throw new ArgeoException("Repository " + alias
+               // + " is not a maintained repository");
+               // }
+       }
+
+       // protected MaintainedRepository getMaintainedRepository() {
+       // return (MaintainedRepository) getRepository();
+       // }
+}
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/jcr/model/RemoteRepositoryElem.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/jcr/model/RemoteRepositoryElem.java
new file mode 100644 (file)
index 0000000..382f356
--- /dev/null
@@ -0,0 +1,76 @@
+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.api.cms.keyring.Keyring;
+import org.argeo.cms.ArgeoNames;
+import org.argeo.cms.jcr.CmsJcrUtils;
+import org.argeo.cms.ux.widgets.TreeParent;
+import org.argeo.eclipse.ui.EclipseUiException;
+
+/** Root of a remote repository */
+public class RemoteRepositoryElem extends RepositoryElem {
+       private final Keyring keyring;
+       /**
+        * A session of the logged in user on the default workspace of the node
+        * repository.
+        */
+       private final Session userSession;
+       private final String remoteNodePath;
+
+       private final RepositoryFactory repositoryFactory;
+       private final String uri;
+
+       public RemoteRepositoryElem(String alias, RepositoryFactory repositoryFactory, String uri, TreeParent parent,
+                       Session userSession, Keyring keyring, String remoteNodePath) {
+               super(alias, null, parent);
+               this.repositoryFactory = repositoryFactory;
+               this.uri = uri;
+               this.keyring = keyring;
+               this.userSession = userSession;
+               this.remoteNodePath = remoteNodePath;
+       }
+
+       @Override
+       protected Session repositoryLogin(String workspaceName) throws RepositoryException {
+               Node remoteRepository = userSession.getNode(remoteNodePath);
+               String userID = remoteRepository.getProperty(ArgeoNames.ARGEO_USER_ID).getString();
+               if (userID.trim().equals("")) {
+                       return getRepository().login(workspaceName);
+               } else {
+                       String pwdPath = remoteRepository.getPath() + '/' + ArgeoNames.ARGEO_PASSWORD;
+                       char[] password = keyring.getAsChars(pwdPath);
+                       try {
+                               SimpleCredentials credentials = new SimpleCredentials(userID, password);
+                               return getRepository().login(credentials, workspaceName);
+                       } finally {
+                               Arrays.fill(password, 0, password.length, ' ');
+                       }
+               }
+       }
+
+       @Override
+       public Repository getRepository() {
+               if (repository == null)
+                       repository = CmsJcrUtils.getRepositoryByUri(repositoryFactory, uri);
+               return super.getRepository();
+       }
+
+       public void remove() {
+               try {
+                       Node remoteNode = userSession.getNode(remoteNodePath);
+                       remoteNode.remove();
+                       remoteNode.getSession().save();
+               } catch (RepositoryException e) {
+                       throw new EclipseUiException("Cannot remove " + remoteNodePath, e);
+               }
+       }
+
+}
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/jcr/model/RepositoriesElem.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/jcr/model/RepositoriesElem.java
new file mode 100644 (file)
index 0000000..6218e04
--- /dev/null
@@ -0,0 +1,112 @@
+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.api.cms.keyring.Keyring;
+import org.argeo.cms.ArgeoNames;
+import org.argeo.cms.jcr.CmsJcrUtils;
+import org.argeo.cms.ui.jcr.RepositoryRegister;
+import org.argeo.cms.ux.widgets.TreeParent;
+import org.argeo.eclipse.ui.EclipseUiException;
+import org.argeo.eclipse.ui.dialogs.ErrorFeedback;
+
+/**
+ * UI Tree component that implements the Argeo abstraction of a
+ * {@link RepositoryFactory} that enable a user to "mount" various repositories
+ * in a single Tree like View. It is usually meant to be at the root of the UI
+ * Tree and thus {@link #getParent()} method will return null.
+ * 
+ * The {@link RepositoryFactory} is injected at instantiation time and must be
+ * use get or register new {@link Repository} objects upon which a reference is
+ * kept here.
+ */
+
+public class RepositoriesElem extends TreeParent implements ArgeoNames {
+       private final RepositoryRegister repositoryRegister;
+       private final RepositoryFactory repositoryFactory;
+
+       /**
+        * A session of the logged in user on the default workspace of the node
+        * repository.
+        */
+       private final Session userSession;
+       private final Keyring keyring;
+
+       public RepositoriesElem(String name, RepositoryRegister repositoryRegister, RepositoryFactory repositoryFactory,
+                       TreeParent parent, Session userSession, Keyring keyring) {
+               super(name);
+               this.repositoryRegister = repositoryRegister;
+               this.repositoryFactory = repositoryFactory;
+               this.userSession = userSession;
+               this.keyring = keyring;
+       }
+
+       /**
+        * Override normal behavior to initialize the various repositories only at
+        * request time
+        */
+       @Override
+       public synchronized Object[] getChildren() {
+               if (isLoaded()) {
+                       return super.getChildren();
+               } else {
+                       // initialize current object
+                       Map<String, Repository> refRepos = repositoryRegister.getRepositories();
+                       for (String name : refRepos.keySet()) {
+                               Repository repository = refRepos.get(name);
+                               // if (repository instanceof MaintainedRepository)
+                               // super.addChild(new MaintainedRepositoryElem(name,
+                               // repository, this));
+                               // else
+                               super.addChild(new RepositoryElem(name, repository, this));
+                       }
+
+                       // remote
+                       if (keyring != null) {
+                               try {
+                                       addRemoteRepositories(keyring);
+                               } catch (RepositoryException e) {
+                                       throw new EclipseUiException("Cannot browse remote repositories", e);
+                               }
+                       }
+                       return super.getChildren();
+               }
+       }
+
+       protected void addRemoteRepositories(Keyring jcrKeyring) throws RepositoryException {
+               Node userHome = CmsJcrUtils.getUserHome(userSession);
+               if (userHome != null && userHome.hasNode(ARGEO_REMOTE)) {
+                       NodeIterator it = userHome.getNode(ARGEO_REMOTE).getNodes();
+                       while (it.hasNext()) {
+                               Node remoteNode = it.nextNode();
+                               String uri = remoteNode.getProperty(ARGEO_URI).getString();
+                               try {
+                                       RemoteRepositoryElem remoteRepositoryNode = new RemoteRepositoryElem(remoteNode.getName(),
+                                                       repositoryFactory, uri, this, userSession, jcrKeyring, remoteNode.getPath());
+                                       super.addChild(remoteRepositoryNode);
+                               } catch (Exception e) {
+                                       ErrorFeedback.show("Cannot add remote repository " + remoteNode, e);
+                               }
+                       }
+               }
+       }
+
+       public void registerNewRepository(String alias, Repository repository) {
+               // TODO: implement this
+               // Create a new RepositoryNode Object
+               // add it
+               // super.addChild(new RepositoriesNode(...));
+       }
+
+       /** Returns the {@link RepositoryRegister} wrapped by this object. */
+       public RepositoryRegister getRepositoryRegister() {
+               return repositoryRegister;
+       }
+}
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/jcr/model/RepositoryElem.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/jcr/model/RepositoryElem.java
new file mode 100644 (file)
index 0000000..296c369
--- /dev/null
@@ -0,0 +1,98 @@
+package org.argeo.cms.ui.jcr.model;
+
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.cms.ux.widgets.TreeParent;
+import org.argeo.eclipse.ui.EclipseUiException;
+import org.argeo.jcr.JcrUtils;
+
+/**
+ * UI Tree component that wraps a JCR {@link Repository}. It also keeps a
+ * reference to its parent Tree Ui component; typically the unique
+ * {@link RepositoriesElem} object of the current view to enable bi-directionnal
+ * browsing in the tree.
+ */
+
+public class RepositoryElem extends TreeParent {
+       private String alias;
+       protected Repository repository;
+       private Session defaultSession = null;
+
+       /** Create a new repository with distinct name and alias */
+       public RepositoryElem(String alias, Repository repository, TreeParent parent) {
+               super(alias);
+               this.repository = repository;
+               setParent(parent);
+               this.alias = alias;
+       }
+
+       public void login() {
+               try {
+                       defaultSession = repositoryLogin(CmsConstants.SYS_WORKSPACE);
+                       String[] wkpNames = defaultSession.getWorkspace().getAccessibleWorkspaceNames();
+                       for (String wkpName : wkpNames) {
+                               if (wkpName.equals(defaultSession.getWorkspace().getName()))
+                                       addChild(new WorkspaceElem(this, wkpName, defaultSession));
+                               else
+                                       addChild(new WorkspaceElem(this, wkpName));
+                       }
+               } catch (RepositoryException e) {
+                       throw new EclipseUiException("Cannot connect to repository " + alias, e);
+               }
+       }
+
+       public synchronized void logout() {
+               for (Object child : getChildren()) {
+                       if (child instanceof WorkspaceElem)
+                               ((WorkspaceElem) child).logout();
+               }
+               clearChildren();
+               JcrUtils.logoutQuietly(defaultSession);
+               defaultSession = null;
+       }
+
+       /**
+        * Actual call to the {@link Repository#login(javax.jcr.Credentials, String)}
+        * method. To be overridden.
+        */
+       protected Session repositoryLogin(String workspaceName) throws RepositoryException {
+               return repository.login(workspaceName);
+       }
+
+       public String[] getAccessibleWorkspaceNames() {
+               try {
+                       return defaultSession.getWorkspace().getAccessibleWorkspaceNames();
+               } catch (RepositoryException e) {
+                       throw new EclipseUiException("Cannot retrieve workspace names", e);
+               }
+       }
+
+       public void createWorkspace(String workspaceName) {
+               if (!isConnected())
+                       login();
+               try {
+                       defaultSession.getWorkspace().createWorkspace(workspaceName);
+               } catch (RepositoryException e) {
+                       throw new EclipseUiException("Cannot create workspace", e);
+               }
+       }
+
+       /** returns the {@link Repository} referenced by the current UI Node */
+       public Repository getRepository() {
+               return repository;
+       }
+
+       public String getAlias() {
+               return alias;
+       }
+
+       public Boolean isConnected() {
+               if (defaultSession != null && defaultSession.isLive())
+                       return true;
+               else
+                       return false;
+       }
+}
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/jcr/model/SingleJcrNodeElem.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/jcr/model/SingleJcrNodeElem.java
new file mode 100644 (file)
index 0000000..a2584a5
--- /dev/null
@@ -0,0 +1,84 @@
+package org.argeo.cms.ui.jcr.model;
+
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.RepositoryException;
+import javax.jcr.Workspace;
+
+import org.argeo.cms.ux.widgets.TreeParent;
+import org.argeo.eclipse.ui.EclipseUiException;
+
+/**
+ * UI Tree component. Wraps a node of a JCR {@link Workspace}. It also keeps a
+ * reference to its parent node that can either be a {@link WorkspaceElem}, a
+ * {@link SingleJcrNodeElem} or null if the node is "mounted" as the root of the
+ * UI tree.
+ */
+public class SingleJcrNodeElem extends TreeParent {
+
+       private final Node node;
+       private String alias = null;
+
+       /** Creates a new UiNode in the UI Tree */
+       public SingleJcrNodeElem(TreeParent parent, Node node, String name) {
+               super(name);
+               setParent(parent);
+               this.node = node;
+       }
+
+       /**
+        * Creates a new UiNode in the UI Tree, keeping a reference to the alias of
+        * the corresponding repository in the current UI environment. It is useful
+        * to be able to mount nodes as roots of the UI tree.
+        */
+       public SingleJcrNodeElem(TreeParent parent, Node node, String name, String alias) {
+               super(name);
+               setParent(parent);
+               this.node = node;
+               this.alias = alias;
+       }
+
+       /** Returns the node wrapped by the current UI object */
+       public Node getNode() {
+               return node;
+       }
+
+       protected String getRepositoryAlias() {
+               return alias;
+       }
+
+       /**
+        * Overrides normal behaviour to initialise children only when first
+        * requested
+        */
+       @Override
+       public synchronized Object[] getChildren() {
+               if (isLoaded()) {
+                       return super.getChildren();
+               } else {
+                       // initialize current object
+                       try {
+                               NodeIterator ni = node.getNodes();
+                               while (ni.hasNext()) {
+                                       Node curNode = ni.nextNode();
+                                       addChild(new SingleJcrNodeElem(this, curNode, curNode.getName()));
+                               }
+                               return super.getChildren();
+                       } catch (RepositoryException re) {
+                               throw new EclipseUiException("Cannot initialize SingleJcrNode children", re);
+                       }
+               }
+       }
+
+       @Override
+       public boolean hasChildren() {
+               try {
+                       if (node.getSession().isLive())
+                               return node.hasNodes();
+                       else
+                               return false;
+               } catch (RepositoryException re) {
+                       throw new EclipseUiException("Cannot check children node existence", re);
+               }
+       }
+}
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/jcr/model/WorkspaceElem.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/jcr/model/WorkspaceElem.java
new file mode 100644 (file)
index 0000000..2d78666
--- /dev/null
@@ -0,0 +1,117 @@
+package org.argeo.cms.ui.jcr.model;
+
+import javax.jcr.AccessDeniedException;
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+// import javax.jcr.Workspace;
+import javax.jcr.Workspace;
+
+import org.argeo.cms.ux.widgets.TreeParent;
+import org.argeo.eclipse.ui.EclipseUiException;
+import org.argeo.jcr.JcrUtils;
+
+/**
+ * UI Tree component. Wraps the root node of a JCR {@link Workspace}. It also
+ * keeps a reference to its parent {@link RepositoryElem}, to be able to
+ * retrieve alias of the current used repository
+ */
+public class WorkspaceElem extends TreeParent {
+       private Session session = null;
+
+       public WorkspaceElem(RepositoryElem parent, String name) {
+               this(parent, name, null);
+       }
+
+       public WorkspaceElem(RepositoryElem parent, String name, Session session) {
+               super(name);
+               this.session = session;
+               setParent(parent);
+       }
+
+       public synchronized Session getSession() {
+               return session;
+       }
+
+       public synchronized Node getRootNode() {
+               try {
+                       if (session != null)
+                               return session.getRootNode();
+                       else
+                               return null;
+               } catch (RepositoryException e) {
+                       throw new EclipseUiException("Cannot get root node of workspace " + getName(), e);
+               }
+       }
+
+       public synchronized void login() {
+               try {
+                       session = ((RepositoryElem) getParent()).repositoryLogin(getName());
+               } catch (RepositoryException e) {
+                       throw new EclipseUiException("Cannot connect to repository " + getName(), e);
+               }
+       }
+
+       public Boolean isConnected() {
+               if (session != null && session.isLive())
+                       return true;
+               else
+                       return false;
+       }
+
+       @Override
+       public synchronized void dispose() {
+               logout();
+               super.dispose();
+       }
+
+       /** Logouts the session, does not nothing if there is no live session. */
+       public synchronized void logout() {
+               clearChildren();
+               JcrUtils.logoutQuietly(session);
+               session = null;
+       }
+
+       @Override
+       public synchronized boolean hasChildren() {
+               try {
+                       if (isConnected())
+                               try {
+                                       return session.getRootNode().hasNodes();
+                               } catch (AccessDeniedException e) {
+                                       // current user may not have access to the root node
+                                       return false;
+                               }
+                       else
+                               return false;
+               } catch (RepositoryException re) {
+                       throw new EclipseUiException("Unexpected error while checking children node existence", re);
+               }
+       }
+
+       /** Override normal behaviour to initialize display of the workspace */
+       @Override
+       public synchronized Object[] getChildren() {
+               if (isLoaded()) {
+                       return super.getChildren();
+               } else {
+                       // initialize current object
+                       try {
+                               Node rootNode;
+                               if (session == null)
+                                       return null;
+                               else
+                                       rootNode = session.getRootNode();
+                               NodeIterator ni = rootNode.getNodes();
+                               while (ni.hasNext()) {
+                                       Node node = ni.nextNode();
+                                       addChild(new SingleJcrNodeElem(this, node, node.getName()));
+                               }
+                               return super.getChildren();
+                       } catch (RepositoryException e) {
+                               throw new EclipseUiException("Cannot initialize WorkspaceNode UI object." + getName(), e);
+                       }
+               }
+       }
+}
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/jcr/model/package-info.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/jcr/model/package-info.java
new file mode 100644 (file)
index 0000000..8f54744
--- /dev/null
@@ -0,0 +1,2 @@
+/** Model for SWT/JFace JCR components. */
+package org.argeo.cms.ui.jcr.model;
\ No newline at end of file
diff --git a/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/jcr/package-info.java b/swt/org.argeo.tool.devops.e4/src/org/argeo/cms/ui/jcr/package-info.java
new file mode 100644 (file)
index 0000000..26ae330
--- /dev/null
@@ -0,0 +1,2 @@
+/** SWT/JFace JCR components. */
+package org.argeo.cms.ui.jcr;
\ No newline at end of file
diff --git a/swt/org.argeo.tool.swt/.classpath b/swt/org.argeo.tool.swt/.classpath
new file mode 100644 (file)
index 0000000..81fe078
--- /dev/null
@@ -0,0 +1,7 @@
+<?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>
diff --git a/swt/org.argeo.tool.swt/.project b/swt/org.argeo.tool.swt/.project
new file mode 100644 (file)
index 0000000..0ffd546
--- /dev/null
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+       <name>org.argeo.tool.swt</name>
+       <comment></comment>
+       <projects>
+       </projects>
+       <buildSpec>
+               <buildCommand>
+                       <name>org.eclipse.jdt.core.javabuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.ManifestBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.SchemaBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+       </buildSpec>
+       <natures>
+               <nature>org.eclipse.pde.PluginNature</nature>
+               <nature>org.eclipse.jdt.core.javanature</nature>
+       </natures>
+</projectDescription>
diff --git a/swt/org.argeo.tool.swt/bnd.bnd b/swt/org.argeo.tool.swt/bnd.bnd
new file mode 100644 (file)
index 0000000..2b2a02f
--- /dev/null
@@ -0,0 +1 @@
+Bundle-ActivationPolicy: lazy
diff --git a/swt/org.argeo.tool.swt/build.properties b/swt/org.argeo.tool.swt/build.properties
new file mode 100644 (file)
index 0000000..34d2e4d
--- /dev/null
@@ -0,0 +1,4 @@
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+               .
diff --git a/swt/org.argeo.tool.swt/icons/actions/add.png b/swt/org.argeo.tool.swt/icons/actions/add.png
new file mode 100644 (file)
index 0000000..5c06bf0
Binary files /dev/null and b/swt/org.argeo.tool.swt/icons/actions/add.png differ
diff --git a/swt/org.argeo.tool.swt/icons/actions/close-all.png b/swt/org.argeo.tool.swt/icons/actions/close-all.png
new file mode 100644 (file)
index 0000000..81bfc95
Binary files /dev/null and b/swt/org.argeo.tool.swt/icons/actions/close-all.png differ
diff --git a/swt/org.argeo.tool.swt/icons/actions/delete.png b/swt/org.argeo.tool.swt/icons/actions/delete.png
new file mode 100644 (file)
index 0000000..9712723
Binary files /dev/null and b/swt/org.argeo.tool.swt/icons/actions/delete.png differ
diff --git a/swt/org.argeo.tool.swt/icons/actions/edit.png b/swt/org.argeo.tool.swt/icons/actions/edit.png
new file mode 100644 (file)
index 0000000..ad3db9f
Binary files /dev/null and b/swt/org.argeo.tool.swt/icons/actions/edit.png differ
diff --git a/swt/org.argeo.tool.swt/icons/actions/save-all.png b/swt/org.argeo.tool.swt/icons/actions/save-all.png
new file mode 100644 (file)
index 0000000..f48ed32
Binary files /dev/null and b/swt/org.argeo.tool.swt/icons/actions/save-all.png differ
diff --git a/swt/org.argeo.tool.swt/icons/actions/save.png b/swt/org.argeo.tool.swt/icons/actions/save.png
new file mode 100644 (file)
index 0000000..1c58ada
Binary files /dev/null and b/swt/org.argeo.tool.swt/icons/actions/save.png differ
diff --git a/swt/org.argeo.tool.swt/icons/active.gif b/swt/org.argeo.tool.swt/icons/active.gif
new file mode 100644 (file)
index 0000000..7d24707
Binary files /dev/null and b/swt/org.argeo.tool.swt/icons/active.gif differ
diff --git a/swt/org.argeo.tool.swt/icons/add.gif b/swt/org.argeo.tool.swt/icons/add.gif
new file mode 100644 (file)
index 0000000..252d7eb
Binary files /dev/null and b/swt/org.argeo.tool.swt/icons/add.gif differ
diff --git a/swt/org.argeo.tool.swt/icons/add.png b/swt/org.argeo.tool.swt/icons/add.png
new file mode 100644 (file)
index 0000000..c7edfec
Binary files /dev/null and b/swt/org.argeo.tool.swt/icons/add.png differ
diff --git a/swt/org.argeo.tool.swt/icons/addFolder.gif b/swt/org.argeo.tool.swt/icons/addFolder.gif
new file mode 100644 (file)
index 0000000..d3f43d9
Binary files /dev/null and b/swt/org.argeo.tool.swt/icons/addFolder.gif differ
diff --git a/swt/org.argeo.tool.swt/icons/addPrivileges.gif b/swt/org.argeo.tool.swt/icons/addPrivileges.gif
new file mode 100644 (file)
index 0000000..a6b251f
Binary files /dev/null and b/swt/org.argeo.tool.swt/icons/addPrivileges.gif differ
diff --git a/swt/org.argeo.tool.swt/icons/addRepo.gif b/swt/org.argeo.tool.swt/icons/addRepo.gif
new file mode 100644 (file)
index 0000000..26d81c0
Binary files /dev/null and b/swt/org.argeo.tool.swt/icons/addRepo.gif differ
diff --git a/swt/org.argeo.tool.swt/icons/addWorkspace.png b/swt/org.argeo.tool.swt/icons/addWorkspace.png
new file mode 100644 (file)
index 0000000..bbee775
Binary files /dev/null and b/swt/org.argeo.tool.swt/icons/addWorkspace.png differ
diff --git a/swt/org.argeo.tool.swt/icons/adminLog.gif b/swt/org.argeo.tool.swt/icons/adminLog.gif
new file mode 100644 (file)
index 0000000..6ef3bca
Binary files /dev/null and b/swt/org.argeo.tool.swt/icons/adminLog.gif differ
diff --git a/swt/org.argeo.tool.swt/icons/batch.gif b/swt/org.argeo.tool.swt/icons/batch.gif
new file mode 100644 (file)
index 0000000..b8ca14a
Binary files /dev/null and b/swt/org.argeo.tool.swt/icons/batch.gif differ
diff --git a/swt/org.argeo.tool.swt/icons/begin.gif b/swt/org.argeo.tool.swt/icons/begin.gif
new file mode 100755 (executable)
index 0000000..feb8e94
Binary files /dev/null and b/swt/org.argeo.tool.swt/icons/begin.gif differ
diff --git a/swt/org.argeo.tool.swt/icons/binary.png b/swt/org.argeo.tool.swt/icons/binary.png
new file mode 100644 (file)
index 0000000..fdf4f82
Binary files /dev/null and b/swt/org.argeo.tool.swt/icons/binary.png differ
diff --git a/swt/org.argeo.tool.swt/icons/browser.gif b/swt/org.argeo.tool.swt/icons/browser.gif
new file mode 100644 (file)
index 0000000..6c7320c
Binary files /dev/null and b/swt/org.argeo.tool.swt/icons/browser.gif differ
diff --git a/swt/org.argeo.tool.swt/icons/bundles.gif b/swt/org.argeo.tool.swt/icons/bundles.gif
new file mode 100644 (file)
index 0000000..e9a6bd9
Binary files /dev/null and b/swt/org.argeo.tool.swt/icons/bundles.gif differ
diff --git a/swt/org.argeo.tool.swt/icons/changePassword.gif b/swt/org.argeo.tool.swt/icons/changePassword.gif
new file mode 100644 (file)
index 0000000..274a850
Binary files /dev/null and b/swt/org.argeo.tool.swt/icons/changePassword.gif differ
diff --git a/swt/org.argeo.tool.swt/icons/clear.gif b/swt/org.argeo.tool.swt/icons/clear.gif
new file mode 100644 (file)
index 0000000..6bc10f9
Binary files /dev/null and b/swt/org.argeo.tool.swt/icons/clear.gif differ
diff --git a/swt/org.argeo.tool.swt/icons/close-all.png b/swt/org.argeo.tool.swt/icons/close-all.png
new file mode 100644 (file)
index 0000000..85d4d42
Binary files /dev/null and b/swt/org.argeo.tool.swt/icons/close-all.png differ
diff --git a/swt/org.argeo.tool.swt/icons/commit.gif b/swt/org.argeo.tool.swt/icons/commit.gif
new file mode 100755 (executable)
index 0000000..876f3eb
Binary files /dev/null and b/swt/org.argeo.tool.swt/icons/commit.gif differ
diff --git a/swt/org.argeo.tool.swt/icons/delete.png b/swt/org.argeo.tool.swt/icons/delete.png
new file mode 100644 (file)
index 0000000..676a39d
Binary files /dev/null and b/swt/org.argeo.tool.swt/icons/delete.png differ
diff --git a/swt/org.argeo.tool.swt/icons/dumpNode.gif b/swt/org.argeo.tool.swt/icons/dumpNode.gif
new file mode 100644 (file)
index 0000000..14eb1be
Binary files /dev/null and b/swt/org.argeo.tool.swt/icons/dumpNode.gif differ
diff --git a/swt/org.argeo.tool.swt/icons/file.gif b/swt/org.argeo.tool.swt/icons/file.gif
new file mode 100644 (file)
index 0000000..ef30288
Binary files /dev/null and b/swt/org.argeo.tool.swt/icons/file.gif differ
diff --git a/swt/org.argeo.tool.swt/icons/folder.gif b/swt/org.argeo.tool.swt/icons/folder.gif
new file mode 100644 (file)
index 0000000..42e027c
Binary files /dev/null and b/swt/org.argeo.tool.swt/icons/folder.gif differ
diff --git a/swt/org.argeo.tool.swt/icons/getSize.gif b/swt/org.argeo.tool.swt/icons/getSize.gif
new file mode 100644 (file)
index 0000000..b05bf3e
Binary files /dev/null and b/swt/org.argeo.tool.swt/icons/getSize.gif differ
diff --git a/swt/org.argeo.tool.swt/icons/group.png b/swt/org.argeo.tool.swt/icons/group.png
new file mode 100644 (file)
index 0000000..cc6683a
Binary files /dev/null and b/swt/org.argeo.tool.swt/icons/group.png differ
diff --git a/swt/org.argeo.tool.swt/icons/home.gif b/swt/org.argeo.tool.swt/icons/home.gif
new file mode 100644 (file)
index 0000000..fd0c669
Binary files /dev/null and b/swt/org.argeo.tool.swt/icons/home.gif differ
diff --git a/swt/org.argeo.tool.swt/icons/home.png b/swt/org.argeo.tool.swt/icons/home.png
new file mode 100644 (file)
index 0000000..5eb0967
Binary files /dev/null and b/swt/org.argeo.tool.swt/icons/home.png differ
diff --git a/swt/org.argeo.tool.swt/icons/import_fs.png b/swt/org.argeo.tool.swt/icons/import_fs.png
new file mode 100644 (file)
index 0000000..d7c890c
Binary files /dev/null and b/swt/org.argeo.tool.swt/icons/import_fs.png differ
diff --git a/swt/org.argeo.tool.swt/icons/installed.gif b/swt/org.argeo.tool.swt/icons/installed.gif
new file mode 100644 (file)
index 0000000..2988716
Binary files /dev/null and b/swt/org.argeo.tool.swt/icons/installed.gif differ
diff --git a/swt/org.argeo.tool.swt/icons/log.gif b/swt/org.argeo.tool.swt/icons/log.gif
new file mode 100644 (file)
index 0000000..e3ecc55
Binary files /dev/null and b/swt/org.argeo.tool.swt/icons/log.gif differ
diff --git a/swt/org.argeo.tool.swt/icons/logout.png b/swt/org.argeo.tool.swt/icons/logout.png
new file mode 100644 (file)
index 0000000..f2952fa
Binary files /dev/null and b/swt/org.argeo.tool.swt/icons/logout.png differ
diff --git a/swt/org.argeo.tool.swt/icons/maintenance.gif b/swt/org.argeo.tool.swt/icons/maintenance.gif
new file mode 100644 (file)
index 0000000..e5690ec
Binary files /dev/null and b/swt/org.argeo.tool.swt/icons/maintenance.gif differ
diff --git a/swt/org.argeo.tool.swt/icons/node.gif b/swt/org.argeo.tool.swt/icons/node.gif
new file mode 100644 (file)
index 0000000..364c0e7
Binary files /dev/null and b/swt/org.argeo.tool.swt/icons/node.gif differ
diff --git a/swt/org.argeo.tool.swt/icons/nodes.gif b/swt/org.argeo.tool.swt/icons/nodes.gif
new file mode 100644 (file)
index 0000000..bba3dbc
Binary files /dev/null and b/swt/org.argeo.tool.swt/icons/nodes.gif differ
diff --git a/swt/org.argeo.tool.swt/icons/osgi_explorer.gif b/swt/org.argeo.tool.swt/icons/osgi_explorer.gif
new file mode 100644 (file)
index 0000000..e9a6bd9
Binary files /dev/null and b/swt/org.argeo.tool.swt/icons/osgi_explorer.gif differ
diff --git a/swt/org.argeo.tool.swt/icons/password.gif b/swt/org.argeo.tool.swt/icons/password.gif
new file mode 100644 (file)
index 0000000..a6b251f
Binary files /dev/null and b/swt/org.argeo.tool.swt/icons/password.gif differ
diff --git a/swt/org.argeo.tool.swt/icons/person-logged-in.png b/swt/org.argeo.tool.swt/icons/person-logged-in.png
new file mode 100644 (file)
index 0000000..87acc14
Binary files /dev/null and b/swt/org.argeo.tool.swt/icons/person-logged-in.png differ
diff --git a/swt/org.argeo.tool.swt/icons/person.png b/swt/org.argeo.tool.swt/icons/person.png
new file mode 100644 (file)
index 0000000..7d979a5
Binary files /dev/null and b/swt/org.argeo.tool.swt/icons/person.png differ
diff --git a/swt/org.argeo.tool.swt/icons/query.png b/swt/org.argeo.tool.swt/icons/query.png
new file mode 100644 (file)
index 0000000..54c089d
Binary files /dev/null and b/swt/org.argeo.tool.swt/icons/query.png differ
diff --git a/swt/org.argeo.tool.swt/icons/refresh.png b/swt/org.argeo.tool.swt/icons/refresh.png
new file mode 100644 (file)
index 0000000..71b3481
Binary files /dev/null and b/swt/org.argeo.tool.swt/icons/refresh.png differ
diff --git a/swt/org.argeo.tool.swt/icons/remote_connected.gif b/swt/org.argeo.tool.swt/icons/remote_connected.gif
new file mode 100644 (file)
index 0000000..1492b4e
Binary files /dev/null and b/swt/org.argeo.tool.swt/icons/remote_connected.gif differ
diff --git a/swt/org.argeo.tool.swt/icons/remote_disconnected.gif b/swt/org.argeo.tool.swt/icons/remote_disconnected.gif
new file mode 100644 (file)
index 0000000..6c54da9
Binary files /dev/null and b/swt/org.argeo.tool.swt/icons/remote_disconnected.gif differ
diff --git a/swt/org.argeo.tool.swt/icons/remove.gif b/swt/org.argeo.tool.swt/icons/remove.gif
new file mode 100644 (file)
index 0000000..0ae6dec
Binary files /dev/null and b/swt/org.argeo.tool.swt/icons/remove.gif differ
diff --git a/swt/org.argeo.tool.swt/icons/removePrivileges.gif b/swt/org.argeo.tool.swt/icons/removePrivileges.gif
new file mode 100644 (file)
index 0000000..aa78fd2
Binary files /dev/null and b/swt/org.argeo.tool.swt/icons/removePrivileges.gif differ
diff --git a/swt/org.argeo.tool.swt/icons/rename.gif b/swt/org.argeo.tool.swt/icons/rename.gif
new file mode 100644 (file)
index 0000000..8048405
Binary files /dev/null and b/swt/org.argeo.tool.swt/icons/rename.gif differ
diff --git a/swt/org.argeo.tool.swt/icons/repositories.gif b/swt/org.argeo.tool.swt/icons/repositories.gif
new file mode 100644 (file)
index 0000000..c13bea1
Binary files /dev/null and b/swt/org.argeo.tool.swt/icons/repositories.gif differ
diff --git a/swt/org.argeo.tool.swt/icons/repository_connected.gif b/swt/org.argeo.tool.swt/icons/repository_connected.gif
new file mode 100644 (file)
index 0000000..a15fa55
Binary files /dev/null and b/swt/org.argeo.tool.swt/icons/repository_connected.gif differ
diff --git a/swt/org.argeo.tool.swt/icons/repository_disconnected.gif b/swt/org.argeo.tool.swt/icons/repository_disconnected.gif
new file mode 100644 (file)
index 0000000..4576dc5
Binary files /dev/null and b/swt/org.argeo.tool.swt/icons/repository_disconnected.gif differ
diff --git a/swt/org.argeo.tool.swt/icons/resolved.gif b/swt/org.argeo.tool.swt/icons/resolved.gif
new file mode 100644 (file)
index 0000000..f4a1ea1
Binary files /dev/null and b/swt/org.argeo.tool.swt/icons/resolved.gif differ
diff --git a/swt/org.argeo.tool.swt/icons/role.gif b/swt/org.argeo.tool.swt/icons/role.gif
new file mode 100644 (file)
index 0000000..274a850
Binary files /dev/null and b/swt/org.argeo.tool.swt/icons/role.gif differ
diff --git a/swt/org.argeo.tool.swt/icons/rollback.gif b/swt/org.argeo.tool.swt/icons/rollback.gif
new file mode 100755 (executable)
index 0000000..c753995
Binary files /dev/null and b/swt/org.argeo.tool.swt/icons/rollback.gif differ
diff --git a/swt/org.argeo.tool.swt/icons/save-all.png b/swt/org.argeo.tool.swt/icons/save-all.png
new file mode 100644 (file)
index 0000000..b68a29b
Binary files /dev/null and b/swt/org.argeo.tool.swt/icons/save-all.png differ
diff --git a/swt/org.argeo.tool.swt/icons/save.gif b/swt/org.argeo.tool.swt/icons/save.gif
new file mode 100644 (file)
index 0000000..654ad7b
Binary files /dev/null and b/swt/org.argeo.tool.swt/icons/save.gif differ
diff --git a/swt/org.argeo.tool.swt/icons/save.png b/swt/org.argeo.tool.swt/icons/save.png
new file mode 100644 (file)
index 0000000..f27ef2d
Binary files /dev/null and b/swt/org.argeo.tool.swt/icons/save.png differ
diff --git a/swt/org.argeo.tool.swt/icons/save_security.png b/swt/org.argeo.tool.swt/icons/save_security.png
new file mode 100644 (file)
index 0000000..ca41dc9
Binary files /dev/null and b/swt/org.argeo.tool.swt/icons/save_security.png differ
diff --git a/swt/org.argeo.tool.swt/icons/save_security_disabled.png b/swt/org.argeo.tool.swt/icons/save_security_disabled.png
new file mode 100644 (file)
index 0000000..fb7d08d
Binary files /dev/null and b/swt/org.argeo.tool.swt/icons/save_security_disabled.png differ
diff --git a/swt/org.argeo.tool.swt/icons/security.gif b/swt/org.argeo.tool.swt/icons/security.gif
new file mode 100644 (file)
index 0000000..57fb95e
Binary files /dev/null and b/swt/org.argeo.tool.swt/icons/security.gif differ
diff --git a/swt/org.argeo.tool.swt/icons/service_published.gif b/swt/org.argeo.tool.swt/icons/service_published.gif
new file mode 100644 (file)
index 0000000..17f771a
Binary files /dev/null and b/swt/org.argeo.tool.swt/icons/service_published.gif differ
diff --git a/swt/org.argeo.tool.swt/icons/service_referenced.gif b/swt/org.argeo.tool.swt/icons/service_referenced.gif
new file mode 100644 (file)
index 0000000..c24a95f
Binary files /dev/null and b/swt/org.argeo.tool.swt/icons/service_referenced.gif differ
diff --git a/swt/org.argeo.tool.swt/icons/sort.gif b/swt/org.argeo.tool.swt/icons/sort.gif
new file mode 100644 (file)
index 0000000..23c5d0b
Binary files /dev/null and b/swt/org.argeo.tool.swt/icons/sort.gif differ
diff --git a/swt/org.argeo.tool.swt/icons/starting.gif b/swt/org.argeo.tool.swt/icons/starting.gif
new file mode 100644 (file)
index 0000000..563743d
Binary files /dev/null and b/swt/org.argeo.tool.swt/icons/starting.gif differ
diff --git a/swt/org.argeo.tool.swt/icons/sync.gif b/swt/org.argeo.tool.swt/icons/sync.gif
new file mode 100644 (file)
index 0000000..b4fa052
Binary files /dev/null and b/swt/org.argeo.tool.swt/icons/sync.gif differ
diff --git a/swt/org.argeo.tool.swt/icons/user.gif b/swt/org.argeo.tool.swt/icons/user.gif
new file mode 100644 (file)
index 0000000..90a0014
Binary files /dev/null and b/swt/org.argeo.tool.swt/icons/user.gif differ
diff --git a/swt/org.argeo.tool.swt/icons/users.gif b/swt/org.argeo.tool.swt/icons/users.gif
new file mode 100644 (file)
index 0000000..2de7edd
Binary files /dev/null and b/swt/org.argeo.tool.swt/icons/users.gif differ
diff --git a/swt/org.argeo.tool.swt/icons/workgroup.png b/swt/org.argeo.tool.swt/icons/workgroup.png
new file mode 100644 (file)
index 0000000..7fef996
Binary files /dev/null and b/swt/org.argeo.tool.swt/icons/workgroup.png differ
diff --git a/swt/org.argeo.tool.swt/icons/workgroup.xcf b/swt/org.argeo.tool.swt/icons/workgroup.xcf
new file mode 100644 (file)
index 0000000..f517c82
Binary files /dev/null and b/swt/org.argeo.tool.swt/icons/workgroup.xcf differ
diff --git a/swt/org.argeo.tool.swt/icons/workspace_connected.png b/swt/org.argeo.tool.swt/icons/workspace_connected.png
new file mode 100644 (file)
index 0000000..0430baa
Binary files /dev/null and b/swt/org.argeo.tool.swt/icons/workspace_connected.png differ
diff --git a/swt/org.argeo.tool.swt/icons/workspace_disconnected.png b/swt/org.argeo.tool.swt/icons/workspace_disconnected.png
new file mode 100644 (file)
index 0000000..fddcb8c
Binary files /dev/null and b/swt/org.argeo.tool.swt/icons/workspace_disconnected.png differ
diff --git a/swt/org.argeo.tool.swt/src/org/argeo/cms/ui/theme/CmsImages.java b/swt/org.argeo.tool.swt/src/org/argeo/cms/ui/theme/CmsImages.java
new file mode 100644 (file)
index 0000000..1c4d79e
--- /dev/null
@@ -0,0 +1,49 @@
+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());
+       }
+
+}
diff --git a/swt/org.argeo.tool.swt/src/org/argeo/cms/ui/theme/package-info.java b/swt/org.argeo.tool.swt/src/org/argeo/cms/ui/theme/package-info.java
new file mode 100644 (file)
index 0000000..7d3a260
--- /dev/null
@@ -0,0 +1,2 @@
+/** Argeo CMS core theme images. */
+package org.argeo.cms.ui.theme;
\ No newline at end of file
diff --git a/swt/rap/org.argeo.tool.rap.cli/.classpath b/swt/rap/org.argeo.tool.rap.cli/.classpath
new file mode 100644 (file)
index 0000000..81fe078
--- /dev/null
@@ -0,0 +1,7 @@
+<?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>
diff --git a/swt/rap/org.argeo.tool.rap.cli/.project b/swt/rap/org.argeo.tool.rap.cli/.project
new file mode 100644 (file)
index 0000000..ea553de
--- /dev/null
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+       <name>org.argeo.tool.rap.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>
diff --git a/swt/rap/org.argeo.tool.rap.cli/META-INF/native-image/jni-config.json b/swt/rap/org.argeo.tool.rap.cli/META-INF/native-image/jni-config.json
new file mode 100644 (file)
index 0000000..25530bb
--- /dev/null
@@ -0,0 +1,33 @@
+[
+{
+  "name":"java.lang.Boolean",
+  "methods":[{"name":"getBoolean","parameterTypes":["java.lang.String"] }]
+},
+{
+  "name":"java.lang.ClassLoader",
+  "methods":[
+    {"name":"getPlatformClassLoader","parameterTypes":[] }, 
+    {"name":"loadClass","parameterTypes":["java.lang.String"] }
+  ]
+},
+{
+  "name":"jdk.internal.loader.ClassLoaders$PlatformClassLoader"
+},
+{
+  "name":"org.graalvm.jniutils.JNIExceptionWrapperEntryPoints",
+  "methods":[{"name":"getClassName","parameterTypes":["java.lang.Class"] }]
+},
+{
+  "name":"sun.management.VMManagementImpl",
+  "fields":[
+    {"name":"compTimeMonitoringSupport"}, 
+    {"name":"currentThreadCpuTimeSupport"}, 
+    {"name":"objectMonitorUsageSupport"}, 
+    {"name":"otherThreadCpuTimeSupport"}, 
+    {"name":"remoteDiagnosticCommandsSupport"}, 
+    {"name":"synchronizerUsageSupport"}, 
+    {"name":"threadAllocatedMemorySupport"}, 
+    {"name":"threadContentionMonitoringSupport"}
+  ]
+}
+]
diff --git a/swt/rap/org.argeo.tool.rap.cli/META-INF/native-image/predefined-classes-config.json b/swt/rap/org.argeo.tool.rap.cli/META-INF/native-image/predefined-classes-config.json
new file mode 100644 (file)
index 0000000..0e79b2c
--- /dev/null
@@ -0,0 +1,8 @@
+[
+  {
+    "type":"agent-extracted",
+    "classes":[
+    ]
+  }
+]
+
diff --git a/swt/rap/org.argeo.tool.rap.cli/META-INF/native-image/proxy-config.json b/swt/rap/org.argeo.tool.rap.cli/META-INF/native-image/proxy-config.json
new file mode 100644 (file)
index 0000000..5d1d13d
--- /dev/null
@@ -0,0 +1,8 @@
+[
+  {
+    "interfaces":["javax.servlet.http.HttpServletRequest"]}
+  ,
+  {
+    "interfaces":["javax.servlet.http.HttpServletResponse"]}
+  
+]
diff --git a/swt/rap/org.argeo.tool.rap.cli/META-INF/native-image/reflect-config.json b/swt/rap/org.argeo.tool.rap.cli/META-INF/native-image/reflect-config.json
new file mode 100644 (file)
index 0000000..e9910e1
--- /dev/null
@@ -0,0 +1,393 @@
+[
+{
+  "name":"[B"
+},
+{
+  "name":"[Ljava.lang.String;"
+},
+{
+  "name":"[Lorg.eclipse.swt.widgets.TableColumn;"
+},
+{
+  "name":"[Lorg.eclipse.swt.widgets.TreeColumn;"
+},
+{
+  "name":"[Lsun.security.pkcs.SignerInfo;"
+},
+{
+  "name":"com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl",
+  "methods":[{"name":"<init>","parameterTypes":[] }]
+},
+{
+  "name":"java.lang.Boolean",
+  "queriedMethods":[{"name":"valueOf","parameterTypes":["java.lang.String"] }]
+},
+{
+  "name":"java.lang.Byte",
+  "queriedMethods":[{"name":"valueOf","parameterTypes":["java.lang.String"] }]
+},
+{
+  "name":"java.lang.Class"
+},
+{
+  "name":"java.lang.Double",
+  "queriedMethods":[{"name":"valueOf","parameterTypes":["java.lang.String"] }]
+},
+{
+  "name":"java.lang.Float",
+  "queriedMethods":[{"name":"valueOf","parameterTypes":["java.lang.String"] }]
+},
+{
+  "name":"java.lang.Integer",
+  "queriedMethods":[{"name":"valueOf","parameterTypes":["java.lang.String"] }]
+},
+{
+  "name":"java.lang.Long",
+  "queriedMethods":[{"name":"valueOf","parameterTypes":["java.lang.String"] }]
+},
+{
+  "name":"java.lang.Object",
+  "queriedMethods":[
+    {"name":"equals","parameterTypes":["java.lang.Object"] }, 
+    {"name":"hashCode","parameterTypes":[] }, 
+    {"name":"toString","parameterTypes":[] }
+  ]
+},
+{
+  "name":"java.lang.Short",
+  "queriedMethods":[{"name":"valueOf","parameterTypes":["java.lang.String"] }]
+},
+{
+  "name":"java.lang.String"
+},
+{
+  "name":"java.lang.management.ManagementFactory",
+  "methods":[{"name":"getRuntimeMXBean","parameterTypes":[] }]
+},
+{
+  "name":"java.lang.management.RuntimeMXBean",
+  "methods":[{"name":"getUptime","parameterTypes":[] }]
+},
+{
+  "name":"java.security.AlgorithmParametersSpi"
+},
+{
+  "name":"java.security.SecureRandomParameters"
+},
+{
+  "name":"java.util.Date"
+},
+{
+  "name":"javax.security.auth.login.Configuration$Parameters"
+},
+{
+  "name":"javax.security.auth.x500.X500Principal",
+  "fields":[{"name":"thisX500Name"}],
+  "queriedMethods":[{"name":"<init>","parameterTypes":["sun.security.x509.X500Name"] }]
+},
+{
+  "name":"javax.servlet.ServletRequest",
+  "queriedMethods":[
+    {"name":"getAsyncContext","parameterTypes":[] }, 
+    {"name":"getAttribute","parameterTypes":["java.lang.String"] }, 
+    {"name":"getAttributeNames","parameterTypes":[] }, 
+    {"name":"getCharacterEncoding","parameterTypes":[] }, 
+    {"name":"getContentLength","parameterTypes":[] }, 
+    {"name":"getContentLengthLong","parameterTypes":[] }, 
+    {"name":"getContentType","parameterTypes":[] }, 
+    {"name":"getDispatcherType","parameterTypes":[] }, 
+    {"name":"getInputStream","parameterTypes":[] }, 
+    {"name":"getLocalAddr","parameterTypes":[] }, 
+    {"name":"getLocalName","parameterTypes":[] }, 
+    {"name":"getLocalPort","parameterTypes":[] }, 
+    {"name":"getLocale","parameterTypes":[] }, 
+    {"name":"getLocales","parameterTypes":[] }, 
+    {"name":"getParameter","parameterTypes":["java.lang.String"] }, 
+    {"name":"getParameterMap","parameterTypes":[] }, 
+    {"name":"getParameterNames","parameterTypes":[] }, 
+    {"name":"getParameterValues","parameterTypes":["java.lang.String"] }, 
+    {"name":"getProtocol","parameterTypes":[] }, 
+    {"name":"getReader","parameterTypes":[] }, 
+    {"name":"getRealPath","parameterTypes":["java.lang.String"] }, 
+    {"name":"getRemoteAddr","parameterTypes":[] }, 
+    {"name":"getRemoteHost","parameterTypes":[] }, 
+    {"name":"getRemotePort","parameterTypes":[] }, 
+    {"name":"getRequestDispatcher","parameterTypes":["java.lang.String"] }, 
+    {"name":"getScheme","parameterTypes":[] }, 
+    {"name":"getServerName","parameterTypes":[] }, 
+    {"name":"getServerPort","parameterTypes":[] }, 
+    {"name":"getServletContext","parameterTypes":[] }, 
+    {"name":"isAsyncStarted","parameterTypes":[] }, 
+    {"name":"isAsyncSupported","parameterTypes":[] }, 
+    {"name":"isSecure","parameterTypes":[] }, 
+    {"name":"removeAttribute","parameterTypes":["java.lang.String"] }, 
+    {"name":"setAttribute","parameterTypes":["java.lang.String","java.lang.Object"] }, 
+    {"name":"setCharacterEncoding","parameterTypes":["java.lang.String"] }, 
+    {"name":"startAsync","parameterTypes":[] }, 
+    {"name":"startAsync","parameterTypes":["javax.servlet.ServletRequest","javax.servlet.ServletResponse"] }
+  ]
+},
+{
+  "name":"javax.servlet.ServletResponse"
+},
+{
+  "name":"javax.servlet.http.HttpServletRequest",
+  "methods":[{"name":"<init>","parameterTypes":["java.lang.reflect.InvocationHandler"] }],
+  "queriedMethods":[
+    {"name":"authenticate","parameterTypes":["javax.servlet.http.HttpServletResponse"] }, 
+    {"name":"changeSessionId","parameterTypes":[] }, 
+    {"name":"getAuthType","parameterTypes":[] }, 
+    {"name":"getContextPath","parameterTypes":[] }, 
+    {"name":"getCookies","parameterTypes":[] }, 
+    {"name":"getDateHeader","parameterTypes":["java.lang.String"] }, 
+    {"name":"getHeader","parameterTypes":["java.lang.String"] }, 
+    {"name":"getHeaderNames","parameterTypes":[] }, 
+    {"name":"getHeaders","parameterTypes":["java.lang.String"] }, 
+    {"name":"getHttpServletMapping","parameterTypes":[] }, 
+    {"name":"getIntHeader","parameterTypes":["java.lang.String"] }, 
+    {"name":"getMethod","parameterTypes":[] }, 
+    {"name":"getPart","parameterTypes":["java.lang.String"] }, 
+    {"name":"getParts","parameterTypes":[] }, 
+    {"name":"getPathInfo","parameterTypes":[] }, 
+    {"name":"getPathTranslated","parameterTypes":[] }, 
+    {"name":"getQueryString","parameterTypes":[] }, 
+    {"name":"getRemoteUser","parameterTypes":[] }, 
+    {"name":"getRequestURI","parameterTypes":[] }, 
+    {"name":"getRequestURL","parameterTypes":[] }, 
+    {"name":"getRequestedSessionId","parameterTypes":[] }, 
+    {"name":"getServletPath","parameterTypes":[] }, 
+    {"name":"getSession","parameterTypes":[] }, 
+    {"name":"getSession","parameterTypes":["boolean"] }, 
+    {"name":"getTrailerFields","parameterTypes":[] }, 
+    {"name":"getUserPrincipal","parameterTypes":[] }, 
+    {"name":"isRequestedSessionIdFromCookie","parameterTypes":[] }, 
+    {"name":"isRequestedSessionIdFromURL","parameterTypes":[] }, 
+    {"name":"isRequestedSessionIdFromUrl","parameterTypes":[] }, 
+    {"name":"isRequestedSessionIdValid","parameterTypes":[] }, 
+    {"name":"isTrailerFieldsReady","parameterTypes":[] }, 
+    {"name":"isUserInRole","parameterTypes":["java.lang.String"] }, 
+    {"name":"login","parameterTypes":["java.lang.String","java.lang.String"] }, 
+    {"name":"logout","parameterTypes":[] }, 
+    {"name":"newPushBuilder","parameterTypes":[] }, 
+    {"name":"upgrade","parameterTypes":["java.lang.Class"] }
+  ]
+},
+{
+  "name":"javax.servlet.http.HttpServletResponse"
+},
+{
+  "name":"org.apache.xerces.impl.dv.dtd.DTDDVFactoryImpl",
+  "methods":[{"name":"<init>","parameterTypes":[] }]
+},
+{
+  "name":"org.apache.xerces.impl.dv.dtd.XML11DTDDVFactoryImpl",
+  "methods":[{"name":"<init>","parameterTypes":[] }]
+},
+{
+  "name":"org.apache.xerces.impl.dv.xs.ExtendedSchemaDVFactoryImpl",
+  "methods":[{"name":"<init>","parameterTypes":[] }]
+},
+{
+  "name":"org.apache.xerces.impl.dv.xs.SchemaDVFactoryImpl",
+  "methods":[{"name":"<init>","parameterTypes":[] }]
+},
+{
+  "name":"org.apache.xerces.impl.xs.XSMessageFormatter",
+  "methods":[{"name":"<init>","parameterTypes":[] }]
+},
+{
+  "name":"org.apache.xerces.parsers.XIncludeAwareParserConfiguration",
+  "methods":[{"name":"<init>","parameterTypes":[] }]
+},
+{
+  "name":"org.argeo.cms.auth.AnonymousLoginModule",
+  "methods":[{"name":"<init>","parameterTypes":[] }]
+},
+{
+  "name":"org.argeo.cms.auth.DataAdminLoginModule",
+  "methods":[{"name":"<init>","parameterTypes":[] }]
+},
+{
+  "name":"org.argeo.cms.auth.IdentLoginModule",
+  "methods":[{"name":"<init>","parameterTypes":[] }]
+},
+{
+  "name":"org.argeo.cms.auth.RemoteSessionLoginModule",
+  "methods":[{"name":"<init>","parameterTypes":[] }]
+},
+{
+  "name":"org.argeo.cms.auth.UserAdminLoginModule",
+  "methods":[{"name":"<init>","parameterTypes":[] }]
+},
+{
+  "name":"org.eclipse.jetty.servlet.DefaultServlet",
+  "methods":[{"name":"<init>","parameterTypes":[] }]
+},
+{
+  "name":"org.eclipse.jetty.util.TypeUtil",
+  "methods":[
+    {"name":"getClassLoaderLocation","parameterTypes":["java.lang.Class"] }, 
+    {"name":"getCodeSourceLocation","parameterTypes":["java.lang.Class"] }, 
+    {"name":"getModuleLocation","parameterTypes":["java.lang.Class"] }, 
+    {"name":"getSystemClassLoaderLocation","parameterTypes":["java.lang.Class"] }
+  ]
+},
+{
+  "name":"org.eclipse.rap.rwt.internal.client.BrowserNavigationImpl",
+  "methods":[{"name":"<init>","parameterTypes":[] }]
+},
+{
+  "name":"org.eclipse.rap.rwt.internal.client.ClientInfoImpl",
+  "methods":[{"name":"<init>","parameterTypes":[] }]
+},
+{
+  "name":"org.eclipse.rap.rwt.internal.client.ExitConfirmationImpl",
+  "methods":[{"name":"<init>","parameterTypes":[] }]
+},
+{
+  "name":"org.eclipse.rap.rwt.internal.client.StartupParametersImpl",
+  "methods":[{"name":"<init>","parameterTypes":[] }]
+},
+{
+  "name":"org.eclipse.rap.rwt.internal.client.WebClientMessages",
+  "methods":[{"name":"<init>","parameterTypes":[] }]
+},
+{
+  "name":"org.eclipse.rap.rwt.internal.lifecycle.RWTLifeCycle",
+  "methods":[{"name":"<init>","parameterTypes":["org.eclipse.rap.rwt.internal.application.ApplicationContextImpl"] }]
+},
+{
+  "name":"org.eclipse.rap.rwt.internal.lifecycle.RequestCounter",
+  "methods":[{"name":"<init>","parameterTypes":[] }]
+},
+{
+  "name":"org.eclipse.rap.rwt.internal.remote.RemoteObjectRegistry",
+  "methods":[{"name":"<init>","parameterTypes":[] }]
+},
+{
+  "name":"org.eclipse.rap.rwt.internal.serverpush.ServerPushManager",
+  "methods":[{"name":"<init>","parameterTypes":[] }]
+},
+{
+  "name":"org.eclipse.rap.rwt.internal.textsize.ProbeResultStore",
+  "methods":[{"name":"<init>","parameterTypes":[] }]
+},
+{
+  "name":"org.eclipse.swt.SWT",
+  "allDeclaredFields":true
+},
+{
+  "name":"org.eclipse.swt.graphics.Color",
+  "methods":[{"name":"<init>","parameterTypes":["int"] }]
+},
+{
+  "name":"org.eclipse.swt.graphics.Font",
+  "methods":[{"name":"<init>","parameterTypes":["org.eclipse.swt.graphics.FontData"] }]
+},
+{
+  "name":"org.eclipse.swt.internal.image.GIFFileFormat",
+  "methods":[{"name":"<init>","parameterTypes":[] }]
+},
+{
+  "name":"org.eclipse.swt.internal.image.JPEGFileFormat",
+  "methods":[{"name":"<init>","parameterTypes":[] }]
+},
+{
+  "name":"org.eclipse.swt.internal.image.PNGFileFormat",
+  "methods":[{"name":"<init>","parameterTypes":[] }]
+},
+{
+  "name":"org.eclipse.swt.internal.image.WinBMPFileFormat",
+  "methods":[{"name":"<init>","parameterTypes":[] }]
+},
+{
+  "name":"org.eclipse.swt.internal.image.WinICOFileFormat",
+  "methods":[{"name":"<init>","parameterTypes":[] }]
+},
+{
+  "name":"org.eclipse.swt.internal.widgets.IdGenerator",
+  "methods":[{"name":"<init>","parameterTypes":[] }]
+},
+{
+  "name":"org.eclipse.swt.internal.widgets.displaykit.DisplayLCA",
+  "methods":[{"name":"<init>","parameterTypes":[] }]
+},
+{
+  "name":"org.eclipse.swt.internal.widgets.scrollbarkit.ScrollBarThemeAdapter",
+  "methods":[{"name":"<init>","parameterTypes":[] }]
+},
+{
+  "name":"org.eclipse.swt.internal.widgets.shellkit.ShellThemeAdapter",
+  "methods":[{"name":"<init>","parameterTypes":[] }]
+},
+{
+  "name":"org.eclipse.swt.internal.widgets.tablekit.TableThemeAdapter",
+  "methods":[{"name":"<init>","parameterTypes":[] }]
+},
+{
+  "name":"org.eclipse.swt.internal.widgets.treekit.TreeThemeAdapter",
+  "methods":[{"name":"<init>","parameterTypes":[] }]
+},
+{
+  "name":"sun.security.provider.ConfigFile$Spi",
+  "methods":[{"name":"<init>","parameterTypes":["javax.security.auth.login.Configuration$Parameters"] }]
+},
+{
+  "name":"sun.security.provider.DRBG",
+  "methods":[{"name":"<init>","parameterTypes":["java.security.SecureRandomParameters"] }]
+},
+{
+  "name":"sun.security.provider.DSAKeyFactory",
+  "methods":[{"name":"<init>","parameterTypes":[] }]
+},
+{
+  "name":"sun.security.provider.DSAParameters",
+  "methods":[{"name":"<init>","parameterTypes":[] }]
+},
+{
+  "name":"sun.security.provider.NativePRNG",
+  "methods":[{"name":"<init>","parameterTypes":[] }]
+},
+{
+  "name":"sun.security.provider.SHA",
+  "methods":[{"name":"<init>","parameterTypes":[] }]
+},
+{
+  "name":"sun.security.provider.SHA2$SHA256",
+  "methods":[{"name":"<init>","parameterTypes":[] }]
+},
+{
+  "name":"sun.security.provider.X509Factory",
+  "methods":[{"name":"<init>","parameterTypes":[] }]
+},
+{
+  "name":"sun.security.rsa.RSAKeyFactory$Legacy",
+  "methods":[{"name":"<init>","parameterTypes":[] }]
+},
+{
+  "name":"sun.security.util.ObjectIdentifier"
+},
+{
+  "name":"sun.security.x509.AuthorityKeyIdentifierExtension",
+  "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }]
+},
+{
+  "name":"sun.security.x509.BasicConstraintsExtension",
+  "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }]
+},
+{
+  "name":"sun.security.x509.CertificateExtensions"
+},
+{
+  "name":"sun.security.x509.ExtendedKeyUsageExtension",
+  "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }]
+},
+{
+  "name":"sun.security.x509.KeyUsageExtension",
+  "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }]
+},
+{
+  "name":"sun.security.x509.SubjectKeyIdentifierExtension",
+  "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }]
+}
+]
diff --git a/swt/rap/org.argeo.tool.rap.cli/META-INF/native-image/resource-config.json b/swt/rap/org.argeo.tool.rap.cli/META-INF/native-image/resource-config.json
new file mode 100644 (file)
index 0000000..0f3300a
--- /dev/null
@@ -0,0 +1,735 @@
+{
+  "resources":{
+  "includes":[
+    {
+      "pattern":"\\QMETA-INF/MANIFEST.MF\\E"
+    }, 
+    {
+      "pattern":"\\QMETA-INF/services/javax.xml.parsers.DocumentBuilderFactory\\E"
+    }, 
+    {
+      "pattern":"\\QMETA-INF/services/javax.xml.validation.SchemaFactory\\E"
+    }, 
+    {
+      "pattern":"\\QMETA-INF/services/org.eclipse.jetty.http.HttpFieldPreEncoder\\E"
+    }, 
+    {
+      "pattern":"\\Qclient.files\\E"
+    }, 
+    {
+      "pattern":"\\Qclient.js\\E"
+    }, 
+    {
+      "pattern":"\\Qjetty-dir.css\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/argeo/cms/acr/schemas/DSMLv2.xsd\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/argeo/cms/acr/schemas/SVG.xsd\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/argeo/cms/acr/schemas/XForms-11-Schema.xsd\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/argeo/cms/acr/schemas/XMLSchema.xsd\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/argeo/cms/acr/schemas/cr.xsd\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/argeo/cms/acr/schemas/docbook.xsd\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/argeo/cms/acr/schemas/schema-for-xslt20.xsd\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/argeo/cms/acr/schemas/xlink.xsd\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/argeo/cms/acr/schemas/xml-events-attribs-1.xsd\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/argeo/cms/acr/schemas/xml.xsd\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/argeo/cms/internal/runtime/jaas.cfg\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/jetty/http/encoding.properties\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/jetty/http/mime.properties\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/jetty/version/build.properties\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/nebula/widgets/grid/internal/gridkit/Grid.appearances.js\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/nebula/widgets/grid/internal/gridkit/Grid.default.css\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/nebula/widgets/grid/internal/gridkit/Grid.theme.xml\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/rap/rwt/internal/service/rwt-index.html\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/rap/rwt/internal/widgets/dropdownkit/DropDown.appearances.js\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/rap/rwt/internal/widgets/dropdownkit/DropDown.default.css\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/rap/rwt/internal/widgets/dropdownkit/DropDown.theme.xml\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/rap/rwt/internal/widgets/fileuploadkit/FileUpload.appearances.js\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/rap/rwt/internal/widgets/fileuploadkit/FileUpload.default.css\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/rap/rwt/internal/widgets/fileuploadkit/FileUpload.theme.xml\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/swt/internal/browser/browserkit/Browser.appearances.js\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/swt/internal/browser/browserkit/Browser.default.css\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/swt/internal/browser/browserkit/Browser.theme.xml\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/swt/internal/custom/ccombokit/CCombo.appearances.js\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/swt/internal/custom/ccombokit/CCombo.default.css\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/swt/internal/custom/ccombokit/CCombo.theme.xml\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/swt/internal/custom/clabelkit/CLabel.appearances.js\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/swt/internal/custom/clabelkit/CLabel.default.css\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/swt/internal/custom/clabelkit/CLabel.theme.xml\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/swt/internal/custom/ctabfolderkit/CTabFolder.appearances.js\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/swt/internal/custom/ctabfolderkit/CTabFolder.default.css\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/swt/internal/custom/ctabfolderkit/CTabFolder.theme.xml\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/swt/internal/custom/scrolledcompositekit/ScrolledComposite.appearances.js\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/swt/internal/widgets/buttonkit/Button.appearances.js\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/swt/internal/widgets/buttonkit/Button.default.css\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/swt/internal/widgets/buttonkit/Button.theme.xml\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/swt/internal/widgets/combokit/Combo.appearances.js\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/swt/internal/widgets/combokit/Combo.default.css\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/swt/internal/widgets/combokit/Combo.theme.xml\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/swt/internal/widgets/compositekit/Composite.appearances.js\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/swt/internal/widgets/compositekit/Composite.default.css\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/swt/internal/widgets/compositekit/Composite.theme.xml\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/swt/internal/widgets/controlkit/Control.default.css\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/swt/internal/widgets/controlkit/Control.theme.xml\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/swt/internal/widgets/coolbarkit/CoolBar.appearances.js\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/swt/internal/widgets/coolbarkit/CoolBar.default.css\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/swt/internal/widgets/coolbarkit/CoolBar.theme.xml\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/swt/internal/widgets/datetimekit/DateTime.appearances.js\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/swt/internal/widgets/datetimekit/DateTime.default.css\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/swt/internal/widgets/datetimekit/DateTime.theme.xml\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/swt/internal/widgets/expandbarkit/ExpandBar.appearances.js\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/swt/internal/widgets/expandbarkit/ExpandBar.default.css\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/swt/internal/widgets/expandbarkit/ExpandBar.theme.xml\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/swt/internal/widgets/groupkit/Group.appearances.js\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/swt/internal/widgets/groupkit/Group.default.css\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/swt/internal/widgets/groupkit/Group.theme.xml\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/swt/internal/widgets/labelkit/Label.appearances.js\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/swt/internal/widgets/labelkit/Label.default.css\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/swt/internal/widgets/labelkit/Label.theme.xml\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/swt/internal/widgets/linkkit/Link.appearances.js\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/swt/internal/widgets/linkkit/Link.default.css\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/swt/internal/widgets/linkkit/Link.theme.xml\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/swt/internal/widgets/listkit/List.appearances.js\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/swt/internal/widgets/listkit/List.default.css\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/swt/internal/widgets/listkit/List.theme.xml\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/swt/internal/widgets/menukit/Menu.appearances.js\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/swt/internal/widgets/menukit/Menu.default.css\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/swt/internal/widgets/menukit/Menu.theme.xml\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/swt/internal/widgets/progressbarkit/ProgressBar.appearances.js\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/swt/internal/widgets/progressbarkit/ProgressBar.default.css\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/swt/internal/widgets/progressbarkit/ProgressBar.theme.xml\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/swt/internal/widgets/sashkit/Sash.appearances.js\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/swt/internal/widgets/sashkit/Sash.default.css\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/swt/internal/widgets/sashkit/Sash.theme.xml\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/swt/internal/widgets/scalekit/Scale.appearances.js\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/swt/internal/widgets/scalekit/Scale.default.css\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/swt/internal/widgets/scalekit/Scale.theme.xml\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/swt/internal/widgets/scrollbarkit/ScrollBar.appearances.js\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/swt/internal/widgets/scrollbarkit/ScrollBar.default.css\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/swt/internal/widgets/scrollbarkit/ScrollBar.theme.xml\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/swt/internal/widgets/shellkit/Shell.appearances.js\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/swt/internal/widgets/shellkit/Shell.default.css\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/swt/internal/widgets/shellkit/Shell.theme.xml\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/swt/internal/widgets/sliderkit/Slider.appearances.js\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/swt/internal/widgets/sliderkit/Slider.default.css\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/swt/internal/widgets/sliderkit/Slider.theme.xml\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/swt/internal/widgets/spinnerkit/Spinner.appearances.js\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/swt/internal/widgets/spinnerkit/Spinner.default.css\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/swt/internal/widgets/spinnerkit/Spinner.theme.xml\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/swt/internal/widgets/tabfolderkit/TabFolder.appearances.js\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/swt/internal/widgets/tabfolderkit/TabFolder.default.css\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/swt/internal/widgets/tabfolderkit/TabFolder.theme.xml\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/swt/internal/widgets/tablekit/Table.appearances.js\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/swt/internal/widgets/tablekit/Table.default.css\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/swt/internal/widgets/tablekit/Table.theme.xml\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/swt/internal/widgets/textkit/Text.appearances.js\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/swt/internal/widgets/textkit/Text.default.css\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/swt/internal/widgets/textkit/Text.theme.xml\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/swt/internal/widgets/toolbarkit/ToolBar.appearances.js\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/swt/internal/widgets/toolbarkit/ToolBar.default.css\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/swt/internal/widgets/toolbarkit/ToolBar.theme.xml\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/swt/internal/widgets/tooltipkit/ToolTip.appearances.js\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/swt/internal/widgets/tooltipkit/ToolTip.default.css\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/swt/internal/widgets/tooltipkit/ToolTip.theme.xml\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/swt/internal/widgets/treekit/Tree.appearances.js\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/swt/internal/widgets/treekit/Tree.default.css\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/swt/internal/widgets/treekit/Tree.theme.xml\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/swt/internal/widgets/widgetkit/Widget.appearances.js\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/swt/internal/widgets/widgetkit/Widget.default.css\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/eclipse/swt/internal/widgets/widgetkit/Widget.theme.xml\\E"
+    }, 
+    {
+      "pattern":"\\Qorg/slf4j/impl/StaticLoggerBinder.class\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/static/html/blank.html\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/static/image/blank.gif\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/theme/default.css\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/arrows/chevron-left-hover.png\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/arrows/chevron-left.png\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/arrows/chevron-right-hover.png\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/arrows/chevron-right.png\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/arrows/tooltip-down.png\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/arrows/tooltip-left.png\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/arrows/tooltip-right.png\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/arrows/tooltip-up.png\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/button/arrow-down.png\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/button/arrow-left.png\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/button/arrow-right.png\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/button/arrow-up.png\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/button/check-grayed-hover.png\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/button/check-grayed.png\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/button/check-selected-hover.png\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/button/check-selected.png\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/button/check-unselected-hover.png\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/button/check-unselected.png\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/button/radio-selected-hover.png\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/button/radio-selected.png\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/button/radio-unselected-hover.png\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/button/radio-unselected.png\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/calendar/lastMonth-hover.png\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/calendar/lastMonth.png\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/calendar/lastYear-hover.png\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/calendar/lastYear.png\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/calendar/nextMonth-hover.png\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/calendar/nextMonth.png\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/calendar/nextYear-hover.png\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/calendar/nextYear.png\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/ccombo/down-hover.png\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/ccombo/down.png\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/column/sort-indicator-down.png\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/column/sort-indicator-up.png\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/combo/down-hover.png\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/combo/down.png\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/ctabfolder/close.gif\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/ctabfolder/close_hover.gif\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/ctabfolder/ctabfolder-dropdown-hover.png\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/ctabfolder/ctabfolder-dropdown-left-hover.png\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/ctabfolder/ctabfolder-dropdown-left.png\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/ctabfolder/ctabfolder-dropdown.png\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/ctabfolder/maximize.gif\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/ctabfolder/minimize.gif\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/ctabfolder/restore.gif\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/cursors/alias.gif\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/cursors/copy.gif\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/cursors/move.gif\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/cursors/nodrop.gif\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/cursors/up_arrow.cur\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/datetime/down-hover.png\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/datetime/down.png\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/datetime/up-hover.png\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/datetime/up.png\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/dialog/error.png\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/dialog/information.png\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/dialog/question.png\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/dialog/warning.png\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/display/browser_bg.png\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/display/loading.gif\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/expanditem/expanditem-collapse-hover.png\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/expanditem/expanditem-collapse.png\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/expanditem/expanditem-expand-hover.png\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/expanditem/expanditem-expand.png\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/menu/arrow-left.gif\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/menu/arrow-right.gif\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/menu/checkbox.gif\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/menu/radiobutton.gif\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/progressbar/progressbar-background.png\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/sash/sash-handle-horizontal.png\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/sash/sash-handle-vertical.png\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/scale/h_line.gif\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/scale/v_line.gif\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/scrollbar/down.png\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/scrollbar/left.png\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/scrollbar/right.png\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/scrollbar/scrollbar-background.png\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/scrollbar/up.png\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/slider/down.png\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/slider/left.png\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/slider/right.png\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/slider/slider-background.png\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/slider/up.png\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/spinner/down-hover.png\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/spinner/down.png\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/spinner/up-hover.png\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/spinner/up.png\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/text/clear.gif\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/text/find.png\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/toolbar/down.png\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/tooltip/error.png\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/tooltip/information.png\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/tooltip/warning.png\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/tree/loading.gif\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/tree/tree-collapsed-hover-left.png\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/tree/tree-collapsed-hover.png\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/tree/tree-collapsed-left.png\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/tree/tree-collapsed.png\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/tree/tree-expanded-hover-left.png\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/tree/tree-expanded-hover.png\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/tree/tree-expanded-left.png\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/tree/tree-expanded.png\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/window/shell-close-hover.png\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/window/shell-close.png\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/window/shell-max-hover.png\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/window/shell-max.png\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/window/shell-min-hover.png\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/window/shell-min.png\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/window/shell-restore-hover.png\\E"
+    }, 
+    {
+      "pattern":"\\Qresource/widget/rap/window/shell-restore.png\\E"
+    }
+  ]},
+  "bundles":[
+    {
+      "name":"javax.servlet.LocalStrings",
+      "locales":[""]
+    }, 
+    {
+      "name":"javax.servlet.http.LocalStrings",
+      "locales":[""]
+    }, 
+    {
+      "name":"org.apache.xerces.impl.xpath.regex.message",
+      "locales":[
+        "", 
+        "en"
+      ]
+    }, 
+    {
+      "name":"org.eclipse.rap.rwt.internal.RWTMessages",
+      "locales":[""]
+    }, 
+    {
+      "name":"sun.security.util.Resources",
+      "classNames":["sun.security.util.Resources"]
+    }
+  ]
+}
diff --git a/swt/rap/org.argeo.tool.rap.cli/META-INF/native-image/serialization-config.json b/swt/rap/org.argeo.tool.rap.cli/META-INF/native-image/serialization-config.json
new file mode 100644 (file)
index 0000000..bf554e0
--- /dev/null
@@ -0,0 +1,6 @@
+{
+  "types":[
+  ],
+  "lambdaCapturingTypes":[
+  ]
+}
diff --git a/swt/rap/org.argeo.tool.rap.cli/bnd.bnd b/swt/rap/org.argeo.tool.rap.cli/bnd.bnd
new file mode 100644 (file)
index 0000000..9d8b55f
--- /dev/null
@@ -0,0 +1,9 @@
+Import-Package: \
+javax.websocket.server,\
+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,\
+*
\ No newline at end of file
diff --git a/swt/rap/org.argeo.tool.rap.cli/build.properties b/swt/rap/org.argeo.tool.rap.cli/build.properties
new file mode 100644 (file)
index 0000000..34d2e4d
--- /dev/null
@@ -0,0 +1,4 @@
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+               .
diff --git a/swt/rap/org.argeo.tool.rap.cli/src/org/argeo/tool/rap/cli/CmsRapCli.java b/swt/rap/org.argeo.tool.rap.cli/src/org/argeo/tool/rap/cli/CmsRapCli.java
new file mode 100644 (file)
index 0000000..1ffb026
--- /dev/null
@@ -0,0 +1,128 @@
+package org.argeo.tool.rap.cli;
+
+import java.io.IOException;
+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.acr.spi.ProvidedRepository;
+import org.argeo.api.cli.CommandsCli;
+import org.argeo.api.cli.DescribedCommand;
+import org.argeo.api.cms.CmsApp;
+import org.argeo.api.cms.CmsContext;
+import org.argeo.api.cms.CmsState;
+import org.argeo.api.register.Component;
+import org.argeo.api.register.ComponentRegister;
+import org.argeo.cms.jetty.CmsJettyServer;
+import org.argeo.cms.runtime.StaticCms;
+import org.argeo.cms.swt.app.CmsUserApp;
+import org.argeo.cms.web.CmsWebApp;
+import org.eclipse.rap.rwt.application.ApplicationConfiguration;
+
+public class CmsRapCli extends CommandsCli {
+
+       public CmsRapCli(String commandName) {
+               super(commandName);
+               addCommand("user", new Launch());
+       }
+
+       @Override
+       public String getDescription() {
+               return "Argeo CMS utilities.";
+       }
+
+       public static void main(String[] args) {
+               mainImpl(new CmsRapCli("web"), args);
+       }
+
+       static class Launch implements DescribedCommand<String> {
+               private Option dataOption;
+               private Option uiOption;
+
+               @Override
+               public Options getOptions() {
+                       Options options = new Options();
+                       dataOption = Option.builder().longOpt("data").hasArg().required()
+                                       .desc("path to the writable data area (mandatory)").build();
+                       uiOption = Option.builder().longOpt("ui").desc("open a user interface").build();
+                       options.addOption(dataOption);
+                       options.addOption(uiOption);
+                       return options;
+               }
+
+               @Override
+               public String apply(List<String> args) {
+                       CommandLine cl = toCommandLine(args);
+                       String dataPath = cl.getOptionValue(dataOption);
+                       boolean ui = cl.hasOption(uiOption);
+
+                       Path instancePath = Paths.get(dataPath);
+                       System.setProperty("osgi.instance.area", instancePath.toUri().toString());
+                       System.setProperty("argeo.http.port", "0");
+
+                       StaticCms staticCms = new StaticCms() {
+                               @Override
+                               protected void addComponents(ComponentRegister register) {
+                                       if (ui) {
+                                               CmsUserApp cmsApp = new CmsUserApp();
+                                               Component<CmsUserApp> cmsAppC = new Component.Builder<>(cmsApp) //
+                                                               .addType(CmsApp.class) //
+                                                               .addType(CmsUserApp.class) //
+                                                               .addDependency(register.getSingleton(CmsContext.class), cmsApp::setCmsContext, null) //
+                                                               .addDependency(register.getSingleton(ProvidedRepository.class),
+                                                                               cmsApp::setContentRepository, null) //
+                                                               .build(register);
+
+                                               CmsWebApp cmsWebApp = new CmsWebApp();
+                                               Component<CmsWebApp> cmsWebAppC = new Component.Builder<>(cmsWebApp) //
+                                                               .addType(ApplicationConfiguration.class) //
+                                                               .addType(CmsWebApp.class) //
+                                                               .addDependency(cmsAppC.getType(CmsApp.class), cmsWebApp::setCmsApp, null) //
+                                                               .build(register);
+
+                                               RapJettyServer rwtRunner = new RapJettyServer();
+                                               Component<RapJettyServer> rwtRunnerC = new Component.Builder<>(rwtRunner) //
+                                                               .addActivation(rwtRunner::start) //
+                                                               .addDeactivation(rwtRunner::stop) //
+                                                               .addType(CmsJettyServer.class) //
+                                                               .addDependency(register.getSingleton(CmsState.class), rwtRunner::setCmsState, null) //
+                                                               .addDependency(cmsWebAppC.getType(CmsWebApp.class), rwtRunner::setCmsWebApp, null) //
+                                                               .build(register);
+                                       }
+                               }
+
+                       };
+                       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.");
+
+                       if (ui) {
+                               try {
+                                       // open browser in app mode
+                                       Thread.sleep(2000);// wait for RWT to be ready
+                                       String browserCommand = "google-chrome --app=http://localhost:"
+                                                       + staticCms.getComponentRegister().getObject(CmsJettyServer.class).getHttpPort() + "/data";
+                                       Runtime.getRuntime().exec(browserCommand);
+                               } catch (InterruptedException | IOException e) {
+                                       e.printStackTrace();
+                               }
+                       }
+
+                       staticCms.waitForStop();
+
+                       return null;
+               }
+
+               @Override
+               public String getDescription() {
+                       return "Launch a static CMS.";
+               }
+
+       }
+}
diff --git a/swt/rap/org.argeo.tool.rap.cli/src/org/argeo/tool/rap/cli/RapJettyServer.java b/swt/rap/org.argeo.tool.rap.cli/src/org/argeo/tool/rap/cli/RapJettyServer.java
new file mode 100644 (file)
index 0000000..6007fea
--- /dev/null
@@ -0,0 +1,59 @@
+package org.argeo.tool.rap.cli;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+import javax.servlet.ServletException;
+
+import org.argeo.cms.jetty.CmsJettyServer;
+import org.argeo.cms.web.CmsWebApp;
+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.rap.rwt.application.ApplicationRunner;
+import org.eclipse.rap.rwt.engine.RWTServlet;
+
+public class RapJettyServer extends CmsJettyServer {
+       private CmsWebApp cmsWebApp;
+
+       @Override
+       protected void addServlets(ServletContextHandler servletContextHandler) throws ServletException {
+               // rwt-resources requires a file system
+               try {
+                       Path tempDir = Files.createTempDirectory("argeo-rwtRunner");
+                       servletContextHandler.setBaseResource(Resource.newResource(tempDir.resolve("www").toString()));
+               } catch (IOException e) {
+                       throw new IllegalStateException("Cannot create temporary directory", e);
+               }
+               servletContextHandler.addEventListener(new ServletContextListener() {
+                       ApplicationRunner applicationRunner;
+
+                       @Override
+                       public void contextInitialized(ServletContextEvent sce) {
+                               applicationRunner = new ApplicationRunner(cmsWebApp, sce.getServletContext());
+                               applicationRunner.start();
+                       }
+
+                       @Override
+                       public void contextDestroyed(ServletContextEvent sce) {
+                               applicationRunner.stop();
+                       }
+               });
+               for (String uiName : cmsWebApp.getCmsApp().getUiNames())
+                       servletContextHandler.addServlet(new ServletHolder(new RWTServlet()), "/" + uiName);
+
+               // Required to serve rwt-resources. It is important that this is last.
+               ServletHolder holderPwd = new ServletHolder("default", DefaultServlet.class);
+               servletContextHandler.addServlet(holderPwd, "/");
+
+       }
+
+       public void setCmsWebApp(CmsWebApp cmsWebApp) {
+               this.cmsWebApp = cmsWebApp;
+       }
+
+}
diff --git a/swt/rap/org.argeo.tool.rap.cli/src/org/argeo/tool/rap/cli/RwtRunner.java b/swt/rap/org.argeo.tool.rap.cli/src/org/argeo/tool/rap/cli/RwtRunner.java
new file mode 100644 (file)
index 0000000..0974a77
--- /dev/null
@@ -0,0 +1,146 @@
+package org.argeo.tool.rap.cli;
+
+import java.io.IOException;
+import java.lang.management.ManagementFactory;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Objects;
+
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+
+import org.argeo.minidesktop.MiniDesktopManager;
+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.ApplicationConfiguration;
+import org.eclipse.rap.rwt.application.ApplicationRunner;
+import org.eclipse.rap.rwt.application.EntryPoint;
+import org.eclipse.rap.rwt.application.Application.OperationMode;
+import org.eclipse.rap.rwt.engine.RWTServlet;
+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;
+
+       private ApplicationConfiguration applicationConfiguration;
+
+       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, 0);
+       }
+
+       public void init() {
+               Objects.requireNonNull(applicationConfiguration);
+
+               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(applicationConfiguration, 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);
+               }
+               Runtime.getRuntime().addShutdownHook(new Thread(() -> destroy(), "Jetty shutdown"));
+
+               long jvmUptime = ManagementFactory.getRuntimeMXBean().getUptime();
+               System.out.println("RWT App available in " + jvmUptime + " ms, on port " + getEffectivePort());
+       }
+
+       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 void setApplicationConfiguration(ApplicationConfiguration applicationConfiguration) {
+               this.applicationConfiguration = applicationConfiguration;
+       }
+
+       public static void main(String[] args) throws Exception {
+               RwtRunner rwtRunner = new RwtRunner();
+
+               String entryPoint = "app";
+               ApplicationConfiguration applicationConfiguration = (application) -> {
+                       application.setOperationMode(OperationMode.SWT_COMPATIBILITY);
+                       application.addEntryPoint("/" + entryPoint, () -> new EntryPoint() {
+                               @Override
+                               public int createUI() {
+                                       MiniDesktopManager miniDesktopManager = new MiniDesktopManager(false, false);
+                                       miniDesktopManager.init();
+                                       miniDesktopManager.run();
+                                       return 0;
+                               }
+                       }, null);
+               };
+
+               rwtRunner.setApplicationConfiguration(applicationConfiguration);
+               rwtRunner.init();
+
+               // 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();
+       }
+}
diff --git a/swt/rap/org.argeo.tool.server/.classpath b/swt/rap/org.argeo.tool.server/.classpath
new file mode 100644 (file)
index 0000000..81fe078
--- /dev/null
@@ -0,0 +1,7 @@
+<?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>
diff --git a/swt/rap/org.argeo.tool.server/.project b/swt/rap/org.argeo.tool.server/.project
new file mode 100644 (file)
index 0000000..efdc151
--- /dev/null
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+       <name>org.argeo.tool.server</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>
diff --git a/swt/rap/org.argeo.tool.server/bnd.bnd b/swt/rap/org.argeo.tool.server/bnd.bnd
new file mode 100644 (file)
index 0000000..091bd95
--- /dev/null
@@ -0,0 +1,3 @@
+Import-Package: \
+org.apache.commons.cli, \
+*
\ No newline at end of file
diff --git a/swt/rap/org.argeo.tool.server/build.properties b/swt/rap/org.argeo.tool.server/build.properties
new file mode 100644 (file)
index 0000000..34d2e4d
--- /dev/null
@@ -0,0 +1,4 @@
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+               .
diff --git a/swt/rap/org.argeo.tool.server/src/org/argeo/tool/server/ArgeoServer.java b/swt/rap/org.argeo.tool.server/src/org/argeo/tool/server/ArgeoServer.java
new file mode 100644 (file)
index 0000000..4779d05
--- /dev/null
@@ -0,0 +1,22 @@
+package org.argeo.tool.server;
+
+import org.argeo.cms.cli.ArgeoCli;
+import org.argeo.tool.rap.cli.CmsRapCli;
+
+/** Argeo command line tools. */
+public class ArgeoServer extends ArgeoCli {
+       public ArgeoServer(String commandName) {
+               super(commandName);
+               addCommandsCli(new CmsRapCli("cms"));
+       }
+
+       @Override
+       public String getDescription() {
+               return "Argeo server utilities";
+       }
+
+       public static void main(String[] args) {
+               mainImpl(new ArgeoServer("argeo"), args);
+       }
+
+}
diff --git a/swt/rcp/org.argeo.tool.desktop/.classpath b/swt/rcp/org.argeo.tool.desktop/.classpath
new file mode 100644 (file)
index 0000000..81fe078
--- /dev/null
@@ -0,0 +1,7 @@
+<?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>
diff --git a/swt/rcp/org.argeo.tool.desktop/.project b/swt/rcp/org.argeo.tool.desktop/.project
new file mode 100644 (file)
index 0000000..30608d3
--- /dev/null
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+       <name>org.argeo.tool.desktop</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>
diff --git a/swt/rcp/org.argeo.tool.desktop/bnd.bnd b/swt/rcp/org.argeo.tool.desktop/bnd.bnd
new file mode 100644 (file)
index 0000000..cd7978b
--- /dev/null
@@ -0,0 +1,5 @@
+Main-Class: org.argeo.slc.tool.Main
+
+Import-Package: \
+org.apache.sshd.client.session,\
+*
\ No newline at end of file
diff --git a/swt/rcp/org.argeo.tool.desktop/build.properties b/swt/rcp/org.argeo.tool.desktop/build.properties
new file mode 100644 (file)
index 0000000..5b9b216
--- /dev/null
@@ -0,0 +1,8 @@
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+               .
+additional.bundles = org.slf4j.api,\
+                     org.argeo.ext.slf4j,\
+                     org.apache.tomcat.jni
+               
\ No newline at end of file
diff --git a/swt/rcp/org.argeo.tool.desktop/src/org/argeo/tool/desktop/ArgeoDesktop.java b/swt/rcp/org.argeo.tool.desktop/src/org/argeo/tool/desktop/ArgeoDesktop.java
new file mode 100644 (file)
index 0000000..99f94f7
--- /dev/null
@@ -0,0 +1,23 @@
+package org.argeo.tool.desktop;
+
+import org.argeo.cms.cli.ArgeoCli;
+import org.argeo.cms.swt.rcp.cli.CmsCli;
+
+/** Argeo command line tools. */
+public class ArgeoDesktop extends ArgeoCli {
+       public ArgeoDesktop(String commandName) {
+               super(commandName);
+               addCommandsCli(new CmsCli("cms"));
+               addCommandsCli(new MiniDesktopCli("minidesktop"));
+       }
+
+       @Override
+       public String getDescription() {
+               return "Argeo desktop utilities";
+       }
+
+       public static void main(String[] args) {
+               mainImpl(new ArgeoDesktop("argeo-desktop"), args);
+       }
+
+}
diff --git a/swt/rcp/org.argeo.tool.desktop/src/org/argeo/tool/desktop/MiniDesktopCli.java b/swt/rcp/org.argeo.tool.desktop/src/org/argeo/tool/desktop/MiniDesktopCli.java
new file mode 100644 (file)
index 0000000..071bc79
--- /dev/null
@@ -0,0 +1,76 @@
+package org.argeo.tool.desktop;
+
+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.minidesktop.MiniBrowser;
+import org.argeo.minidesktop.MiniDesktopManager;
+import org.argeo.minidesktop.MiniDesktopSpecific;
+import org.eclipse.swt.browser.Browser;
+
+public class MiniDesktopCli extends CommandsCli {
+
+       public MiniDesktopCli(String commandName) {
+               super(commandName);
+               addCommand("launch", new Launch());
+       }
+
+       @Override
+       public String getDescription() {
+               return "A minimalistic desktop manager based on Java and Eclipse SWT.";
+       }
+
+       public static void main(String[] args) {
+               mainImpl(new MiniDesktopCli("minidesktop"), args);
+       }
+
+       static class Launch implements DescribedCommand<String> {
+               @Override
+               public Options getOptions() {
+                       Options options = new Options();
+                       options.addOption(Option.builder().longOpt("fullscreen")
+                                       .desc("take control of the whole screen (default is to run in a window)").build());
+                       options.addOption(Option.builder().longOpt("stacking")
+                                       .desc("open apps as tabs (default is to create new windows)").build());
+                       return options;
+               }
+
+               @Override
+               public String apply(List<String> args) {
+                       CommandLine cl = toCommandLine(args);
+                       boolean fullscreen = cl.hasOption("fullscreen");
+                       boolean stacking = cl.hasOption("stacking");
+
+                       MiniDesktopSpecific.setMiniDesktopSpecific(new MiniDesktopSpecific() {
+                               @Override
+                               protected void addBrowserTitleListener(MiniBrowser miniBrowser, Browser browser) {
+                                       browser.addTitleListener(e -> miniBrowser.titleChanged(e.title));
+                               }
+
+                               @Override
+                               protected void addBrowserOpenWindowListener(MiniBrowser miniBrowser, Browser browser) {
+                                       browser.addOpenWindowListener((e) -> {
+                                               e.browser = miniBrowser.openNewBrowserWindow();
+                                       });
+                               }
+
+                       });
+
+                       MiniDesktopManager desktopManager = new MiniDesktopManager(fullscreen, stacking);
+                       desktopManager.init();
+                       desktopManager.run();
+                       desktopManager.dispose();
+                       return null;
+               }
+
+               @Override
+               public String getDescription() {
+                       return "Launch a minidesktop manager.";
+               }
+
+       }
+}
diff --git a/swt/rcp/org.argeo.tool.rcp.cli/.classpath b/swt/rcp/org.argeo.tool.rcp.cli/.classpath
new file mode 100644 (file)
index 0000000..81fe078
--- /dev/null
@@ -0,0 +1,7 @@
+<?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>
diff --git a/swt/rcp/org.argeo.tool.rcp.cli/.project b/swt/rcp/org.argeo.tool.rcp.cli/.project
new file mode 100644 (file)
index 0000000..c0c5b6a
--- /dev/null
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+       <name>org.argeo.tool.rcp.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>
diff --git a/swt/rcp/org.argeo.tool.rcp.cli/bnd.bnd b/swt/rcp/org.argeo.tool.rcp.cli/bnd.bnd
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/swt/rcp/org.argeo.tool.rcp.cli/build.properties b/swt/rcp/org.argeo.tool.rcp.cli/build.properties
new file mode 100644 (file)
index 0000000..34d2e4d
--- /dev/null
@@ -0,0 +1,4 @@
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+               .
diff --git a/swt/rcp/org.argeo.tool.rcp.cli/src/org/argeo/cms/swt/rcp/cli/CmsCli.java b/swt/rcp/org.argeo.tool.rcp.cli/src/org/argeo/cms/swt/rcp/cli/CmsCli.java
new file mode 100644 (file)
index 0000000..0d7de08
--- /dev/null
@@ -0,0 +1,110 @@
+package org.argeo.cms.swt.rcp.cli;
+
+import java.lang.management.ManagementFactory;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+import java.util.concurrent.ForkJoinPool;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.Options;
+import org.argeo.api.acr.spi.ProvidedRepository;
+import org.argeo.api.cli.CommandsCli;
+import org.argeo.api.cli.DescribedCommand;
+import org.argeo.api.cms.CmsApp;
+import org.argeo.cms.runtime.StaticCms;
+import org.argeo.cms.swt.app.CmsUserApp;
+import org.argeo.cms.ui.rcp.CmsRcpDisplayFactory;
+import org.argeo.api.register.Component;
+import org.argeo.api.register.ComponentRegister;
+
+public class CmsCli extends CommandsCli {
+
+       public CmsCli(String commandName) {
+               super(commandName);
+               addCommand("static", new Launch());
+       }
+
+       @Override
+       public String getDescription() {
+               return "Argeo CMS utilities.";
+       }
+
+       static class Launch implements DescribedCommand<String> {
+               private Option dataOption;
+               private Option uiOption;
+
+               @Override
+               public Options getOptions() {
+                       Options options = new Options();
+                       dataOption = Option.builder().longOpt("data").hasArg().required()
+                                       .desc("path to the writable data area (mandatory)").build();
+                       uiOption = Option.builder().longOpt("ui").desc("open a user interface").build();
+                       options.addOption(dataOption);
+                       options.addOption(uiOption);
+                       return options;
+               }
+
+               @Override
+               public String apply(List<String> args) {
+                       CommandLine cl = toCommandLine(args);
+                       String dataPath = cl.getOptionValue(dataOption);
+                       boolean ui = cl.hasOption(uiOption);
+
+                       Path instancePath = Paths.get(dataPath);
+                       System.setProperty("osgi.instance.area", instancePath.toUri().toString());
+
+                       StaticCms staticCms = new StaticCms() {
+                               @Override
+                               protected void addComponents(ComponentRegister register) {
+                                       if (ui) {
+                                               Component<? extends ProvidedRepository> contentRepositoryC = register
+                                                               .find(ProvidedRepository.class, null).first();
+                                               CmsUserApp cmsApp = new CmsUserApp();
+                                               Component<CmsUserApp> cmsAppC = new Component.Builder<>(cmsApp) //
+                                                               .addType(CmsApp.class) //
+                                                               .addType(CmsUserApp.class) //
+                                                               .addDependency(contentRepositoryC.getType(ProvidedRepository.class),
+                                                                               cmsApp::setContentRepository, null) //
+                                                               .build(register);
+
+                                               CmsRcpDisplayFactory displayFactory = new CmsRcpDisplayFactory();
+                                               Component<CmsRcpDisplayFactory> displayFactoryC = new Component.Builder<>(displayFactory) //
+                                                               .addActivation(displayFactory::init) //
+                                                               .addDeactivation(displayFactory::destroy) //
+                                                               .build(register);
+
+                                       }
+                               }
+
+                               @Override
+                               protected void postActivation(ComponentRegister register) {
+                                       if (ui) {
+                                               Component<? extends CmsUserApp> cmsAppC = register.find(CmsUserApp.class, null).first();
+                                               CmsRcpDisplayFactory.openCmsApp(cmsAppC.get(), "data", (e) -> {
+                                                       // asynchronous in order to avoid deadlock in UI thread
+                                                       ForkJoinPool.commonPool().execute(() -> stop());
+                                               });
+                                       }
+                               }
+
+                       };
+                       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.";
+               }
+
+       }
+}
diff --git a/tp/Make.java b/tp/Make.java
deleted file mode 100644 (file)
index 36447e3..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-import java.nio.file.Path;
-import java.nio.file.Paths;
-
-import org.argeo.slc.factory.A2Factory;
-
-class Make {
-       public static void main(String[] args) {
-               if(args.length < 1) {
-                       System.err.println("Usage: <path to a2 output dir>");
-                       System.exit(1);
-               }
-               Path a2Base = Paths.get(args[0]).toAbsolutePath().normalize();
-               A2Factory factory = new A2Factory(a2Base);
-
-               Path descriptorsBase = Paths.get("./tp").toAbsolutePath().normalize();
-
-//             factory.processSingleM2ArtifactDistributionUnit(descriptorsBase.resolve("org.argeo.tp.apache").resolve("org.apache.xml.resolver.bnd"));
-//             factory.processM2BasedDistributionUnit(descriptorsBase.resolve("org.argeo.tp/slf4j"));
-//             System.exit(0);
-
-               // Eclipse
-               factory.processEclipseArchive(
-                               descriptorsBase.resolve("org.argeo.tp.eclipse.equinox").resolve("eclipse-equinox"));
-               factory.processEclipseArchive(descriptorsBase.resolve("org.argeo.tp.eclipse.rap").resolve("eclipse-rap"));
-               factory.processEclipseArchive(descriptorsBase.resolve("org.argeo.tp.eclipse.rcp").resolve("eclipse-rcp"));
-               factory.processCategory(descriptorsBase.resolve("org.argeo.tp.eclipse.rcp"));
-
-               // Maven
-               factory.processCategory(descriptorsBase.resolve("org.argeo.tp.sdk"));
-               factory.processCategory(descriptorsBase.resolve("org.argeo.tp"));
-               factory.processCategory(descriptorsBase.resolve("org.argeo.tp.apache"));
-               factory.processCategory(descriptorsBase.resolve("org.argeo.tp.jetty"));
-               factory.processCategory(descriptorsBase.resolve("org.argeo.tp.jcr"));
-               factory.processCategory(descriptorsBase.resolve("org.argeo.tp.formats"));
-               factory.processCategory(descriptorsBase.resolve("org.argeo.tp.poi"));
-               factory.processCategory(descriptorsBase.resolve("org.argeo.tp.gis"));
-       }
-
-}
\ No newline at end of file
diff --git a/tp/org.argeo.tp.apache/apache-sshd/common.bnd b/tp/org.argeo.tp.apache/apache-sshd/common.bnd
deleted file mode 100644 (file)
index 8180da3..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-Bundle-License: Apache-2.0
-SLC-Origin-M2: :2.5.1
\ No newline at end of file
diff --git a/tp/org.argeo.tp.apache/apache-sshd/org.apache.sshd.cli.bnd b/tp/org.argeo.tp.apache/apache-sshd/org.apache.sshd.cli.bnd
deleted file mode 100644 (file)
index 7b00c2c..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-Bundle-SymbolicName: org.apache.sshd.cli
-SLC-Origin-M2: org.apache.sshd:sshd-cli
diff --git a/tp/org.argeo.tp.apache/apache-sshd/org.apache.sshd.common.bnd b/tp/org.argeo.tp.apache/apache-sshd/org.apache.sshd.common.bnd
deleted file mode 100644 (file)
index b8414e9..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-Bundle-SymbolicName: org.apache.sshd.common
-Fragment-Host: org.apache.sshd.core
-Import-Package: org.bouncycastle.jce.provider;resolution:=optional,
-                                               net.i2p.crypto.eddsa;resolution:=optional,
-                                               *
-SLC-Origin-M2: org.apache.sshd:sshd-common
diff --git a/tp/org.argeo.tp.apache/apache-sshd/org.apache.sshd.core.bnd b/tp/org.argeo.tp.apache/apache-sshd/org.apache.sshd.core.bnd
deleted file mode 100644 (file)
index 3ba3f50..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-Bundle-SymbolicName: org.apache.sshd.core
-SLC-Origin-M2: org.apache.sshd:sshd-core
-Import-Package: \
-org.apache.tomcat.jni.*;resolution:=optional,\
-*
\ No newline at end of file
diff --git a/tp/org.argeo.tp.apache/apache-sshd/org.apache.sshd.git.bnd b/tp/org.argeo.tp.apache/apache-sshd/org.apache.sshd.git.bnd
deleted file mode 100644 (file)
index 7d9ef2b..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-Bundle-SymbolicName: org.apache.sshd.git
-SLC-Origin-M2: org.apache.sshd:sshd-git
-Import-Package: \
-org.eclipse.jgit.pgm.*;resolution:=optional,\
-org.kohsuke.*;resolution:=optional,\
-*
\ No newline at end of file
diff --git a/tp/org.argeo.tp.apache/apache-sshd/org.apache.sshd.putty.bnd b/tp/org.argeo.tp.apache/apache-sshd/org.apache.sshd.putty.bnd
deleted file mode 100644 (file)
index cf8a99a..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-Bundle-SymbolicName: org.apache.sshd.putty
-SLC-Origin-M2: org.apache.sshd:sshd-putty
-Import-Package: \
-net.i2p.*;resolution:=optional,\
-*
\ No newline at end of file
diff --git a/tp/org.argeo.tp.apache/apache-sshd/org.apache.sshd.scp.bnd b/tp/org.argeo.tp.apache/apache-sshd/org.apache.sshd.scp.bnd
deleted file mode 100644 (file)
index 6744473..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-Bundle-SymbolicName: org.apache.sshd.scp
-SLC-Origin-M2: org.apache.sshd:sshd-scp
diff --git a/tp/org.argeo.tp.apache/apache-sshd/org.apache.sshd.sftp.bnd b/tp/org.argeo.tp.apache/apache-sshd/org.apache.sshd.sftp.bnd
deleted file mode 100644 (file)
index b6b8b0d..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-Bundle-SymbolicName: org.apache.sshd.sftp
-SLC-Origin-M2: org.apache.sshd:sshd-sftp
diff --git a/tp/org.argeo.tp.apache/org.apache.commons.cli.bnd b/tp/org.argeo.tp.apache/org.apache.commons.cli.bnd
deleted file mode 100644 (file)
index 7e34d59..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-Bundle-License: Apache-2.0
-Bundle-SymbolicName: org.apache.commons.cli
-SLC-Origin-M2: commons-cli:commons-cli:1.5.0
diff --git a/tp/org.argeo.tp.apache/org.apache.commons.codec.bnd b/tp/org.argeo.tp.apache/org.apache.commons.codec.bnd
deleted file mode 100644 (file)
index fe4bfa6..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-Bundle-License: Apache-2.0
-Bundle-SymbolicName: org.apache.commons.codec
-SLC-Origin-M2: commons-codec:commons-codec:1.15
diff --git a/tp/org.argeo.tp.apache/org.apache.commons.compress.bnd b/tp/org.argeo.tp.apache/org.apache.commons.compress.bnd
deleted file mode 100644 (file)
index e126b0a..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-Bundle-License: Apache-2.0
-Bundle-SymbolicName: org.apache.commons.compress
-SLC-Origin-M2: org.apache.commons:commons-compress:1.21
-Import-Package: \
-org.brotli.*;resolution:=optional,
-org.tukaani.*;resolution:=optional,
-com.github.luben.zstd;resolution:=optional,
-*
\ No newline at end of file
diff --git a/tp/org.argeo.tp.apache/org.apache.commons.exec.bnd b/tp/org.argeo.tp.apache/org.apache.commons.exec.bnd
deleted file mode 100644 (file)
index b188587..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-Bundle-License: Apache-2.0
-Bundle-SymbolicName: org.apache.commons.exec
-SLC-Origin-M2: org.apache.commons:commons-exec:1.3
\ No newline at end of file
diff --git a/tp/org.argeo.tp.apache/org.apache.commons.fileupload.bnd b/tp/org.argeo.tp.apache/org.apache.commons.fileupload.bnd
deleted file mode 100644 (file)
index 40a58d2..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-Bundle-License: Apache-2.0
-Bundle-SymbolicName: org.apache.commons.fileupload
-Import-Package: javax.portlet;resolution:=optional,\
-*
-SLC-Origin-M2: commons-fileupload:commons-fileupload:1.4
diff --git a/tp/org.argeo.tp.apache/org.apache.commons.httpclient.bnd b/tp/org.argeo.tp.apache/org.apache.commons.httpclient.bnd
deleted file mode 100644 (file)
index a064d48..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-Bundle-License: Apache-2.0
-Bundle-SymbolicName: org.apache.commons.httpclient
-SLC-Origin-M2: commons-httpclient:commons-httpclient:3.1
diff --git a/tp/org.argeo.tp.apache/org.apache.commons.io.bnd b/tp/org.argeo.tp.apache/org.apache.commons.io.bnd
deleted file mode 100644 (file)
index e1e25b3..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-Bundle-License: Apache-2.0
-Bundle-SymbolicName: org.apache.commons.io
-SLC-Origin-M2: commons-io:commons-io:2.11.0
-Import-Package: sun.*;resolution:=optional, *
diff --git a/tp/org.argeo.tp.apache/org.apache.commons.vfs.bnd b/tp/org.argeo.tp.apache/org.apache.commons.vfs.bnd
deleted file mode 100644 (file)
index 3a73689..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-Bundle-License: Apache-2.0
-Bundle-SymbolicName: org.apache.commons.vfs
-Import-Package: org.apache.tools.ant.*;resolution:=optional,
-*
-SLC-Origin-M2: org.apache.commons:commons-vfs2:2.9.0
\ No newline at end of file
diff --git a/tp/org.argeo.tp.apache/org.apache.httpcomponents.httpclient.bnd b/tp/org.argeo.tp.apache/org.apache.httpcomponents.httpclient.bnd
deleted file mode 100644 (file)
index eead3b2..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-Bundle-License: Apache-2.0
-Bundle-SymbolicName: org.apache.httpcomponents.httpclient
-SLC-Origin-M2: org.apache.httpcomponents:httpclient:4.5.13
\ No newline at end of file
diff --git a/tp/org.argeo.tp.apache/org.apache.httpcomponents.httpcore.bnd b/tp/org.argeo.tp.apache/org.apache.httpcomponents.httpcore.bnd
deleted file mode 100644 (file)
index b0d0c7a..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-Bundle-License: Apache-2.0
-Bundle-SymbolicName: org.apache.httpcomponents.httpcore
-SLC-Origin-M2: org.apache.httpcomponents:httpcore:4.4.15
\ No newline at end of file
diff --git a/tp/org.argeo.tp.apache/org.apache.httpcomponents.httpmime.bnd b/tp/org.argeo.tp.apache/org.apache.httpcomponents.httpmime.bnd
deleted file mode 100644 (file)
index 1623fb5..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-Bundle-License: Apache-2.0
-Bundle-SymbolicName: org.apache.httpcomponents.httpmime
-SLC-Origin-M2: org.apache.httpcomponents:httpmime:4.5.13
\ No newline at end of file
diff --git a/tp/org.argeo.tp.apache/org.apache.tika.core.bnd b/tp/org.argeo.tp.apache/org.apache.tika.core.bnd
deleted file mode 100644 (file)
index 38b1663..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-Bundle-License: Apache-2.0
-Bundle-SymbolicName: org.apache.tika.core
-SLC-Origin-M2: org.apache.tika:tika-core:1.27
-SLC-Origin-ManifestNotModified: true
diff --git a/tp/org.argeo.tp.apache/org.apache.tika.parsers.bnd b/tp/org.argeo.tp.apache/org.apache.tika.parsers.bnd
deleted file mode 100644 (file)
index 380c58f..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-Bundle-Activator: org.apache.tika.parser.internal.Activator
-Bundle-License: Apache-2.0
-Bundle-SymbolicName: org.apache.tika.parsers
-Import-Package: *;resolution:=optional
-SLC-Origin-M2: org.apache.tika:tika-parsers:1.27
\ No newline at end of file
diff --git a/tp/org.argeo.tp.apache/org.apache.xalan.bnd b/tp/org.argeo.tp.apache/org.apache.xalan.bnd
deleted file mode 100644 (file)
index 1d2106b..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-Bundle-License: Apache-2.0
-Bundle-SymbolicName: org.apache.xalan
-SLC-Origin-M2: xalan:xalan:2.7.2
\ No newline at end of file
diff --git a/tp/org.argeo.tp.apache/org.apache.xalan.serializer.bnd b/tp/org.argeo.tp.apache/org.apache.xalan.serializer.bnd
deleted file mode 100644 (file)
index e0fb19c..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-Bundle-License: Apache-2.0
-Bundle-SymbolicName: org.apache.xalan.serializer
-SLC-Origin-M2: xalan:serializer:2.7.2
\ No newline at end of file
diff --git a/tp/org.argeo.tp.apache/org.apache.xerces.bnd b/tp/org.argeo.tp.apache/org.apache.xerces.bnd
deleted file mode 100644 (file)
index b66a199..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-Bundle-License: Apache-2.0
-Bundle-SymbolicName: org.apache.xerces
-Import-Package: sun.*;resolution:=optional,
-*
-SLC-Origin-M2: xerces:xercesImpl:2.12.2
\ No newline at end of file
diff --git a/tp/org.argeo.tp.apache/org.apache.xml.resolver.bnd b/tp/org.argeo.tp.apache/org.apache.xml.resolver.bnd
deleted file mode 100644 (file)
index 95f9a5e..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-Bundle-License: Apache-2.0
-Bundle-SymbolicName: org.apache.xml.resolver
-SLC-Origin-M2: xml-resolver:xml-resolver:1.2
\ No newline at end of file
diff --git a/tp/org.argeo.tp.eclipse.equinox/eclipse-equinox/common.bnd b/tp/org.argeo.tp.eclipse.equinox/eclipse-equinox/common.bnd
deleted file mode 100644 (file)
index 4411b9a..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-Bundle-License: EPL-1.0
-SLC-Origin-ManifestNotModified: true
-SLC-Origin-URI: http://www.eclipse.org/downloads/equinox/drops/R-4.22-202111241800/equinox-SDK-4.22.zip
diff --git a/tp/org.argeo.tp.eclipse.equinox/eclipse-equinox/includes.properties b/tp/org.argeo.tp.eclipse.equinox/eclipse-equinox/includes.properties
deleted file mode 100644 (file)
index 06cc3e9..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-plugins/org.eclipse.osgi_*.jar=org.argeo.tp.eclipse.equinox
-plugins/org.eclipse.osgi.source_*.jar=org.argeo.tp.eclipse.equinox
-plugins/org.eclipse.osgi.util*.jar=org.argeo.tp.eclipse.equinox
-plugins/org.eclipse.osgi.services*.jar=org.argeo.tp.eclipse.equinox
-plugins/jakarta.servlet-api*.jar=org.argeo.tp.javax
-plugins/org.apache.felix.gogo.*.jar=org.argeo.tp.apache.felix
-plugins/org.apache.felix.scr*.jar=org.argeo.tp.apache.felix
-plugins/org.eclipse.equinox.app*.jar=org.argeo.tp.eclipse.equinox
-plugins/org.eclipse.equinox.cm*.jar=org.argeo.tp.eclipse.equinox
-plugins/org.eclipse.equinox.common*.jar=org.argeo.tp.eclipse.equinox
-plugins/org.eclipse.equinox.console_*.jar=org.argeo.tp.eclipse.equinox
-plugins/org.eclipse.equinox.console.source_*.jar=org.argeo.tp.eclipse.equinox
-plugins/org.eclipse.equinox.ds*.jar=org.argeo.tp.eclipse.equinox
-plugins/org.eclipse.equinox.metatype*.jar=org.argeo.tp.eclipse.equinox
-plugins/org.eclipse.equinox.event*.jar=org.argeo.tp.eclipse.equinox
-plugins/org.eclipse.equinox.http.jetty*.jar=org.argeo.tp.eclipse.equinox
-plugins/org.eclipse.equinox.http.registry*.jar=org.argeo.tp.eclipse.equinox
-plugins/org.eclipse.equinox.http.servlet*.jar=org.argeo.tp.eclipse.equinox
-plugins/org.eclipse.equinox.http.servletbridge*.jar=org.argeo.tp.eclipse.equinox
-plugins/org.eclipse.equinox.preferences*.jar=org.argeo.tp.eclipse.equinox
-plugins/org.eclipse.equinox.registry*.jar=org.argeo.tp.eclipse.equinox
-plugins/org.eclipse.equinox.servletbridge*.jar=org.argeo.tp.eclipse.equinox
-plugins/org.eclipse.equinox.util*.jar=org.argeo.tp.eclipse.equinox
diff --git a/tp/org.argeo.tp.eclipse.rap/eclipse-rap/common.bnd b/tp/org.argeo.tp.eclipse.rap/eclipse-rap/common.bnd
deleted file mode 100644 (file)
index 5c332a0..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-Bundle-License: EPL-1.0
-SLC-Origin-ManifestNotModified: true
-SLC-Origin-URI: http://www.eclipse.org/downloads/rt/rap/3.19/e4/rap-e4-3.19.0-R-20211130-1934.zip
diff --git a/tp/org.argeo.tp.eclipse.rap/eclipse-rap/includes.properties b/tp/org.argeo.tp.eclipse.rap/eclipse-rap/includes.properties
deleted file mode 100644 (file)
index 1bfa0a6..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-plugins/javax.inject*.jar=org.argeo.tp.javax
-plugins/javax.annotation*.jar=org.argeo.tp.javax
-plugins/org.apache.commons.jxpath*.jar=org.argeo.tp.eclipse.rap
-plugins/org.eclipse.core.*.jar=org.argeo.tp.eclipse.rap
-plugins/org.eclipse.rap.rwt_*.jar=org.argeo.tp.eclipse.rap
-plugins/org.eclipse.rap.rwt.source_*.jar=org.argeo.tp.eclipse.rap
-plugins/org.eclipse.rap.rwt.osgi_*.jar=org.argeo.tp.eclipse.rap
-plugins/org.eclipse.rap.rwt.osgi.source_*.jar=org.argeo.tp.eclipse.rap
-plugins/org.eclipse.rap.fileupload*.jar=org.argeo.tp.eclipse.rap
-plugins/org.eclipse.rap.filedialog*.jar=org.argeo.tp.eclipse.rap
-plugins/org.eclipse.e4*.jar=org.argeo.tp.eclipse.rap
-plugins/org.eclipse.emf*.jar=org.argeo.tp.eclipse.rap
-plugins/org.eclipse.rap.e4_*.jar=org.argeo.tp.eclipse.rap
-plugins/org.eclipse.rap.e4.source_*.jar=org.argeo.tp.eclipse.rap
-plugins/org.eclipse.rap.ui.workbench*.jar=org.argeo.tp.eclipse.rap
-plugins/org.eclipse.rap.ui_*.jar=org.argeo.tp.eclipse.rap
-plugins/org.eclipse.rap.ui.source_*.jar=org.argeo.tp.eclipse.rap
-plugins/org.eclipse.rap.ui.views*.jar=org.argeo.tp.eclipse.rap
-plugins/org.eclipse.rap.jface*.jar=org.argeo.tp.eclipse.rap
-plugins/org.eclipse.rap.ui.forms*.jar=org.argeo.tp.eclipse.rap
-plugins/org.eclipse.help*.jar=org.argeo.tp.eclipse.rap
-plugins/org.eclipse.rap.nebula.*.jar=org.argeo.tp.eclipse.rap
-plugins/com.ibm.icu*.jar=org.argeo.tp.eclipse.rap
diff --git a/tp/org.argeo.tp.eclipse.rcp/eclipse-rcp/common.bnd b/tp/org.argeo.tp.eclipse.rcp/eclipse-rcp/common.bnd
deleted file mode 100644 (file)
index e290997..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-Bundle-License: EPL-1.0
-SLC-Origin-ManifestNotModified: true
-SLC-Origin-URI: http://www.eclipse.org/downloads/eclipse/downloads/drops4/R-4.22-202111241800/org.eclipse.rcp.source-4.22.zip
diff --git a/tp/org.argeo.tp.eclipse.rcp/eclipse-rcp/includes.properties b/tp/org.argeo.tp.eclipse.rcp/eclipse-rcp/includes.properties
deleted file mode 100644 (file)
index 2ddb580..0000000
+++ /dev/null
@@ -1 +0,0 @@
-plugins/*.jar=org.argeo.tp.eclipse.rcp
diff --git a/tp/org.argeo.tp.eclipse.rcp/org.eclipse.emf.common.bnd b/tp/org.argeo.tp.eclipse.rcp/org.eclipse.emf.common.bnd
deleted file mode 100644 (file)
index edd9030..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-Bundle-License: EPEL-2.0
-SLC-Origin-M2: org.eclipse.emf:org.eclipse.emf.common:2.23.0
-SLC-Origin-ManifestNotModified: true
diff --git a/tp/org.argeo.tp.eclipse.rcp/org.eclipse.emf.ecore.bnd b/tp/org.argeo.tp.eclipse.rcp/org.eclipse.emf.ecore.bnd
deleted file mode 100644 (file)
index c914536..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-Bundle-License: EPEL-2.0
-SLC-Origin-M2: org.eclipse.emf:org.eclipse.emf.ecore:2.25.0
-SLC-Origin-ManifestNotModified: true
diff --git a/tp/org.argeo.tp.eclipse.rcp/org.eclipse.emf.ecore.change.bnd b/tp/org.argeo.tp.eclipse.rcp/org.eclipse.emf.ecore.change.bnd
deleted file mode 100644 (file)
index ba733ef..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-Bundle-License: EPEL-2.0
-SLC-Origin-M2: org.eclipse.emf:org.eclipse.emf.ecore.change:2.14.0
-SLC-Origin-ManifestNotModified: true
diff --git a/tp/org.argeo.tp.eclipse.rcp/org.eclipse.emf.ecore.xmi.bnd b/tp/org.argeo.tp.eclipse.rcp/org.eclipse.emf.ecore.xmi.bnd
deleted file mode 100644 (file)
index 4a40881..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-Bundle-License: EPEL-2.0
-SLC-Origin-M2: org.eclipse.emf:org.eclipse.emf.ecore.xmi:2.16.0
-SLC-Origin-ManifestNotModified: true
diff --git a/tp/org.argeo.tp.formats/batik/common.bnd b/tp/org.argeo.tp.formats/batik/common.bnd
deleted file mode 100644 (file)
index bc76a97..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-Bundle-License: Apache-2.0
-SLC-Origin-M2: :1.14
\ No newline at end of file
diff --git a/tp/org.argeo.tp.formats/batik/org.apache.batik.anim.bnd b/tp/org.argeo.tp.formats/batik/org.apache.batik.anim.bnd
deleted file mode 100644 (file)
index 26debce..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-Bundle-SymbolicName: org.apache.batik.anim
-SLC-Origin-M2: org.apache.xmlgraphics:batik-anim
diff --git a/tp/org.argeo.tp.formats/batik/org.apache.batik.awt.util.bnd b/tp/org.argeo.tp.formats/batik/org.apache.batik.awt.util.bnd
deleted file mode 100644 (file)
index e6e775a..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-Bundle-SymbolicName: org.apache.batik.awt.util
-SLC-Origin-M2: org.apache.xmlgraphics:batik-awt-util
diff --git a/tp/org.argeo.tp.formats/batik/org.apache.batik.bridge.bnd b/tp/org.argeo.tp.formats/batik/org.apache.batik.bridge.bnd
deleted file mode 100644 (file)
index 33328eb..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-Bundle-SymbolicName: org.apache.batik.bridge
-SLC-Origin-M2: org.apache.xmlgraphics:batik-bridge
diff --git a/tp/org.argeo.tp.formats/batik/org.apache.batik.constants.bnd b/tp/org.argeo.tp.formats/batik/org.apache.batik.constants.bnd
deleted file mode 100644 (file)
index fea69fa..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-Bundle-SymbolicName: org.apache.batik.constants
-SLC-Origin-M2: org.apache.xmlgraphics:batik-constants
diff --git a/tp/org.argeo.tp.formats/batik/org.apache.batik.css.bnd b/tp/org.argeo.tp.formats/batik/org.apache.batik.css.bnd
deleted file mode 100644 (file)
index 8ec1225..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-Bundle-SymbolicName: org.apache.batik.css
-SLC-Origin-M2: org.apache.xmlgraphics:batik-css
diff --git a/tp/org.argeo.tp.formats/batik/org.apache.batik.dom.bnd b/tp/org.argeo.tp.formats/batik/org.apache.batik.dom.bnd
deleted file mode 100644 (file)
index c5dd20b..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-Bundle-SymbolicName: org.apache.batik.dom
-SLC-Origin-M2: org.apache.xmlgraphics:batik-dom
diff --git a/tp/org.argeo.tp.formats/batik/org.apache.batik.ext.bnd b/tp/org.argeo.tp.formats/batik/org.apache.batik.ext.bnd
deleted file mode 100644 (file)
index 2349f6e..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-Bundle-SymbolicName: org.apache.batik.ext
-SLC-Origin-M2: org.apache.xmlgraphics:batik-ext
diff --git a/tp/org.argeo.tp.formats/batik/org.apache.batik.extension.bnd b/tp/org.argeo.tp.formats/batik/org.apache.batik.extension.bnd
deleted file mode 100644 (file)
index bff767b..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-Bundle-SymbolicName: org.apache.batik.extension
-SLC-Origin-M2: org.apache.xmlgraphics:batik-extension
diff --git a/tp/org.argeo.tp.formats/batik/org.apache.batik.gui.util.bnd b/tp/org.argeo.tp.formats/batik/org.apache.batik.gui.util.bnd
deleted file mode 100644 (file)
index 5ecc421..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-Bundle-SymbolicName: org.apache.batik.gui.util
-SLC-Origin-M2: org.apache.xmlgraphics:batik-gui-util
diff --git a/tp/org.argeo.tp.formats/batik/org.apache.batik.gvt.bnd b/tp/org.argeo.tp.formats/batik/org.apache.batik.gvt.bnd
deleted file mode 100644 (file)
index fd52651..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-Bundle-SymbolicName: org.apache.batik.gvt
-SLC-Origin-M2: org.apache.xmlgraphics:batik-gvt
diff --git a/tp/org.argeo.tp.formats/batik/org.apache.batik.i18n.bnd b/tp/org.argeo.tp.formats/batik/org.apache.batik.i18n.bnd
deleted file mode 100644 (file)
index 321ae88..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-Bundle-SymbolicName: org.apache.batik.i18n
-SLC-Origin-M2: org.apache.xmlgraphics:batik-i18n
diff --git a/tp/org.argeo.tp.formats/batik/org.apache.batik.parser.bnd b/tp/org.argeo.tp.formats/batik/org.apache.batik.parser.bnd
deleted file mode 100644 (file)
index 9379201..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-Bundle-SymbolicName: org.apache.batik.parser
-SLC-Origin-M2: org.apache.xmlgraphics:batik-parser
diff --git a/tp/org.argeo.tp.formats/batik/org.apache.batik.resources.bnd b/tp/org.argeo.tp.formats/batik/org.apache.batik.resources.bnd
deleted file mode 100644 (file)
index 08564a4..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-Bundle-SymbolicName: org.apache.batik.resources
-SLC-Origin-M2: org.apache.xmlgraphics:batik-shared-resources
diff --git a/tp/org.argeo.tp.formats/batik/org.apache.batik.script.bnd b/tp/org.argeo.tp.formats/batik/org.apache.batik.script.bnd
deleted file mode 100644 (file)
index 7356915..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-Bundle-SymbolicName: org.apache.batik.script
-SLC-Origin-M2: org.apache.xmlgraphics:batik-script
-Import-Package: \
-org.python.*;resolution:="optional",\
-*
\ No newline at end of file
diff --git a/tp/org.argeo.tp.formats/batik/org.apache.batik.svg.dom.bnd b/tp/org.argeo.tp.formats/batik/org.apache.batik.svg.dom.bnd
deleted file mode 100644 (file)
index f6a187b..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-Bundle-SymbolicName: org.apache.batik.svg.dom
-SLC-Origin-M2: org.apache.xmlgraphics:batik-svg-dom
diff --git a/tp/org.argeo.tp.formats/batik/org.apache.batik.svggen.bnd b/tp/org.argeo.tp.formats/batik/org.apache.batik.svggen.bnd
deleted file mode 100644 (file)
index 33fa542..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-Bundle-SymbolicName: org.apache.batik.svggen
-SLC-Origin-M2: org.apache.xmlgraphics:batik-svggen
diff --git a/tp/org.argeo.tp.formats/batik/org.apache.batik.swing.bnd b/tp/org.argeo.tp.formats/batik/org.apache.batik.swing.bnd
deleted file mode 100644 (file)
index ddf9a78..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-Bundle-SymbolicName: org.apache.batik.swing
-SLC-Origin-M2: org.apache.xmlgraphics:batik-swing
diff --git a/tp/org.argeo.tp.formats/batik/org.apache.batik.transcoder.bnd b/tp/org.argeo.tp.formats/batik/org.apache.batik.transcoder.bnd
deleted file mode 100644 (file)
index 8afd5af..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-Bundle-SymbolicName: org.apache.batik.transcoder
-SLC-Origin-M2: org.apache.xmlgraphics:batik-transcoder
diff --git a/tp/org.argeo.tp.formats/batik/org.apache.batik.util.bnd b/tp/org.argeo.tp.formats/batik/org.apache.batik.util.bnd
deleted file mode 100644 (file)
index 0b672dc..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-Bundle-SymbolicName: org.apache.batik.util
-SLC-Origin-M2: org.apache.xmlgraphics:batik-util
diff --git a/tp/org.argeo.tp.formats/batik/org.apache.batik.xml.bnd b/tp/org.argeo.tp.formats/batik/org.apache.batik.xml.bnd
deleted file mode 100644 (file)
index c42c15c..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-Bundle-SymbolicName: org.apache.batik.xml
-SLC-Origin-M2: org.apache.xmlgraphics:batik-xml
diff --git a/tp/org.argeo.tp.formats/com.adobe.xmp.xmpcore.bnd b/tp/org.argeo.tp.formats/com.adobe.xmp.xmpcore.bnd
deleted file mode 100644 (file)
index e706659..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-Bundle-License: BSD-3-Clause
-Bundle-SymbolicName: com.adobe.xmp.xmpcore
-# No source available in Maven Central for 6.1.11
-SLC-Origin-M2: com.adobe.xmp:xmpcore:6.1.10
diff --git a/tp/org.argeo.tp.formats/com.drew.metadata.bnd b/tp/org.argeo.tp.formats/com.drew.metadata.bnd
deleted file mode 100644 (file)
index f32421a..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-Bundle-License: Apache-2.0
-Bundle-SymbolicName: com.drew.metadata
-SLC-Origin-M2: com.drewnoakes:metadata-extractor:2.16.0
diff --git a/tp/org.argeo.tp.formats/com.graphbuilder.bnd b/tp/org.argeo.tp.formats/com.graphbuilder.bnd
deleted file mode 100644 (file)
index d400fd7..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-Bundle-License: BSD-3-Clause
-Bundle-SymbolicName: com.graphbuilder
-SLC-Origin-M2: com.github.virtuald:curvesapi:1.07
diff --git a/tp/org.argeo.tp.formats/de.rototor.pdfbox.graphics2d.bnd b/tp/org.argeo.tp.formats/de.rototor.pdfbox.graphics2d.bnd
deleted file mode 100644 (file)
index b6f5736..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-Bundle-License: Apache-2.0
-Bundle-SymbolicName: de.rototor.pdfbox.graphics2d
-SLC-Origin-M2: de.rototor.pdfbox:graphics2d:0.35
diff --git a/tp/org.argeo.tp.formats/fop/common.bnd b/tp/org.argeo.tp.formats/fop/common.bnd
deleted file mode 100644 (file)
index 64a95da..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-Bundle-License: Apache-2.0
-SLC-Origin-M2: :2.7
\ No newline at end of file
diff --git a/tp/org.argeo.tp.formats/fop/org.apache.fop.core.bnd b/tp/org.argeo.tp.formats/fop/org.apache.fop.core.bnd
deleted file mode 100644 (file)
index ba69e0b..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-Bundle-SymbolicName: org.apache.fop.core
-SLC-Origin-M2: org.apache.xmlgraphics:fop-core
-Import-Package: \
-javax.media.jai;resolution:="optional",\
-org.apache.tools.ant.*;resolution:="optional",\
-*
-Export-Package: \
-!org.apache.fop.util.*,\
-*
\ No newline at end of file
diff --git a/tp/org.argeo.tp.formats/fop/org.apache.fop.events.bnd b/tp/org.argeo.tp.formats/fop/org.apache.fop.events.bnd
deleted file mode 100644 (file)
index 31265cc..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-Bundle-SymbolicName: org.apache.fop.events
-SLC-Origin-M2: org.apache.xmlgraphics:fop-events
-Import-Package: \
-com.thoughtworks.qdox.*;resolution:="optional",\
-org.apache.tools.ant.*;resolution:="optional",\
-*
diff --git a/tp/org.argeo.tp.formats/fop/org.apache.fop.util.bnd b/tp/org.argeo.tp.formats/fop/org.apache.fop.util.bnd
deleted file mode 100644 (file)
index c2ea982..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-Bundle-SymbolicName: org.apache.fop.util
-SLC-Origin-M2: org.apache.xmlgraphics:fop-util
diff --git a/tp/org.argeo.tp.formats/javax.activation.bnd b/tp/org.argeo.tp.formats/javax.activation.bnd
deleted file mode 100644 (file)
index 024dda8..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-Bundle-License: CDDL-1.0
-SLC-Origin-M2: com.sun.activation:javax.activation:1.2.0
-SLC-Origin-ManifestNotModified: true
diff --git a/tp/org.argeo.tp.formats/javax.mail.bnd b/tp/org.argeo.tp.formats/javax.mail.bnd
deleted file mode 100644 (file)
index 8166bc7..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-Bundle-License: CDDL-1.0 OR GPL-2.0 WITH Classpath-exception-2.0
-Bundle-SymbolicName: javax.mail
-SLC-Origin-M2: com.sun.mail:javax.mail:1.6.2
-SLC-Origin-ManifestNotModified: true
diff --git a/tp/org.argeo.tp.formats/javax.xml.bind.bnd b/tp/org.argeo.tp.formats/javax.xml.bind.bnd
deleted file mode 100644 (file)
index a827549..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-Bundle-License: CDDL-1.0 OR GPL-2.0 WITH Classpath-exception-2.0
-Bundle-SymbolicName: javax.xml.bind
-Bundle-Version: 2.4.0
-SLC-Origin-M2: javax.xml.bind:jaxb-api:2.4.0-b180830.0359
-Import-Package: \
-org.glassfish.hk2.osgiresourcelocator;resolution:="optional",\
-*
diff --git a/tp/org.argeo.tp.formats/org.apache.commons.collections4.bnd b/tp/org.argeo.tp.formats/org.apache.commons.collections4.bnd
deleted file mode 100644 (file)
index 4379a19..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-Bundle-License: Apache-2.0
-Bundle-SymbolicName: org.apache.commons.collections4
-SLC-Origin-M2: org.apache.commons:commons-collections4:4.4
\ No newline at end of file
diff --git a/tp/org.argeo.tp.formats/org.apache.commons.csv.bnd b/tp/org.argeo.tp.formats/org.apache.commons.csv.bnd
deleted file mode 100644 (file)
index 94f8301..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-Bundle-License: Apache-2.0
-Bundle-SymbolicName: org.apache.commons.csv
-SLC-Origin-M2: org.apache.commons:commons-csv:1.9.0
diff --git a/tp/org.argeo.tp.formats/org.apache.commons.imaging.bnd b/tp/org.argeo.tp.formats/org.apache.commons.imaging.bnd
deleted file mode 100644 (file)
index 16e0882..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-Bundle-License: Apache-2.0
-Bundle-SymbolicName: org.apache.commons.imaging
-Bundle-Version: 0.99.2
-SLC-Origin-M2: org.apache.commons:commons-imaging:1.0-alpha2
diff --git a/tp/org.argeo.tp.formats/org.apache.commons.lang3.bnd b/tp/org.argeo.tp.formats/org.apache.commons.lang3.bnd
deleted file mode 100644 (file)
index 373f647..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-Bundle-License: Apache-2.0
-Bundle-SymbolicName: org.apache.commons.lang3
-SLC-Origin-M2: org.apache.commons:commons-lang3:3.12.0
diff --git a/tp/org.argeo.tp.formats/org.apache.commons.math3.bnd b/tp/org.argeo.tp.formats/org.apache.commons.math3.bnd
deleted file mode 100644 (file)
index cc8d131..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-Bundle-License: Apache-2.0
-Bundle-SymbolicName: org.apache.commons.math3
-SLC-Origin-M2: org.apache.commons:commons-math3:3.5
diff --git a/tp/org.argeo.tp.formats/org.apache.commons.text.bnd b/tp/org.argeo.tp.formats/org.apache.commons.text.bnd
deleted file mode 100644 (file)
index b85c146..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-Bundle-License: Apache-2.0
-Bundle-SymbolicName: org.apache.commons.text
-SLC-Origin-M2: org.apache.commons:commons-text:1.9
diff --git a/tp/org.argeo.tp.formats/org.apache.pdfbox.jempbox.bnd b/tp/org.argeo.tp.formats/org.apache.pdfbox.jempbox.bnd
deleted file mode 100644 (file)
index 0689779..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-Bundle-License: Apache-2.0
-Bundle-SymbolicName: org.apache.pdfbox.jempbox
-SLC-Origin-M2: org.apache.pdfbox:jempbox:1.8.16
diff --git a/tp/org.argeo.tp.formats/org.apache.xml.security.bnd b/tp/org.argeo.tp.formats/org.apache.xml.security.bnd
deleted file mode 100644 (file)
index 8600cbb..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-Bundle-License: Apache-2.0
-Bundle-SymbolicName: org.apache.xml.security
-SLC-Origin-M2: org.apache.santuario:xmlsec:2.1.2
diff --git a/tp/org.argeo.tp.formats/org.apache.xmlgraphics.bnd b/tp/org.argeo.tp.formats/org.apache.xmlgraphics.bnd
deleted file mode 100644 (file)
index 254a0af..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-Bundle-License: Apache-2.0
-Bundle-SymbolicName: org.apache.xmlgraphics
-SLC-Origin-M2: org.apache.xmlgraphics:xmlgraphics-commons:2.7
diff --git a/tp/org.argeo.tp.formats/org.mozilla.javascript.bnd b/tp/org.argeo.tp.formats/org.mozilla.javascript.bnd
deleted file mode 100644 (file)
index 277db30..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-Bundle-License: MPL-2.0
-Bundle-SymbolicName: org.mozilla.javascript
-SLC-Origin-M2: org.mozilla:rhino:1.7.14
-SLC-Origin-ManifestNotModified: true
diff --git a/tp/org.argeo.tp.formats/org.w3c.dom.smil.bnd b/tp/org.argeo.tp.formats/org.w3c.dom.smil.bnd
deleted file mode 100644 (file)
index 05b9dd6..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-Bundle-License: W3C
-Bundle-SymbolicName: org.w3c.dom.smil
-Bundle-Version: 1.0
-SLC-Origin-M2: org.axsl.org.w3c.dom.smil:smil-boston-dom-java:2000-02-25
diff --git a/tp/org.argeo.tp.formats/org.w3c.dom.svg.bnd b/tp/org.argeo.tp.formats/org.w3c.dom.svg.bnd
deleted file mode 100644 (file)
index 44cb2b1..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-Bundle-License: W3C
-Bundle-SymbolicName: org.w3c.dom.svg
-SLC-Origin-M2: org.axsl.org.w3c.dom.svg:svg-dom-java:1.1
diff --git a/tp/org.argeo.tp.formats/pdfbox/common.bnd b/tp/org.argeo.tp.formats/pdfbox/common.bnd
deleted file mode 100644 (file)
index 165d7c4..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-Bundle-License: Apache-2.0
-SLC-Origin-M2: :2.0.25
\ No newline at end of file
diff --git a/tp/org.argeo.tp.formats/pdfbox/org.apache.pdfbox.bnd b/tp/org.argeo.tp.formats/pdfbox/org.apache.pdfbox.bnd
deleted file mode 100644 (file)
index 1adc204..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-Bundle-SymbolicName: org.apache.pdfbox
-SLC-Origin-M2: org.apache.pdfbox:pdfbox
-Import-Package: \
-sun.java2d.cmm.kcms;resolution:="optional",\
-*
\ No newline at end of file
diff --git a/tp/org.argeo.tp.formats/pdfbox/org.apache.pdfbox.fontbox.bnd b/tp/org.argeo.tp.formats/pdfbox/org.apache.pdfbox.fontbox.bnd
deleted file mode 100644 (file)
index 519f90f..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-Bundle-SymbolicName: org.apache.pdfbox.fontbox
-SLC-Origin-M2: org.apache.pdfbox:fontbox
diff --git a/tp/org.argeo.tp.formats/pdfbox/org.apache.pdfbox.xmpbox.bnd b/tp/org.argeo.tp.formats/pdfbox/org.apache.pdfbox.xmpbox.bnd
deleted file mode 100644 (file)
index 8a0c26c..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-Bundle-SymbolicName: org.apache.pdfbox.xmpbox
-SLC-Origin-M2: org.apache.pdfbox:xmpbox
diff --git a/tp/org.argeo.tp.gis/geotools/merge.bnd b/tp/org.argeo.tp.gis/geotools/merge.bnd
deleted file mode 100644 (file)
index 9b50b63..0000000
+++ /dev/null
@@ -1,45 +0,0 @@
-Bundle-License: LGPL-2.1-only
-Bundle-SymbolicName: org.geotools
-SLC-Origin-M2: :26.3
-SLC-Origin-M2-Repo: https://repo.osgeo.org/repository/release/
-Export-Package: org.geotools.*, org.opengis.*, net.opengis.*, org.w3.xlink.*
-Import-Package: \
-org.locationtech.jts.*,\
-org.geotools.gui.swing.*;resolution:="optional",\
-org.geotools.*,\
-org.opengis.*,\
-net.opengis.*,\
-javax.measure.*, \
-tech.units.indriya.*, \
-si.uom.*, \
-systems.uom.*, \
-*;resolution:="optional"
-SLC-Origin-M2-Merge: \
-org.geotools:gt-metadata, \
-org.geotools:gt-property, \
-org.geotools:gt-opengis, \
-org.geotools:gt-main, \
-org.geotools:gt-cql, \
-org.geotools:gt-http, \
-org.geotools:gt-coverage, \
-org.geotools:gt-referencing, \
-org.geotools:gt-process, \
-org.geotools:gt-epsg-extension, \
-org.geotools:gt-epsg-wkt, \
-org.geotools:gt-transform, \
-org.geotools:gt-geojson, \
-org.geotools:gt-shapefile, \
-org.geotools:gt-xml, \
-org.geotools:gt-svg, \
-org.geotools:gt-image, \
-org.geotools:gt-geotiff, \
-org.geotools:gt-render, \
-org.geotools:gt-swing, \
-org.geotools:gt-wms, \
-org.geotools:gt-jdbc, \
-org.geotools.jdbc:gt-jdbc-postgis, \
-org.geotools.jdbc:gt-jdbc-h2, \
-org.geotools.ogc:net.opengis.ows, \
-org.geotools.ogc:net.opengis.wfs, \
-org.geotools.ogc:net.opengis.fes, \
-org.geotools.ogc:org.w3.xlink, \
diff --git a/tp/org.argeo.tp.gis/org.json.simple.bnd b/tp/org.argeo.tp.gis/org.json.simple.bnd
deleted file mode 100644 (file)
index 71ce052..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-Bundle-license: Apache-2.0
-SLC-Origin-M2: com.googlecode.json-simple:json-simple:1.1.1
\ No newline at end of file
diff --git a/tp/org.argeo.tp.gis/org.locationtech.jts.bnd b/tp/org.argeo.tp.gis/org.locationtech.jts.bnd
deleted file mode 100644 (file)
index 8e2e958..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-Bundle-License: BSD-3-Clause
-SLC-Origin-M2: org.locationtech.jts:jts-core:1.18.2
\ No newline at end of file
diff --git a/tp/org.argeo.tp.gis/units/merge.bnd b/tp/org.argeo.tp.gis/units/merge.bnd
deleted file mode 100644 (file)
index 2a40486..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-Bundle-License: BSD-3-Clause
-Bundle-SymbolicName: tech.units.indriya
-SLC-Origin-M2: :2.0.2
-Export-Package: javax.measure.*, tech.units.indriya.*, si.uom.*, systems.uom.*
-Import-Package: \
-*;resolution:="optional"
-SLC-Origin-M2-Merge: \
-javax.measure:unit-api:2.0, \
-tech.units:indriya, \
-tech.uom.lib:uom-lib-common:2.0, \
-si.uom:si-units:2.0.1, \
-si.uom:si-quantity:2.0.1, \
-systems.uom:systems-common:2.0.2, \
-systems.uom:systems-quantity:2.0.2, \
diff --git a/tp/org.argeo.tp.javax/javax.xml.bind.bnd.deactivated b/tp/org.argeo.tp.javax/javax.xml.bind.bnd.deactivated
deleted file mode 100644 (file)
index 5a76458..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-Bundle-SymbolicName: javax.xml.bind
-Bundle-Version: 2.4.0
-SLC-Origin-M2: javax.xml.bind:jaxb-api:2.4.0-b180830.0359
-Export-Package: javax.*
\ No newline at end of file
diff --git a/tp/org.argeo.tp.jcr/EDU.oswego.cs.dl.util.concurrent.bnd b/tp/org.argeo.tp.jcr/EDU.oswego.cs.dl.util.concurrent.bnd
deleted file mode 100644 (file)
index d2432b7..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-Bundle-License: Public Domain
-Bundle-SymbolicName: EDU.oswego.cs.dl.util.concurrent
-SLC-Origin-M2: concurrent:concurrent:1.3.4
\ No newline at end of file
diff --git a/tp/org.argeo.tp.jcr/com.google.guava.bnd.retired b/tp/org.argeo.tp.jcr/com.google.guava.bnd.retired
deleted file mode 100644 (file)
index a8a926f..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-Bundle-License: Apache-2.0
-Bundle-SymbolicName: com.google.guava
-Bundle-Version: 27.1.0.jre
-SLC-Origin-M2: com.google.guava:guava:27.1-jre
-SLC-Origin-ManifestNotModified: true
diff --git a/tp/org.argeo.tp.jcr/com.google.guava.failureaccess.bnd.retired b/tp/org.argeo.tp.jcr/com.google.guava.failureaccess.bnd.retired
deleted file mode 100644 (file)
index e7c19ce..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-Bundle-License: Apache-2.0
-Bundle-SymbolicName: com.google.guava.failureaccess
-SLC-Origin-M2: com.google.guava:failureaccess:1.0.1
-SLC-Origin-ManifestNotModified: true
diff --git a/tp/org.argeo.tp.jcr/jackrabbit/common.bnd b/tp/org.argeo.tp.jcr/jackrabbit/common.bnd
deleted file mode 100644 (file)
index d48a04c..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-Bundle-License: Apache-2.0
-SLC-Origin-M2: :2.20.4
\ No newline at end of file
diff --git a/tp/org.argeo.tp.jcr/jackrabbit/org.apache.jackrabbit.api.bnd.retired b/tp/org.argeo.tp.jcr/jackrabbit/org.apache.jackrabbit.api.bnd.retired
deleted file mode 100644 (file)
index 6ebc779..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-SLC-Origin-M2: org.apache.jackrabbit:jackrabbit-api
-Bundle-SymbolicName: org.apache.jackrabbit.api
-Export-Package: org.apache.jackrabbit.*
diff --git a/tp/org.argeo.tp.jcr/jackrabbit/org.apache.jackrabbit.core.bnd b/tp/org.argeo.tp.jcr/jackrabbit/org.apache.jackrabbit.core.bnd
deleted file mode 100644 (file)
index a746cae..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-SLC-Origin-M2: org.apache.jackrabbit:jackrabbit-core
-Bundle-SymbolicName: org.apache.jackrabbit.core
-Import-Package: org.apache.jackrabbit.test;resolution:=optional,\
-org.apache.derby.*;resolution:=optional,\
-org.h2;resolution:=optional,\
-org.postgresql;resolution:=optional,\
-oracle.jdbc;resolution:=optional,\
-org.gjt.mm.mysql;resolution:=optional,\
-com.mysql.jdbc;resolution:=optional,\
-com.microsoft.sqlserver.jdbc;resolution:=optional,\
-net.sourceforge.jtds.jdbc;resolution:=optional,\
-org.hsqldb;resolution:=optional,\
-*
diff --git a/tp/org.argeo.tp.jcr/jackrabbit/org.apache.jackrabbit.data.bnd b/tp/org.argeo.tp.jcr/jackrabbit/org.apache.jackrabbit.data.bnd
deleted file mode 100644 (file)
index 981b846..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-SLC-Origin-M2: org.apache.jackrabbit:jackrabbit-data
-Bundle-SymbolicName: org.apache.jackrabbit.data
-Fragment-Host: org.apache.jackrabbit.core
-Import-Package: org.apache.jackrabbit.test;resolution:=optional,\
-org.apache.derby.*;resolution:=optional,\
-org.h2;resolution:=optional,\
-org.postgresql;resolution:=optional,\
-oracle.jdbc;resolution:=optional,\
-org.gjt.mm.mysql;resolution:=optional,\
-com.mysql.jdbc;resolution:=optional,\
-com.microsoft.sqlserver.jdbc;resolution:=optional,\
-net.sourceforge.jtds.jdbc;resolution:=optional,\
-org.hsqldb;resolution:=optional,\
-*
diff --git a/tp/org.argeo.tp.jcr/jackrabbit/org.apache.jackrabbit.jcr.client.bnd b/tp/org.argeo.tp.jcr/jackrabbit/org.apache.jackrabbit.jcr.client.bnd
deleted file mode 100644 (file)
index 9c083a3..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-SLC-Origin-M2: org.apache.jackrabbit:jackrabbit-jcr-client
-Bundle-SymbolicName: org.apache.jackrabbit.jcr.client
diff --git a/tp/org.argeo.tp.jcr/jackrabbit/org.apache.jackrabbit.jcr.commons.bnd b/tp/org.argeo.tp.jcr/jackrabbit/org.apache.jackrabbit.jcr.commons.bnd
deleted file mode 100644 (file)
index 7af2a80..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-SLC-Origin-M2: org.apache.jackrabbit:jackrabbit-jcr-commons
-Bundle-SymbolicName: org.apache.jackrabbit.jcr.commons
diff --git a/tp/org.argeo.tp.jcr/jackrabbit/org.apache.jackrabbit.jcr2dav.bnd b/tp/org.argeo.tp.jcr/jackrabbit/org.apache.jackrabbit.jcr2dav.bnd
deleted file mode 100644 (file)
index 6ec7dd9..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-SLC-Origin-M2: org.apache.jackrabbit:jackrabbit-jcr2dav
-Bundle-SymbolicName: org.apache.jackrabbit.jcr2dav
diff --git a/tp/org.argeo.tp.jcr/jackrabbit/org.apache.jackrabbit.jcr2spi.bnd b/tp/org.argeo.tp.jcr/jackrabbit/org.apache.jackrabbit.jcr2spi.bnd
deleted file mode 100644 (file)
index 5809e9e..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-SLC-Origin-M2: org.apache.jackrabbit:jackrabbit-jcr2spi
-Bundle-SymbolicName: org.apache.jackrabbit.jcr2spi
diff --git a/tp/org.argeo.tp.jcr/jackrabbit/org.apache.jackrabbit.server.bnd b/tp/org.argeo.tp.jcr/jackrabbit/org.apache.jackrabbit.server.bnd
deleted file mode 100644 (file)
index ef7d703..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-SLC-Origin-M2: org.apache.jackrabbit:jackrabbit-jcr-server
-Bundle-SymbolicName: org.apache.jackrabbit.server
diff --git a/tp/org.argeo.tp.jcr/jackrabbit/org.apache.jackrabbit.servlet.bnd b/tp/org.argeo.tp.jcr/jackrabbit/org.apache.jackrabbit.servlet.bnd
deleted file mode 100644 (file)
index 01f4326..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-SLC-Origin-M2: org.apache.jackrabbit:jackrabbit-jcr-servlet
-Bundle-SymbolicName: org.apache.jackrabbit.servlet
-Fragment-Host: org.apache.jackrabbit.core
-Import-Package: org.apache.jackrabbit.rmi.*;resolution:=optional,*
diff --git a/tp/org.argeo.tp.jcr/jackrabbit/org.apache.jackrabbit.spi.bnd b/tp/org.argeo.tp.jcr/jackrabbit/org.apache.jackrabbit.spi.bnd
deleted file mode 100644 (file)
index 6d0c7e4..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-SLC-Origin-M2: org.apache.jackrabbit:jackrabbit-spi
-Bundle-SymbolicName: org.apache.jackrabbit.spi
diff --git a/tp/org.argeo.tp.jcr/jackrabbit/org.apache.jackrabbit.spi.commons.bnd b/tp/org.argeo.tp.jcr/jackrabbit/org.apache.jackrabbit.spi.commons.bnd
deleted file mode 100644 (file)
index 3014a8c..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-SLC-Origin-M2: org.apache.jackrabbit:jackrabbit-spi-commons
-Bundle-SymbolicName: org.apache.jackrabbit.spi.commons
diff --git a/tp/org.argeo.tp.jcr/jackrabbit/org.apache.jackrabbit.spi2dav.bnd b/tp/org.argeo.tp.jcr/jackrabbit/org.apache.jackrabbit.spi2dav.bnd
deleted file mode 100644 (file)
index 7c7de9c..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-SLC-Origin-M2: org.apache.jackrabbit:jackrabbit-spi2dav
-Bundle-SymbolicName: org.apache.jackrabbit.spi2dav
diff --git a/tp/org.argeo.tp.jcr/jackrabbit/org.apache.jackrabbit.spi2jcr.bnd b/tp/org.argeo.tp.jcr/jackrabbit/org.apache.jackrabbit.spi2jcr.bnd
deleted file mode 100644 (file)
index 9474f30..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-SLC-Origin-M2: org.apache.jackrabbit:jackrabbit-spi2jcr
-Bundle-SymbolicName: org.apache.jackrabbit.spi2jcr
diff --git a/tp/org.argeo.tp.jcr/jackrabbit/org.apache.jackrabbit.webdav.bnd b/tp/org.argeo.tp.jcr/jackrabbit/org.apache.jackrabbit.webdav.bnd
deleted file mode 100644 (file)
index 9a453bf..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-SLC-Origin-M2: org.apache.jackrabbit:jackrabbit-webdav
-Bundle-SymbolicName: org.apache.jackrabbit.webdav
diff --git a/tp/org.argeo.tp.jcr/javax.jcr.bnd b/tp/org.argeo.tp.jcr/javax.jcr.bnd
deleted file mode 100644 (file)
index 10bd6c3..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-Bundle-License: https://www.adobe.io/experience-manager/reference-materials/spec/jcr/2.0/license.html
-Bundle-SymbolicName: javax.jcr
-Bundle-Version: 2.0.0
-SLC-Origin-M2: javax.jcr:jcr:2.0
-SLC-Origin-ManifestNotModified: true
diff --git a/tp/org.argeo.tp.jcr/oak/common.bnd b/tp/org.argeo.tp.jcr/oak/common.bnd
deleted file mode 100644 (file)
index d998340..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-Bundle-License: Apache-2.0
-SLC-Origin-M2: :1.40.0
\ No newline at end of file
diff --git a/tp/org.argeo.tp.jcr/oak/org.apache.jackrabbit.api.bnd b/tp/org.argeo.tp.jcr/oak/org.apache.jackrabbit.api.bnd
deleted file mode 100644 (file)
index ebd491d..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-SLC-Origin-M2: org.apache.jackrabbit:oak-jackrabbit-api
-Bundle-SymbolicName: org.apache.jackrabbit.api
-Export-Package: org.apache.jackrabbit.*
diff --git a/tp/org.argeo.tp.jcr/org.apache.commons.collections.bnd b/tp/org.argeo.tp.jcr/org.apache.commons.collections.bnd
deleted file mode 100644 (file)
index b81a7e3..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-Bundle-License: Apache-2.0
-Bundle-SymbolicName: org.apache.commons.collections
-SLC-Origin-M2: commons-collections:commons-collections:3.2.2
diff --git a/tp/org.argeo.tp.jcr/org.apache.commons.dbcp.bnd b/tp/org.argeo.tp.jcr/org.apache.commons.dbcp.bnd
deleted file mode 100644 (file)
index 7d03c4d..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-Bundle-License: Apache-2.0
-Bundle-SymbolicName: org.apache.commons.dbcp
-SLC-Origin-M2: commons-dbcp:commons-dbcp:1.4
-Import-Package: javax.transaction.*; resolution:="optional",\
-*
diff --git a/tp/org.argeo.tp.jcr/org.apache.commons.pool.bnd b/tp/org.argeo.tp.jcr/org.apache.commons.pool.bnd
deleted file mode 100644 (file)
index c0ae270..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-Bundle-License: Apache-2.0
-Bundle-SymbolicName: org.apache.commons.pool
-SLC-Origin-M2: commons-pool:commons-pool:1.6
diff --git a/tp/org.argeo.tp.jcr/org.apache.lucene.bnd b/tp/org.argeo.tp.jcr/org.apache.lucene.bnd
deleted file mode 100644 (file)
index 8126ffd..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-Bundle-License: Apache-2.0
-Bundle-SymbolicName: org.apache.lucene
-SLC-Origin-M2: org.apache.lucene:lucene-core:3.6.2
\ No newline at end of file
diff --git a/tp/org.argeo.tp.jetty.websocket/javax.websocket.bnd b/tp/org.argeo.tp.jetty.websocket/javax.websocket.bnd
deleted file mode 100644 (file)
index cefcff8..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-Bundle-SymbolicName: javax.websocket
-Bundle-License: GPL-2.0-only OR CDDL-1.1
-SLC-Origin-M2: javax.websocket:javax.websocket-api:1.1
-SLC-Origin-ManifestNotModified: true
diff --git a/tp/org.argeo.tp.jetty.websocket/jetty-websocket/common.bnd b/tp/org.argeo.tp.jetty.websocket/jetty-websocket/common.bnd
deleted file mode 100644 (file)
index c26721c..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-Bundle-License: Apache-2.0
-SLC-Origin-M2: :10.0.8
diff --git a/tp/org.argeo.tp.jetty.websocket/jetty-websocket/org.eclipse.jetty.websocket.api.bnd b/tp/org.argeo.tp.jetty.websocket/jetty-websocket/org.eclipse.jetty.websocket.api.bnd
deleted file mode 100644 (file)
index 3723f35..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-SLC-Origin-M2: org.eclipse.jetty.websocket:websocket-jetty-api
-SLC-Origin-ManifestNotModified: true
-Bundle-SymbolicName: org.eclipse.jetty.websocket.api
diff --git a/tp/org.argeo.tp.jetty.websocket/jetty-websocket/org.eclipse.jetty.websocket.client.bnd b/tp/org.argeo.tp.jetty.websocket/jetty-websocket/org.eclipse.jetty.websocket.client.bnd
deleted file mode 100644 (file)
index 12795c3..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-SLC-Origin-M2: org.eclipse.jetty.websocket:websocket-jetty-client
-SLC-Origin-ManifestNotModified: true
-Bundle-SymbolicName: org.eclipse.jetty.websocket.client
diff --git a/tp/org.argeo.tp.jetty.websocket/jetty-websocket/org.eclipse.jetty.websocket.common.bnd b/tp/org.argeo.tp.jetty.websocket/jetty-websocket/org.eclipse.jetty.websocket.common.bnd
deleted file mode 100644 (file)
index d97b5e7..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-SLC-Origin-M2: org.eclipse.jetty.websocket:websocket-jetty-common
-SLC-Origin-ManifestNotModified: true
-Bundle-SymbolicName: org.eclipse.jetty.websocket.common
diff --git a/tp/org.argeo.tp.jetty.websocket/jetty-websocket/org.eclipse.jetty.websocket.core.client.bnd b/tp/org.argeo.tp.jetty.websocket/jetty-websocket/org.eclipse.jetty.websocket.core.client.bnd
deleted file mode 100644 (file)
index e6f4338..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-SLC-Origin-M2: org.eclipse.jetty.websocket:websocket-core-client
-SLC-Origin-ManifestNotModified: true
-Bundle-SymbolicName: org.eclipse.jetty.websocket.core.client
diff --git a/tp/org.argeo.tp.jetty.websocket/jetty-websocket/org.eclipse.jetty.websocket.core.common.bnd b/tp/org.argeo.tp.jetty.websocket/jetty-websocket/org.eclipse.jetty.websocket.core.common.bnd
deleted file mode 100644 (file)
index 9d8bbe9..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-SLC-Origin-M2: org.eclipse.jetty.websocket:websocket-core-common
-SLC-Origin-ManifestNotModified: true
-Bundle-SymbolicName: org.eclipse.jetty.websocket.core.common
diff --git a/tp/org.argeo.tp.jetty.websocket/jetty-websocket/org.eclipse.jetty.websocket.core.server.bnd b/tp/org.argeo.tp.jetty.websocket/jetty-websocket/org.eclipse.jetty.websocket.core.server.bnd
deleted file mode 100644 (file)
index e6f4338..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-SLC-Origin-M2: org.eclipse.jetty.websocket:websocket-core-client
-SLC-Origin-ManifestNotModified: true
-Bundle-SymbolicName: org.eclipse.jetty.websocket.core.client
diff --git a/tp/org.argeo.tp.jetty.websocket/jetty-websocket/org.eclipse.jetty.websocket.jakarta.websocket.client.bnd.disabled b/tp/org.argeo.tp.jetty.websocket/jetty-websocket/org.eclipse.jetty.websocket.jakarta.websocket.client.bnd.disabled
deleted file mode 100644 (file)
index 29ee719..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-SLC-Origin-M2: org.eclipse.jetty.websocket:websocket-jakarta-client
-SLC-Origin-ManifestNotModified: true
-Bundle-SymbolicName: org.eclipse.jetty.websocket.jakarta.client
diff --git a/tp/org.argeo.tp.jetty.websocket/jetty-websocket/org.eclipse.jetty.websocket.jakarta.websocket.common.bnd.disabled b/tp/org.argeo.tp.jetty.websocket/jetty-websocket/org.eclipse.jetty.websocket.jakarta.websocket.common.bnd.disabled
deleted file mode 100644 (file)
index b2d60af..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-SLC-Origin-M2: org.eclipse.jetty.websocket:websocket-jakarta-common
-SLC-Origin-ManifestNotModified: true
-Bundle-SymbolicName: org.eclipse.jetty.websocket.jakarta.common
diff --git a/tp/org.argeo.tp.jetty.websocket/jetty-websocket/org.eclipse.jetty.websocket.jakarta.websocket.server.bnd.disabled b/tp/org.argeo.tp.jetty.websocket/jetty-websocket/org.eclipse.jetty.websocket.jakarta.websocket.server.bnd.disabled
deleted file mode 100644 (file)
index 999440a..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-SLC-Origin-M2: org.eclipse.jetty.websocket:websocket-jakarta-server
-SLC-Origin-ManifestNotModified: true
-Bundle-SymbolicName: org.eclipse.jetty.websocket.jakarta.server
diff --git a/tp/org.argeo.tp.jetty.websocket/jetty-websocket/org.eclipse.jetty.websocket.server.bnd b/tp/org.argeo.tp.jetty.websocket/jetty-websocket/org.eclipse.jetty.websocket.server.bnd
deleted file mode 100644 (file)
index c515899..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-SLC-Origin-M2: org.eclipse.jetty.websocket:websocket-jetty-server
-SLC-Origin-ManifestNotModified: true
-Bundle-SymbolicName: org.eclipse.jetty.websocket.server
diff --git a/tp/org.argeo.tp.jetty.websocket/jetty-websocket/org.eclipse.jetty.websocket.servlet.bnd b/tp/org.argeo.tp.jetty.websocket/jetty-websocket/org.eclipse.jetty.websocket.servlet.bnd
deleted file mode 100644 (file)
index 3db77e2..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-SLC-Origin-M2: org.eclipse.jetty.websocket:websocket-servlet
-SLC-Origin-ManifestNotModified: true
-Bundle-SymbolicName: org.eclipse.jetty.websocket.servlet
diff --git a/tp/org.argeo.tp.jetty/jetty/common.bnd b/tp/org.argeo.tp.jetty/jetty/common.bnd
deleted file mode 100644 (file)
index c26721c..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-Bundle-License: Apache-2.0
-SLC-Origin-M2: :10.0.8
diff --git a/tp/org.argeo.tp.jetty/jetty/org.eclipse.jetty.client.bnd.retired b/tp/org.argeo.tp.jetty/jetty/org.eclipse.jetty.client.bnd.retired
deleted file mode 100644 (file)
index 8bce1ab..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-SLC-Origin-M2: org.eclipse.jetty:jetty-client
-SLC-Origin-ManifestNotModified: true
-Bundle-SymbolicName: org.eclipse.jetty.client
diff --git a/tp/org.argeo.tp.jetty/jetty/org.eclipse.jetty.continuation.bnd.retired b/tp/org.argeo.tp.jetty/jetty/org.eclipse.jetty.continuation.bnd.retired
deleted file mode 100644 (file)
index 5cf169d..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-SLC-Origin-M2: org.eclipse.jetty:jetty-continuation
-SLC-Origin-ManifestNotModified: true
-Bundle-SymbolicName: org.eclipse.jetty.continuation
diff --git a/tp/org.argeo.tp.jetty/jetty/org.eclipse.jetty.http.bnd b/tp/org.argeo.tp.jetty/jetty/org.eclipse.jetty.http.bnd
deleted file mode 100644 (file)
index 4a4e18c..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-SLC-Origin-M2: org.eclipse.jetty:jetty-http
-SLC-Origin-ManifestNotModified: true
-Bundle-SymbolicName: org.eclipse.jetty.http
diff --git a/tp/org.argeo.tp.jetty/jetty/org.eclipse.jetty.io.bnd b/tp/org.argeo.tp.jetty/jetty/org.eclipse.jetty.io.bnd
deleted file mode 100644 (file)
index 4037c70..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-SLC-Origin-M2: org.eclipse.jetty:jetty-io
-SLC-Origin-ManifestNotModified: true
-Bundle-SymbolicName: org.eclipse.jetty.io
diff --git a/tp/org.argeo.tp.jetty/jetty/org.eclipse.jetty.jmx.bnd b/tp/org.argeo.tp.jetty/jetty/org.eclipse.jetty.jmx.bnd
deleted file mode 100644 (file)
index dbdf6e1..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-SLC-Origin-M2: org.eclipse.jetty:jetty-jmx
-SLC-Origin-ManifestNotModified: true
-Bundle-SymbolicName: org.eclipse.jetty.jmx
diff --git a/tp/org.argeo.tp.jetty/jetty/org.eclipse.jetty.security.bnd b/tp/org.argeo.tp.jetty/jetty/org.eclipse.jetty.security.bnd
deleted file mode 100644 (file)
index f28b886..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-SLC-Origin-M2: org.eclipse.jetty:jetty-security
-SLC-Origin-ManifestNotModified: true
-Bundle-SymbolicName: org.eclipse.jetty.security
diff --git a/tp/org.argeo.tp.jetty/jetty/org.eclipse.jetty.server.bnd b/tp/org.argeo.tp.jetty/jetty/org.eclipse.jetty.server.bnd
deleted file mode 100644 (file)
index d66a70d..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-SLC-Origin-M2: org.eclipse.jetty:jetty-server
-SLC-Origin-ManifestNotModified: true
-Bundle-SymbolicName: org.eclipse.jetty.server
diff --git a/tp/org.argeo.tp.jetty/jetty/org.eclipse.jetty.servlet.bnd b/tp/org.argeo.tp.jetty/jetty/org.eclipse.jetty.servlet.bnd
deleted file mode 100644 (file)
index 68469c1..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-SLC-Origin-M2: org.eclipse.jetty:jetty-servlet
-SLC-Origin-ManifestNotModified: true
-Bundle-SymbolicName: org.eclipse.jetty.servlet
diff --git a/tp/org.argeo.tp.jetty/jetty/org.eclipse.jetty.servlets.bnd b/tp/org.argeo.tp.jetty/jetty/org.eclipse.jetty.servlets.bnd
deleted file mode 100644 (file)
index 27a8f2e..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-SLC-Origin-M2: org.eclipse.jetty:jetty-servlets
-SLC-Origin-ManifestNotModified: true
-Bundle-SymbolicName: org.eclipse.jetty.servlets
diff --git a/tp/org.argeo.tp.jetty/jetty/org.eclipse.jetty.util.ajax.bnd b/tp/org.argeo.tp.jetty/jetty/org.eclipse.jetty.util.ajax.bnd
deleted file mode 100644 (file)
index ec671fa..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-SLC-Origin-M2: org.eclipse.jetty:jetty-util-ajax
-SLC-Origin-ManifestNotModified: true
-Bundle-SymbolicName: org.eclipse.jetty.util.ajax
diff --git a/tp/org.argeo.tp.jetty/jetty/org.eclipse.jetty.util.bnd b/tp/org.argeo.tp.jetty/jetty/org.eclipse.jetty.util.bnd
deleted file mode 100644 (file)
index 600cc70..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-SLC-Origin-M2: org.eclipse.jetty:jetty-util
-SLC-Origin-ManifestNotModified: true
-Bundle-SymbolicName: org.eclipse.jetty.util
diff --git a/tp/org.argeo.tp.jetty/jetty/org.eclipse.jetty.xml.bnd b/tp/org.argeo.tp.jetty/jetty/org.eclipse.jetty.xml.bnd
deleted file mode 100644 (file)
index 0ccef1a..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-SLC-Origin-M2: org.eclipse.jetty:jetty-xml
-SLC-Origin-ManifestNotModified: true
-Bundle-SymbolicName: org.eclipse.jetty.xml
diff --git a/tp/org.argeo.tp.poi/org.apache.xmlbeans.bnd b/tp/org.argeo.tp.poi/org.apache.xmlbeans.bnd
deleted file mode 100644 (file)
index 83d6d31..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-Bundle-License: Apache-2.0
-Bundle-SymbolicName: org.apache.xmlbeans
-DynamicImport-Package: *
-Import-Package: org.apache.tools.ant.*;resolution:=optional,
-net.sf.saxon.*;resolution:=optional,
-com.sun.*;resolution:=optional,
-*
-SLC-Origin-M2: org.apache.xmlbeans:xmlbeans:3.1.0
\ No newline at end of file
diff --git a/tp/org.argeo.tp.poi/poi/common.bnd b/tp/org.argeo.tp.poi/poi/common.bnd
deleted file mode 100644 (file)
index c676bba..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-Bundle-License: Apache-2.0
-SLC-Origin-M2: :5.2.1
\ No newline at end of file
diff --git a/tp/org.argeo.tp.poi/poi/org.apache.poi.bnd b/tp/org.argeo.tp.poi/poi/org.apache.poi.bnd
deleted file mode 100644 (file)
index 9856bd2..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-Bundle-SymbolicName: org.apache.poi
-SLC-Origin-M2: org.apache.poi:poi
-Import-Package: \
-org.apache.logging.log4j.*;resolution:=optional,\
-*
diff --git a/tp/org.argeo.tp.poi/poi/org.apache.poi.ooxml.bnd b/tp/org.argeo.tp.poi/poi/org.apache.poi.ooxml.bnd
deleted file mode 100644 (file)
index 75bea7c..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-Bundle-SymbolicName: org.apache.poi.ooxml
-Import-Package: \
-com.graphbuilder.*;resolution:=optional,\
-com.graphbuilder.*;resolution:=optional,\
-org.etsi.uri.*;resolution:=optional,\
-org.apache.logging.log4j.*;resolution:=optional,\
-*
-SLC-Origin-M2: org.apache.poi:poi-ooxml
diff --git a/tp/org.argeo.tp.poi/poi/org.apache.poi.ooxml.schemas.bnd b/tp/org.argeo.tp.poi/poi/org.apache.poi.ooxml.schemas.bnd
deleted file mode 100644 (file)
index 7b4fa6e..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-Bundle-SymbolicName: org.apache.poi.ooxml.schemas
-Import-Package: \
-com.microsoft.schemas.*;resolution:=optional, \
-org.openxmlformats.schemas.*;resolution:=optional, \
-*
-Require-Bundle: org.apache.xmlbeans
-SLC-Origin-M2: org.apache.poi:poi-ooxml-full
diff --git a/tp/org.argeo.tp.poi/poi/org.apache.poi.scratchpad.bnd b/tp/org.argeo.tp.poi/poi/org.apache.poi.scratchpad.bnd
deleted file mode 100644 (file)
index 50b0d3f..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-Bundle-SymbolicName: org.apache.poi.scratchpad
-SLC-Origin-M2: org.apache.poi:poi-scratchpad
-Import-Package: \
-org.apache.logging.log4j.*;resolution:=optional,\
-*
diff --git a/tp/org.argeo.tp.sdk/biz.aQute.bndlib.bnd b/tp/org.argeo.tp.sdk/biz.aQute.bndlib.bnd
deleted file mode 100644 (file)
index 0fa851b..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-Bundle-License: Apache-2.0
-SLC-Origin-M2: biz.aQute.bnd:biz.aQute.bndlib:5.3.0
-SLC-Origin-ManifestNotModified: true
diff --git a/tp/org.argeo.tp.sdk/org.eclipse.jdt.core.compiler.batch.bnd b/tp/org.argeo.tp.sdk/org.eclipse.jdt.core.compiler.batch.bnd
deleted file mode 100644 (file)
index 54f802f..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-Bundle-License: EPL-2.0
-SLC-Origin-M2: org.eclipse.jdt:ecj:3.28.0
-SLC-Origin-ManifestNotModified: true
-Main-Class: org.eclipse.jdt.internal.compiler.batch.Main
\ No newline at end of file
diff --git a/tp/org.argeo.tp.sdk/org.eclipse.jgit.bnd b/tp/org.argeo.tp.sdk/org.eclipse.jgit.bnd
deleted file mode 100644 (file)
index c4df6b9..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-Bundle-License: BSD-3-Clause
-SLC-Origin-M2: org.eclipse.jgit:org.eclipse.jgit:5.13.0.202109080827-r
-Import-Package: \
-sun.*;resolution:=optional, \
-com.jcraft.jsch;resolution:=optional, \
-*
diff --git a/tp/org.argeo.tp.sdk/org.hamcrest.bnd b/tp/org.argeo.tp.sdk/org.hamcrest.bnd
deleted file mode 100644 (file)
index f59732a..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-Bundle-License: BSD-3-Clause
-Bundle-SymbolicName: org.hamcrest
-Bundle-Version: 2.1.0
-SLC-Origin-M2: org.hamcrest:hamcrest:2.2
-SLC-Origin-ManifestNotModified: true
diff --git a/tp/org.argeo.tp.sdk/org.junit.bnd b/tp/org.argeo.tp.sdk/org.junit.bnd
deleted file mode 100644 (file)
index 58bf1ca..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-Bundle-License: EPL-1.0
-Bundle-SymbolicName: org.junit
-Bundle-Version: 4.12.0
-Import-Package: org.hamcrest;resolution:=optional,\
-org.hamcrest.core;resolution:=optional,\
-*
-SLC-Origin-M2: junit:junit:4.13.2
\ No newline at end of file
diff --git a/tp/org.argeo.tp.sdk/org.redline-rpm.bnd b/tp/org.argeo.tp.sdk/org.redline-rpm.bnd
deleted file mode 100644 (file)
index 6a32d26..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-Bundle-License: MIT
-Bundle-SymbolicName: org.redline-rpm
-Import-Package: sun.security.*;resolution:=optional,
-org.apache.tools.ant.*;resolution:=optional,
-org.apache.tools.zip;resolution:=optional,
-*
-SLC-Origin-M2: org.redline-rpm:redline:1.2.10
diff --git a/tp/org.argeo.tp/bouncycastle/bcpg.bnd b/tp/org.argeo.tp/bouncycastle/bcpg.bnd
deleted file mode 100644 (file)
index 545d83f..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-Bundle-SymbolicName: bcpg
-SLC-Origin-ManifestNotModified: true
-SLC-Origin-M2: org.bouncycastle:bcpg-jdk15on
diff --git a/tp/org.argeo.tp/bouncycastle/bcpkix.bnd b/tp/org.argeo.tp/bouncycastle/bcpkix.bnd
deleted file mode 100644 (file)
index 0e922b0..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-Bundle-SymbolicName: bcpkix
-SLC-Origin-ManifestNotModified: true
-SLC-Origin-M2: org.bouncycastle:bcpkix-jdk15on
diff --git a/tp/org.argeo.tp/bouncycastle/bcprov.bnd b/tp/org.argeo.tp/bouncycastle/bcprov.bnd
deleted file mode 100644 (file)
index e81167a..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-Bundle-SymbolicName: bcprov
-SLC-Origin-ManifestNotModified: true
-SLC-Origin-M2: org.bouncycastle:bcprov-jdk15on
diff --git a/tp/org.argeo.tp/bouncycastle/bcutil.bnd b/tp/org.argeo.tp/bouncycastle/bcutil.bnd
deleted file mode 100644 (file)
index d40f6dd..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-Bundle-SymbolicName: bcutil
-SLC-Origin-ManifestNotModified: true
-SLC-Origin-M2: org.bouncycastle:bcutil-jdk15on
diff --git a/tp/org.argeo.tp/bouncycastle/common.bnd b/tp/org.argeo.tp/bouncycastle/common.bnd
deleted file mode 100644 (file)
index 4c701f3..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-Bundle-License: MIT
-SLC-Origin-M2: :1.70
diff --git a/tp/org.argeo.tp/com.googlecode.javaewah.JavaEWAH.bnd b/tp/org.argeo.tp/com.googlecode.javaewah.JavaEWAH.bnd
deleted file mode 100644 (file)
index 4355de4..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-Bundle-License: Apache-2.0
-Bundle-SymbolicName: com.googlecode.javaewah.JavaEWAH
-SLC-Origin-M2: com.googlecode.javaewah:JavaEWAH:1.1.13
-SLC-Origin-ManifestNotModified: true
diff --git a/tp/org.argeo.tp/com.zaxxer.sparsebits.bnd b/tp/org.argeo.tp/com.zaxxer.sparsebits.bnd
deleted file mode 100644 (file)
index 0f72baf..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-Bundle-License: Apache-2.0
-Bundle-SymbolicName: com.zaxxer.sparsebits
-SLC-Origin-M2: com.zaxxer:SparseBitSet:1.2
diff --git a/tp/org.argeo.tp/jackson/com.fasterxml.jackson.core.jackson-annotations.bnd b/tp/org.argeo.tp/jackson/com.fasterxml.jackson.core.jackson-annotations.bnd
deleted file mode 100644 (file)
index 94b69a1..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-Bundle-SymbolicName: com.fasterxml.jackson.core.jackson-annotations
-SLC-Origin-M2: com.fasterxml.jackson.core:jackson-annotations
-SLC-Origin-ManifestNotModified: true
diff --git a/tp/org.argeo.tp/jackson/com.fasterxml.jackson.core.jackson-core.bnd b/tp/org.argeo.tp/jackson/com.fasterxml.jackson.core.jackson-core.bnd
deleted file mode 100644 (file)
index a4b359f..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-Bundle-SymbolicName: com.fasterxml.jackson.core.jackson-core
-SLC-Origin-M2: com.fasterxml.jackson.core:jackson-core
-SLC-Origin-ManifestNotModified: true
diff --git a/tp/org.argeo.tp/jackson/com.fasterxml.jackson.core.jackson-databind.bnd b/tp/org.argeo.tp/jackson/com.fasterxml.jackson.core.jackson-databind.bnd
deleted file mode 100644 (file)
index 32ded22..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-Bundle-SymbolicName: com.fasterxml.jackson.core.jackson-databind
-SLC-Origin-M2: com.fasterxml.jackson.core:jackson-databind
-SLC-Origin-ManifestNotModified: true
diff --git a/tp/org.argeo.tp/jackson/common.bnd b/tp/org.argeo.tp/jackson/common.bnd
deleted file mode 100644 (file)
index b9a12cc..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-Bundle-License: Apache-2.0
-SLC-Origin-M2: :2.13.1
diff --git a/tp/org.argeo.tp/org.h2.bnd b/tp/org.argeo.tp/org.h2.bnd
deleted file mode 100644 (file)
index a6090fa..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-Bundle-License: MPL-2.0
-Bundle-SymbolicName: org.h2
-SLC-Origin-M2: com.h2database:h2:1.4.200
-SLC-Origin-ManifestNotModified: true
diff --git a/tp/org.argeo.tp/org.postgresql.jdbc42.bnd b/tp/org.argeo.tp/org.postgresql.jdbc42.bnd
deleted file mode 100644 (file)
index e9d1393..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-Bundle-License: BSD-2-Clause
-Bundle-SymbolicName: org.postgresql.jdbc42
-SLC-Origin-M2: org.postgresql:postgresql:42.3.2
-SLC-Origin-ManifestNotModified: true
diff --git a/tp/org.argeo.tp/org.tukaani.xz.bnd b/tp/org.argeo.tp/org.tukaani.xz.bnd
deleted file mode 100644 (file)
index 3ea900d..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-Bundle-License: Public Domain
-Bundle-SymbolicName: org.tukaani.xz
-SLC-Origin-M2: org.tukaani:xz:1.9
diff --git a/tp/org.argeo.tp/slf4j/common.bnd b/tp/org.argeo.tp/slf4j/common.bnd
deleted file mode 100644 (file)
index 2c5cf1d..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-Bundle-License: Apache-2.0
-SLC-Origin-M2: :1.7.36
diff --git a/tp/org.argeo.tp/slf4j/org.slf4j.api.bnd b/tp/org.argeo.tp/slf4j/org.slf4j.api.bnd
deleted file mode 100644 (file)
index 75afe68..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-Bundle-SymbolicName: org.slf4j.api
-SLC-Origin-M2: org.slf4j:slf4j-api
diff --git a/tp/org.argeo.tp/slf4j/org.slf4j.commons.logging.bnd b/tp/org.argeo.tp/slf4j/org.slf4j.commons.logging.bnd
deleted file mode 100644 (file)
index 9b2ffdb..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-Bundle-SymbolicName: org.slf4j.commons.logging
-SLC-Origin-M2: org.slf4j:jcl-over-slf4j
-Export-Package: org.apache.commons.logging.*;version="1.2"
\ No newline at end of file
diff --git a/tp/org.argeo.tp/slf4j/org.slf4j.log4j.bnd.deactivated b/tp/org.argeo.tp/slf4j/org.slf4j.log4j.bnd.deactivated
deleted file mode 100644 (file)
index 4275c6a..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-Bundle-SymbolicName: org.slf4j.log4j
-SLC-Origin-M2: org.slf4j:log4j-over-slf4j
-Export-Package: org.apache.logging.log4j.*;version="2.17"
\ No newline at end of file